diff --git a/.gn b/.gn
index 0eaf794..8954741 100644
--- a/.gn
+++ b/.gn
@@ -66,7 +66,6 @@
   "//extensions:*",  # 28 errors
   "//headless:*",  # 107 errors
   "//ppapi/proxy:ipc_sources",  # 13 errors
-  "//ppapi/thunk:*",  # 1071 errors
   "//remoting/host/security_key:*",  # 10 errors
   "//remoting/host/win:*",  # 43 errors
   "//sandbox/win:*",  # 7 errors
diff --git a/DEPS b/DEPS
index 0aded74..98c7711 100644
--- a/DEPS
+++ b/DEPS
@@ -228,7 +228,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '9cd9d0f3de06099dff988c4f9abdcd5eb61633c3',
+  'skia_revision': '2cbb3f55ea7bbb4d0f0c6f13593f534a499b9285',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -240,15 +240,15 @@
   # 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': '1f0e2916e59c47bbc475444928d139261183474c',
+  'angle_revision': 'f99426400493a4623d55d59a9c51cfee87b30bbf',
   # 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': '1bc8669c8b787df9d30e954a3300d925d581d38a',
+  'swiftshader_revision': 'd4483095765ecd7914fbc90dc254a1f98565a915',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'fdbfb3c44434f3de59d2e23a8dbfeefe52608167',
+  'pdfium_revision': 'c716d47fba24d8da451d3db291ede1eb9d596bcb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -275,7 +275,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling NaCl
   # and whatever else without interference from each other.
-  'nacl_revision': 'a2161789b39b6811ba2a8c64a2341b0a78d25f79',
+  'nacl_revision': '55656f56c1e0dfdc73ff624f002257c346be8587',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -347,7 +347,7 @@
   # 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': '8e957160b1540712f996266595b8eb11458f239a',
+  'dawn_revision': '8ec325cfc61e727e3b791e5520f52d5d9b826e50',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -767,7 +767,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'yBqUm9V81rpvY4pY_cgtkc_eCKPE0Imj3MnzzGoWliwC',
+          'version': 'Er2u3dWX5_7MBWu-tE6iYNOJSmn3SOqDuD-0R0c-o2YC',
       },
     ],
     'condition': 'checkout_android',
@@ -1621,7 +1621,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '26da66e2987afd8fb426c8b83f7b96d500c3fb01',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '49c9b4ec3748a7f8e37865f44a89c1be8ed6dd8c',
+    Var('webrtc_git') + '/src.git' + '@' + '5654a1395036d40b9ef40f01f0bf985f41b4f0a1',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1682,7 +1682,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@1097ffdceae6ff5ef5da4fbaec9187c1a9934586',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3ecbeba2cd223e670bde8609e94537c5dded0d30',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_quota_manager_bridge.cc b/android_webview/browser/aw_quota_manager_bridge.cc
index 8d7c51e..87d3dbe 100644
--- a/android_webview/browser/aw_quota_manager_bridge.cc
+++ b/android_webview/browser/aw_quota_manager_bridge.cc
@@ -52,8 +52,8 @@
   friend class base::RefCountedThreadSafe<GetStorageKeysTask>;
   ~GetStorageKeysTask();
 
-  void OnStorageKeysObtained(const std::set<blink::StorageKey>& storage_keys,
-                             blink::mojom::StorageType type);
+  void OnStorageKeysObtained(blink::mojom::StorageType type,
+                             const std::set<blink::StorageKey>& storage_keys);
 
   void OnUsageAndQuotaObtained(const blink::StorageKey& storage_key,
                                blink::mojom::QuotaStatusCode status_code,
@@ -90,16 +90,15 @@
   content::GetIOThreadTaskRunner({})->PostTask(
       FROM_HERE,
       base::BindOnce(
-          &QuotaManager::GetStorageKeysModifiedBetween, quota_manager_,
+          &QuotaManager::GetStorageKeysForType, quota_manager_,
           blink::mojom::StorageType::kTemporary,
-          base::Time() /* Since beginning of time. */,
-          base::Time::Max() /* Until the end of time. */,
-          base::BindOnce(&GetStorageKeysTask::OnStorageKeysObtained, this)));
+          base::BindOnce(&GetStorageKeysTask::OnStorageKeysObtained, this,
+                         blink::mojom::StorageType::kTemporary)));
 }
 
 void GetStorageKeysTask::OnStorageKeysObtained(
-    const std::set<blink::StorageKey>& storage_keys,
-    blink::mojom::StorageType type) {
+    blink::mojom::StorageType type,
+    const std::set<blink::StorageKey>& storage_keys) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   num_callbacks_to_wait_ = storage_keys.size();
   num_callbacks_received_ = 0u;
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index db6573d..b6f555e 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -1583,6 +1583,13 @@
   return accelerator_history_.get();
 }
 
+bool AcceleratorControllerImpl::DoesAcceleratorMatchAction(
+    const ui::Accelerator& accelerator,
+    AcceleratorAction action) {
+  AcceleratorAction* action_ptr = accelerators_.Find(accelerator);
+  return action_ptr && *action_ptr == action;
+}
+
 bool AcceleratorControllerImpl::IsPreferred(
     const ui::Accelerator& accelerator) const {
   const AcceleratorAction* action_ptr = accelerators_.Find(accelerator);
diff --git a/ash/accelerators/accelerator_controller_impl.h b/ash/accelerators/accelerator_controller_impl.h
index d68bd0ff..24dbbe1 100644
--- a/ash/accelerators/accelerator_controller_impl.h
+++ b/ash/accelerators/accelerator_controller_impl.h
@@ -207,6 +207,8 @@
   bool OnMenuAccelerator(const ui::Accelerator& accelerator) override;
   bool IsRegistered(const ui::Accelerator& accelerator) const override;
   AcceleratorHistoryImpl* GetAcceleratorHistory() override;
+  bool DoesAcceleratorMatchAction(const ui::Accelerator& accelerator,
+                                  AcceleratorAction action) override;
 
   // Returns true if the |accelerator| is preferred. A preferred accelerator
   // is handled before being passed to an window/web contents, unless
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index 8ce6beb..b7a7a4a 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -39,6 +39,8 @@
 #include "ash/system/brightness_control_delegate.h"
 #include "ash/system/keyboard_brightness_control_delegate.h"
 #include "ash/system/power/power_button_controller_test_api.h"
+#include "ash/system/status_area_widget_test_helper.h"
+#include "ash/system/unified/unified_system_tray.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/test_media_client.h"
 #include "ash/wm/lock_state_controller.h"
@@ -1308,6 +1310,23 @@
   GetAppListTestHelper()->CheckVisibility(true);
 }
 
+TEST_F(AcceleratorControllerTest, GlobalAcceleratorsToggleQuickSettings) {
+  UnifiedSystemTray* tray =
+      StatusAreaWidgetTestHelper::GetStatusAreaWidget()->unified_system_tray();
+
+  auto* generator = GetEventGenerator();
+
+  // Pressing accelerator once should show the quick settings bubble.
+  generator->PressKey(ui::VKEY_S, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(tray->IsBubbleShown());
+
+  // Pressing accelerator a second time should dismiss the bubble.
+  generator->PressKey(ui::VKEY_S, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(tray->IsBubbleShown());
+}
+
 TEST_F(AcceleratorControllerTest, GlobalAcceleratorsToggleAppListFullscreen) {
   base::HistogramTester histogram_tester;
 
diff --git a/ash/display/display_move_window_util_unittest.cc b/ash/display/display_move_window_util_unittest.cc
index eaf3e8b..9039b7a 100644
--- a/ash/display/display_move_window_util_unittest.cc
+++ b/ash/display/display_move_window_util_unittest.cc
@@ -140,7 +140,7 @@
   EXPECT_EQ(GetDefaultLeftSnappedBoundsInDisplay(
                 screen->GetDisplayNearestWindow(window)),
             window->GetBoundsInScreen());
-  EXPECT_EQ(0.5f, *window_state->snapped_width_ratio());
+  EXPECT_EQ(0.5f, *window_state->snap_ratio());
   PerformMoveWindowAccel();
   EXPECT_EQ(display_manager()->GetDisplayAt(1).id(),
             screen->GetDisplayNearestWindow(window).id());
@@ -149,7 +149,7 @@
   EXPECT_EQ(GetDefaultLeftSnappedBoundsInDisplay(
                 screen->GetDisplayNearestWindow(window)),
             window->GetBoundsInScreen());
-  EXPECT_EQ(0.5f, *window_state->snapped_width_ratio());
+  EXPECT_EQ(0.5f, *window_state->snap_ratio());
 }
 
 // Tests that movement follows cycling through sorted display id list.
diff --git a/ash/public/cpp/accelerators.h b/ash/public/cpp/accelerators.h
index 9a6b2c4..bd57004 100644
--- a/ash/public/cpp/accelerators.h
+++ b/ash/public/cpp/accelerators.h
@@ -223,6 +223,11 @@
   // Returns the accelerator histotry.
   virtual AcceleratorHistory* GetAcceleratorHistory() = 0;
 
+  // Returns true if the provided accelerator matches the provided accelerator
+  // action.
+  virtual bool DoesAcceleratorMatchAction(const ui::Accelerator& accelerator,
+                                          const AcceleratorAction action) = 0;
+
  protected:
   AcceleratorController();
   virtual ~AcceleratorController();
diff --git a/ash/public/cpp/wallpaper/wallpaper_controller.h b/ash/public/cpp/wallpaper/wallpaper_controller.h
index 49684c6..7996110 100644
--- a/ash/public/cpp/wallpaper/wallpaper_controller.h
+++ b/ash/public/cpp/wallpaper/wallpaper_controller.h
@@ -49,7 +49,6 @@
   // Sets wallpaper from a local file and updates the saved wallpaper info for
   // the user.
   // |account_id|: The user's account id.
-  // |wallpaper_files_id|: The file id for |account_id|.
   // |file_path|: The path of the image file to read.
   // |layout|: The layout of the wallpaper, used for wallpaper resizing.
   // |preview_mode|: If true, show the wallpaper immediately but doesn't change
@@ -58,7 +57,6 @@
   // |callback|: called when the image is read from file and decoded.
   using SetCustomWallpaperCallback = base::OnceCallback<void(bool success)>;
   virtual void SetCustomWallpaper(const AccountId& account_id,
-                                  const std::string& wallpaper_files_id,
                                   const base::FilePath& file_path,
                                   WallpaperLayout layout,
                                   bool preview_mode,
@@ -67,7 +65,6 @@
   // Sets wallpaper from a local file and updates the saved wallpaper info for
   // the user.
   // |account_id|: The user's account id.
-  // |wallpaper_files_id|: The file id for |account_id|.
   // |file_name|: The name of the wallpaper file.
   // |layout|: The layout of the wallpaper, used for wallpaper resizing.
   // |image|: The wallpaper image.
@@ -75,7 +72,6 @@
   //                 the user wallpaper info until |ConfirmPreviewWallpaper| is
   //                 called.
   virtual void SetCustomWallpaper(const AccountId& account_id,
-                                  const std::string& wallpaper_files_id,
                                   const std::string& file_name,
                                   WallpaperLayout layout,
                                   const gfx::ImageSkia& image,
@@ -121,11 +117,9 @@
   // Sets the user's wallpaper to be the default wallpaper. Note: different user
   // types may have different default wallpapers.
   // |account_id|: The user's account id.
-  // |wallpaper_files_id|: The file id for |account_id|.
   // |show_wallpaper|: If false, don't show the new wallpaper now but only
   //                   update cache.
   virtual void SetDefaultWallpaper(const AccountId& account_id,
-                                   const std::string& wallpaper_files_id,
                                    bool show_wallpaper) = 0;
 
   // Sets the paths of the customized default wallpaper to be used wherever a
@@ -145,10 +139,8 @@
   // next time |ShowUserWallpaper| is called. Note: it is different from device
   // policy.
   // |account_id|: The user's account id.
-  // |wallpaper_files_id|: The file id for |account_id|.
   // |data|: The data used to decode the image.
   virtual void SetPolicyWallpaper(const AccountId& account_id,
-                                  const std::string& wallpaper_files_id,
                                   const std::string& data) = 0;
 
   // Sets the path of device policy wallpaper.
@@ -169,7 +161,6 @@
   // 1) the user is not permitted to change wallpaper, or
   // 2) updating the on-screen wallpaper is not allowed at the given moment.
   virtual bool SetThirdPartyWallpaper(const AccountId& account_id,
-                                      const std::string& wallpaper_files_id,
                                       const std::string& file_name,
                                       WallpaperLayout layout,
                                       const gfx::ImageSkia& image) = 0;
@@ -220,9 +211,7 @@
 
   // Removes all of the user's saved wallpapers and related info.
   // |account_id|: The user's account id.
-  // |wallpaper_files_id|: The file id for |account_id|.
-  virtual void RemoveUserWallpaper(const AccountId& account_id,
-                                   const std::string& wallpaper_files_id) = 0;
+  virtual void RemoveUserWallpaper(const AccountId& account_id) = 0;
 
   // Removes all of the user's saved wallpapers and related info if the
   // wallpaper was set by |SetPolicyWallpaper|. In addition, sets the user's
@@ -230,9 +219,7 @@
   // wallpaper immediately, otherwise, the default wallpaper will be shown the
   // next time |ShowUserWallpaper| is called.
   // |account_id|: The user's account id.
-  // |wallpaper_files_id|: The file id for |account_id|.
-  virtual void RemovePolicyWallpaper(const AccountId& account_id,
-                                     const std::string& wallpaper_files_id) = 0;
+  virtual void RemovePolicyWallpaper(const AccountId& account_id) = 0;
 
   // Returns the urls of the wallpapers that exist in local file system (i.e.
   // |SetOnlineWallpaperFromData| was called earlier). The url is used as id
diff --git a/ash/public/cpp/wallpaper/wallpaper_controller_client.h b/ash/public/cpp/wallpaper/wallpaper_controller_client.h
index 4e45be4..6c267aa 100644
--- a/ash/public/cpp/wallpaper/wallpaper_controller_client.h
+++ b/ash/public/cpp/wallpaper/wallpaper_controller_client.h
@@ -46,6 +46,10 @@
   // Returns true if image was successfully saved.
   virtual bool SaveWallpaperToDriveFs(const AccountId& account_id,
                                       const base::FilePath& origin) = 0;
+
+  virtual void GetFilesId(
+      const AccountId& account_id,
+      base::OnceCallback<void(const std::string&)> files_id_callback) const = 0;
 };
 
 }  // namespace ash
diff --git a/ash/system/tray/tray_bubble_view.cc b/ash/system/tray/tray_bubble_view.cc
index c6b9e255..7e0e49d 100644
--- a/ash/system/tray/tray_bubble_view.cc
+++ b/ash/system/tray/tray_bubble_view.cc
@@ -7,7 +7,9 @@
 #include <algorithm>
 #include <numeric>
 
+#include "ash/accelerators/accelerator_controller_impl.h"
 #include "ash/accessibility/accessibility_controller_impl.h"
+#include "ash/public/cpp/accelerators.h"
 #include "ash/shell.h"
 #include "ash/style/ash_color_provider.h"
 #include "ash/system/tray/tray_constants.h"
@@ -20,6 +22,7 @@
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/aura/env.h"
 #include "ui/aura/window.h"
+#include "ui/base/accelerators/accelerator.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_type.h"
@@ -141,6 +144,13 @@
 
 void TrayBubbleView::Delegate::HideBubble(const TrayBubbleView* bubble_view) {}
 
+absl::optional<AcceleratorAction>
+TrayBubbleView::Delegate::GetAcceleratorAction() const {
+  // TODO(crbug/1234891) Make this a pure virtual function so all
+  // bubble delegates need to specify accelerator actions.
+  return absl::nullopt;
+}
+
 TrayBubbleView::InitParams::InitParams() = default;
 
 TrayBubbleView::InitParams::InitParams(const InitParams& other) = default;
@@ -203,6 +213,18 @@
   ViewsDelegate::ProcessMenuAcceleratorResult result =
       ViewsDelegate::GetInstance()->ProcessAcceleratorWhileMenuShowing(
           accelerator);
+
+  // crbug/1212857 Do not manually close the bubble if the accelerator action
+  // is going to do it. The accelerator action is executed asynchronously
+  // because the accelerator may have to destroy the bubble view. So closing the
+  // bubble below will result in the accelerator action reopening the bubble.
+  if (tray_bubble_view_->GetAcceleratorAction().has_value() &&
+      AcceleratorControllerImpl::Get()->DoesAcceleratorMatchAction(
+          ui::Accelerator(*event),
+          tray_bubble_view_->GetAcceleratorAction().value())) {
+    return;
+  }
+
   if (result == ViewsDelegate::ProcessMenuAcceleratorResult::CLOSE_MENU)
     tray_bubble_view_->CloseBubbleView();
 }
@@ -324,6 +346,10 @@
   return bubble_border_ ? bubble_border_->GetInsets() : gfx::Insets();
 }
 
+absl::optional<AcceleratorAction> TrayBubbleView::GetAcceleratorAction() const {
+  return delegate_->GetAcceleratorAction();
+}
+
 void TrayBubbleView::ResetDelegate() {
   reroute_event_handler_.reset();
 
diff --git a/ash/system/tray/tray_bubble_view.h b/ash/system/tray/tray_bubble_view.h
index da80d38..5690ee8b 100644
--- a/ash/system/tray/tray_bubble_view.h
+++ b/ash/system/tray/tray_bubble_view.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "ash/ash_export.h"
+#include "ash/public/cpp/accelerators.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/system/status_area_widget.h"
 #include "base/macros.h"
@@ -67,6 +68,10 @@
     // child view was closed).
     virtual void HideBubble(const TrayBubbleView* bubble_view);
 
+    // Returns the accelerator action associated with the delegate's bubble
+    // view.
+    virtual absl::optional<AcceleratorAction> GetAcceleratorAction() const;
+
    private:
     DISALLOW_COPY_AND_ASSIGN(Delegate);
   };
@@ -129,6 +134,9 @@
   // Returns the border insets. Called by TrayEventFilter.
   gfx::Insets GetBorderInsets() const;
 
+  // Returns the accelerator action associated with this bubble view.
+  absl::optional<AcceleratorAction> GetAcceleratorAction() const;
+
   // Called when the delegate is destroyed. This must be called before the
   // delegate is actually destroyed. TrayBubbleView will do clean up in
   // ResetDelegate.
diff --git a/ash/system/unified/unified_system_tray.cc b/ash/system/unified/unified_system_tray.cc
index c8dd06a..207a7be 100644
--- a/ash/system/unified/unified_system_tray.cc
+++ b/ash/system/unified/unified_system_tray.cc
@@ -349,6 +349,11 @@
   return "UnifiedSystemTray";
 }
 
+absl::optional<AcceleratorAction> UnifiedSystemTray::GetAcceleratorAction()
+    const {
+  return absl::make_optional(TOGGLE_SYSTEM_TRAY_BUBBLE);
+}
+
 void UnifiedSystemTray::OnShelfConfigUpdated() {
   // Ensure the margin is updated correctly depending on whether dense shelf
   // is currently shown or not.
diff --git a/ash/system/unified/unified_system_tray.h b/ash/system/unified/unified_system_tray.h
index 18b3a10..6cab5ae 100644
--- a/ash/system/unified/unified_system_tray.h
+++ b/ash/system/unified/unified_system_tray.h
@@ -9,6 +9,7 @@
 #include <memory>
 
 #include "ash/ash_export.h"
+#include "ash/public/cpp/accelerators.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/system/tray/tray_background_view.h"
 #include "base/time/time.h"
@@ -152,6 +153,7 @@
   bool ShouldEnableExtraKeyboardAccessibility() override;
   views::Widget* GetBubbleWidget() const override;
   const char* GetClassName() const override;
+  absl::optional<AcceleratorAction> GetAcceleratorAction() const override;
 
   // ShelfConfig::Observer:
   void OnShelfConfigUpdated() override;
diff --git a/ash/test/ash_test_views_delegate.cc b/ash/test/ash_test_views_delegate.cc
index 6ae77b5..8c1b798a 100644
--- a/ash/test/ash_test_views_delegate.cc
+++ b/ash/test/ash_test_views_delegate.cc
@@ -4,11 +4,20 @@
 
 #include "ash/test/ash_test_views_delegate.h"
 
+#include "ash/accelerators/accelerator_controller_impl.h"
 #include "ash/shell.h"
 #include "chromeos/ui/frame/frame_utils.h"
 
 namespace ash {
 
+namespace {
+
+void ProcessAcceleratorNow(const ui::Accelerator& accelerator) {
+  ash::AcceleratorController::Get()->Process(accelerator);
+}
+
+}  // namespace
+
 AshTestViewsDelegate::AshTestViewsDelegate() = default;
 
 AshTestViewsDelegate::~AshTestViewsDelegate() = default;
@@ -28,10 +37,14 @@
 views::TestViewsDelegate::ProcessMenuAcceleratorResult
 AshTestViewsDelegate::ProcessAcceleratorWhileMenuShowing(
     const ui::Accelerator& accelerator) {
-  if (accelerator == close_menu_accelerator_)
-    return ProcessMenuAcceleratorResult::CLOSE_MENU;
+  if (ash::AcceleratorController::Get()->OnMenuAccelerator(accelerator)) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(ProcessAcceleratorNow, accelerator));
+    return views::ViewsDelegate::ProcessMenuAcceleratorResult::CLOSE_MENU;
+  }
 
-  return ProcessMenuAcceleratorResult::LEAVE_MENU_OPEN;
+  ProcessAcceleratorNow(accelerator);
+  return views::ViewsDelegate::ProcessMenuAcceleratorResult::LEAVE_MENU_OPEN;
 }
 
 }  // namespace ash
diff --git a/ash/wallpaper/test_wallpaper_controller_client.cc b/ash/wallpaper/test_wallpaper_controller_client.cc
index 4bd969c..1d35734 100644
--- a/ash/wallpaper/test_wallpaper_controller_client.cc
+++ b/ash/wallpaper/test_wallpaper_controller_client.cc
@@ -4,6 +4,7 @@
 
 #include "ash/wallpaper/test_wallpaper_controller_client.h"
 
+#include "base/logging.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
@@ -19,6 +20,7 @@
   fetch_daily_refresh_wallpaper_param_ = std::string();
   fetch_daily_refresh_info_fails_ = false;
   save_wallpaper_to_drive_fs_account_id.clear();
+  fake_files_ids_.clear();
 }
 
 // WallpaperControllerClient:
@@ -58,4 +60,15 @@
   return true;
 }
 
+void TestWallpaperControllerClient::GetFilesId(
+    const AccountId& account_id,
+    base::OnceCallback<void(const std::string&)> files_id_callback) const {
+  auto iter = fake_files_ids_.find(account_id);
+  if (iter == fake_files_ids_.end()) {
+    LOG(ERROR) << "No fake files id for account id: " << account_id;
+    return;
+  }
+  std::move(files_id_callback).Run(iter->second);
+}
+
 }  // namespace ash
diff --git a/ash/wallpaper/test_wallpaper_controller_client.h b/ash/wallpaper/test_wallpaper_controller_client.h
index d940733a..86bc4bb 100644
--- a/ash/wallpaper/test_wallpaper_controller_client.h
+++ b/ash/wallpaper/test_wallpaper_controller_client.h
@@ -7,6 +7,8 @@
 
 #include <stddef.h>
 
+#include <unordered_map>
+
 #include "ash/public/cpp/wallpaper/wallpaper_controller_client.h"
 #include "components/account_id/account_id.h"
 
@@ -40,6 +42,11 @@
     fetch_daily_refresh_info_fails_ = fails;
   }
 
+  void set_fake_files_id_for_account_id(const AccountId& account_id,
+                                        std::string fake_files_id) {
+    fake_files_ids_[account_id] = fake_files_id;
+  }
+
   void ResetCounts();
 
   // WallpaperControllerClient:
@@ -53,6 +60,9 @@
       DailyWallpaperUrlFetchedCallback callback) override;
   bool SaveWallpaperToDriveFs(const AccountId& account_id,
                               const base::FilePath& origin) override;
+  void GetFilesId(const AccountId& account_id,
+                  base::OnceCallback<void(const std::string&)>
+                      files_id_callback) const override;
 
  private:
   size_t open_count_ = 0;
@@ -62,6 +72,7 @@
   std::string fetch_daily_refresh_wallpaper_param_;
   bool fetch_daily_refresh_info_fails_ = false;
   AccountId save_wallpaper_to_drive_fs_account_id;
+  std::unordered_map<AccountId, std::string> fake_files_ids_;
 };
 
 }  // namespace ash
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index 0bd5bd7..8a7aa7c 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -998,21 +998,19 @@
 
 void WallpaperControllerImpl::SetCustomWallpaper(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const base::FilePath& file_path,
     WallpaperLayout layout,
     bool preview_mode,
     SetCustomWallpaperCallback callback) {
   ReadAndDecodeWallpaper(
       base::BindOnce(&WallpaperControllerImpl::OnCustomWallpaperDecoded,
-                     weak_factory_.GetWeakPtr(), account_id, wallpaper_files_id,
-                     file_path, layout, preview_mode, std::move(callback)),
+                     weak_factory_.GetWeakPtr(), account_id, file_path, layout,
+                     preview_mode, std::move(callback)),
       sequenced_task_runner_, file_path);
 }
 
 void WallpaperControllerImpl::SetCustomWallpaper(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const std::string& file_name,
     WallpaperLayout layout,
     const gfx::ImageSkia& image,
@@ -1024,11 +1022,10 @@
   const bool is_active_user = IsActiveUser(account_id);
   if (preview_mode) {
     DCHECK(is_active_user);
-    confirm_preview_wallpaper_callback_ =
-        base::BindOnce(&WallpaperControllerImpl::SaveAndSetWallpaper,
-                       weak_factory_.GetWeakPtr(), account_id,
-                       wallpaper_files_id, file_name, CUSTOMIZED, layout,
-                       /*show_wallpaper=*/false, image);
+    confirm_preview_wallpaper_callback_ = base::BindOnce(
+        &WallpaperControllerImpl::SaveAndSetWallpaper,
+        weak_factory_.GetWeakPtr(), account_id, file_name, CUSTOMIZED, layout,
+        /*show_wallpaper=*/false, image);
     reload_preview_wallpaper_callback_ = base::BindRepeating(
         &WallpaperControllerImpl::ShowWallpaperImage,
         weak_factory_.GetWeakPtr(), image,
@@ -1041,7 +1038,7 @@
     reload_preview_wallpaper_callback_.Run();
   } else {
     SaveAndSetWallpaperWithCompletion(
-        account_id, wallpaper_files_id, file_name, CUSTOMIZED, layout,
+        account_id, file_name, CUSTOMIZED, layout,
         /*show_wallpaper=*/is_active_user, image,
         base::BindOnce(&WallpaperControllerImpl::SaveWallpaperToDriveFs,
                        weak_factory_.GetWeakPtr(), account_id));
@@ -1111,14 +1108,12 @@
   DecodeImageData(std::move(decoded_callback), image_data);
 }
 
-void WallpaperControllerImpl::SetDefaultWallpaper(
-    const AccountId& account_id,
-    const std::string& wallpaper_files_id,
-    bool show_wallpaper) {
+void WallpaperControllerImpl::SetDefaultWallpaper(const AccountId& account_id,
+                                                  bool show_wallpaper) {
   if (!CanSetUserWallpaper(account_id))
     return;
 
-  RemoveUserWallpaper(account_id, wallpaper_files_id);
+  RemoveUserWallpaper(account_id);
   if (!InitializeUserWallpaperInfo(account_id)) {
     LOG(ERROR) << "Initializing user wallpaper info fails. This should never "
                   "happen except in tests.";
@@ -1145,7 +1140,6 @@
 
 void WallpaperControllerImpl::SetPolicyWallpaper(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const std::string& data) {
   // There is no visible wallpaper in kiosk mode.
   if (IsInKioskMode())
@@ -1155,8 +1149,8 @@
   const bool show_wallpaper = IsActiveUser(account_id);
   DecodeImageCallback callback = base::BindOnce(
       &WallpaperControllerImpl::SaveAndSetWallpaper, weak_factory_.GetWeakPtr(),
-      account_id, wallpaper_files_id, kPolicyWallpaperFile, POLICY,
-      WALLPAPER_LAYOUT_CENTER_CROPPED, show_wallpaper);
+      account_id, kPolicyWallpaperFile, POLICY, WALLPAPER_LAYOUT_CENTER_CROPPED,
+      show_wallpaper);
 
   if (bypass_decode_for_testing_) {
     std::move(callback).Run(CreateSolidColorWallpaper(kDefaultWallpaperColor));
@@ -1185,7 +1179,6 @@
 
 bool WallpaperControllerImpl::SetThirdPartyWallpaper(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const std::string& file_name,
     WallpaperLayout layout,
     const gfx::ImageSkia& image) {
@@ -1193,8 +1186,8 @@
   bool allowed_to_show_wallpaper = IsActiveUser(account_id);
 
   if (allowed_to_set_wallpaper) {
-    SaveAndSetWallpaper(account_id, wallpaper_files_id, file_name, CUSTOMIZED,
-                        layout, allowed_to_show_wallpaper, image);
+    SaveAndSetWallpaper(account_id, file_name, CUSTOMIZED, layout,
+                        allowed_to_show_wallpaper, image);
   }
   return allowed_to_set_wallpaper && allowed_to_show_wallpaper;
 }
@@ -1376,16 +1369,13 @@
   ReloadWallpaper(/*clear_cache=*/false);
 }
 
-void WallpaperControllerImpl::RemoveUserWallpaper(
-    const AccountId& account_id,
-    const std::string& wallpaper_files_id) {
+void WallpaperControllerImpl::RemoveUserWallpaper(const AccountId& account_id) {
   RemoveUserWallpaperInfo(account_id);
-  RemoveUserWallpaperImpl(account_id, wallpaper_files_id);
+  RemoveUserWallpaperImpl(account_id);
 }
 
 void WallpaperControllerImpl::RemovePolicyWallpaper(
-    const AccountId& account_id,
-    const std::string& wallpaper_files_id) {
+    const AccountId& account_id) {
   if (!IsPolicyControlled(account_id))
     return;
 
@@ -1395,7 +1385,7 @@
   // Removes the wallpaper info so that the user is no longer policy controlled,
   // otherwise setting default wallpaper is not allowed.
   RemoveUserWallpaperInfo(account_id);
-  SetDefaultWallpaper(account_id, wallpaper_files_id, show_wallpaper);
+  SetDefaultWallpaper(account_id, show_wallpaper);
 }
 
 void WallpaperControllerImpl::GetOfflineWallpaperList(
@@ -1743,6 +1733,20 @@
 }
 
 void WallpaperControllerImpl::RemoveUserWallpaperImpl(
+    const AccountId& account_id) {
+  if (wallpaper_controller_client_) {
+    wallpaper_controller_client_->GetFilesId(
+        account_id,
+        base::BindOnce(
+            &WallpaperControllerImpl::RemoveUserWallpaperImplWithFilesId,
+            weak_factory_.GetWeakPtr(), account_id));
+  } else {
+    LOG(ERROR) << "Failed to remove wallpaper. wallpaper_controller_client_ no "
+                  "longer exists.";
+  }
+}
+
+void WallpaperControllerImpl::RemoveUserWallpaperImplWithFilesId(
     const AccountId& account_id,
     const std::string& wallpaper_files_id) {
   if (wallpaper_files_id.empty())
@@ -2035,26 +2039,42 @@
 
 void WallpaperControllerImpl::SaveAndSetWallpaper(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const std::string& file_name,
     WallpaperType type,
     WallpaperLayout layout,
     bool show_wallpaper,
     const gfx::ImageSkia& image) {
-  SaveAndSetWallpaperWithCompletion(account_id, wallpaper_files_id, file_name,
-                                    type, layout, show_wallpaper, image,
-                                    base::DoNothing());
+  SaveAndSetWallpaperWithCompletion(account_id, file_name, type, layout,
+                                    show_wallpaper, image, base::DoNothing());
 }
 
 void WallpaperControllerImpl::SaveAndSetWallpaperWithCompletion(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const std::string& file_name,
     WallpaperType type,
     WallpaperLayout layout,
     bool show_wallpaper,
     const gfx::ImageSkia& image,
     FilePathCallback image_saved_callback) {
+  if (wallpaper_controller_client_) {
+    wallpaper_controller_client_->GetFilesId(
+        account_id,
+        base::BindOnce(
+            &WallpaperControllerImpl::SaveAndSetWallpaperWithCompletionFilesId,
+            weak_factory_.GetWeakPtr(), account_id, file_name, type, layout,
+            show_wallpaper, image, std::move(image_saved_callback)));
+  }
+}
+
+void WallpaperControllerImpl::SaveAndSetWallpaperWithCompletionFilesId(
+    const AccountId& account_id,
+    const std::string& file_name,
+    WallpaperType type,
+    WallpaperLayout layout,
+    bool show_wallpaper,
+    const gfx::ImageSkia& image,
+    FilePathCallback image_saved_callback,
+    const std::string& wallpaper_files_id) {
   // If the image of the new wallpaper is empty, the current wallpaper is still
   // kept instead of reverting to the default.
   if (image.isNull()) {
@@ -2114,7 +2134,6 @@
 
 void WallpaperControllerImpl::OnCustomWallpaperDecoded(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const base::FilePath& path,
     WallpaperLayout layout,
     bool preview_mode,
@@ -2122,8 +2141,8 @@
     const gfx::ImageSkia& image) {
   bool success = !image.isNull();
   if (success) {
-    SetCustomWallpaper(account_id, wallpaper_files_id, path.BaseName().value(),
-                       layout, image, preview_mode);
+    SetCustomWallpaper(account_id, path.BaseName().value(), layout, image,
+                       preview_mode);
   }
   std::move(callback).Run(success);
 }
@@ -2394,9 +2413,7 @@
       NOTIMPLEMENTED();
       break;
     case DEFAULT:
-      if (wallpaper_controller_client_) {
-        wallpaper_controller_client_->SetDefaultWallpaper(account_id, true);
-      }
+      SetDefaultWallpaper(account_id, /*show_wallpaper=*/true);
       break;
     case DAILY:
     case ONLINE:
diff --git a/ash/wallpaper/wallpaper_controller_impl.h b/ash/wallpaper/wallpaper_controller_impl.h
index 5a0abbd..df4e67c 100644
--- a/ash/wallpaper/wallpaper_controller_impl.h
+++ b/ash/wallpaper/wallpaper_controller_impl.h
@@ -236,13 +236,11 @@
             const base::FilePath& custom_wallpapers,
             const base::FilePath& device_policy_wallpaper) override;
   void SetCustomWallpaper(const AccountId& account_id,
-                          const std::string& wallpaper_files_id,
                           const base::FilePath& file_path,
                           WallpaperLayout layout,
                           bool preview_mode,
                           SetCustomWallpaperCallback callback) override;
   void SetCustomWallpaper(const AccountId& account_id,
-                          const std::string& wallpaper_files_id,
                           const std::string& file_name,
                           WallpaperLayout layout,
                           const gfx::ImageSkia& image,
@@ -255,18 +253,15 @@
                                   const std::string& image_data,
                                   SetOnlineWallpaperCallback callback) override;
   void SetDefaultWallpaper(const AccountId& account_id,
-                           const std::string& wallpaper_files_id,
                            bool show_wallpaper) override;
   void SetCustomizedDefaultWallpaperPaths(
       const base::FilePath& customized_default_small_path,
       const base::FilePath& customized_default_large_path) override;
   void SetPolicyWallpaper(const AccountId& account_id,
-                          const std::string& wallpaper_files_id,
                           const std::string& data) override;
   void SetDevicePolicyWallpaperPath(
       const base::FilePath& device_policy_wallpaper_path) override;
   bool SetThirdPartyWallpaper(const AccountId& account_id,
-                              const std::string& wallpaper_files_id,
                               const std::string& file_name,
                               WallpaperLayout layout,
                               const gfx::ImageSkia& image) override;
@@ -279,10 +274,8 @@
   void ShowOneShotWallpaper(const gfx::ImageSkia& image) override;
   void ShowAlwaysOnTopWallpaper(const base::FilePath& image_path) override;
   void RemoveAlwaysOnTopWallpaper() override;
-  void RemoveUserWallpaper(const AccountId& account_id,
-                           const std::string& wallpaper_files_id) override;
-  void RemovePolicyWallpaper(const AccountId& account_id,
-                             const std::string& wallpaper_files_id) override;
+  void RemoveUserWallpaper(const AccountId& account_id) override;
+  void RemovePolicyWallpaper(const AccountId& account_id) override;
   void GetOfflineWallpaperList(
       GetOfflineWallpaperListCallback callback) override;
   void SetAnimationDuration(base::TimeDelta animation_duration) override;
@@ -397,8 +390,11 @@
 
   // Implementation of |RemoveUserWallpaper|, which deletes |account_id|'s
   // custom wallpapers and directories.
-  void RemoveUserWallpaperImpl(const AccountId& account_id,
-                               const std::string& wallpaper_files_id);
+  void RemoveUserWallpaperImpl(const AccountId& account_id);
+
+  void RemoveUserWallpaperImplWithFilesId(
+      const AccountId& account_id,
+      const std::string& wallpaper_files_id);
 
   // Implementation of |SetDefaultWallpaper|. Sets wallpaper to default if
   // |show_wallpaper| is true. Otherwise just save the defaut wallpaper to
@@ -467,7 +463,6 @@
   // |show_wallpaper| is true, otherwise only sets the wallpaper info and
   // updates the cache.
   void SaveAndSetWallpaper(const AccountId& account_id,
-                           const std::string& wallpaper_files_id,
                            const std::string& file_name,
                            WallpaperType type,
                            WallpaperLayout layout,
@@ -477,7 +472,6 @@
   // |image_saved| is only called on success.
   void SaveAndSetWallpaperWithCompletion(
       const AccountId& account_id,
-      const std::string& wallpaper_files_id,
       const std::string& file_name,
       WallpaperType type,
       WallpaperLayout layout,
@@ -485,8 +479,17 @@
       const gfx::ImageSkia& image,
       base::OnceCallback<void(const base::FilePath&)> image_saved_callback);
 
+  void SaveAndSetWallpaperWithCompletionFilesId(
+      const AccountId& account_id,
+      const std::string& file_name,
+      WallpaperType type,
+      WallpaperLayout layout,
+      bool show_wallpaper,
+      const gfx::ImageSkia& image,
+      base::OnceCallback<void(const base::FilePath&)> image_saved_callback,
+      const std::string& wallpaper_files_id);
+
   void OnCustomWallpaperDecoded(const AccountId& account_id,
-                                const std::string& wallpaper_files_id,
                                 const base::FilePath& path,
                                 WallpaperLayout layout,
                                 bool preview_mode,
diff --git a/ash/wallpaper/wallpaper_controller_unittest.cc b/ash/wallpaper/wallpaper_controller_unittest.cc
index cdac5e4d..2dd071e 100644
--- a/ash/wallpaper/wallpaper_controller_unittest.cc
+++ b/ash/wallpaper/wallpaper_controller_unittest.cc
@@ -388,6 +388,12 @@
     base::FilePath policy_wallpaper;
     controller_->Init(user_data_dir_.GetPath(), online_wallpaper_dir_.GetPath(),
                       custom_wallpaper_dir_.GetPath(), policy_wallpaper);
+    client_.ResetCounts();
+    controller_->SetClient(&client_);
+    client_.set_fake_files_id_for_account_id(account_id_1,
+                                             wallpaper_files_id_1);
+    client_.set_fake_files_id_for_account_id(account_id_2,
+                                             wallpaper_files_id_2);
   }
 
   void TearDown() override {
@@ -479,6 +485,7 @@
   // can retrieve the images and info.
   void CreateAndSaveWallpapers(const AccountId& account_id) {
     std::string wallpaper_files_id = GetDummyFileId(account_id);
+
     std::string file_name = GetDummyFileName(account_id);
     base::FilePath small_wallpaper_path =
         GetCustomWallpaperPath(WallpaperControllerImpl::kSmallWallpaperSubDir,
@@ -638,10 +645,10 @@
 
   user_manager::FakeUserManager* fake_user_manager_ = nullptr;
   std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
+  TestWallpaperControllerClient client_;
   std::unique_ptr<TestImageDownloader> test_image_downloader_;
 
   const AccountId kChildAccountId = AccountId::FromUserEmail(kChildEmail);
-  const std::string kChildWallpaperFilesId = GetDummyFileId(kChildAccountId);
 
  private:
   data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
@@ -650,16 +657,13 @@
 };
 
 TEST_F(WallpaperControllerTest, Client) {
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   base::FilePath empty_path;
   controller_->Init(empty_path, empty_path, empty_path, empty_path);
 
-  EXPECT_EQ(0u, client.open_count());
+  EXPECT_EQ(0u, client_.open_count());
   EXPECT_TRUE(controller_->CanOpenWallpaperPicker());
   controller_->OpenWallpaperPickerIfAllowed();
-  EXPECT_EQ(1u, client.open_count());
+  EXPECT_EQ(1u, client_.open_count());
 }
 
 TEST_F(WallpaperControllerTest, BasicReparenting) {
@@ -976,16 +980,12 @@
   gfx::ImageSkia image = CreateImage(640, 480, kWallpaperColor);
   WallpaperLayout layout = WALLPAPER_LAYOUT_CENTER;
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SimulateUserLogin(kUser1);
 
   // Set a custom wallpaper for |kUser1|. Verify the wallpaper is set
   // successfully and wallpaper info is updated.
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, layout, image,
+  controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, image,
                                   false /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
@@ -997,7 +997,7 @@
       base::FilePath(wallpaper_files_id_1).Append(file_name_1).value(), layout,
       CUSTOMIZED, base::Time::Now().LocalMidnight());
   EXPECT_EQ(wallpaper_info, expected_wallpaper_info);
-  EXPECT_EQ(account_id_1, client.get_save_wallpaper_to_drive_fs_account_id());
+  EXPECT_EQ(account_id_1, client_.get_save_wallpaper_to_drive_fs_account_id());
 
   // Now set another custom wallpaper for |kUser1|. Verify that the on-screen
   // wallpaper doesn't change since |kUser1| is not active, but wallpaper info
@@ -1006,8 +1006,7 @@
   const SkColor custom_wallpaper_color = SK_ColorCYAN;
   image = CreateImage(640, 480, custom_wallpaper_color);
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, layout, image,
+  controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, image,
                                   false /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(0, GetWallpaperCount());
@@ -1069,9 +1068,9 @@
   // subsequent calls will be no-op since we intentionally prevent reloading the
   // same wallpaper.)
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(
-      account_id_1, wallpaper_files_id_1, file_name_1, layout,
-      CreateImage(640, 480, kWallpaperColor), /*preview_mode=*/false);
+  controller_->SetCustomWallpaper(account_id_1, file_name_1, layout,
+                                  CreateImage(640, 480, kWallpaperColor),
+                                  /*preview_mode=*/false);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), CUSTOMIZED);
@@ -1243,8 +1242,7 @@
   // Set a policy wallpaper. Verify that the user becomes policy controlled and
   // the wallpaper info is updated.
   ClearWallpaperCount();
-  controller_->SetPolicyWallpaper(account_id_1, wallpaper_files_id_1,
-                                  std::string() /*data=*/);
+  controller_->SetPolicyWallpaper(account_id_1, std::string() /*data=*/);
   RunAllTasksUntilIdle();
   EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &wallpaper_info));
   WallpaperInfo policy_wallpaper_info(base::FilePath(wallpaper_files_id_1)
@@ -1276,7 +1274,7 @@
   // Remove the policy wallpaper. Verify the wallpaper info is reset to default
   // and the user is no longer policy controlled.
   ClearWallpaperCount();
-  controller_->RemovePolicyWallpaper(account_id_1, wallpaper_files_id_1);
+  controller_->RemovePolicyWallpaper(account_id_1);
   WaitUntilCustomWallpapersDeleted(account_id_1);
   EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &wallpaper_info));
   WallpaperInfo default_wallpaper_info(std::string(),
@@ -1301,6 +1299,7 @@
   auto verify_custom_wallpaper_info = [&]() {
     EXPECT_EQ(CUSTOMIZED, controller_->GetWallpaperType());
     EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
+
     WallpaperInfo wallpaper_info;
     EXPECT_TRUE(
         controller_->GetUserWallpaperInfo(account_id_1, &wallpaper_info));
@@ -1315,7 +1314,7 @@
   SimulateUserLogin(kUser1);
   ClearWallpaperCount();
   controller_->SetCustomWallpaper(
-      account_id_1, wallpaper_files_id_1, file_name_1, WALLPAPER_LAYOUT_CENTER,
+      account_id_1, file_name_1, WALLPAPER_LAYOUT_CENTER,
       CreateImage(640, 480, kWallpaperColor), false /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
@@ -1324,7 +1323,7 @@
 
   // Verify RemovePolicyWallpaper() is a no-op when the user doesn't have a
   // policy wallpaper.
-  controller_->RemovePolicyWallpaper(account_id_1, wallpaper_files_id_1);
+  controller_->RemovePolicyWallpaper(account_id_1);
   RunAllTasksUntilIdle();
   verify_custom_wallpaper_info();
 }
@@ -1343,8 +1342,7 @@
   gfx::ImageSkia third_party_wallpaper = CreateImage(640, 480, kWallpaperColor);
   ClearWallpaperCount();
   EXPECT_TRUE(controller_->SetThirdPartyWallpaper(
-      account_id_1, wallpaper_files_id_1, file_name_1, layout,
-      third_party_wallpaper));
+      account_id_1, file_name_1, layout, third_party_wallpaper));
   // Verify the wallpaper is shown.
   EXPECT_EQ(1, GetWallpaperCount());
   // Verify the user wallpaper info is updated.
@@ -1360,22 +1358,20 @@
   SimulateUserLogin(kUser2);
   ClearWallpaperCount();
   EXPECT_FALSE(controller_->SetThirdPartyWallpaper(
-      account_id_1, wallpaper_files_id_2, file_name_2, layout,
-      third_party_wallpaper));
+      account_id_1, file_name_2, layout, third_party_wallpaper));
   // Verify the wallpaper is not shown.
   EXPECT_EQ(0, GetWallpaperCount());
   // Verify the wallpaper info for |kUser1| is updated, because setting
   // wallpaper is still allowed for non-active users.
   EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &wallpaper_info));
   WallpaperInfo expected_wallpaper_info_2(
-      base::FilePath(wallpaper_files_id_2).Append(file_name_2).value(), layout,
+      base::FilePath(wallpaper_files_id_1).Append(file_name_2).value(), layout,
       CUSTOMIZED, base::Time::Now().LocalMidnight());
   EXPECT_EQ(wallpaper_info, expected_wallpaper_info_2);
 
   // Set a policy wallpaper for |kUser2|. Verify that |kUser2| becomes policy
   // controlled.
-  controller_->SetPolicyWallpaper(account_id_2, wallpaper_files_id_2,
-                                  std::string() /*data=*/);
+  controller_->SetPolicyWallpaper(account_id_2, std::string() /*data=*/);
   RunAllTasksUntilIdle();
   EXPECT_TRUE(controller_->IsPolicyControlled(account_id_2));
   EXPECT_TRUE(controller_->IsActiveUserWallpaperControlledByPolicy());
@@ -1384,8 +1380,7 @@
   // third-party wallpapers cannot be set for policy controlled users.
   ClearWallpaperCount();
   EXPECT_FALSE(controller_->SetThirdPartyWallpaper(
-      account_id_2, wallpaper_files_id_1, file_name_1, layout,
-      third_party_wallpaper));
+      account_id_2, file_name_1, layout, third_party_wallpaper));
   // Verify the wallpaper is not shown.
   EXPECT_EQ(0, GetWallpaperCount());
   // Verify |kUser2| is still policy controlled and has the policy wallpaper
@@ -1421,8 +1416,7 @@
   RunAllTasksUntilIdle();
   ClearWallpaperCount();
   ClearDecodeFilePaths();
-  controller_->SetDefaultWallpaper(account_id_1, wallpaper_files_id_1,
-                                   true /*show_wallpaper=*/);
+  controller_->SetDefaultWallpaper(account_id_1, true /*show_wallpaper=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT);
@@ -1442,8 +1436,7 @@
   RunAllTasksUntilIdle();
   ClearWallpaperCount();
   ClearDecodeFilePaths();
-  controller_->SetDefaultWallpaper(account_id_1, wallpaper_files_id_1,
-                                   true /*show_wallpaper=*/);
+  controller_->SetDefaultWallpaper(account_id_1, true /*show_wallpaper=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT);
@@ -1463,8 +1456,7 @@
   RunAllTasksUntilIdle();
   ClearWallpaperCount();
   ClearDecodeFilePaths();
-  controller_->SetDefaultWallpaper(account_id_1, wallpaper_files_id_1,
-                                   true /*show_wallpaper=*/);
+  controller_->SetDefaultWallpaper(account_id_1, true /*show_wallpaper=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT);
@@ -1488,8 +1480,7 @@
   RunAllTasksUntilIdle();
   ClearWallpaperCount();
   ClearDecodeFilePaths();
-  controller_->SetDefaultWallpaper(kChildAccountId, kChildWallpaperFilesId,
-                                   true /*show_wallpaper=*/);
+  controller_->SetDefaultWallpaper(kChildAccountId, true /*show_wallpaper=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT);
@@ -1503,8 +1494,7 @@
   RunAllTasksUntilIdle();
   ClearWallpaperCount();
   ClearDecodeFilePaths();
-  controller_->SetDefaultWallpaper(kChildAccountId, kChildWallpaperFilesId,
-                                   true /*show_wallpaper=*/);
+  controller_->SetDefaultWallpaper(kChildAccountId, true /*show_wallpaper=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT);
@@ -1536,8 +1526,7 @@
   const AccountId guest_id =
       AccountId::FromUserEmail(user_manager::kGuestUserName);
   fake_user_manager_->AddGuestUser(guest_id);
-  controller_->SetDefaultWallpaper(guest_id, wallpaper_files_id_1,
-                                   /*show_wallpaper=*/true);
+  controller_->SetDefaultWallpaper(guest_id, /*show_wallpaper=*/true);
 
   WallpaperInfo wallpaper_info;
   WallpaperInfo default_wallpaper_info(std::string(),
@@ -1556,12 +1545,11 @@
   // Second, set a user policy for which is being set for another
   // user and verifying that the policy has been applied successfully.
   WallpaperInfo policy_wallpaper_info;
-  controller_->SetPolicyWallpaper(account_id_1, wallpaper_files_id_2,
-                                  /*data=*/std::string());
+  controller_->SetPolicyWallpaper(account_id_1, /*data=*/std::string());
   EXPECT_TRUE(
       controller_->GetUserWallpaperInfo(account_id_1, &policy_wallpaper_info));
   WallpaperInfo expected_policy_wallpaper_info(
-      base::FilePath(wallpaper_files_id_2)
+      base::FilePath(wallpaper_files_id_1)
           .Append("policy-controlled.jpeg")
           .value(),
       WALLPAPER_LAYOUT_CENTER_CROPPED, POLICY,
@@ -1605,8 +1593,7 @@
   RunAllTasksUntilIdle();
   ClearWallpaperCount();
   ClearDecodeFilePaths();
-  controller_->SetDefaultWallpaper(guest_id, wallpaper_files_id_1,
-                                   true /*show_wallpaper=*/);
+  controller_->SetDefaultWallpaper(guest_id, true /*show_wallpaper=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT);
@@ -1620,8 +1607,7 @@
   RunAllTasksUntilIdle();
   ClearWallpaperCount();
   ClearDecodeFilePaths();
-  controller_->SetDefaultWallpaper(guest_id, wallpaper_files_id_1,
-                                   true /*show_wallpaper=*/);
+  controller_->SetDefaultWallpaper(guest_id, true /*show_wallpaper=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT);
@@ -1637,8 +1623,8 @@
   // Verify that |SetCustomWallpaper| doesn't set wallpaper in kiosk mode, and
   // |account_id_1|'s wallpaper info is not updated.
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, WALLPAPER_LAYOUT_CENTER, image,
+  controller_->SetCustomWallpaper(account_id_1, file_name_1,
+                                  WALLPAPER_LAYOUT_CENTER, image,
                                   false /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(0, GetWallpaperCount());
@@ -1668,8 +1654,7 @@
   // Verify that |SetDefaultWallpaper| doesn't set wallpaper in kiosk mode, and
   // |account_id_1|'s wallpaper info is not updated.
   ClearWallpaperCount();
-  controller_->SetDefaultWallpaper(account_id_1, wallpaper_files_id_1,
-                                   true /*show_wallpaper=*/);
+  controller_->SetDefaultWallpaper(account_id_1, true /*show_wallpaper=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(0, GetWallpaperCount());
   EXPECT_FALSE(
@@ -1688,16 +1673,15 @@
   SimulateUserLogin(kUser1);
 
   // Set a policy wallpaper for the user. Verify the user is policy controlled.
-  controller_->SetPolicyWallpaper(account_id_1, wallpaper_files_id_1,
-                                  std::string() /*data=*/);
+  controller_->SetPolicyWallpaper(account_id_1, std::string() /*data=*/);
   RunAllTasksUntilIdle();
   EXPECT_TRUE(controller_->IsPolicyControlled(account_id_1));
 
   // Verify that |SetCustomWallpaper| doesn't set wallpaper when policy is
   // enforced, and the user wallpaper info is not updated.
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, WALLPAPER_LAYOUT_CENTER, image,
+  controller_->SetCustomWallpaper(account_id_1, file_name_1,
+                                  WALLPAPER_LAYOUT_CENTER, image,
                                   false /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(0, GetWallpaperCount());
@@ -1732,8 +1716,7 @@
   // Verify that |SetDefaultWallpaper| doesn't set wallpaper when policy is
   // enforced, and the user wallpaper info is not updated.
   ClearWallpaperCount();
-  controller_->SetDefaultWallpaper(account_id_1, wallpaper_files_id_1,
-                                   true /*show_wallpaper=*/);
+  controller_->SetDefaultWallpaper(account_id_1, true /*show_wallpaper=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(0, GetWallpaperCount());
   EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &wallpaper_info));
@@ -1774,15 +1757,14 @@
   EXPECT_TRUE(controller_->GetPathFromCache(account_id_1, &path));
 
   // Verify |SetDefaultWallpaper| clears wallpaper cache.
-  controller_->SetDefaultWallpaper(account_id_1, wallpaper_files_id_1,
-                                   true /*show_wallpaper=*/);
+  controller_->SetDefaultWallpaper(account_id_1, true /*show_wallpaper=*/);
   EXPECT_FALSE(
       controller_->GetWallpaperFromCache(account_id_1, &cached_wallpaper));
   EXPECT_FALSE(controller_->GetPathFromCache(account_id_1, &path));
 
   // Verify |SetCustomWallpaper| updates wallpaper cache for |user1|.
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, WALLPAPER_LAYOUT_CENTER, image,
+  controller_->SetCustomWallpaper(account_id_1, file_name_1,
+                                  WALLPAPER_LAYOUT_CENTER, image,
                                   false /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_TRUE(
@@ -1790,7 +1772,7 @@
   EXPECT_TRUE(controller_->GetPathFromCache(account_id_1, &path));
 
   // Verify |RemoveUserWallpaper| clears wallpaper cache.
-  controller_->RemoveUserWallpaper(account_id_1, wallpaper_files_id_1);
+  controller_->RemoveUserWallpaper(account_id_1);
   EXPECT_FALSE(
       controller_->GetWallpaperFromCache(account_id_1, &cached_wallpaper));
   EXPECT_FALSE(controller_->GetPathFromCache(account_id_1, &path));
@@ -1921,7 +1903,7 @@
   WindowState::Get(wallpaper_picker_window.get())->Activate();
   ClearWallpaperCount();
   controller_->SetCustomWallpaper(
-      account_id_1, wallpaper_files_id_1, file_name_1, WALLPAPER_LAYOUT_CENTER,
+      account_id_1, file_name_1, WALLPAPER_LAYOUT_CENTER,
       CreateImage(640, 480, kWallpaperColor), true /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
@@ -1962,8 +1944,7 @@
   // Set a custom wallpaper for the user. Verify that it's set successfully
   // and the wallpaper info is updated.
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, layout, image,
+  controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, image,
                                   false /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
@@ -2026,6 +2007,7 @@
   base::FilePath small_wallpaper_path_1 =
       GetCustomWallpaperPath(WallpaperControllerImpl::kSmallWallpaperSubDir,
                              wallpaper_files_id_1, file_name_1);
+
   // Set a custom wallpaper for |kUser1| and verify the wallpaper exists.
   CreateAndSaveWallpapers(account_id_1);
   EXPECT_TRUE(base::PathExists(small_wallpaper_path_1));
@@ -2039,7 +2021,7 @@
   EXPECT_TRUE(base::PathExists(small_wallpaper_path_2));
 
   // Simulate the removal of |kUser2|.
-  controller_->RemoveUserWallpaper(account_id_2, wallpaper_files_id_2);
+  controller_->RemoveUserWallpaper(account_id_2);
   // Wait until all files under the user's custom wallpaper directory are
   // removed.
   WaitUntilCustomWallpapersDeleted(account_id_2);
@@ -2062,11 +2044,10 @@
 
   // Now login another user and set a default wallpaper for the user.
   SimulateUserLogin(kUser2);
-  controller_->SetDefaultWallpaper(account_id_2, wallpaper_files_id_2,
-                                   true /*show_wallpaper=*/);
+  controller_->SetDefaultWallpaper(account_id_2, true /*show_wallpaper=*/);
 
   // Simulate the removal of |kUser2|.
-  controller_->RemoveUserWallpaper(account_id_2, wallpaper_files_id_2);
+  controller_->RemoveUserWallpaper(account_id_2);
 
   // Verify that the other user's wallpaper is not affected.
   EXPECT_TRUE(base::PathExists(small_wallpaper_path_1));
@@ -2083,8 +2064,7 @@
   EXPECT_FALSE(controller_->IsActiveUserWallpaperControlledByPolicy());
   // Set a policy wallpaper for the active user. Verify that the active user
   // becomes policy controlled.
-  controller_->SetPolicyWallpaper(account_id_1, wallpaper_files_id_1,
-                                  std::string() /*data=*/);
+  controller_->SetPolicyWallpaper(account_id_1, std::string() /*data=*/);
   RunAllTasksUntilIdle();
   EXPECT_TRUE(controller_->IsActiveUserWallpaperControlledByPolicy());
 
@@ -2326,9 +2306,6 @@
 }
 
 TEST_F(WallpaperControllerTest, ClosePreviewWallpaperOnOverviewStart) {
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   // Verify the user starts with a default wallpaper and the user wallpaper info
   // is initialized with default values.
   SimulateUserLogin(kUser1);
@@ -2355,9 +2332,8 @@
   gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor);
   EXPECT_NE(kWallpaperColor, GetWallpaperColor());
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, layout, custom_wallpaper,
-                                  true /*preview_mode=*/);
+  controller_->SetCustomWallpaper(account_id_1, file_name_1, layout,
+                                  custom_wallpaper, true /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
@@ -2377,13 +2353,10 @@
   EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT);
   EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
   EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
-  EXPECT_EQ(1u, client.close_preview_count());
+  EXPECT_EQ(1u, client_.close_preview_count());
 }
 
 TEST_F(WallpaperControllerTest, ClosePreviewWallpaperOnWindowCycleStart) {
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   // Verify the user starts with a default wallpaper and the user wallpaper info
   // is initialized with default values.
   SimulateUserLogin(kUser1);
@@ -2410,9 +2383,8 @@
   gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor);
   EXPECT_NE(kWallpaperColor, GetWallpaperColor());
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, layout, custom_wallpaper,
-                                  true /*preview_mode=*/);
+  controller_->SetCustomWallpaper(account_id_1, file_name_1, layout,
+                                  custom_wallpaper, true /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
@@ -2432,7 +2404,7 @@
   EXPECT_EQ(controller_->GetWallpaperType(), DEFAULT);
   EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
   EXPECT_TRUE(Shell::Get()->window_cycle_controller()->IsCycling());
-  EXPECT_EQ(1u, client.close_preview_count());
+  EXPECT_EQ(1u, client_.close_preview_count());
 }
 
 TEST_F(WallpaperControllerTest, ConfirmPreviewWallpaper) {
@@ -2462,9 +2434,8 @@
   gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor);
   EXPECT_NE(kWallpaperColor, GetWallpaperColor());
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, layout, custom_wallpaper,
-                                  true /*preview_mode=*/);
+  controller_->SetCustomWallpaper(account_id_1, file_name_1, layout,
+                                  custom_wallpaper, true /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
@@ -2481,6 +2452,7 @@
   RunAllTasksUntilIdle();
   EXPECT_EQ(0, GetWallpaperCount());
   EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
+
   // Verify that the user wallpaper info is now updated to the custom wallpaper
   // info.
   WallpaperInfo custom_wallpaper_info(
@@ -2571,9 +2543,8 @@
   gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor);
   EXPECT_NE(kWallpaperColor, GetWallpaperColor());
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, layout, custom_wallpaper,
-                                  true /*preview_mode=*/);
+  controller_->SetCustomWallpaper(account_id_1, file_name_1, layout,
+                                  custom_wallpaper, true /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
@@ -2650,9 +2621,8 @@
   gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor);
   EXPECT_NE(kWallpaperColor, GetWallpaperColor());
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, layout, custom_wallpaper,
-                                  true /*preview_mode=*/);
+  controller_->SetCustomWallpaper(account_id_1, file_name_1, layout,
+                                  custom_wallpaper, true /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
@@ -2669,15 +2639,15 @@
   gfx::ImageSkia synced_custom_wallpaper =
       CreateImage(640, 480, synced_custom_wallpaper_color);
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_2,
-                                  file_name_2, layout, synced_custom_wallpaper,
+  controller_->SetCustomWallpaper(account_id_1, file_name_2, layout,
+                                  synced_custom_wallpaper,
                                   false /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(0, GetWallpaperCount());
   EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
   // However, the user wallpaper info should already be updated to the new info.
   WallpaperInfo synced_custom_wallpaper_info(
-      base::FilePath(wallpaper_files_id_2).Append(file_name_2).value(), layout,
+      base::FilePath(wallpaper_files_id_1).Append(file_name_2).value(), layout,
       CUSTOMIZED, base::Time::Now().LocalMidnight());
   EXPECT_TRUE(
       controller_->GetUserWallpaperInfo(account_id_1, &user_wallpaper_info));
@@ -2812,13 +2782,13 @@
 TEST_F(WallpaperControllerTest, ShowOneShotWallpaper) {
   gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor);
   WallpaperLayout layout = WALLPAPER_LAYOUT_CENTER;
+
   SimulateUserLogin(kUser1);
   // First, set a custom wallpaper for |kUser1|. Verify the wallpaper is shown
   // successfully and the user wallpaper info is updated.
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, layout, custom_wallpaper,
-                                  false /*preview_mode=*/);
+  controller_->SetCustomWallpaper(account_id_1, file_name_1, layout,
+                                  custom_wallpaper, false /*preview_mode=*/);
   RunAllTasksUntilIdle();
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
@@ -2910,8 +2880,8 @@
   EXPECT_FALSE(controller_->GetPathFromCache(account_id_1, &path));
 
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, WALLPAPER_LAYOUT_CENTER,
+  controller_->SetCustomWallpaper(account_id_1, file_name_1,
+                                  WALLPAPER_LAYOUT_CENTER,
                                   CreateImage(640, 480, kWallpaperColor),
                                   /*preview_mode=*/false);
   RunAllTasksUntilIdle();
@@ -3097,9 +3067,6 @@
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   WallpaperInfo expected_info = InfoWithType(CUSTOMIZED);
   PutWallpaperInfoInPrefs(account_id_1, expected_info, GetLocalPrefService(),
                           prefs::kUserWallpaperInfo);
@@ -3114,9 +3081,6 @@
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SetBypassDecode();
 
   PutWallpaperInfoInPrefs(account_id_1, InfoWithType(ONLINE),
@@ -3137,9 +3101,6 @@
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SimulateUserLogin(account_id_1.GetUserEmail());
   PutWallpaperInfoInPrefs(account_id_1, InfoWithType(DEFAULT),
                           GetProfilePrefService(account_id_1),
@@ -3148,34 +3109,32 @@
   PutWallpaperInfoInPrefs(account_id_1, InfoWithType(THIRDPARTY),
                           GetLocalPrefService(), prefs::kUserWallpaperInfo);
 
-  client.ResetCounts();
+  client_.ResetCounts();
 
   controller_->OnActiveUserPrefServiceChanged(
       GetProfilePrefService(account_id_1));
-  EXPECT_EQ(client.set_default_wallpaper_count(), 1u);
+  WallpaperInfo actual_info;
+  EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &actual_info));
+  EXPECT_EQ(DEFAULT, actual_info.type);
 }
 
 TEST_F(WallpaperControllerTest, HandleWallpaperInfoSyncedDefault) {
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SimulateUserLogin(kUser1);
-  EXPECT_EQ(client.set_default_wallpaper_count(), 0u);
   PutWallpaperInfoInPrefs(account_id_1, InfoWithType(DEFAULT),
                           GetProfilePrefService(account_id_1),
                           prefs::kSyncableWallpaperInfo);
-  EXPECT_EQ(client.set_default_wallpaper_count(), 1u);
+  WallpaperInfo actual_info;
+  EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &actual_info));
+  EXPECT_EQ(DEFAULT, actual_info.type);
 }
 
 TEST_F(WallpaperControllerTest, HandleWallpaperInfoSyncedLocalIsPolicy) {
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
   PutWallpaperInfoInPrefs(account_id_1, InfoWithType(POLICY),
                           GetLocalPrefService(), prefs::kUserWallpaperInfo);
 
@@ -3183,15 +3142,15 @@
   PutWallpaperInfoInPrefs(account_id_1, InfoWithType(DEFAULT),
                           GetProfilePrefService(account_id_1),
                           prefs::kSyncableWallpaperInfo);
-  EXPECT_EQ(client.set_default_wallpaper_count(), 0u);
+  WallpaperInfo actual_info;
+  EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &actual_info));
+  EXPECT_NE(DEFAULT, actual_info.type);
 }
 
 TEST_F(WallpaperControllerTest, HandleWallpaperInfoSyncedLocalIsThirdParty) {
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
   PutWallpaperInfoInPrefs(account_id_1, InfoWithType(THIRDPARTY),
                           GetLocalPrefService(), prefs::kUserWallpaperInfo);
 
@@ -3199,16 +3158,15 @@
   PutWallpaperInfoInPrefs(account_id_1, InfoWithType(DEFAULT),
                           GetProfilePrefService(account_id_1),
                           prefs::kSyncableWallpaperInfo);
-  EXPECT_EQ(client.set_default_wallpaper_count(), 1u);
+  WallpaperInfo actual_info;
+  EXPECT_TRUE(controller_->GetUserWallpaperInfo(account_id_1, &actual_info));
+  EXPECT_EQ(DEFAULT, actual_info.type);
 }
 
 TEST_F(WallpaperControllerTest, HandleWallpaperInfoSyncedOnline) {
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SetBypassDecode();
   SimulateUserLogin(kUser1);
 
@@ -3228,10 +3186,9 @@
   // subsequent calls will be no-op since we intentionally prevent reloading the
   // same wallpaper.)
   ClearWallpaperCount();
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, WALLPAPER_LAYOUT_CENTER_CROPPED,
-                                  CreateImage(640, 480, kWallpaperColor),
-                                  false /*preview_mode=*/);
+  controller_->SetCustomWallpaper(
+      account_id_1, file_name_1, WALLPAPER_LAYOUT_CENTER_CROPPED,
+      CreateImage(640, 480, kWallpaperColor), false /*preview_mode=*/);
   RunAllTasksUntilIdle();
 
   // Attempt to set an online wallpaper without providing the image data. Verify
@@ -3253,11 +3210,8 @@
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SimulateUserLogin(account_id_1.GetUserEmail());
-  EXPECT_EQ(client.migrate_collection_id_from_chrome_app_count(), 1u);
+  EXPECT_EQ(client_.migrate_collection_id_from_chrome_app_count(), 1u);
 }
 
 TEST_F(
@@ -3266,26 +3220,20 @@
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SimulateUserLogin(account_id_1.GetUserEmail());
   controller_->SetDailyRefreshCollectionId("fun_collection");
 
-  EXPECT_EQ(client.migrate_collection_id_from_chrome_app_count(), 1u);
+  EXPECT_EQ(client_.migrate_collection_id_from_chrome_app_count(), 1u);
 
   ClearLogin();
   SimulateUserLogin(account_id_1.GetUserEmail());
-  EXPECT_EQ(client.migrate_collection_id_from_chrome_app_count(), 1u);
+  EXPECT_EQ(client_.migrate_collection_id_from_chrome_app_count(), 1u);
 }
 
 TEST_F(WallpaperControllerTest, SetDailyRefreshCollectionId) {
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   std::string expected{"fun_collection"};
   SimulateUserLogin(kUser1);
   controller_->SetDailyRefreshCollectionId(expected);
@@ -3298,9 +3246,6 @@
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SimulateUserLogin(kUser1);
   controller_->SetDailyRefreshCollectionId(std::string());
   PrefService* pref_service = GetProfilePrefService(account_id_1);
@@ -3312,9 +3257,6 @@
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   std::string expected{"fun_collection"};
   SimulateUserLogin(kUser1);
 
@@ -3324,16 +3266,13 @@
                                   DayBeforeYesterdayish()));
 
   controller_->UpdateDailyRefreshWallpaperForTesting();
-  EXPECT_EQ(expected, client.get_fetch_daily_refresh_wallpaper_param());
+  EXPECT_EQ(expected, client_.get_fetch_daily_refresh_wallpaper_param());
 }
 
 TEST_F(WallpaperControllerTest, UpdateDailyRefreshWallpaperCalledOnLogin) {
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   std::string expected{"fun_collection"};
   SimulateUserLogin(kUser1);
 
@@ -3349,23 +3288,20 @@
   // within the task that it is called in.
   RunAllTasksUntilIdle();
 
-  EXPECT_EQ(expected, client.get_fetch_daily_refresh_wallpaper_param());
+  EXPECT_EQ(expected, client_.get_fetch_daily_refresh_wallpaper_param());
 }
 
 TEST_F(WallpaperControllerTest, UpdateDailyRefreshWallpaper_NotEnabled) {
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SimulateUserLogin(kUser1);
   controller_->SetUserWallpaperInfo(
       account_id_1, WallpaperInfo(std::string(), WALLPAPER_LAYOUT_CENTER, DAILY,
                                   DayBeforeYesterdayish()));
 
   controller_->UpdateDailyRefreshWallpaperForTesting();
-  EXPECT_EQ(std::string(), client.get_fetch_daily_refresh_wallpaper_param());
+  EXPECT_EQ(std::string(), client_.get_fetch_daily_refresh_wallpaper_param());
 }
 
 TEST_F(WallpaperControllerTest,
@@ -3373,9 +3309,6 @@
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   using base::Time;
   using base::TimeDelta;
 
@@ -3409,9 +3342,7 @@
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-  client.set_fetch_daily_refresh_info_fails(true);
+  client_.set_fetch_daily_refresh_info_fails(true);
 
   SimulateUserLogin(kUser1);
   controller_->SetDailyRefreshCollectionId("fun_collection");
@@ -3438,9 +3369,6 @@
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SimulateUserLogin(kUser1);
   controller_->SetDailyRefreshCollectionId("fun_collection");
   controller_->SetUserWallpaperInfo(
@@ -3467,12 +3395,9 @@
   gfx::ImageSkia image = CreateImage(640, 480, kWallpaperColor);
   WallpaperLayout layout = WALLPAPER_LAYOUT_CENTER;
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
   SimulateUserLogin(kUser1);
 
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, layout, image,
+  controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, image,
                                   false /*preview_mode=*/);
   RunAllTasksUntilIdle();
   ClearLogin();
@@ -3481,7 +3406,7 @@
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
 
   SimulateUserLogin(account_id_1.GetUserEmail());
-  EXPECT_EQ(account_id_1, client.get_save_wallpaper_to_drive_fs_account_id());
+  EXPECT_EQ(account_id_1, client_.get_save_wallpaper_to_drive_fs_account_id());
 }
 
 TEST_F(WallpaperControllerTest, OnGoogleDriveMounted) {
@@ -3492,12 +3417,9 @@
   PutWallpaperInfoInPrefs(account_id_1, local_info, GetLocalPrefService(),
                           prefs::kUserWallpaperInfo);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SimulateUserLogin(kUser1);
   controller_->OnGoogleDriveMounted();
-  EXPECT_EQ(account_id_1, client.get_save_wallpaper_to_drive_fs_account_id());
+  EXPECT_EQ(account_id_1, client_.get_save_wallpaper_to_drive_fs_account_id());
 }
 
 TEST_F(WallpaperControllerTest, OnGoogleDriveMounted_WallpaperIsntCustom) {
@@ -3508,11 +3430,8 @@
   PutWallpaperInfoInPrefs(account_id_1, local_info, GetLocalPrefService(),
                           prefs::kUserWallpaperInfo);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   controller_->OnGoogleDriveMounted();
-  EXPECT_TRUE(client.get_save_wallpaper_to_drive_fs_account_id().empty());
+  EXPECT_TRUE(client_.get_save_wallpaper_to_drive_fs_account_id().empty());
 }
 
 TEST_F(WallpaperControllerTest, OnGoogleDriveMounted_AlreadySynced) {
@@ -3523,24 +3442,20 @@
   PutWallpaperInfoInPrefs(account_id_1, local_info, GetLocalPrefService(),
                           prefs::kUserWallpaperInfo);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SimulateUserLogin(kUser1);
 
   gfx::ImageSkia image = CreateImage(640, 480, kWallpaperColor);
   WallpaperLayout layout = WALLPAPER_LAYOUT_CENTER;
 
-  controller_->SetCustomWallpaper(account_id_1, wallpaper_files_id_1,
-                                  file_name_1, layout, image,
+  controller_->SetCustomWallpaper(account_id_1, file_name_1, layout, image,
                                   false /*preview_mode=*/);
   RunAllTasksUntilIdle();
 
-  client.ResetCounts();
+  client_.ResetCounts();
 
   // Should not reupload image if it has already been synced.
   controller_->OnGoogleDriveMounted();
-  EXPECT_FALSE(client.get_save_wallpaper_to_drive_fs_account_id().is_valid());
+  EXPECT_FALSE(client_.get_save_wallpaper_to_drive_fs_account_id().is_valid());
 }
 
 TEST_F(WallpaperControllerTest, OnGoogleDriveMounted_OldLocalInfo) {
@@ -3560,13 +3475,10 @@
                           GetProfilePrefService(account_id_1),
                           prefs::kSyncableWallpaperInfo);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SimulateUserLogin(kUser1);
 
   controller_->OnGoogleDriveMounted();
-  EXPECT_FALSE(client.get_save_wallpaper_to_drive_fs_account_id().is_valid());
+  EXPECT_FALSE(client_.get_save_wallpaper_to_drive_fs_account_id().is_valid());
 }
 
 TEST_F(WallpaperControllerTest, OnGoogleDriveMounted_NewLocalInfo) {
@@ -3586,13 +3498,10 @@
                           GetProfilePrefService(account_id_1),
                           prefs::kSyncableWallpaperInfo);
 
-  TestWallpaperControllerClient client;
-  controller_->SetClient(&client);
-
   SimulateUserLogin(kUser1);
 
   controller_->OnGoogleDriveMounted();
-  EXPECT_EQ(account_id_1, client.get_save_wallpaper_to_drive_fs_account_id());
+  EXPECT_EQ(account_id_1, client_.get_save_wallpaper_to_drive_fs_account_id());
 }
 
 }  // namespace ash
diff --git a/ash/wm/window_positioning_utils.cc b/ash/wm/window_positioning_utils.cc
index 42646f1..4e6fa19 100644
--- a/ash/wm/window_positioning_utils.cc
+++ b/ash/wm/window_positioning_utils.cc
@@ -150,8 +150,8 @@
   }
 
   // Compute size of the side of the window bound that should be proportional
-  // |WindowState::snapped_width_ratio_| to that of the work area, i.e. width
-  // for horizontal layout and height for vertical layout.
+  // |WindowState::snap_ratio_| to that of the work area, i.e. width for
+  // horizontal layout and height for vertical layout.
   gfx::Rect snap_bounds = gfx::Rect(work_area);
   const int work_area_axis_length =
       is_horizontal ? work_area.width() : work_area.height();
diff --git a/ash/wm/window_state.cc b/ash/wm/window_state.cc
index 5a1314a..544a7e54 100644
--- a/ash/wm/window_state.cc
+++ b/ash/wm/window_state.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "ash/constants/ash_constants.h"
+#include "ash/constants/ash_features.h"
 #include "ash/focus_cycler.h"
 #include "ash/metrics/pip_uma.h"
 #include "ash/public/cpp/app_types_util.h"
@@ -149,9 +150,14 @@
   return WM_EVENT_NORMAL;
 }
 
-float GetCurrentSnappedWidthRatio(aura::Window* window) {
+float GetCurrentSnapRatio(aura::Window* window) {
   gfx::Rect maximized_bounds =
       screen_util::GetMaximizedWindowBoundsInParent(window);
+  if (features::IsVerticalSplitScreenEnabled() &&
+      !SplitViewController::IsLayoutHorizontal()) {
+    return static_cast<float>(window->GetTargetBounds().height()) /
+           static_cast<float>(maximized_bounds.height());
+  }
   return static_cast<float>(window->GetTargetBounds().width()) /
          static_cast<float>(maximized_bounds.width());
 }
@@ -407,7 +413,7 @@
 void WindowState::OnWMEvent(const WMEvent* event) {
   current_state_->OnWMEvent(this, event);
 
-  UpdateSnappedWidthRatio(event);
+  UpdateSnapRatio(event);
 
   PersistentDesksBarController* bar_controller =
       Shell::Get()->persistent_desks_bar_controller();
@@ -514,28 +520,26 @@
   return old_object;
 }
 
-void WindowState::UpdateSnappedWidthRatio(const WMEvent* event) {
+void WindowState::UpdateSnapRatio(const WMEvent* event) {
   if (!IsSnapped()) {
-    snapped_width_ratio_.reset();
+    snap_ratio_.reset();
     return;
   }
 
   const WMEventType type = event->type();
-  // Initializes |snapped_width_ratio_| whenever |event| is snapping event.
+  // Initializes |snap_ratio_| whenever |event| is snapping event.
   if (type == WM_EVENT_SNAP_PRIMARY || type == WM_EVENT_SNAP_SECONDARY ||
       type == WM_EVENT_CYCLE_SNAP_PRIMARY ||
       type == WM_EVENT_CYCLE_SNAP_SECONDARY) {
-    // Since |UpdateSnappedWidthRatio()| is called post WMEvent taking effect,
+    // Since |UpdateSnapRatio()| is called post WMEvent taking effect,
     // |window_|'s bounds is in a correct state for ratio update.
-    snapped_width_ratio_ =
-        absl::make_optional(GetCurrentSnappedWidthRatio(window_));
+    snap_ratio_ = absl::make_optional(GetCurrentSnapRatio(window_));
     return;
   }
 
-  // |snapped_width_ratio_| under snapped state may change due to bounds event.
+  // |snap_ratio_| under snapped state may change due to bounds event.
   if (event->IsBoundsEvent()) {
-    snapped_width_ratio_ =
-        absl::make_optional(GetCurrentSnappedWidthRatio(window_));
+    snap_ratio_ = absl::make_optional(GetCurrentSnapRatio(window_));
   }
 }
 
@@ -685,22 +689,44 @@
 }
 
 void WindowState::AdjustSnappedBounds(gfx::Rect* bounds) {
-  if (is_dragged() || !IsSnapped())
+  auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller();
+  const bool in_tablet =
+      tablet_mode_controller && tablet_mode_controller->InTabletMode();
+
+  // Tablet mode should use bounds calculation in SplitViewController.
+  // However, transient state from transitioning clamshell to tablet mode
+  // might end up calling this function during work area changes, so we avoid
+  // unnecessary task in that case when it will be overwritten by tablet mode
+  // work.
+  if (is_dragged() || !IsSnapped() || in_tablet)
     return;
   gfx::Rect maximized_bounds =
       screen_util::GetMaximizedWindowBoundsInParent(window_);
-  // TODO(crbug.com/1233342): Update snap width ratio to snap ratio to support
-  // vertical split.
-  if (snapped_width_ratio_) {
-    bounds->set_width(
-        static_cast<int>(*snapped_width_ratio_ * maximized_bounds.width()));
-  }
-  if (GetStateType() == WindowStateType::kPrimarySnapped)
-    bounds->set_x(maximized_bounds.x());
-  else if (GetStateType() == WindowStateType::kSecondarySnapped)
-    bounds->set_x(maximized_bounds.right() - bounds->width());
-  bounds->set_y(maximized_bounds.y());
-  bounds->set_height(maximized_bounds.height());
+
+  const display::Display display =
+      display::Screen::GetScreen()->GetDisplayNearestWindow(window_);
+
+  // For snapped window, `GetSnappedWindowBounds` computes bounds position
+  // from snap type and size from |snap_ratio|.
+  gfx::Rect snapped_bounds =
+      snap_ratio_ ? GetSnappedWindowBounds(
+                        maximized_bounds, window_,
+                        GetStateType() == WindowStateType::kPrimarySnapped
+                            ? ash::SnapViewType::kPrimary
+                            : ash::SnapViewType::kSecondary,
+                        *snap_ratio_)
+                  : maximized_bounds;
+  bounds->set_origin(snapped_bounds.origin());
+
+  // If |snap_ratio_| exists adjust the size of the window. Otherwise only
+  // maximize it vertically for horizontal screen and maximize horizontally for
+  // vertical screen.
+  if (snap_ratio_)
+    bounds->set_size(snapped_bounds.size());
+  else if (display.size().width() > display.size().height())
+    bounds->set_height(snapped_bounds.height());
+  else
+    bounds->set_width(snapped_bounds.width());
 }
 
 void WindowState::UpdateWindowPropertiesFromStateType() {
diff --git a/ash/wm/window_state.h b/ash/wm/window_state.h
index dcb780d..5d95eaa 100644
--- a/ash/wm/window_state.h
+++ b/ash/wm/window_state.h
@@ -213,11 +213,9 @@
   // by this object and the returned object will be owned by the caller.
   std::unique_ptr<State> SetStateObject(std::unique_ptr<State> new_state);
 
-  // Updates |snapped_width_ratio_| based on |event|.
-  void UpdateSnappedWidthRatio(const WMEvent* event);
-  absl::optional<float> snapped_width_ratio() const {
-    return snapped_width_ratio_;
-  }
+  // Updates |snap_ratio_| based on |event|.
+  void UpdateSnapRatio(const WMEvent* event);
+  absl::optional<float> snap_ratio() const { return snap_ratio_; }
 
   // True if the window should be unminimized to the restore bounds, as
   // opposed to the window's current bounds. |unminimized_to_restore_bounds_| is
@@ -410,8 +408,8 @@
   void SetBoundsInScreen(const gfx::Rect& bounds_in_screen);
 
   // Adjusts the |bounds| so that they are flush with the edge of the
-  // workspace if the window represented by |window_state| is side snapped. It
-  // is called for workspace events.
+  // workspace in clamshell mode if the window represented by |window_state|
+  // is side snapped. It is called for workspace events.
   void AdjustSnappedBounds(gfx::Rect* bounds);
 
   // Updates the window properties(show state, pin type) according to the
@@ -485,10 +483,11 @@
   ui::ZOrderLevel cached_z_order_;
   bool allow_set_bounds_direct_ = false;
 
-  // A property to save the ratio between snapped window width and display
-  // workarea width. It is used to update snapped window width on
-  // AdjustSnappedBounds() when handling workspace events.
-  absl::optional<float> snapped_width_ratio_;
+  // A property to save the ratio between snapped window width (or height
+  // for vertical layout) and display workarea width (or height). It is used
+  // to update snapped window width (or height) on AdjustSnappedBounds() when
+  // handling workspace events.
+  absl::optional<float> snap_ratio_;
 
   // A property to remember the window position which was set before the
   // auto window position manager changed the window bounds, so that it can
diff --git a/ash/wm/window_state_unittest.cc b/ash/wm/window_state_unittest.cc
index eaf6ebd..281b0bd7 100644
--- a/ash/wm/window_state_unittest.cc
+++ b/ash/wm/window_state_unittest.cc
@@ -352,7 +352,7 @@
   EXPECT_EQ(work_area_size.ToString(), window->bounds().size().ToString());
 }
 
-// Tests UpdateSnappedWidthRatio. (1) It should have ratio reset when window
+// Tests UpdateSnapRatio. (1) It should have ratio reset when window
 // enters snapped state; (2) it should update ratio on bounds event when
 // snapped.
 TEST_F(WindowStateTest, UpdateSnapWidthRatioTest) {
@@ -371,7 +371,7 @@
       gfx::Rect(kWorkAreaBounds.x(), kWorkAreaBounds.y(),
                 kWorkAreaBounds.width() / 2, kWorkAreaBounds.height());
   EXPECT_EQ(expected, window->GetBoundsInScreen());
-  EXPECT_EQ(0.5f, *window_state->snapped_width_ratio());
+  EXPECT_EQ(0.5f, *window_state->snap_ratio());
 
   // Drag to change snapped window width.
   const int kIncreasedWidth = 225;
@@ -384,18 +384,18 @@
   expected.set_width(expected.width() + kIncreasedWidth);
   EXPECT_EQ(expected, window->GetBoundsInScreen());
   EXPECT_EQ(WindowStateType::kPrimarySnapped, window_state->GetStateType());
-  EXPECT_EQ(0.75f, *window_state->snapped_width_ratio());
+  EXPECT_EQ(0.75f, *window_state->snap_ratio());
 
   // Another cycle snap left event will restore window state to normal.
   window_state->OnWMEvent(&cycle_snap_left);
   EXPECT_EQ(WindowStateType::kNormal, window_state->GetStateType());
-  EXPECT_FALSE(window_state->snapped_width_ratio());
+  EXPECT_FALSE(window_state->snap_ratio());
 
   // Another cycle snap left event will snap window and reset snapped width
   // ratio.
   window_state->OnWMEvent(&cycle_snap_left);
   EXPECT_EQ(WindowStateType::kPrimarySnapped, window_state->GetStateType());
-  EXPECT_EQ(0.5f, *window_state->snapped_width_ratio());
+  EXPECT_EQ(0.5f, *window_state->snap_ratio());
 }
 
 // Tests that dragging and snapping the snapped window update the width ratio
@@ -429,7 +429,7 @@
   window->layer()->GetAnimator()->Step(base::TimeTicks::Now() +
                                        base::TimeDelta::FromSeconds(1));
   EXPECT_EQ(expected, window->GetBoundsInScreen());
-  EXPECT_EQ(0.5f, *window_state->snapped_width_ratio());
+  EXPECT_EQ(0.5f, *window_state->snap_ratio());
 
   // Drag the window to unsnap but do not release.
   ui::test::EventGenerator* generator = GetEventGenerator();
@@ -438,7 +438,7 @@
   generator->MoveMouseBy(5, 0);
   // While dragged, the window size should restore to its normal bound.
   EXPECT_EQ(window_normal_size, window->bounds().size());
-  EXPECT_EQ(1.0f, *window_state->snapped_width_ratio());
+  EXPECT_EQ(1.0f, *window_state->snap_ratio());
 
   // Continue dragging the window and snap it back to the same position.
   generator->MoveMouseBy(-405, 0);
@@ -446,7 +446,7 @@
 
   // The snapped ratio should be correct regardless of whether the animation
   // is finished or not.
-  EXPECT_EQ(0.5f, *window_state->snapped_width_ratio());
+  EXPECT_EQ(0.5f, *window_state->snap_ratio());
 }
 
 // Test that snapping left/right preserves the restore bounds.
diff --git a/ash/wm/workspace/multi_window_resize_controller_unittest.cc b/ash/wm/workspace/multi_window_resize_controller_unittest.cc
index 3c7323bd..eb72f3d 100644
--- a/ash/wm/workspace/multi_window_resize_controller_unittest.cc
+++ b/ash/wm/workspace/multi_window_resize_controller_unittest.cc
@@ -609,8 +609,8 @@
   const WMEvent snap_right(WM_EVENT_SNAP_SECONDARY);
   w2_state->OnWMEvent(&snap_right);
   EXPECT_EQ(WindowStateType::kSecondarySnapped, w2_state->GetStateType());
-  EXPECT_EQ(0.5f, *w1_state->snapped_width_ratio());
-  EXPECT_EQ(0.5f, *w2_state->snapped_width_ratio());
+  EXPECT_EQ(0.5f, *w1_state->snap_ratio());
+  EXPECT_EQ(0.5f, *w2_state->snap_ratio());
 
   ui::test::EventGenerator* generator = GetEventGenerator();
   generator->MoveMouseTo(w1->bounds().CenterPoint());
@@ -644,8 +644,8 @@
   EXPECT_EQ(gfx::Rect(0, 0, 300, bottom_inset), w1->bounds());
   EXPECT_EQ(WindowStateType::kSecondarySnapped, w2_state->GetStateType());
   EXPECT_EQ(gfx::Rect(300, 0, 100, bottom_inset), w2->bounds());
-  EXPECT_EQ(0.75f, *w1_state->snapped_width_ratio());
-  EXPECT_EQ(0.25f, *w2_state->snapped_width_ratio());
+  EXPECT_EQ(0.75f, *w1_state->snap_ratio());
+  EXPECT_EQ(0.25f, *w2_state->snap_ratio());
 
   // Dragging should call the WindowStateDelegate.
   EXPECT_EQ(HTRIGHT, window_state_delegate1->GetComponentAndReset());
diff --git a/base/files/file_path.h b/base/files/file_path.h
index e11d706c..e7ef1df 100644
--- a/base/files/file_path.h
+++ b/base/files/file_path.h
@@ -272,6 +272,11 @@
   //   - calling this function with "foo.tar.blah" will return just ".blah"
   //     until ".*.blah" is added to the hard-coded allow-list.
   //
+  // That hard-coded allow-list is case-insensitive: ".GZ" and ".gz" are
+  // equivalent. However, the StringType returned is not canonicalized for
+  // case: "foo.TAR.bz2" input will produce ".TAR.bz2", not ".tar.bz2", and
+  // "bar.EXT", which is not a double-extension, will produce ".EXT".
+  //
   // The following code should always work regardless of the value of path.
   //   new_path = path.RemoveExtension().value().append(path.Extension());
   //   ASSERT(new_path == path.value());
diff --git a/base/files/file_path_unittest.cc b/base/files/file_path_unittest.cc
index 9e99a41..c5ba954 100644
--- a/base/files/file_path_unittest.cc
+++ b/base/files/file_path_unittest.cc
@@ -771,6 +771,8 @@
     { FPL("C:\\foo.bar\\.."),        FPL("") },
     { FPL("C:\\foo.bar\\..\\\\"),    FPL("") },
 #endif
+    { FPL("/foo/bar/baz.EXT"),       FPL(".EXT") },
+    { FPL("/foo/bar/baz.Ext"),       FPL(".Ext") },
     { FPL("/foo/bar/baz.ext"),       FPL(".ext") },
     { FPL("/foo/bar/baz."),          FPL(".") },
     { FPL("/foo/bar/baz.."),         FPL(".") },
@@ -795,6 +797,7 @@
   const struct UnaryTestData double_extension_cases[] = {
     // `kCommonDoubleExtensionSuffixes` cases. Blah is not on that allow-list.
     // Membership is (ASCII) case-insensitive: both ".Z" and ".z" match.
+    { FPL("/foo.TAR.bz2"),           FPL(".TAR.bz2") },
     { FPL("/foo.tar.Z"),             FPL(".tar.Z") },
     { FPL("/foo.tar.blah"),          FPL(".blah") },
     { FPL("/foo.tar.bz"),            FPL(".tar.bz") },
diff --git a/build/OWNERS.setnoparent b/build/OWNERS.setnoparent
index 99554a4..02d1d28 100644
--- a/build/OWNERS.setnoparent
+++ b/build/OWNERS.setnoparent
@@ -28,6 +28,9 @@
 # expose to the open web.
 file://third_party/blink/API_OWNERS
 
+# third_party/blink/web_tests/VirtualTestSuites need special care.
+file://third_party/blink/web_tests/VIRTUAL_OWNERS
+
 # Extension related files.
 file://chrome/browser/extensions/component_extensions_allowlist/EXTENSION_ALLOWLIST_OWNERS
 file://extensions/common/api/API_OWNERS
@@ -64,4 +67,4 @@
 # Changes to the CQ configuration can have a significant impact on the CQ cost
 # and performance. Approval should be limited to a small subset of the users
 # that can make infra changes.
-file://infra/config/groups/cq-usage/CQ_USAGE_OWNERS
\ No newline at end of file
+file://infra/config/groups/cq-usage/CQ_USAGE_OWNERS
diff --git a/chrome/VERSION b/chrome/VERSION
index b2f39e3..96f61c8f 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=94
 MINOR=0
-BUILD=4597
+BUILD=4598
 PATCH=0
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 0b0a7be..636ab80a8 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -530,6 +530,8 @@
   "java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java",
   "java/src/org/chromium/chrome/browser/download/DownloadItem.java",
   "java/src/org/chromium/chrome/browser/download/DownloadManagerService.java",
+  "java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java",
+  "java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java",
   "java/src/org/chromium/chrome/browser/download/DownloadMetrics.java",
   "java/src/org/chromium/chrome/browser/download/DownloadNotificationFactory.java",
   "java/src/org/chromium/chrome/browser/download/DownloadNotificationService.java",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index 4295afff..a56765d 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -181,6 +181,7 @@
   "javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java",
   "javatests/src/org/chromium/chrome/browser/download/DownloadLocationChangeEnd2EndTest.java",
   "javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java",
+  "javatests/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerTest.java",
   "javatests/src/org/chromium/chrome/browser/download/DownloadNotificationServiceTest.java",
   "javatests/src/org/chromium/chrome/browser/download/DownloadTest.java",
   "javatests/src/org/chromium/chrome/browser/download/DownloadTestRule.java",
diff --git a/chrome/android/expectations/lint-suppressions.xml b/chrome/android/expectations/lint-suppressions.xml
index 3746207..8563dc91 100644
--- a/chrome/android/expectations/lint-suppressions.xml
+++ b/chrome/android/expectations/lint-suppressions.xml
@@ -202,6 +202,16 @@
     <ignore regexp="The resource `R.string.translate_dont_offer_site` appears to be unused"/>
     <ignore regexp="The resource `R.string.translate_dont_offer_lang` appears to be unused"/>
 
+    <!-- crbug.com/1231370 remove this line and the following 8 lines after the bug is resolved -->
+    <ignore regexp="The resource `R.string.download_message_download_in_progress_description` appears to be unused"/>
+    <ignore regexp="The resource `R.string.download_message_download_complete_description` appears to be unused"/>
+    <ignore regexp="The resource `R.plurals.download_message_multiple_download_in_progress` appears to be unused"/>
+    <ignore regexp="The resource `R.plurals.download_message_multiple_download_complete` appears to be unused"/>
+    <ignore regexp="The resource `R.plurals.download_message_multiple_download_failed` appears to be unused"/>
+    <ignore regexp="The resource `R.plurals.download_message_multiple_download_pending` appears to be unused"/>
+    <ignore regexp="The resource `R.plurals.download_message_multiple_download_scheduled` appears to be unused"/>
+    <ignore regexp="The resource `R.string.download_message_download_scheduled_description` appears to be unused"/>
+
     <!-- Old-style and new-style WebAPKs use same resources for simplicity. Old-style WebAPKs do
          not use R.style.SplashTheme but new-style WebAPKs do.
          TODO(crbug.com/971254): Remove suppression once old-style WebAPKs are deprecated. -->
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AccessorySheetTabViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AccessorySheetTabViewTest.java
index 796d9fb..666823e 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AccessorySheetTabViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AccessorySheetTabViewTest.java
@@ -50,7 +50,7 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class AccessorySheetTabViewTest {
-    private final AccessorySheetTabModel mModel = new AccessorySheetTabModel();
+    private AccessorySheetTabModel mModel;
     private AtomicReference<RecyclerView> mView = new AtomicReference<>();
 
     @Rule
@@ -65,6 +65,7 @@
     private void openLayoutInAccessorySheet(
             @LayoutRes int layout, KeyboardAccessoryData.Tab.Listener listener) {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mModel = new AccessorySheetTabModel();
             AccessorySheetCoordinator accessorySheet =
                     new AccessorySheetCoordinator(mActivityTestRule.getActivity().findViewById(
                             R.id.keyboard_accessory_sheet_stub));
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
index 60bacba..7f24d951 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
@@ -53,7 +53,7 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class AddressAccessorySheetViewTest {
-    private final AccessorySheetTabModel mModel = new AccessorySheetTabModel();
+    private AccessorySheetTabModel mModel;
     private AtomicReference<RecyclerView> mView = new AtomicReference<>();
 
     @Rule
@@ -63,6 +63,8 @@
     public void setUp() throws InterruptedException {
         mActivityTestRule.startMainActivityOnBlankPage();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mModel = new AccessorySheetTabModel();
+
             AccessorySheetCoordinator accessorySheet =
                     new AccessorySheetCoordinator(mActivityTestRule.getActivity().findViewById(
                             R.id.keyboard_accessory_sheet_stub));
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java
index 1399cda..892ae24 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java
@@ -51,7 +51,7 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class PasswordAccessorySheetViewTest {
-    private final AccessorySheetTabModel mModel = new AccessorySheetTabModel();
+    private AccessorySheetTabModel mModel;
     private AtomicReference<RecyclerView> mView = new AtomicReference<>();
 
     @Rule
@@ -66,6 +66,7 @@
     private void openLayoutInAccessorySheet(
             @LayoutRes int layout, KeyboardAccessoryData.Tab.Listener listener) {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mModel = new AccessorySheetTabModel();
             AccessorySheetCoordinator accessorySheet =
                     new AccessorySheetCoordinator(mActivityTestRule.getActivity().findViewById(
                             R.id.keyboard_accessory_sheet_stub));
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
index d27a327..9bd4c349 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/InstantStartTest.java
@@ -1285,7 +1285,8 @@
         StartSurfaceTestUtils.createThumbnailBitmapAndWriteToFile(0);
         TabAttributeCache.setTitleForTesting(0, "Google");
 
-        HomepageManager.getInstance().setPrefHomepageEnabled(false);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> HomepageManager.getInstance().setPrefHomepageEnabled(false));
         Assert.assertFalse(HomepageManager.isHomepageEnabled());
 
         // Launches Chrome and verifies that the Tab switcher is showing.
@@ -1308,7 +1309,8 @@
         StartSurfaceTestUtils.createThumbnailBitmapAndWriteToFile(0);
         TabAttributeCache.setTitleForTesting(0, "Google");
 
-        HomepageManager.getInstance().setPrefHomepageEnabled(false);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> HomepageManager.getInstance().setPrefHomepageEnabled(false));
         Assert.assertFalse(HomepageManager.isHomepageEnabled());
 
         // Launches Chrome and verifies that the last visited Tab is showing.
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderTest.java
index ceaa1cf..7bbc5254 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderTest.java
@@ -295,7 +295,7 @@
 
     @Override
     public void tearDownTest() throws Exception {
-        mMCP.destroy();
+        TestThreadUtils.runOnUiThreadBlocking(mMCP::destroy);
         super.tearDownTest();
     }
 }
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
index 5029caaa..d985c3bd 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
@@ -858,11 +858,12 @@
         while (i < newContentList.size()) {
             NtpListContentManager.FeedContent content = newContentList.get(i);
 
-            // If this is an existing content, moves it to new position.
+            // If this is an existing content, moves it to new position, offset by header count.
             if (existingContentMap.containsKey(content.getKey())) {
                 hasContentChange = true;
                 mContentManager.moveContent(
-                        mContentManager.findContentPositionByKey(content.getKey()), i);
+                        mContentManager.findContentPositionByKey(content.getKey()),
+                        i + mHeaderCount);
                 ++i;
                 continue;
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index 3fbda4b..8c06113 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -104,7 +104,6 @@
 import org.chromium.chrome.browser.dependency_injection.ModuleFactoryOverrides;
 import org.chromium.chrome.browser.device.DeviceClassManager;
 import org.chromium.chrome.browser.dom_distiller.DomDistillerUIUtils;
-import org.chromium.chrome.browser.download.DownloadManagerService;
 import org.chromium.chrome.browser.download.DownloadUtils;
 import org.chromium.chrome.browser.download.items.OfflineContentAggregatorNotificationBridgeUiFactory;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
@@ -1655,7 +1654,6 @@
         mNativeInitialized = true;
         OfflineContentAggregatorNotificationBridgeUiFactory.instance();
         maybeRemoveWindowBackground();
-        DownloadManagerService.getDownloadManagerService().onActivityLaunched();
 
         VrModuleProvider.maybeInit();
         VrModuleProvider.getDelegate().onNativeLibraryAvailable();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
index a81ee8b..1a3ed473 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadInfoBarController.java
@@ -68,7 +68,7 @@
  * This class is responsible for tracking various updates for download items, offline items, android
  * downloads and computing the the state of the {@link DownloadProgressInfoBar} .
  */
-public class DownloadInfoBarController implements OfflineContentProvider.Observer {
+public class DownloadInfoBarController implements DownloadMessageUiController {
     private static final String SPEEDING_UP_MESSAGE_ENABLED = "speeding_up_message_enabled";
     private static final long DURATION_ACCELERATED_INFOBAR_IN_MS = 3000;
     private static final long DURATION_SHOW_RESULT_IN_MS = 6000;
@@ -317,16 +317,12 @@
         mHandler.post(() -> getOfflineContentProvider().addObserver(this));
     }
 
-    /**
-     * Shows the message that download has started. Unlike other methods in this class, this
-     * method doesn't require an {@link OfflineItem} and is invoked by the backend to provide a
-     * responsive feedback to the users even before the download has actually started.
-     */
+    @Override
     public void onDownloadStarted() {
         computeNextStepForUpdate(null, true, false, false);
     }
 
-    /** Updates the InfoBar when new information about a download comes in. */
+    @Override
     public void onDownloadItemUpdated(DownloadItem downloadItem) {
         if (mUseNewDownloadPath) return;
 
@@ -346,14 +342,13 @@
         computeNextStepForUpdate(offlineItem);
     }
 
-    /** Updates the InfoBar after a download has been removed. */
+    @Override
     public void onDownloadItemRemoved(ContentId contentId) {
         if (mUseNewDownloadPath) return;
         onItemRemoved(contentId);
     }
 
-    /** Associates a notification ID with the tracked download for future usage. */
-    // TODO(shaktisahu): Find an alternative way after moving to offline content provider.
+    @Override
     public void onNotificationShown(ContentId id, int notificationId) {
         mNotificationIds.put(id, notificationId);
     }
@@ -374,7 +369,6 @@
                 });
     }
 
-    // OfflineContentProvider.Observer implementation.
     @Override
     public void onItemsAdded(List<OfflineItem> items) {
         for (OfflineItem item : items) {
@@ -409,7 +403,7 @@
         computeNextStepForUpdate(item);
     }
 
-    /** @return Whether the infobar is currently showing. */
+    @Override
     public boolean isShowing() {
         return mCurrentInfoBar != null;
     }
@@ -835,6 +829,7 @@
     @VisibleForTesting
     protected void showInfoBar(@DownloadInfoBarState int state, DownloadProgressInfoBarData info) {
         if (!ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_PROGRESS_INFOBAR)) return;
+        assert !ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_PROGRESS_MESSAGE);
 
         mCurrentInfo = info;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
index 5db3edd..4743e6c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.download;
 
 import android.annotation.SuppressLint;
+import android.app.Activity;
 import android.app.DownloadManager;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
@@ -55,6 +56,7 @@
 import org.chromium.components.external_intents.ExternalNavigationHandler;
 import org.chromium.components.feature_engagement.EventConstants;
 import org.chromium.components.feature_engagement.Tracker;
+import org.chromium.components.messages.MessageDispatcher;
 import org.chromium.components.offline_items_collection.ContentId;
 import org.chromium.components.offline_items_collection.FailState;
 import org.chromium.components.offline_items_collection.LegacyHelpers;
@@ -67,6 +69,7 @@
 import org.chromium.net.ConnectionType;
 import org.chromium.net.NetworkChangeNotifierAutoDetect;
 import org.chromium.net.RegistrationPolicyAlwaysRegister;
+import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.widget.Toast;
 
 import java.io.File;
@@ -142,6 +145,9 @@
     private DownloadInfoBarController mInfoBarController;
     private HashMap<OTRProfileID, DownloadInfoBarController> mIncognitoInfoBarControllerMap =
             new HashMap<>();
+    private DownloadMessageUiController mMessageUiController;
+    private HashMap<OTRProfileID, DownloadMessageUiController> mIncognitoMessageUiControllerMap =
+            new HashMap<>();
     private long mNativeDownloadManagerService;
     private NetworkChangeNotifierAutoDetect mNetworkChangeNotifier;
     // Flag to track if we need to post a task to update download notifications.
@@ -278,21 +284,22 @@
         return mDownloadNotifier;
     }
 
-    /** @return The {@link DownloadInfoBarController} controller associated with the profile. */
-    public DownloadInfoBarController getInfoBarController(OTRProfileID otrProfileID) {
-        if (!OTRProfileID.isOffTheRecord(otrProfileID)) return mInfoBarController;
+    /** @return The {@link DownloadMessageUiController} controller associated with the profile. */
+    public DownloadMessageUiController getInfoBarController(OTRProfileID otrProfileID) {
+        boolean showMessageUi =
+                ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_PROGRESS_MESSAGE);
+        if (!OTRProfileID.isOffTheRecord(otrProfileID)) {
+            return showMessageUi ? mMessageUiController : mInfoBarController;
+        }
 
-        // If the OTR profile does not exist, we should not create regarding info bar controller.
+        // If the OTR profile does not exist, we should not create the controller.
         if (!Profile.getLastUsedRegularProfile().hasOffTheRecordProfile(otrProfileID)) {
             return null;
         }
 
-        DownloadInfoBarController controller = mIncognitoInfoBarControllerMap.get(otrProfileID);
-        if (controller == null) {
-            controller = new DownloadInfoBarController(otrProfileID);
-            mIncognitoInfoBarControllerMap.put(otrProfileID, controller);
-        }
-        return controller;
+        // TODO(shaktisahu, sideyilmaz): If we have multiple OTR profiles, will this be ever null?
+        return showMessageUi ? mIncognitoMessageUiControllerMap.get(otrProfileID)
+                             : mIncognitoInfoBarControllerMap.get(otrProfileID);
     }
 
     /** For testing only. */
@@ -400,14 +407,24 @@
      * Called when browser activity is launched. For background resumption and cancellation, this
      * will not be called.
      */
-    public void onActivityLaunched() {
+    public void onActivityLaunched(Activity activity, MessageDispatcher messageDispatcher,
+            ModalDialogManager modalDialogManager) {
         if (!mActivityLaunched) {
+            OTRProfileID primaryOTRProfileID = OTRProfileID.getPrimaryOTRProfileID();
+
             // The info bar controller for regular and primary OTR profile should be created when
             // activity is launched.
-            mInfoBarController = new DownloadInfoBarController(/*otrProfileID=*/null);
-            OTRProfileID primaryOTRProfileID = OTRProfileID.getPrimaryOTRProfileID();
-            mIncognitoInfoBarControllerMap.put(
-                    primaryOTRProfileID, new DownloadInfoBarController(primaryOTRProfileID));
+            if (ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_PROGRESS_MESSAGE)) {
+                mMessageUiController = new DownloadMessageUiControllerImpl(
+                        /*otrProfileID=*/null, activity, messageDispatcher, modalDialogManager);
+                mIncognitoMessageUiControllerMap.put(primaryOTRProfileID,
+                        new DownloadMessageUiControllerImpl(primaryOTRProfileID, activity,
+                                messageDispatcher, modalDialogManager));
+            } else {
+                mInfoBarController = new DownloadInfoBarController(/*otrProfileID=*/null);
+                mIncognitoInfoBarControllerMap.put(
+                        primaryOTRProfileID, new DownloadInfoBarController(primaryOTRProfileID));
+            }
 
             DownloadNotificationService.clearResumptionAttemptLeft();
 
@@ -418,7 +435,7 @@
     }
 
     private void updateDownloadInfoBar(DownloadItem item) {
-        DownloadInfoBarController infobarController =
+        DownloadMessageUiController infobarController =
                 getInfoBarController(item.getDownloadInfo().getOTRProfileId());
         if (infobarController != null) infobarController.onDownloadItemUpdated(item);
     }
@@ -740,7 +757,9 @@
             return;
         }
 
-        getInfoBarController(downloadItem.getDownloadInfo().getOTRProfileId()).onDownloadStarted();
+        DownloadMessageUiController infoBarController =
+                getInfoBarController(downloadItem.getDownloadInfo().getOTRProfileId());
+        if (infoBarController != null) infoBarController.onDownloadStarted();
     }
 
     @Nullable
@@ -1027,7 +1046,7 @@
             removeDownloadProgress(id.id);
         } else {
             mDownloadNotifier.notifyDownloadCanceled(id);
-            DownloadInfoBarController infoBarController = getInfoBarController(otrProfileID);
+            DownloadMessageUiController infoBarController = getInfoBarController(otrProfileID);
             if (infoBarController != null) infoBarController.onDownloadItemRemoved(id);
         }
     }
@@ -1156,7 +1175,7 @@
                 item.setSystemDownloadId(systemDownloadId);
                 handleAutoOpenAfterDownload(item);
             } else {
-                DownloadInfoBarController infobarController =
+                DownloadMessageUiController infobarController =
                         getInfoBarController(info.getOTRProfileId());
                 if (infobarController != null) {
                     infobarController.onNotificationShown(info.getContentId(), notificationId);
@@ -1226,8 +1245,13 @@
                                     && item.getDownloadInfo().hasUserGesture() && canResolve) {
                                 handleAutoOpenAfterDownload(item);
                             } else {
-                                getInfoBarController(item.getDownloadInfo().getOTRProfileId())
-                                        .onItemUpdated(DownloadItem.createOfflineItem(item), null);
+                                DownloadMessageUiController infoBarController =
+                                        getInfoBarController(
+                                                item.getDownloadInfo().getOTRProfileId());
+                                if (infoBarController != null) {
+                                    infoBarController.onItemUpdated(
+                                            DownloadItem.createOfflineItem(item), null);
+                                }
                             }
                         }
                     }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@@ -1559,7 +1583,7 @@
     // Deprecated after new download backend.
     @CalledByNative
     private void onDownloadItemRemoved(String guid, OTRProfileID otrProfileID) {
-        DownloadInfoBarController infobarController = getInfoBarController(otrProfileID);
+        DownloadMessageUiController infobarController = getInfoBarController(otrProfileID);
         if (infobarController != null) {
             infobarController.onDownloadItemRemoved(
                     LegacyHelpers.buildLegacyContentId(false, guid));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java
new file mode 100644
index 0000000..748bbb9
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java
@@ -0,0 +1,49 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download;
+
+import org.chromium.components.offline_items_collection.ContentId;
+import org.chromium.components.offline_items_collection.OfflineContentProvider;
+import org.chromium.components.offline_items_collection.OfflineItem;
+import org.chromium.components.offline_items_collection.UpdateDelta;
+
+import java.util.List;
+
+/**
+ * The central class responsible for showing the download progress UI. Implemented by both the info
+ * bar and messages UI versions. Tracks updates for download items, offline items, android
+ * downloads etc. and computes the current state of the UI to be shown.
+ */
+public interface DownloadMessageUiController extends OfflineContentProvider.Observer {
+    /**
+     * Shows the message that download has started. Unlike other methods in this class, this
+     * method doesn't require an {@link OfflineItem} and is invoked by the backend to provide a
+     * responsive feedback to the users even before the download has actually started.
+     */
+    void onDownloadStarted();
+
+    /** Updates the UI when new information about a download comes in. */
+    void onDownloadItemUpdated(DownloadItem downloadItem);
+
+    /** Updates the UI after a download has been removed. */
+    void onDownloadItemRemoved(ContentId contentId);
+
+    /** Associates a notification ID with the tracked download for future usage. */
+    // TODO(shaktisahu): Find an alternative way after moving to offline content provider.
+    void onNotificationShown(ContentId id, int notificationId);
+
+    /** OfflineContentProvider.Observer methods. */
+    @Override
+    void onItemsAdded(List<OfflineItem> items);
+
+    @Override
+    void onItemRemoved(ContentId id);
+
+    @Override
+    void onItemUpdated(OfflineItem item, UpdateDelta updateDelta);
+
+    /** @return Whether the UI is currently showing. */
+    boolean isShowing();
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java
new file mode 100644
index 0000000..b3c97570
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java
@@ -0,0 +1,1010 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.text.TextUtils;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
+import androidx.annotation.PluralsRes;
+import androidx.annotation.VisibleForTesting;
+import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.download.DownloadLaterMetrics.DownloadLaterUiEvent;
+import org.chromium.chrome.browser.download.dialogs.DownloadLaterDialogHelper;
+import org.chromium.chrome.browser.download.dialogs.DownloadLaterDialogHelper.Source;
+import org.chromium.chrome.browser.download.items.OfflineContentAggregatorFactory;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.profiles.OTRProfileID;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.components.download.DownloadState;
+import org.chromium.components.messages.DismissReason;
+import org.chromium.components.messages.MessageBannerProperties;
+import org.chromium.components.messages.MessageDispatcher;
+import org.chromium.components.messages.MessageIdentifier;
+import org.chromium.components.offline_items_collection.ContentId;
+import org.chromium.components.offline_items_collection.LegacyHelpers;
+import org.chromium.components.offline_items_collection.OfflineContentProvider;
+import org.chromium.components.offline_items_collection.OfflineItem;
+import org.chromium.components.offline_items_collection.OfflineItemSchedule;
+import org.chromium.components.offline_items_collection.OfflineItemState;
+import org.chromium.components.offline_items_collection.UpdateDelta;
+import org.chromium.components.prefs.PrefService;
+import org.chromium.components.url_formatter.SchemeDisplay;
+import org.chromium.components.url_formatter.UrlFormatter;
+import org.chromium.components.user_prefs.UserPrefs;
+import org.chromium.ui.modaldialog.ModalDialogManager;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Message UI specific implementation of {@link DownloadMessageUiController}.
+ */
+public class DownloadMessageUiControllerImpl implements DownloadMessageUiController {
+    private static final String SPEEDING_UP_MESSAGE_ENABLED = "speeding_up_message_enabled";
+    private static final long DURATION_ACCELERATED_INFOBAR_IN_MS = 3000;
+    private static final long DURATION_SHOW_RESULT_IN_MS = 6000;
+    private static final long DURATION_SHOW_RESULT_DOWNLOAD_SCHEDULED_IN_MS = 12000;
+
+    // Values for the histogram Android.Download.InfoBar.Shown. Keep this in sync with the
+    // DownloadInfoBar.ShownState enum in enums.xml.
+    @IntDef({UmaInfobarShown.ANY_STATE, UmaInfobarShown.ACCELERATED, UmaInfobarShown.DOWNLOADING,
+            UmaInfobarShown.COMPLETE, UmaInfobarShown.FAILED, UmaInfobarShown.PENDING,
+            UmaInfobarShown.MULTIPLE_DOWNLOADING, UmaInfobarShown.MULTIPLE_COMPLETE,
+            UmaInfobarShown.MULTIPLE_FAILED, UmaInfobarShown.MULTIPLE_PENDING,
+            UmaInfobarShown.SCHEDULED, UmaInfobarShown.MULTIPLE_SCHEDULED})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface UmaInfobarShown {
+        int ANY_STATE = 0;
+        int ACCELERATED = 1;
+        int DOWNLOADING = 2;
+        int COMPLETE = 3;
+        int FAILED = 4;
+        int PENDING = 5;
+        int MULTIPLE_DOWNLOADING = 6;
+        int MULTIPLE_COMPLETE = 7;
+        int MULTIPLE_FAILED = 8;
+        int MULTIPLE_PENDING = 9;
+        int SCHEDULED = 10;
+        int MULTIPLE_SCHEDULED = 11;
+        int NUM_ENTRIES = 12;
+    }
+
+    /**
+     * Represents various UI states that the Message UI cycles through.
+     * Note: This enum is append-only and the values must match the DownloadInfoBarState enum in
+     * enums.xml. Values should be number from 0 and can't have gaps.
+     */
+    @VisibleForTesting
+    @IntDef({DownloadInfoBarState.INITIAL, DownloadInfoBarState.DOWNLOADING,
+            DownloadInfoBarState.SHOW_RESULT, DownloadInfoBarState.CANCELLED})
+    @Retention(RetentionPolicy.SOURCE)
+    protected @interface DownloadInfoBarState {
+        // Default initial state. It is also the final state after all the downloads are paused or
+        // removed. No UI is shown in this state.
+        int INITIAL = 0;
+        // UI is showing a message indicating the downloads in progress. In case of a single
+        // accelerated download, the message would show the speeding-up download message for {@code
+        // DURATION_ACCELERATED_INFOBAR_IN_MS} before transitioning to downloading file(s) message.
+        // If download completes,fails or goes to pending state, the transition happens immediately
+        // to SHOW_RESULT state.
+        int DOWNLOADING = 1;
+        // The message is showing download complete, failed or pending message. The message stays in
+        // this state for {@code DURATION_SHOW_RESULT_IN_MS} before transitioning to the next state,
+        // which can be another SHOW_RESULT or DOWNLOADING state. This can also happen to be the
+        // terminal state if there are no more updates to be shown.
+        // In case of a new download, completed download or cancellation signal, the transition
+        // happens immediately.
+        int SHOW_RESULT = 2;
+        // The state of the message after it was explicitly cancelled by the user. The message UI is
+        // resurfaced only when there is a new download or an existing download moves to completion,
+        // failed or pending state.
+        int CANCELLED = 3;
+        // Number of entries
+        int NUM_ENTRIES = 4;
+    }
+
+    // Represents various result states shown in the message UI.
+    private @interface ResultState {
+        int INVALID = -1;
+        int COMPLETE = 0;
+        int FAILED = 1;
+        int PENDING = 2;
+        int SCHEDULED = 3;
+    }
+
+    /**
+     * Represents the data required to show UI elements of the message.
+     */
+    public static class DownloadProgressMessageUiData {
+        @Nullable
+        public ContentId id;
+
+        public String message;
+        public String description;
+        public String link;
+        public int icon;
+
+        // Whether the icon corresponds to a vector drawable.
+        public boolean hasVectorDrawable;
+
+        // Whether the the message must be shown, even though it was dismissed earlier. This
+        // usually means there is a significant download update, e.g. download completed.
+        public boolean forceShow;
+
+        // Keeps track of the current number of downloads in various states.
+        public DownloadCount downloadCount = new DownloadCount();
+
+        // Used for differentiating various states (e.g. completed, failed, pending etc) in the
+        // SHOW_RESULT state. Keeps track of the state of the currently displayed item(s) and should
+        // be reset to null when moving out DOWNLOADING/SHOW_RESULT state.
+        @ResultState
+        public int resultState;
+
+        // Contains the information to change the download schedule for download later feature.
+        public OfflineItemSchedule schedule;
+
+        @Override
+        public int hashCode() {
+            int result = (id == null ? 0 : id.hashCode());
+            result = 31 * result + (message == null ? 0 : message.hashCode());
+            result = 31 * result + (link == null ? 0 : link.hashCode());
+            result = 31 * result + icon;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this) return true;
+            if (!(obj instanceof DownloadProgressMessageUiData)) return false;
+
+            DownloadProgressMessageUiData other = (DownloadProgressMessageUiData) obj;
+            boolean idEquality = (id == null ? other.id == null : id.equals(other.id));
+            return idEquality && TextUtils.equals(message, other.message)
+                    && TextUtils.equals(description, other.description)
+                    && TextUtils.equals(link, other.link) && icon == other.icon;
+        }
+
+        /** Called to update the value of this object from a given object. */
+        public void update(DownloadProgressMessageUiData other) {
+            id = other.id;
+            message = other.message;
+            link = other.link;
+            icon = other.icon;
+            hasVectorDrawable = other.hasVectorDrawable;
+            forceShow = other.forceShow;
+            downloadCount = other.downloadCount;
+            resultState = other.resultState;
+            schedule = other.schedule;
+        }
+    }
+
+    /**
+     * An utility class to count the number of downloads at different states at any given time.
+     */
+    private static class DownloadCount {
+        public int inProgress;
+        public int pending;
+        public int failed;
+        public int completed;
+        public int scheduled;
+
+        /** @return The total number of downloads being tracked. */
+        public int totalCount() {
+            return inProgress + pending + failed + completed + scheduled;
+        }
+
+        public int getCountForResultState(@ResultState int state) {
+            switch (state) {
+                case ResultState.COMPLETE:
+                    return completed;
+                case ResultState.FAILED:
+                    return failed;
+                case ResultState.PENDING:
+                    return pending;
+                case ResultState.SCHEDULED:
+                    return scheduled;
+                default:
+                    assert false;
+            }
+            return 0;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 31 * inProgress;
+            result = 31 * result + pending;
+            result = 31 * result + failed;
+            result = 31 * result + completed;
+            result = 31 * result + scheduled;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this) return true;
+            if (!(obj instanceof DownloadCount)) return false;
+
+            DownloadCount other = (DownloadCount) obj;
+            return inProgress == other.inProgress && pending == other.pending
+                    && failed == other.failed && completed == other.completed
+                    && scheduled == other.scheduled;
+        }
+    }
+
+    private final boolean mUseNewDownloadPath;
+    private final OTRProfileID mOtrProfileID;
+    private final Handler mHandler = new Handler();
+
+    // Keeps track of a running list of items, which gets updated regularly with every update from
+    // the backend. The entries are removed only when the item has reached a certain completion
+    // state (i.e. complete, failed or pending) or is cancelled/removed from the backend.
+    private final LinkedHashMap<ContentId, OfflineItem> mTrackedItems = new LinkedHashMap<>();
+
+    // Keeps track of all the items that have been seen in the current chrome session.
+    private final Set<ContentId> mSeenItems = new HashSet<>();
+
+    // Keeps track of the items which are being ignored by the controller, e.g. user initiated
+    // paused items.
+    private final Set<ContentId> mIgnoredItems = new HashSet<>();
+
+    // The notification IDs associated with the currently tracked completed items. The notification
+    // should be removed when the message action button is clicked to open the item.
+    private final Map<ContentId, Integer> mNotificationIds = new HashMap<>();
+
+    // The current state of the message UI.
+    private @DownloadInfoBarState int mState = DownloadInfoBarState.INITIAL;
+
+    // This is used when the message UI is currently in a state awaiting timer completion, e.g.
+    // showing the speeding-up message or showing the result of a download. This is used to schedule
+    // a task to determine the next state. If the message UI moves out of the current state, the
+    // scheduled task should be cancelled.
+    private Runnable mEndTimerRunnable;
+
+    // Represents the currently displayed UI data.
+    private DownloadProgressMessageUiData mCurrentInfo;
+
+    // Used to show the download later dialog to change download schedule.
+    private DownloadLaterDialogHelper mDownloadLaterDialogHelper;
+
+    // The associated activity context.
+    private final Context mContext;
+
+    // The message dispatcher for showing the message UI.
+    private final MessageDispatcher mMessageDispatcher;
+
+    // Dialog manager used for creating download later dialogs.
+    private final ModalDialogManager mModalDialogManager;
+
+    // The model used to update the UI properties.
+    private PropertyModel mPropertyModel;
+
+    private Runnable mDismissRunnable;
+
+    /** Constructor. */
+    public DownloadMessageUiControllerImpl(OTRProfileID otrProfileID, Context context,
+            MessageDispatcher messageDispatcher, ModalDialogManager modalDialogManager) {
+        mUseNewDownloadPath =
+                ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER);
+        mOtrProfileID = otrProfileID;
+        mMessageDispatcher = messageDispatcher;
+        mContext = context;
+        mModalDialogManager = modalDialogManager;
+
+        mHandler.post(() -> getOfflineContentProvider().addObserver(this));
+    }
+
+    /**
+     * Shows the message that download has started. Unlike other methods in this class, this
+     * method doesn't require an {@link OfflineItem} and is invoked by the backend to provide a
+     * responsive feedback to the users even before the download has actually started.
+     */
+    @Override
+    public void onDownloadStarted() {
+        computeNextStepForUpdate(null, true, false, false);
+    }
+
+    /** Updates the message UI when new information about a download comes in. */
+    @Override
+    public void onDownloadItemUpdated(DownloadItem downloadItem) {
+        if (mUseNewDownloadPath) return;
+
+        OfflineItem offlineItem = DownloadItem.createOfflineItem(downloadItem);
+        if (!isVisibleToUser(offlineItem)) return;
+
+        if (downloadItem.getDownloadInfo().state() == DownloadState.COMPLETE) {
+            handleDownloadCompletion(downloadItem);
+            return;
+        }
+
+        if (offlineItem.state == OfflineItemState.CANCELLED) {
+            onItemRemoved(offlineItem.id);
+            return;
+        }
+
+        computeNextStepForUpdate(offlineItem);
+    }
+
+    /** Updates the message UI after a download has been removed. */
+    @Override
+    public void onDownloadItemRemoved(ContentId contentId) {
+        if (mUseNewDownloadPath) return;
+        onItemRemoved(contentId);
+    }
+
+    /** Associates a notification ID with the tracked download for future usage. */
+    // TODO(shaktisahu): Find an alternative way after moving to offline content provider.
+    @Override
+    public void onNotificationShown(ContentId id, int notificationId) {
+        mNotificationIds.put(id, notificationId);
+    }
+
+    private void handleDownloadCompletion(DownloadItem downloadItem) {
+        // Multiple OnDownloadUpdated() notifications may be issued while the
+        // download is in the COMPLETE state. Don't handle it if it was previously not in-progress.
+        if (!mTrackedItems.containsKey(downloadItem.getContentId())) return;
+
+        // If the download should be auto-opened, we shouldn't show the UI.
+        DownloadManagerService.getDownloadManagerService().checkIfDownloadWillAutoOpen(
+                downloadItem, (result) -> {
+                    if (result) {
+                        onItemRemoved(downloadItem.getContentId());
+                    } else {
+                        computeNextStepForUpdate(DownloadItem.createOfflineItem(downloadItem));
+                    }
+                });
+    }
+
+    @Override
+    public void onItemsAdded(List<OfflineItem> items) {
+        for (OfflineItem item : items) {
+            if (!isVisibleToUser(item)) continue;
+            computeNextStepForUpdate(item);
+        }
+    }
+
+    @Override
+    public void onItemRemoved(ContentId id) {
+        if (!mSeenItems.contains(id)) return;
+
+        mTrackedItems.remove(id);
+        mNotificationIds.remove(id);
+        computeNextStepForUpdate(null, false, false, true);
+    }
+
+    @Override
+    public void onItemUpdated(OfflineItem item, UpdateDelta updateDelta) {
+        if (!isVisibleToUser(item)) return;
+
+        if (updateDelta != null && !updateDelta.stateChanged
+                && item.state == OfflineItemState.COMPLETE) {
+            return;
+        }
+
+        if (item.state == OfflineItemState.CANCELLED) {
+            onItemRemoved(item.id);
+            return;
+        }
+
+        computeNextStepForUpdate(item);
+    }
+
+    /** @return Whether the message is currently showing. */
+    @Override
+    public boolean isShowing() {
+        return mPropertyModel != null;
+    }
+
+    private boolean isVisibleToUser(OfflineItem offlineItem) {
+        // Need to use serialized OTRProfileID for comparison, since calling
+        // |OTRProfileID#deserialize| method causes crash if the OTR profile is destroyed.
+        String stringOTRProfileID = OTRProfileID.serialize(mOtrProfileID);
+        if (offlineItem.isTransient
+                || !OTRProfileID.areEqual(stringOTRProfileID, offlineItem.otrProfileId)
+                || offlineItem.isSuggested || offlineItem.isDangerous) {
+            return false;
+        }
+        if (LegacyHelpers.isLegacyDownload(offlineItem.id)
+                && TextUtils.isEmpty(offlineItem.filePath)) {
+            return false;
+        }
+
+        if (MimeUtils.canAutoOpenMimeType(offlineItem.mimeType)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private void computeNextStepForUpdate(OfflineItem updatedItem) {
+        computeNextStepForUpdate(updatedItem, false, false, false);
+    }
+
+    /**
+     * Updates the state of the UI based on the update received and current state of the
+     * tracked downloads.
+     * @param updatedItem The item that was updated just now.
+     * @param forceShowDownloadStarted Whether the message should show download started even if
+     * there is no updated item.
+     * @param userCancel Whether the message was cancelled just now.
+     * ended.
+     */
+    private void computeNextStepForUpdate(OfflineItem updatedItem, boolean forceShowDownloadStarted,
+            boolean userCancel, boolean itemWasRemoved) {
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_PROGRESS_INFOBAR)) return;
+
+        if (updatedItem != null && mIgnoredItems.contains(updatedItem.id)) return;
+
+        preProcessUpdatedItem(updatedItem);
+        boolean isNewDownload = forceShowDownloadStarted
+                || (updatedItem != null && updatedItem.state == OfflineItemState.IN_PROGRESS
+                        && updatedItem.schedule == null && !mSeenItems.contains(updatedItem.id));
+        boolean itemResumedFromPending = itemResumedFromPending(updatedItem);
+
+        if (updatedItem != null) {
+            mTrackedItems.put(updatedItem.id, updatedItem);
+            mSeenItems.add(updatedItem.id);
+        }
+
+        boolean itemWasPaused = updatedItem != null && updatedItem.state == OfflineItemState.PAUSED;
+        if (itemWasPaused) {
+            mIgnoredItems.add(updatedItem.id);
+            mTrackedItems.remove(updatedItem.id);
+        }
+
+        DownloadCount downloadCount = getDownloadCount();
+
+        boolean shouldShowResult = (downloadCount.completed + downloadCount.failed
+                                           + downloadCount.pending + downloadCount.scheduled)
+                > 0;
+
+        boolean shouldShowAccelerating =
+                mEndTimerRunnable != null && mState == DownloadInfoBarState.DOWNLOADING;
+
+        @DownloadInfoBarState
+        int nextState = mState;
+        switch (mState) {
+            case DownloadInfoBarState.INITIAL: // Intentional fallthrough.
+            case DownloadInfoBarState.CANCELLED:
+                if (isNewDownload) {
+                    nextState = DownloadInfoBarState.DOWNLOADING;
+                    shouldShowAccelerating =
+                            isAccelerated(updatedItem) && downloadCount.inProgress == 1;
+                } else if (shouldShowResult) {
+                    nextState = DownloadInfoBarState.SHOW_RESULT;
+                }
+                break;
+            case DownloadInfoBarState.DOWNLOADING:
+                if (isNewDownload) shouldShowAccelerating = false;
+
+                if (shouldShowResult) {
+                    nextState = DownloadInfoBarState.SHOW_RESULT;
+                } else if (itemWasPaused || itemWasRemoved) {
+                    nextState = downloadCount.inProgress == 0 ? DownloadInfoBarState.INITIAL
+                                                              : DownloadInfoBarState.DOWNLOADING;
+                }
+                break;
+            case DownloadInfoBarState.SHOW_RESULT:
+                if (isNewDownload) {
+                    nextState = DownloadInfoBarState.DOWNLOADING;
+                    shouldShowAccelerating =
+                            isAccelerated(updatedItem) && downloadCount.inProgress == 1;
+                } else if (!shouldShowResult) {
+                    if (mEndTimerRunnable == null && downloadCount.inProgress > 0) {
+                        nextState = DownloadInfoBarState.DOWNLOADING;
+                    }
+
+                    boolean currentlyShowingPending =
+                            mCurrentInfo != null && mCurrentInfo.resultState == ResultState.PENDING;
+                    if (currentlyShowingPending && itemResumedFromPending) {
+                        nextState = DownloadInfoBarState.DOWNLOADING;
+                    }
+                    if ((itemWasPaused || itemWasRemoved) && mTrackedItems.size() == 0) {
+                        nextState = DownloadInfoBarState.INITIAL;
+                    }
+                }
+                break;
+        }
+
+        if (userCancel) nextState = DownloadInfoBarState.CANCELLED;
+
+        moveToState(nextState, shouldShowAccelerating);
+    }
+
+    private void moveToState(@DownloadInfoBarState int nextState, boolean showAccelerating) {
+        boolean closePreviousMessage = nextState == DownloadInfoBarState.INITIAL
+                || nextState == DownloadInfoBarState.CANCELLED;
+        if (closePreviousMessage) {
+            mCurrentInfo = null;
+            closePreviousMessage();
+            if (nextState == DownloadInfoBarState.INITIAL) {
+                mTrackedItems.clear();
+            } else {
+                clearFinishedItems(ResultState.COMPLETE, ResultState.FAILED, ResultState.PENDING,
+                        ResultState.SCHEDULED);
+            }
+            clearEndTimerRunnable();
+        }
+
+        if (nextState == DownloadInfoBarState.DOWNLOADING
+                || nextState == DownloadInfoBarState.SHOW_RESULT) {
+            int resultState = findOfflineItemStateForInfoBarState(nextState);
+            if (resultState == ResultState.INVALID) {
+                // This is expected in the terminal SHOW_RESULT state when we have cleared the
+                // tracked items but still want to show the infobar indefinitely.
+                return;
+            }
+            createMessageForState(nextState, resultState, showAccelerating);
+        }
+
+        mState = nextState;
+    }
+
+    /**
+     * Determines the {@link OfflineItemState} for the message to be shown on the message. For
+     * DOWNLOADING state, it will return {@link OfflineItemState#IN_PROGRESS}. Otherwise it should
+     * show the result state which can be complete, failed or pending. There is usually a delay of
+     * DURATION_SHOW_RESULT_IN_MS between transition between these states, except for the complete
+     * state which must be shown as soon as received. While the UI is in one of these states,
+     * if we get another download update for the same state, we incorporate that in the existing
+     * message and reset the timer to another full duration. Updates for pending and failed would be
+     * shown in the order received.
+     */
+    private @ResultState int findOfflineItemStateForInfoBarState(
+            @DownloadInfoBarState int infoBarState) {
+        if (infoBarState == DownloadInfoBarState.DOWNLOADING) return OfflineItemState.IN_PROGRESS;
+
+        assert infoBarState == DownloadInfoBarState.SHOW_RESULT;
+
+        DownloadCount downloadCount = getDownloadCount();
+
+        // If there are completed downloads, show immediately.
+        if (downloadCount.completed > 0) return ResultState.COMPLETE;
+        if (downloadCount.scheduled > 0) return ResultState.SCHEDULED;
+
+        // If the infobar is already showing this state, just add this item to the same state.
+        int previousResultState =
+                mCurrentInfo != null ? mCurrentInfo.resultState : ResultState.INVALID;
+        if (previousResultState != ResultState.INVALID
+                && downloadCount.getCountForResultState(previousResultState) > 0) {
+            return previousResultState;
+        }
+
+        // Show any failed or pending states in the order they were received.
+        for (OfflineItem item : mTrackedItems.values()) {
+            int resultState = fromOfflineItemState(item);
+            if (resultState != ResultState.INVALID) return resultState;
+        }
+
+        return ResultState.INVALID;
+    }
+
+    /**
+     * Prepares the message to show the next state. This includes setting the message title,
+     * description, icon, and action.
+     * @param uiState The UI state to be shown.
+     * @param resultState The state of the corresponding offline items to be shown.
+     */
+    private void createMessageForState(@DownloadInfoBarState int uiState,
+            @ResultState int resultState, boolean showAccelerating) {
+        DownloadProgressMessageUiData info = new DownloadProgressMessageUiData();
+
+        @PluralsRes
+        int stringRes = -1;
+        if (uiState == DownloadInfoBarState.DOWNLOADING) {
+            stringRes = R.plurals.download_message_multiple_download_in_progress;
+            info.icon = R.drawable.infobar_downloading;
+        } else if (resultState == ResultState.COMPLETE) {
+            stringRes = R.plurals.download_message_multiple_download_complete;
+            info.icon = R.drawable.infobar_download_complete;
+            info.hasVectorDrawable = true;
+        } else if (resultState == ResultState.FAILED) {
+            stringRes = R.plurals.download_message_multiple_download_failed;
+            info.icon = R.drawable.ic_error_outline_googblue_24dp;
+        } else if (resultState == ResultState.PENDING) {
+            stringRes = R.plurals.download_message_multiple_download_pending;
+            info.icon = R.drawable.ic_error_outline_googblue_24dp;
+        } else if (resultState == ResultState.SCHEDULED) {
+            stringRes = R.plurals.download_message_multiple_download_scheduled;
+            info.icon = R.drawable.ic_file_download_scheduled_24dp;
+        } else {
+            assert false : "Unexpected resultState " + resultState + " and infoBarState " + uiState;
+        }
+
+        OfflineItem itemToShow = null;
+        for (OfflineItem item : mTrackedItems.values()) {
+            if (fromOfflineItemState(item) != resultState) continue;
+            itemToShow = item;
+        }
+
+        DownloadCount downloadCount = getDownloadCount();
+        if (uiState == DownloadInfoBarState.DOWNLOADING) {
+            int inProgressDownloadCount =
+                    downloadCount.inProgress == 0 ? 1 : downloadCount.inProgress;
+            info.message = getContext().getResources().getQuantityString(
+                    stringRes, inProgressDownloadCount, inProgressDownloadCount);
+            info.description = getContext().getString(
+                    R.string.download_message_download_in_progress_description);
+
+            info.link = showAccelerating ? null : getContext().getString(R.string.details_link);
+        } else if (uiState == DownloadInfoBarState.SHOW_RESULT) {
+            int itemCount = getDownloadCount().getCountForResultState(resultState);
+            boolean singleDownloadCompleted = itemCount == 1 && resultState == ResultState.COMPLETE;
+            boolean singleDownloadScheduled =
+                    itemCount == 1 && resultState == ResultState.SCHEDULED;
+            info.message =
+                    getContext().getResources().getQuantityString(stringRes, itemCount, itemCount);
+            if (singleDownloadCompleted) {
+                String bytesString =
+                        org.chromium.components.browser_ui.util.DownloadUtils.getStringForBytes(
+                                getContext(), itemToShow.totalSizeBytes);
+                String displayUrl = UrlFormatter.formatUrlForSecurityDisplay(
+                        itemToShow.url, SchemeDisplay.OMIT_HTTP_AND_HTTPS);
+                info.description = getContext().getString(
+                        R.string.download_message_download_complete_description, bytesString,
+                        displayUrl);
+                info.id = itemToShow.id;
+                info.link = getContext().getString(R.string.open_downloaded_label);
+                info.icon = R.drawable.infobar_download_complete_animation;
+            } else if (singleDownloadScheduled) {
+                // TODO(shaktisahu, xingliu): Find out what the message should be.
+                info.description = getContext().getString(
+                        R.string.download_message_download_scheduled_description);
+                info.link = getContext().getString(R.string.change_link);
+                info.id = itemToShow.id;
+                info.schedule = itemToShow.schedule.clone();
+            } else {
+                // TODO(shaktisahu): Incorporate various types of failure messages.
+                // TODO(shaktisahu, xingliu): Consult UX to handle multiple schedule variations.
+                info.link = getContext().getString(R.string.details_link);
+            }
+        }
+
+        info.resultState = resultState;
+
+        if (info.equals(mCurrentInfo)) return;
+
+        boolean startTimer = showAccelerating || uiState == DownloadInfoBarState.SHOW_RESULT;
+
+        clearEndTimerRunnable();
+
+        if (startTimer) {
+            long delay = getDelayToNextStep(showAccelerating, resultState);
+            mEndTimerRunnable = () -> {
+                mEndTimerRunnable = null;
+                if (mCurrentInfo != null) mCurrentInfo.resultState = ResultState.INVALID;
+                if (uiState == DownloadInfoBarState.SHOW_RESULT) {
+                    clearFinishedItems(resultState);
+                }
+                computeNextStepForUpdate(null, false, false, false);
+            };
+            mHandler.postDelayed(mEndTimerRunnable, delay);
+        }
+
+        setForceShow(info);
+        showMessage(uiState, info);
+    }
+
+    private void setForceShow(DownloadProgressMessageUiData info) {
+        info.downloadCount = getDownloadCount();
+        info.forceShow = !info.downloadCount.equals(
+                mCurrentInfo == null ? null : mCurrentInfo.downloadCount);
+
+        // TODO(xingliu, shaktisahu): downloadCount may not be updated at the correct time, see
+        // https://crbug.com/1127522. For now, scheduled download will always show in new tabs.
+        if (info.downloadCount.scheduled > 0) {
+            info.forceShow = true;
+        }
+    }
+    private void clearEndTimerRunnable() {
+        mHandler.removeCallbacks(mEndTimerRunnable);
+        mEndTimerRunnable = null;
+    }
+
+    private void preProcessUpdatedItem(OfflineItem updatedItem) {
+        if (updatedItem == null) return;
+
+        // INTERRUPTED downloads should be treated as PENDING in the UI. From here onwards,
+        // there should be no INTERRUPTED state in the core logic.
+        if (updatedItem.state == OfflineItemState.INTERRUPTED) {
+            updatedItem.state = OfflineItemState.PENDING;
+        }
+    }
+
+    private boolean itemResumedFromPending(OfflineItem updatedItem) {
+        if (updatedItem == null || !mTrackedItems.containsKey(updatedItem.id)) return false;
+
+        return mTrackedItems.get(updatedItem.id).state == OfflineItemState.PENDING
+                && updatedItem.state == OfflineItemState.IN_PROGRESS;
+    }
+
+    @VisibleForTesting
+    protected long getDelayToNextStep(boolean showAccelerating, @ResultState int resultState) {
+        if (showAccelerating) return DURATION_ACCELERATED_INFOBAR_IN_MS;
+
+        // Scheduled download uses a longer delay to reset tracking downloads states.
+        return resultState == ResultState.SCHEDULED ? DURATION_SHOW_RESULT_DOWNLOAD_SCHEDULED_IN_MS
+                                                    : DURATION_SHOW_RESULT_IN_MS;
+    }
+
+    @VisibleForTesting
+    protected boolean isSpeedingUpMessageEnabled() {
+        return ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
+                ChromeFeatureList.DOWNLOAD_PROGRESS_INFOBAR, SPEEDING_UP_MESSAGE_ENABLED, false);
+    }
+
+    private boolean isAccelerated(OfflineItem offlineItem) {
+        return isSpeedingUpMessageEnabled() && offlineItem != null && offlineItem.isAccelerated;
+    }
+
+    /**
+     * Central function called to show the message UI. If the previous message has been dismissed,
+     * it will be recreated only if |info.forceShow| is true. If the message hasn't been dismissed,
+     * it will be simply updated.
+     * @param state The state of the message to be shown.
+     * @param info Contains the information to be displayed in the UI.
+     */
+    @VisibleForTesting
+    protected void showMessage(
+            @DownloadInfoBarState int state, DownloadProgressMessageUiData info) {
+        assert ChromeFeatureList.isEnabled(ChromeFeatureList.DOWNLOAD_PROGRESS_MESSAGE);
+        recordInfoBarState(state, info);
+
+        boolean shouldShowMessage = info.forceShow || (mPropertyModel != null);
+        if (!shouldShowMessage) return;
+
+        Drawable drawable = info.hasVectorDrawable
+                ? VectorDrawableCompat.create(
+                        getContext().getResources(), info.icon, getContext().getTheme())
+                : ApiCompatibilityUtils.getDrawable(getContext().getResources(), info.icon);
+
+        boolean updateOnly = mPropertyModel != null;
+        if (mPropertyModel == null) {
+            mPropertyModel = new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS)
+                                     .with(MessageBannerProperties.MESSAGE_IDENTIFIER,
+                                             MessageIdentifier.DOWNLOAD_PROGRESS)
+                                     .build();
+        }
+
+        mPropertyModel.set(MessageBannerProperties.ICON, drawable);
+        mPropertyModel.set(MessageBannerProperties.TITLE, info.message);
+        mPropertyModel.set(MessageBannerProperties.DESCRIPTION, info.description);
+        mPropertyModel.set(MessageBannerProperties.PRIMARY_BUTTON_TEXT, info.link);
+        mPropertyModel.set(MessageBannerProperties.ON_DISMISSED, this::onMessageDismissed);
+        mPropertyModel.set(MessageBannerProperties.ON_PRIMARY_ACTION,
+                () -> onPrimaryAction(info.id, info.schedule));
+        mDismissRunnable = () -> {
+            mMessageDispatcher.dismissMessage(mPropertyModel, DismissReason.SCOPE_DESTROYED);
+        };
+
+        if (updateOnly) return;
+        mMessageDispatcher.enqueueWindowScopedMessage(mPropertyModel, /*highPriority=*/false);
+        recordInfoBarCreated();
+    }
+
+    @VisibleForTesting
+    protected void closePreviousMessage() {
+        if (mDismissRunnable != null) mDismissRunnable.run();
+        mPropertyModel = null;
+    }
+
+    private Context getContext() {
+        return mContext;
+    }
+
+    private DownloadCount getDownloadCount() {
+        DownloadCount downloadCount = new DownloadCount();
+        for (OfflineItem item : mTrackedItems.values()) {
+            if (item.schedule != null) {
+                downloadCount.scheduled++;
+                continue;
+            }
+
+            switch (item.state) {
+                case OfflineItemState.IN_PROGRESS:
+                    downloadCount.inProgress++;
+                    break;
+                case OfflineItemState.COMPLETE:
+                    downloadCount.completed++;
+                    break;
+                case OfflineItemState.FAILED:
+                    downloadCount.failed++;
+                    break;
+                case OfflineItemState.CANCELLED:
+                    break;
+                case OfflineItemState.PENDING:
+                    downloadCount.pending++;
+                    break;
+                case OfflineItemState.INTERRUPTED: // intentional fall through
+                case OfflineItemState.PAUSED: // intentional fall through
+                default:
+                    assert false;
+            }
+        }
+
+        return downloadCount;
+    }
+
+    /**
+     * Clears the items in finished state, i.e. completed, failed or pending.
+     * @param states States to be removed.
+     */
+    private void clearFinishedItems(Integer... states) {
+        Set<Integer> statesToRemove = new HashSet<>(Arrays.asList(states));
+        List<ContentId> idsToRemove = new ArrayList<>();
+        for (ContentId id : mTrackedItems.keySet()) {
+            OfflineItem item = mTrackedItems.get(id);
+            if (item == null) continue;
+            for (Integer stateToRemove : statesToRemove) {
+                if (stateToRemove == fromOfflineItemState(item)) {
+                    idsToRemove.add(id);
+                    break;
+                }
+            }
+        }
+
+        for (ContentId id : idsToRemove) {
+            mTrackedItems.remove(id);
+            mNotificationIds.remove(id);
+        }
+    }
+
+    private @ResultState int fromOfflineItemState(OfflineItem offlineItem) {
+        if (offlineItem.schedule != null) return ResultState.SCHEDULED;
+
+        switch (offlineItem.state) {
+            case OfflineItemState.COMPLETE:
+                return ResultState.COMPLETE;
+            case OfflineItemState.FAILED:
+                return ResultState.FAILED;
+            case OfflineItemState.PENDING:
+                return ResultState.PENDING;
+            default:
+                return ResultState.INVALID;
+        }
+    }
+
+    private OfflineContentProvider getOfflineContentProvider() {
+        return OfflineContentAggregatorFactory.get();
+    }
+
+    private void removeNotification(ContentId contentId) {
+        if (!mNotificationIds.containsKey(contentId)) return;
+
+        DownloadInfo downloadInfo = new DownloadInfo.Builder().setContentId(contentId).build();
+        DownloadManagerService.getDownloadManagerService()
+                .getDownloadNotifier()
+                .removeDownloadNotification(mNotificationIds.get(contentId), downloadInfo);
+        mNotificationIds.remove(contentId);
+    }
+
+    private void onPrimaryAction(ContentId itemId, final OfflineItemSchedule schedule) {
+        mTrackedItems.remove(itemId);
+        removeNotification(itemId);
+        if (itemId != null && schedule != null) {
+            onChangeScheduleClicked(itemId, schedule);
+        } else if (itemId != null) {
+            DownloadUtils.openItem(itemId, mOtrProfileID,
+                    DownloadOpenSource.DOWNLOAD_PROGRESS_MESSAGE, getContext());
+            recordLinkClicked(true /*openItem*/);
+        } else {
+            DownloadManagerService.openDownloadsPage(
+                    getContext(), mOtrProfileID, DownloadOpenSource.DOWNLOAD_PROGRESS_MESSAGE);
+            recordLinkClicked(false /*openItem*/);
+        }
+    }
+
+    private void onMessageDismissed(Integer dismissReason) {
+        mPropertyModel = null;
+        if (dismissReason == DismissReason.GESTURE) {
+            recordCloseButtonClicked();
+            computeNextStepForUpdate(null, false, true, false);
+        }
+    }
+
+    private void onChangeScheduleClicked(
+            final ContentId id, final OfflineItemSchedule currentSchedule) {
+        if (mDownloadLaterDialogHelper != null) mDownloadLaterDialogHelper.destroy();
+
+        PrefService prefService = UserPrefs.get(Profile.getLastUsedRegularProfile());
+        // Show the download later dialog to let the user change download schedule.
+        mDownloadLaterDialogHelper =
+                DownloadLaterDialogHelper.create(getContext(), mModalDialogManager, prefService);
+        DownloadLaterMetrics.recordDownloadLaterUiEvent(
+                DownloadLaterUiEvent.DOWNLOAD_INFOBAR_CHANGE_SCHEDULE_CLICKED);
+        mDownloadLaterDialogHelper.showChangeScheduleDialog(
+                currentSchedule, Source.DOWNLOAD_INFOBAR, (newSchedule) -> {
+                    if (newSchedule == null) return;
+                    if (mUseNewDownloadPath) {
+                        OfflineContentAggregatorFactory.get().changeSchedule(id, newSchedule);
+                    } else {
+                        DownloadManagerService.getDownloadManagerService().changeSchedule(
+                                id, newSchedule, mOtrProfileID);
+                    }
+                });
+    }
+
+    private void recordInfoBarState(
+            @DownloadInfoBarState int state, DownloadProgressMessageUiData info) {
+        int shownState = -1;
+        int multipleDownloadState = -1;
+        if (state == DownloadInfoBarState.DOWNLOADING) {
+            shownState = mEndTimerRunnable != null
+                    ? UmaInfobarShown.ACCELERATED
+                    : (info.downloadCount.inProgress == 1 ? UmaInfobarShown.DOWNLOADING
+                                                          : UmaInfobarShown.MULTIPLE_DOWNLOADING);
+        } else if (state == DownloadInfoBarState.SHOW_RESULT) {
+            switch (info.resultState) {
+                case ResultState.COMPLETE:
+                    shownState = info.downloadCount.completed == 1
+                            ? UmaInfobarShown.COMPLETE
+                            : UmaInfobarShown.MULTIPLE_COMPLETE;
+                    break;
+                case ResultState.FAILED:
+                    shownState = info.downloadCount.failed == 1 ? UmaInfobarShown.FAILED
+                                                                : UmaInfobarShown.MULTIPLE_FAILED;
+                    break;
+                case ResultState.PENDING:
+                    shownState = info.downloadCount.pending == 1 ? UmaInfobarShown.PENDING
+                                                                 : UmaInfobarShown.MULTIPLE_PENDING;
+                    break;
+                case ResultState.SCHEDULED:
+                    shownState = info.downloadCount.scheduled == 1
+                            ? UmaInfobarShown.SCHEDULED
+                            : UmaInfobarShown.MULTIPLE_SCHEDULED;
+                    break;
+                default:
+                    assert false : "Unexpected state " + info.resultState;
+                    break;
+            }
+        }
+
+        assert shownState != -1 : "Invalid state " + state;
+
+        RecordHistogram.recordEnumeratedHistogram(
+                "Android.Download.InfoBar.Shown", shownState, UmaInfobarShown.NUM_ENTRIES);
+        RecordHistogram.recordEnumeratedHistogram("Android.Download.InfoBar.Shown",
+                UmaInfobarShown.ANY_STATE, UmaInfobarShown.NUM_ENTRIES);
+        if (multipleDownloadState != -1) {
+            RecordHistogram.recordEnumeratedHistogram("Android.Download.InfoBar.Shown",
+                    multipleDownloadState, UmaInfobarShown.NUM_ENTRIES);
+        }
+    }
+
+    private void recordInfoBarCreated() {
+        RecordUserAction.record("Android.Download.InfoBar.Shown");
+    }
+
+    private void recordCloseButtonClicked() {
+        RecordUserAction.record("Android.Download.InfoBar.CloseButtonClicked");
+        RecordHistogram.recordEnumeratedHistogram("Android.Download.InfoBar.CloseButtonClicked",
+                mState, DownloadInfoBarState.NUM_ENTRIES);
+    }
+
+    private void recordLinkClicked(boolean openItem) {
+        if (openItem) {
+            RecordUserAction.record("Android.Download.InfoBar.LinkClicked.OpenDownload");
+        } else {
+            RecordUserAction.record("Android.Download.InfoBar.LinkClicked.OpenDownloadHome");
+        }
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSnackbarController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSnackbarController.java
index fdd3baa..7edcf0a7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSnackbarController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadSnackbarController.java
@@ -134,7 +134,7 @@
     }
 
     private boolean isShowingDownloadInfoBar(OTRProfileID otrProfileID) {
-        DownloadInfoBarController infoBarController =
+        DownloadMessageUiController infoBarController =
                 DownloadManagerService.getDownloadManagerService().getInfoBarController(
                         otrProfileID);
         return infoBarController == null ? false : infoBarController.isShowing();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java
index 6f6cb8b..6984d1e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java
@@ -864,7 +864,7 @@
     }
 
     private void showDownloadOnInfoBar(DownloadItem downloadItem, int downloadStatus) {
-        DownloadInfoBarController infobarController =
+        DownloadMessageUiController infobarController =
                 DownloadManagerService.getDownloadManagerService().getInfoBarController(
                         downloadItem.getDownloadInfo().getOTRProfileId());
         if (infobarController == null) return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/IPHBubbleDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/IPHBubbleDelegateImpl.java
index a1b856eb..16d7712 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/IPHBubbleDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/IPHBubbleDelegateImpl.java
@@ -69,8 +69,9 @@
                 Profile profile = IncognitoUtils.getProfileFromWindowAndroid(
                         mTab.getWindowAndroid(), mTab.isIncognito());
                 DownloadInfoBarController controller =
-                        DownloadManagerService.getDownloadManagerService().getInfoBarController(
-                                profile.getOTRProfileID());
+                        (DownloadInfoBarController) DownloadManagerService
+                                .getDownloadManagerService()
+                                .getInfoBarController(profile.getOTRProfileID());
                 return controller != null ? controller.getTrackerParameters() : null;
             case InfoBarIdentifier.GROUPED_PERMISSION_INFOBAR_DELEGATE_ANDROID:
                 if (PermissionSettingsBridge.shouldShowNotificationsPromo(mTab.getWebContents())) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index b17b4f1..ccaba10 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -42,6 +42,7 @@
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
 import org.chromium.chrome.browser.crash.PureJavaExceptionReporter;
 import org.chromium.chrome.browser.directactions.DirectActionInitializer;
+import org.chromium.chrome.browser.download.DownloadManagerService;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.findinpage.FindToolbarManager;
 import org.chromium.chrome.browser.findinpage.FindToolbarObserver;
@@ -523,6 +524,8 @@
             mMessageDispatcher.setDelegate(mMessageQueueMediator);
             MessagesFactory.attachMessageDispatcher(mWindowAndroid, mMessageDispatcher);
         }
+        DownloadManagerService.getDownloadManagerService().onActivityLaunched(
+                mActivity, mMessageDispatcher, mActivity.getModalDialogManager());
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/InstalledAppTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/InstalledAppTest.java
index 962b88b7..ff75627 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/InstalledAppTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/InstalledAppTest.java
@@ -23,6 +23,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 
 /** Test suite for navigator.getInstalledRelatedApps functionality. */
@@ -78,12 +79,12 @@
 
         mTab = mActivityTestRule.getActivity().getActivityTab();
         mUpdateWaiter = new InstalledAppUpdateWaiter();
-        mTab.addObserver(mUpdateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mTab.addObserver(mUpdateWaiter));
     }
 
     @After
     public void tearDown() {
-        mTab.removeObserver(mUpdateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mTab.removeObserver(mUpdateWaiter));
         mTestServer.stopAndDestroyServer();
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java
index 94474cd..5adc101 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java
@@ -355,7 +355,7 @@
             }
         };
         Tab tab = mActivityTestRule.getActivity().getActivityTab();
-        tab.addObserver(onPageLoadStartedObserver);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(onPageLoadStartedObserver));
         DOMUtils.clickNode(tab.getWebContents(), "aboutLink");
         ChromeTabUtils.waitForTabPageLoaded(tab, url2);
         Assert.assertEquals("Desired Link not open", url2,
@@ -642,7 +642,7 @@
             }
         };
         Tab tab = mActivityTestRule.getActivity().getActivityTab();
-        tab.addObserver(onPageLoadStartedObserver);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(onPageLoadStartedObserver));
         DOMUtils.clickNode(tab.getWebContents(), "rendererInitiated");
         ChromeTabUtils.waitForTabPageLoaded(tab, finalUrl);
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabTest.java
index 3126972..986ac6a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabTest.java
@@ -59,7 +59,7 @@
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityOnBlankPage();
         mTab = mActivityTestRule.getActivity().getActivityTab();
-        mTab.addObserver(mTabObserver);
+        TestThreadUtils.runOnUiThreadBlocking(() -> mTab.addObserver(mTabObserver));
         mOnTitleUpdatedHelper = new CallbackHelper();
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabThemeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabThemeTest.java
index bfa017e..ff0290bc5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabThemeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabThemeTest.java
@@ -115,10 +115,12 @@
 
         ThemeColorWebContentsObserver colorObserver = new ThemeColorWebContentsObserver();
         CallbackHelper themeColorHelper = colorObserver.getCallbackHelper();
-        mActivityTestRule.getActivity()
-                .getRootUiCoordinatorForTesting()
-                .getTopUiThemeColorProvider()
-                .addThemeColorObserver(colorObserver);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivityTestRule.getActivity()
+                    .getRootUiCoordinatorForTesting()
+                    .getTopUiThemeColorProvider()
+                    .addThemeColorObserver(colorObserver);
+        });
 
         // Navigate to a themed page.
         int curCallCount = themeColorHelper.getCallCount();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/background_sync/BackgroundSyncTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/background_sync/BackgroundSyncTest.java
index f95c885e..5eb9b79 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/background_sync/BackgroundSyncTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/background_sync/BackgroundSyncTest.java
@@ -91,7 +91,9 @@
     @After
     public void tearDown() {
         if (mTestServer != null) mTestServer.stopAndDestroyServer();
-        BackgroundSyncBackgroundTaskScheduler.getInstance().removeObserver(mSchedulerObserver);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            BackgroundSyncBackgroundTaskScheduler.getInstance().removeObserver(mSchedulerObserver);
+        });
     }
 
     @Test
@@ -186,6 +188,8 @@
             }
         };
 
-        BackgroundSyncBackgroundTaskScheduler.getInstance().addObserver(mSchedulerObserver);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            BackgroundSyncBackgroundTaskScheduler.getInstance().addObserver(mSchedulerObserver);
+        });
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
index 2cde8b7..d3ee413 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkTest.java
@@ -1589,7 +1589,9 @@
                 (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(1).itemView;
         toggleSelectionAndEndAnimation(googleId, row);
         CallbackHelper helper = new CallbackHelper();
-        mManager.getSelectionDelegate().addObserver((x) -> { helper.notifyCalled(); });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mManager.getSelectionDelegate().addObserver((x) -> helper.notifyCalled());
+        });
 
         removeBookmark(googleId);
 
@@ -1627,7 +1629,10 @@
                 (BookmarkRow) mItemsContainer.findViewHolderForAdapterPosition(0).itemView;
         toggleSelectionAndEndAnimation(aId, aRow);
         CallbackHelper helper = new CallbackHelper();
-        mManager.getSelectionDelegate().addObserver((x) -> { helper.notifyCalled(); });
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mManager.getSelectionDelegate().addObserver((x) -> helper.notifyCalled());
+        });
 
         removeBookmark(googleId);
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java
index cda8dc60..7caa0ea 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadInfoBarControllerTest.java
@@ -35,6 +35,7 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @Features.EnableFeatures(ChromeFeatureList.DOWNLOAD_PROGRESS_INFOBAR)
+@Features.DisableFeatures(ChromeFeatureList.DOWNLOAD_PROGRESS_MESSAGE)
 @Batch(Batch.PER_CLASS)
 @Batch.SplitByFeature
 public class DownloadInfoBarControllerTest {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java
index 778298b..9ae67bfa 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java
@@ -345,7 +345,7 @@
         createDownloadManagerService(notifier, UPDATE_DELAY_FOR_TEST);
         TestThreadUtils.runOnUiThreadBlocking(
                 (Runnable) () -> DownloadManagerService.setDownloadManagerService(mService));
-        DownloadInfoBarController infoBarController =
+        DownloadMessageUiController infoBarController =
                 mService.getInfoBarController(/*otrProfileID=*/null);
         // Try calling download completed directly.
         DownloadInfo successful = getDownloadInfo();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerTest.java
new file mode 100644
index 0000000..fc66c5f0
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerTest.java
@@ -0,0 +1,446 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download;
+
+import android.support.test.InstrumentationRegistry;
+
+import androidx.test.filters.SmallTest;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.test.ChromeBrowserTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.components.offline_items_collection.LegacyHelpers;
+import org.chromium.components.offline_items_collection.OfflineItem;
+import org.chromium.components.offline_items_collection.OfflineItemSchedule;
+import org.chromium.components.offline_items_collection.OfflineItemState;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+
+import java.util.UUID;
+
+/**
+ * Test class to validate that the {@link DownloadMessageUiController} correctly represents the
+ * state of the downloads in the current chrome session.
+ */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@Features.EnableFeatures(ChromeFeatureList.DOWNLOAD_PROGRESS_MESSAGE)
+@Batch(Batch.PER_CLASS)
+@Batch.SplitByFeature
+public class DownloadMessageUiControllerTest {
+    @Rule
+    public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule();
+
+    private static final String MESSAGE_DOWNLOADING_FILE = "Downloading file…";
+    private static final String MESSAGE_DOWNLOADING_TWO_FILES = "Downloading 2 files…";
+    private static final String MESSAGE_SINGLE_DOWNLOAD_COMPLETE = "File downloaded";
+    private static final String MESSAGE_TWO_DOWNLOAD_COMPLETE = "2 downloads complete";
+    private static final String MESSAGE_DOWNLOAD_FAILED = "1 download failed";
+    private static final String MESSAGE_TWO_DOWNLOAD_FAILED = "2 downloads failed";
+    private static final String MESSAGE_DOWNLOAD_PENDING = "1 download pending";
+    private static final String MESSAGE_TWO_DOWNLOAD_PENDING = "2 downloads pending";
+    private static final String MESSAGE_DOWNLOAD_SCHEDULED_WIFI = "1 download scheduled";
+    private static final String MESSAGE_TWO_DOWNLOAD_SCHEDULED = "2 downloads scheduled";
+
+    private static final String DESCRIPTION_DOWNLOADING =
+            "You can see the download status in your notifications";
+    private static final String DESCRIPTION_DOWNLOAD_COMPLETE = "(0.01 KB)\nexample.com";
+    private static final String DESCRIPTION_DOWNLOAD_SCHEDULED =
+            "Download will start when on Wi-Fi";
+
+    private static final String TEST_FILE_NAME = "TestFile";
+    private static final long TEST_TO_NEXT_STEP_DELAY = 100;
+
+    private TestDownloadMessageUiController mTestController;
+
+    @Before
+    public void before() {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mTestController = new TestDownloadMessageUiController(); });
+    }
+
+    static class TestDownloadMessageUiController extends DownloadMessageUiControllerImpl {
+        private DownloadProgressMessageUiData mInfo;
+
+        public TestDownloadMessageUiController() {
+            super(/*otrProfileID=*/null, InstrumentationRegistry.getTargetContext(),
+                    /*messageDispatcher=*/null, null);
+        }
+
+        @Override
+        protected void showMessage(
+                @DownloadInfoBarState int state, DownloadProgressMessageUiData info) {
+            mInfo = info;
+        }
+
+        @Override
+        protected void closePreviousMessage() {
+            mInfo = null;
+        }
+
+        @Override
+        protected long getDelayToNextStep(boolean showAccelerating, int resultState) {
+            return TEST_TO_NEXT_STEP_DELAY;
+        }
+
+        @Override
+        protected boolean isSpeedingUpMessageEnabled() {
+            return true;
+        }
+
+        public void onItemUpdated(OfflineItem item) {
+            super.onItemUpdated(item.clone(), null);
+        }
+
+        void verify(String message, String description) {
+            Assert.assertNotNull(mInfo);
+            Assert.assertEquals(message, mInfo.message);
+            Assert.assertEquals(description, mInfo.description);
+        }
+
+        void verifyInfoBarClosed() {
+            Assert.assertNull(mInfo);
+        }
+    }
+
+    private static DownloadItem createDownloadItem(OfflineItem offlineItem) {
+        DownloadInfo downloadInfo = DownloadInfo.fromOfflineItem(offlineItem, null);
+        return new DownloadItem(false, downloadInfo);
+    }
+
+    private static OfflineItem createOfflineItem(@OfflineItemState int state) {
+        OfflineItem item = new OfflineItem();
+        String uuid = UUID.randomUUID().toString();
+        item.id = LegacyHelpers.buildLegacyContentId(true, uuid);
+        item.state = state;
+        if (item.state == OfflineItemState.COMPLETE) {
+            markItemComplete(item);
+        }
+        return item;
+    }
+
+    private static void markItemComplete(OfflineItem item) {
+        item.state = OfflineItemState.COMPLETE;
+        item.title = TEST_FILE_NAME;
+        item.url = "https://example.com";
+        item.receivedBytes = 10L;
+        item.totalSizeBytes = 10L;
+    }
+
+    private void waitForMessage(String message) {
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            Criteria.checkThat(mTestController.mInfo, Matchers.notNullValue());
+            Criteria.checkThat(mTestController.mInfo.message, Matchers.is(message));
+        });
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testOfflinePageDownloadStarted() {
+        mTestController.onDownloadStarted();
+        mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    @Features.DisableFeatures(ChromeFeatureList.DOWNLOAD_OFFLINE_CONTENT_PROVIDER)
+    public void testMultipleDownloadInProgress() {
+        OfflineItem item1 = createOfflineItem(OfflineItemState.IN_PROGRESS);
+        mTestController.onDownloadItemUpdated(createDownloadItem(item1));
+        mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING);
+
+        OfflineItem item2 = createOfflineItem(OfflineItemState.IN_PROGRESS);
+        mTestController.onDownloadItemUpdated(createDownloadItem(item2));
+        mTestController.verify(MESSAGE_DOWNLOADING_TWO_FILES, DESCRIPTION_DOWNLOADING);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testSingleOfflineItemComplete() {
+        OfflineItem item = createOfflineItem(OfflineItemState.IN_PROGRESS);
+        mTestController.onItemUpdated(item);
+        mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING);
+
+        markItemComplete(item);
+        mTestController.onItemUpdated(item);
+        mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testMultipleOfflineItemComplete() {
+        OfflineItem item = createOfflineItem(OfflineItemState.IN_PROGRESS);
+        mTestController.onItemUpdated(item);
+        mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING);
+
+        markItemComplete(item);
+        mTestController.onItemUpdated(item);
+        mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE);
+
+        OfflineItem item2 = createOfflineItem(OfflineItemState.COMPLETE);
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_TWO_DOWNLOAD_COMPLETE, null);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testSingleOfflineItemFailed() {
+        OfflineItem item = createOfflineItem(OfflineItemState.FAILED);
+        mTestController.onItemUpdated(item);
+        mTestController.verify(MESSAGE_DOWNLOAD_FAILED, null);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testMultipleOfflineItemFailed() {
+        OfflineItem item = createOfflineItem(OfflineItemState.FAILED);
+        mTestController.onItemUpdated(item);
+        mTestController.verify(MESSAGE_DOWNLOAD_FAILED, null);
+
+        OfflineItem item2 = createOfflineItem(OfflineItemState.FAILED);
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_TWO_DOWNLOAD_FAILED, null);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testSingleOfflineItemPending() {
+        OfflineItem item = createOfflineItem(OfflineItemState.PENDING);
+        mTestController.onItemUpdated(item);
+        mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testMultipleOfflineItemPending() {
+        OfflineItem item = createOfflineItem(OfflineItemState.PENDING);
+        mTestController.onItemUpdated(item);
+        mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null);
+
+        OfflineItem item2 = createOfflineItem(OfflineItemState.PENDING);
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_TWO_DOWNLOAD_PENDING, null);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testSingleOfflineItemScheduled() {
+        OfflineItem item = createOfflineItem(OfflineItemState.PENDING);
+        item.schedule = new OfflineItemSchedule(true, 0);
+        mTestController.onItemUpdated(item);
+        mTestController.verify(MESSAGE_DOWNLOAD_SCHEDULED_WIFI, DESCRIPTION_DOWNLOAD_SCHEDULED);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testMultipleOfflineItemScheduled() {
+        OfflineItem item1 = createOfflineItem(OfflineItemState.IN_PROGRESS);
+        item1.schedule = new OfflineItemSchedule(true, 0);
+        mTestController.onItemUpdated(item1);
+        mTestController.verify(MESSAGE_DOWNLOAD_SCHEDULED_WIFI, DESCRIPTION_DOWNLOAD_SCHEDULED);
+
+        OfflineItem item2 = createOfflineItem(OfflineItemState.PENDING);
+        item2.schedule = new OfflineItemSchedule(true, 0);
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_TWO_DOWNLOAD_SCHEDULED, null);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testNewDownloadShowsUpImmediately() {
+        OfflineItem item1 = createOfflineItem(OfflineItemState.COMPLETE);
+        mTestController.onItemUpdated(item1);
+        mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE);
+
+        OfflineItem item2 = createOfflineItem(OfflineItemState.IN_PROGRESS);
+        item2.isAccelerated = true;
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testPausedDownloadsAreIgnored() {
+        OfflineItem item = createOfflineItem(OfflineItemState.IN_PROGRESS);
+        mTestController.onItemUpdated(item);
+        mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING);
+
+        item.state = OfflineItemState.PAUSED;
+        mTestController.onItemUpdated(item);
+        mTestController.verifyInfoBarClosed();
+
+        item.state = OfflineItemState.IN_PROGRESS;
+        mTestController.onItemUpdated(item);
+        mTestController.verifyInfoBarClosed();
+
+        markItemComplete(item);
+        mTestController.onItemUpdated(item);
+        mTestController.verifyInfoBarClosed();
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testOnItemRemoved() {
+        OfflineItem item1 = createOfflineItem(OfflineItemState.IN_PROGRESS);
+        OfflineItem item2 = createOfflineItem(OfflineItemState.IN_PROGRESS);
+        mTestController.onItemUpdated(item1);
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_DOWNLOADING_TWO_FILES, DESCRIPTION_DOWNLOADING);
+
+        mTestController.onItemRemoved(item1.id);
+        mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING);
+
+        mTestController.onItemRemoved(item2.id);
+        mTestController.verifyInfoBarClosed();
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testCancelledItemWillCloseInfoBar() {
+        OfflineItem item = createOfflineItem(OfflineItemState.PENDING);
+        mTestController.onItemUpdated(item);
+        mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null);
+
+        item.state = OfflineItemState.CANCELLED;
+        mTestController.onItemUpdated(item);
+        mTestController.verifyInfoBarClosed();
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testCompleteFailedComplete() {
+        OfflineItem item1 = createOfflineItem(OfflineItemState.COMPLETE);
+        mTestController.onItemUpdated(item1);
+        mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE);
+
+        OfflineItem item2 = createOfflineItem(OfflineItemState.FAILED);
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE);
+
+        OfflineItem item3 = createOfflineItem(OfflineItemState.COMPLETE);
+        mTestController.onItemUpdated(item3);
+        mTestController.verify(MESSAGE_TWO_DOWNLOAD_COMPLETE, null);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testPendingFailedPending() {
+        OfflineItem item1 = createOfflineItem(OfflineItemState.PENDING);
+        mTestController.onItemUpdated(item1);
+        mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null);
+
+        OfflineItem item2 = createOfflineItem(OfflineItemState.FAILED);
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null);
+
+        OfflineItem item3 = createOfflineItem(OfflineItemState.PENDING);
+        mTestController.onItemUpdated(item3);
+        mTestController.verify(MESSAGE_TWO_DOWNLOAD_PENDING, null);
+
+        waitForMessage(MESSAGE_DOWNLOAD_FAILED);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testProgressCompleteFailedProgress() {
+        OfflineItem item1 = createOfflineItem(OfflineItemState.IN_PROGRESS);
+        mTestController.onItemUpdated(item1);
+        mTestController.verify(MESSAGE_DOWNLOADING_FILE, DESCRIPTION_DOWNLOADING);
+
+        OfflineItem item2 = createOfflineItem(OfflineItemState.COMPLETE);
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE);
+
+        OfflineItem item3 = createOfflineItem(OfflineItemState.FAILED);
+        mTestController.onItemUpdated(item3);
+        mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE);
+
+        waitForMessage(MESSAGE_DOWNLOAD_FAILED);
+        waitForMessage(MESSAGE_DOWNLOADING_FILE);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testCompleteShowsUpImmediately() {
+        OfflineItem item1 = createOfflineItem(OfflineItemState.FAILED);
+        mTestController.onItemUpdated(item1);
+        mTestController.verify(MESSAGE_DOWNLOAD_FAILED, null);
+
+        OfflineItem item2 = createOfflineItem(OfflineItemState.PENDING);
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_DOWNLOAD_FAILED, null);
+
+        markItemComplete(item2);
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_SINGLE_DOWNLOAD_COMPLETE, DESCRIPTION_DOWNLOAD_COMPLETE);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testResumeFromPendingShowsUpImmediately() {
+        OfflineItem item1 = createOfflineItem(OfflineItemState.PENDING);
+        mTestController.onItemUpdated(item1);
+        mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null);
+
+        OfflineItem item2 = createOfflineItem(OfflineItemState.PENDING);
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_TWO_DOWNLOAD_PENDING, null);
+
+        item2.state = OfflineItemState.IN_PROGRESS;
+        mTestController.onItemUpdated(item2);
+        mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null);
+
+        // TODO(shaktisahu): Uncomment the below after fixing.
+        // item1.state = OfflineItemState.IN_PROGRESS;
+        // mTestController.onItemUpdated(item1);
+        // mTestController.verify(MESSAGE_DOWNLOADING_TWO_FILES, DESCRIPTION_DOWNLOADING);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Download"})
+    public void testPausedAfterPendingWillCloseInfoBar() {
+        OfflineItem item = createOfflineItem(OfflineItemState.PENDING);
+        mTestController.onItemUpdated(item);
+        mTestController.verify(MESSAGE_DOWNLOAD_PENDING, null);
+
+        item.state = OfflineItemState.PAUSED;
+        mTestController.onItemUpdated(item);
+        mTestController.verifyInfoBarClosed();
+
+        item.state = OfflineItemState.IN_PROGRESS;
+        mTestController.onItemUpdated(item);
+        mTestController.verifyInfoBarClosed();
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/SystemDownloadNotifierTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/SystemDownloadNotifierTest.java
index ee577dc..a3fa05c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/SystemDownloadNotifierTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/SystemDownloadNotifierTest.java
@@ -25,6 +25,7 @@
 import org.chromium.components.offline_items_collection.ContentId;
 import org.chromium.components.offline_items_collection.OfflineItemSchedule;
 import org.chromium.components.offline_items_collection.PendingState;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.UUID;
 
@@ -47,8 +48,11 @@
 
     @Before
     public void setUp() {
-        mMockDownloadNotificationService = new MockDownloadNotificationService();
-        mSystemDownloadNotifier.setDownloadNotificationService(mMockDownloadNotificationService);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mMockDownloadNotificationService = new MockDownloadNotificationService();
+            mSystemDownloadNotifier.setDownloadNotificationService(
+                    mMockDownloadNotificationService);
+        });
     }
 
     private DownloadInfo getDownloadInfo(ContentId id) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java
index b890384..79378c0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java
@@ -202,7 +202,8 @@
         // |triggerUi| can be invoked by SwipeRefreshHandler on the rendered
         // page. Make sure this won't crash after the handler(and also
         // handler action delegate) is destroyed.
-        Assert.assertFalse(mNavigationHandler.triggerUi(LEFT_EDGE, 0, 0));
+        Assert.assertFalse(TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> mNavigationHandler.triggerUi(LEFT_EDGE, 0, 0)));
 
         // Just check we're still on the same URL.
         Assert.assertEquals(mTestServer.getURL(RENDERED_PAGE),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfobarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfobarTest.java
index 608274f..a4507a5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfobarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/PermissionUpdateInfobarTest.java
@@ -86,7 +86,7 @@
                 () -> mActivityTestRule.getInfoBarContainer() != null);
         InfoBarContainer container = mActivityTestRule.getInfoBarContainer();
         mListener =  new InfoBarTestAnimationListener();
-        container.addAnimationListener(mListener);
+        TestThreadUtils.runOnUiThreadBlocking(() -> container.addAnimationListener(mListener));
 
         final String locationUrl = mTestServer.getURL(GEOLOCATION_PAGE);
         final PermissionInfo geolocationSettings =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBarTest.java
index e9218d6..0faa9bf3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/infobar/SearchGeolocationDisclosureInfoBarTest.java
@@ -30,6 +30,7 @@
 import org.chromium.components.content_settings.ContentSettingValues;
 import org.chromium.components.content_settings.ContentSettingsType;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.concurrent.TimeoutException;
@@ -73,8 +74,8 @@
 
         // Infobar should appear when doing the first search.
         InfoBarContainer container = mActivityTestRule.getInfoBarContainer();
-        InfoBarTestAnimationListener listener = new InfoBarTestAnimationListener();
-        container.addAnimationListener(listener);
+        InfoBarTestAnimationListener listener1 = new InfoBarTestAnimationListener();
+        TestThreadUtils.runOnUiThreadBlocking(() -> container.addAnimationListener(listener1));
         mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE));
         // Note: the number of infobars is checked immediately after the URL is loaded, unlike in
         // other infobar tests where it is checked after animations have completed. This is because
@@ -84,7 +85,7 @@
         // infobars haven't being shown are invalid.
         Assert.assertEquals(
                 "Wrong infobar count after search", 1, mActivityTestRule.getInfoBars().size());
-        listener.addInfoBarAnimationFinished("InfoBar not added.");
+        listener1.addInfoBarAnimationFinished("InfoBar not added.");
 
         // Infobar should not appear again on the same day.
         mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE));
@@ -96,12 +97,12 @@
 
         // Infobar should appear again the next day.
         SearchGeolocationDisclosureTabHelper.setDayOffsetForTesting(1);
-        listener = new InfoBarTestAnimationListener();
-        container.addAnimationListener(listener);
+        InfoBarTestAnimationListener listener2 = new InfoBarTestAnimationListener();
+        TestThreadUtils.runOnUiThreadBlocking(() -> container.addAnimationListener(listener2));
         mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE));
         Assert.assertEquals(
                 "Wrong infobar count after search", 1, mActivityTestRule.getInfoBars().size());
-        listener.addInfoBarAnimationFinished("InfoBar not added.");
+        listener2.addInfoBarAnimationFinished("InfoBar not added.");
 
         // Infobar should not appear again on the same day.
         mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE));
@@ -111,12 +112,12 @@
 
         // Infobar should appear again the next day.
         SearchGeolocationDisclosureTabHelper.setDayOffsetForTesting(2);
-        listener = new InfoBarTestAnimationListener();
-        container.addAnimationListener(listener);
+        InfoBarTestAnimationListener listener3 = new InfoBarTestAnimationListener();
+        TestThreadUtils.runOnUiThreadBlocking(() -> container.addAnimationListener(listener3));
         mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE));
         Assert.assertEquals(
                 "Wrong infobar count after search", 1, mActivityTestRule.getInfoBars().size());
-        listener.addInfoBarAnimationFinished("InfoBar not added.");
+        listener3.addInfoBarAnimationFinished("InfoBar not added.");
 
         // Infobar should not appear again on the same day.
         mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE));
@@ -150,7 +151,7 @@
         // Infobar should appear when doing the first search.
         InfoBarContainer container = mActivityTestRule.getInfoBarContainer();
         InfoBarTestAnimationListener listener = new InfoBarTestAnimationListener();
-        container.addAnimationListener(listener);
+        TestThreadUtils.runOnUiThreadBlocking(() -> container.addAnimationListener(listener));
         mActivityTestRule.loadUrl(mTestServer.getURL(SEARCH_PAGE));
         Assert.assertEquals(
                 "Wrong infobar count after search", 1, mActivityTestRule.getInfoBars().size());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java
index bd0feed4..3a52c853 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java
@@ -144,7 +144,9 @@
                 new PermissionTestRule.PermissionUpdateWaiter(
                         "denied: ", mNotificationTestRule.getActivity());
 
-        mNotificationTestRule.getActivity().getActivityTab().addObserver(updateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mNotificationTestRule.getActivity().getActivityTab().addObserver(updateWaiter);
+        });
 
         mPermissionTestRule.runDenyTest(updateWaiter, NOTIFICATION_TEST_PAGE,
                 "Notification.requestPermission(addCountAndSendToTest)", 1, false, true);
@@ -182,7 +184,9 @@
                 new PermissionTestRule.PermissionUpdateWaiter(
                         "granted: ", mNotificationTestRule.getActivity());
 
-        mNotificationTestRule.getActivity().getActivityTab().addObserver(updateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mNotificationTestRule.getActivity().getActivityTab().addObserver(updateWaiter);
+        });
 
         mPermissionTestRule.runAllowTest(updateWaiter, NOTIFICATION_TEST_PAGE,
                 "Notification.requestPermission(addCountAndSendToTest)", 1, false, true);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
index 2a4d4228..c0b2e5f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
@@ -410,12 +410,14 @@
             OmniboxTestUtils.waitForOmniboxSuggestions(locationBar);
 
             final CallbackHelper loadedCallback = new CallbackHelper();
-            mTab.addObserver(new EmptyTabObserver() {
-                @Override
-                public void onPageLoadFinished(Tab tab, GURL url) {
-                    loadedCallback.notifyCalled();
-                    tab.removeObserver(this);
-                }
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                mTab.addObserver(new EmptyTabObserver() {
+                    @Override
+                    public void onPageLoadFinished(Tab tab, GURL url) {
+                        loadedCallback.notifyCalled();
+                        tab.removeObserver(this);
+                    }
+                });
             });
 
             final View v = urlBar;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
index da192d5..a9cc351 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
@@ -226,7 +226,8 @@
                 onSSLStateUpdatedCallbackHelper.notifyCalled();
             }
         };
-        mActivityTestRule.getActivity().getActivityTab().addObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().getActivityTab().addObserver(observer));
 
         try {
             final String testHttpsUrl =
@@ -278,7 +279,8 @@
                 onSSLStateUpdatedCallbackHelper.notifyCalled();
             }
         };
-        mActivityTestRule.getActivity().getActivityTab().addObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().getActivityTab().addObserver(observer));
 
         try {
             final String testHttpsUrl =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusViewRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusViewRenderTest.java
index aba475e..99bebea2 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusViewRenderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/status/StatusViewRenderTest.java
@@ -108,9 +108,8 @@
     @MediumTest
     @Feature({"RenderTest"})
     public void testStatusViewIncognitoWithIcon() throws IOException {
-        mLocationBarModel.setTab(null, /*  incognito= */ true);
-
         runOnUiThreadBlocking(() -> {
+            mLocationBarModel.setTab(null, /*  incognito= */ true);
             mStatusView.setIncognitoBadgeVisibility(true);
             mStatusModel.set(StatusProperties.STATUS_ICON_RESOURCE,
                     new StatusIconResource(R.drawable.ic_search, 0));
@@ -122,9 +121,8 @@
     @MediumTest
     @Feature({"RenderTest"})
     public void testStatusViewIncognitoNoIcon() throws IOException {
-        mLocationBarModel.setTab(null, /*  incognito= */ true);
-
         runOnUiThreadBlocking(() -> {
+            mLocationBarModel.setTab(null, /*  incognito= */ true);
             mStatusView.setIncognitoBadgeVisibility(true);
             mStatusModel.set(StatusProperties.STATUS_ICON_RESOURCE, null);
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java
index 5a9d154c..3fb9e2e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiTest.java
@@ -87,11 +87,7 @@
             mBottomSheetController =
                     cta.getRootUiCoordinatorForTesting().getBottomSheetController();
             mBottomSheetTestSupport = new BottomSheetTestSupport(mBottomSheetController);
-            mAssistantVoiceSearchConsentUi = new AssistantVoiceSearchConsentUi(
-                    cta.getWindowAndroid(), cta, mSharedPreferencesManager,
-                    ()
-                            -> AutofillAssistantPreferenceFragment.launchSettings(cta),
-                    mBottomSheetController);
+            mAssistantVoiceSearchConsentUi = createConsentUi();
         });
     }
 
@@ -107,6 +103,17 @@
         });
     }
 
+    private AssistantVoiceSearchConsentUi createConsentUi() {
+        return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            ChromeTabbedActivity cta = mActivityTestRule.getActivity();
+            return new AssistantVoiceSearchConsentUi(cta.getWindowAndroid(), cta,
+                    mSharedPreferencesManager,
+                    ()
+                            -> AutofillAssistantPreferenceFragment.launchSettings(cta),
+                    mBottomSheetController);
+        });
+    }
+
     @Test
     @MediumTest
     public void testNoBottomSheetControllerAvailable() {
@@ -258,12 +265,7 @@
 
         // Successful showing of the consent calls destroy(). Need to recreate the new
         // instance to set up the state again.
-        mAssistantVoiceSearchConsentUi = new AssistantVoiceSearchConsentUi(
-                mActivityTestRule.getActivity().getWindowAndroid(), mActivityTestRule.getActivity(),
-                mSharedPreferencesManager, () -> {
-                    AutofillAssistantPreferenceFragment.launchSettings(
-                            mActivityTestRule.getActivity());
-                }, mBottomSheetController);
+        mAssistantVoiceSearchConsentUi = createConsentUi();
         verifyAcceptingConsent();
     }
 
@@ -282,12 +284,7 @@
                     ConsentOutcome.CANCELED_VIA_BACK_BUTTON_PRESS);
             // Successful showing of the consent calls destroy(). Need to recreate the new
             // instance to set up the state again.
-            mAssistantVoiceSearchConsentUi = new AssistantVoiceSearchConsentUi(
-                    mActivityTestRule.getActivity().getWindowAndroid(),
-                    mActivityTestRule.getActivity(), mSharedPreferencesManager, () -> {
-                        AutofillAssistantPreferenceFragment.launchSettings(
-                                mActivityTestRule.getActivity());
-                    }, mBottomSheetController);
+            mAssistantVoiceSearchConsentUi = createConsentUi();
         }
 
         // But the max_taps_ignored+1-th will be treated as a rejection.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java
index 1d5260b..bf99001 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java
@@ -160,17 +160,14 @@
     public void testCloseAllTabs() {
         final CallbackHelper tabClosed = new CallbackHelper();
         final TabModel tabModel = mActivityTestRule.getActivity().getCurrentTabModel();
-        mActivityTestRule.getActivity().getCurrentTabModel().addObserver(new TabModelObserver() {
-            @Override
-            public void didCloseTab(int tabId, boolean incognito) {
-                if (tabModel.getCount() == 0) tabClosed.notifyCalled();
-            }
-        });
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mActivityTestRule.getActivity().getTabModelSelector().closeAllTabs();
-            }
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            tabModel.addObserver(new TabModelObserver() {
+                @Override
+                public void didCloseTab(int tabId, boolean incognito) {
+                    if (tabModel.getCount() == 0) tabClosed.notifyCalled();
+                }
+            });
+            mActivityTestRule.getActivity().getTabModelSelector().closeAllTabs();
         });
 
         try {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/GeolocationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/GeolocationTest.java
index 549318e..e5e2fc1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/GeolocationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/GeolocationTest.java
@@ -18,6 +18,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.LocationSettingsTestUtil;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.device.geolocation.LocationProviderOverrider;
 import org.chromium.device.geolocation.MockLocationProvider;
 
@@ -52,10 +53,10 @@
         Tab tab = mPermissionRule.getActivity().getActivityTab();
         PermissionUpdateWaiter updateWaiter =
                 new PermissionUpdateWaiter("Count:", mPermissionRule.getActivity());
-        tab.addObserver(updateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(updateWaiter));
         mPermissionRule.runAllowTest(
                 updateWaiter, TEST_FILE, javascript, nUpdates, withGesture, isDialog);
-        tab.removeObserver(updateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.removeObserver(updateWaiter));
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MIDITest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MIDITest.java
index 1b0ad81..ee23c258b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MIDITest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MIDITest.java
@@ -17,6 +17,7 @@
 import org.chromium.chrome.browser.permissions.PermissionTestRule.PermissionUpdateWaiter;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Test suite for MIDI permissions requests.
@@ -41,8 +42,8 @@
         Tab tab = mPermissionRule.getActivity().getActivityTab();
         PermissionUpdateWaiter updateWaiter =
                 new PermissionUpdateWaiter("pass", mPermissionRule.getActivity());
-        tab.addObserver(updateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(updateWaiter));
         mPermissionRule.runAllowTest(updateWaiter, TEST_FILE, "", 0, false, true);
-        tab.removeObserver(updateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.removeObserver(updateWaiter));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MediaTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MediaTest.java
index ef4ed884..f05bef5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MediaTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/MediaTest.java
@@ -17,6 +17,7 @@
 import org.chromium.chrome.browser.permissions.PermissionTestRule.PermissionUpdateWaiter;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.common.ContentSwitches;
 
 /**
@@ -43,10 +44,10 @@
         Tab tab = mPermissionRule.getActivity().getActivityTab();
         PermissionUpdateWaiter updateWaiter =
                 new PermissionUpdateWaiter(prefix, mPermissionRule.getActivity());
-        tab.addObserver(updateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(updateWaiter));
         mPermissionRule.runAllowTest(
                 updateWaiter, TEST_FILE, script, numUpdates, withGesture, isDialog);
-        tab.removeObserver(updateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.removeObserver(updateWaiter));
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/NotificationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/NotificationTest.java
index 9a344b4..6617214 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/NotificationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/NotificationTest.java
@@ -17,6 +17,7 @@
 import org.chromium.chrome.browser.permissions.PermissionTestRule.PermissionUpdateWaiter;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Test suite for notifications permissions requests.
@@ -42,9 +43,9 @@
         Tab tab = mPermissionRule.getActivity().getActivityTab();
         PermissionUpdateWaiter updateWaiter = new PermissionUpdateWaiter(
                 "request-callback-granted", mPermissionRule.getActivity());
-        tab.addObserver(updateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(updateWaiter));
         mPermissionRule.runAllowTest(
                 updateWaiter, TEST_FILE, "requestPermission()", 0, false, true);
-        tab.removeObserver(updateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.removeObserver(updateWaiter));
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java
index 8eb8f09..3ad8f1a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionNavigationTest.java
@@ -20,6 +20,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.LocationSettingsTestUtil;
 import org.chromium.content_public.browser.NavigationHandle;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Test suite for interaction between permissions requests and navigation.
@@ -68,9 +69,9 @@
                 callbackHelper.notifyCalled();
             }
         };
-        tab.addObserver(navigationWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(navigationWaiter));
         callbackHelper.waitForCallback(0);
-        tab.removeObserver(navigationWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.removeObserver(navigationWaiter));
 
         mPermissionRule.waitForDialogShownState(false);
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/QuotaTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/QuotaTest.java
index 48fb406..0f8c53c1c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/QuotaTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/QuotaTest.java
@@ -17,6 +17,7 @@
 import org.chromium.chrome.browser.permissions.PermissionTestRule.PermissionUpdateWaiter;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Test suite for quota permissions requests.
@@ -41,10 +42,10 @@
         Tab tab = mPermissionRule.getActivity().getActivityTab();
         PermissionUpdateWaiter updateWaiter =
                 new PermissionUpdateWaiter("Count: ", mPermissionRule.getActivity());
-        tab.addObserver(updateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(updateWaiter));
         mPermissionRule.runAllowTest(
                 updateWaiter, TEST_FILE, script, numUpdates, withGesture, isDialog);
-        tab.removeObserver(updateWaiter);
+        TestThreadUtils.runOnUiThreadBlocking(() -> tab.removeObserver(updateWaiter));
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/reengagement/ReengagementNotificationControllerIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/reengagement/ReengagementNotificationControllerIntegrationTest.java
index 567c44d..fa0aef4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/reengagement/ReengagementNotificationControllerIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/reengagement/ReengagementNotificationControllerIntegrationTest.java
@@ -283,7 +283,10 @@
                 tabAddedCallback.notifyCalled();
             }
         };
-        mTabbedActivityTestRule.getActivity().getTabModelSelector().addObserver(selectorObserver);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTabbedActivityTestRule.getActivity().getTabModelSelector().addObserver(
+                    selectorObserver);
+        });
 
         Intent intent =
                 new Intent(InstrumentationRegistry.getTargetContext(), ReengagementActivity.class);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
index ab3390d..758204e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
@@ -510,7 +510,7 @@
         Assert.assertEquals(0, mTestDelegate.onFinishDeferredInitializationCallback.getCallCount());
 
         // Dismiss the dialog without acting on it.
-        mTestDelegate.shownPromoDialog.dismiss();
+        TestThreadUtils.runOnUiThreadBlocking(() -> mTestDelegate.shownPromoDialog.dismiss());
 
         // SearchActivity should realize the failure case and prevent the user from using it.
         CriteriaHelper.pollUiThread(() -> {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/PriceDropMetricsLoggerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/PriceDropMetricsLoggerTest.java
index f37585c..4267faf 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/PriceDropMetricsLoggerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/PriceDropMetricsLoggerTest.java
@@ -22,6 +22,7 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeBrowserTestRule;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.concurrent.TimeUnit;
 
@@ -42,7 +43,7 @@
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
+        TestThreadUtils.runOnUiThreadBlocking(() -> MockitoAnnotations.initMocks(this));
         doReturn("offer-id").when(mShoppingPersistedTabData).getMainOfferId();
         doReturn(true).when(mShoppingPersistedTabData).hasPriceMicros();
         doReturn(true).when(mShoppingPersistedTabData).hasPreviousPriceMicros();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java
index 3d4b4c9..0d8e502b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java
@@ -22,6 +22,7 @@
 import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.chrome.browser.tab.TabTestUtils;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -109,29 +110,32 @@
     @Test
     @SmallTest
     public void testObserverAddedBeforeInitialize() {
-        TabModelSelectorBase selector = new TabModelSelectorBase(
-                null, EmptyTabModelFilter::new, false) {
-            @Override
-            public void requestToShowTab(Tab tab, int type) {}
+        TabModelSelectorBase selector = TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            return new TabModelSelectorBase(null, EmptyTabModelFilter::new, false) {
+                @Override
+                public void requestToShowTab(Tab tab, int type) {}
 
-            @Override
-            public boolean closeAllTabsRequest(boolean incognito) {
-                return false;
-            }
+                @Override
+                public boolean closeAllTabsRequest(boolean incognito) {
+                    return false;
+                }
 
-            @Override
-            public boolean isSessionRestoreInProgress() {
-                return false;
-            }
+                @Override
+                public boolean isSessionRestoreInProgress() {
+                    return false;
+                }
 
-            @Override
-            public Tab openNewTab(LoadUrlParams loadUrlParams, @TabLaunchType int type, Tab parent,
-                    boolean incognito) {
-                return null;
-            }
-        };
+                @Override
+                public Tab openNewTab(LoadUrlParams loadUrlParams, @TabLaunchType int type,
+                        Tab parent, boolean incognito) {
+                    return null;
+                }
+            };
+        });
         TestTabModelSelectorTabObserver observer = createTabModelSelectorTabObserver();
-        selector.initialize(sTestRule.getNormalTabModel(), sTestRule.getIncognitoTabModel());
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            selector.initialize(sTestRule.getNormalTabModel(), sTestRule.getIncognitoTabModel());
+        });
 
         Tab normalTab1 = createTestTab(false);
         addTab(sTestRule.getNormalTabModel(), normalTab1);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicyTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicyTest.java
index 5e6baa6..1330587 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicyTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicyTest.java
@@ -88,6 +88,9 @@
 
     private TabbedModeTabModelOrchestrator buildTestTabModelSelector(
             int[] normalTabIds, int[] incognitoTabIds) throws Exception {
+        final CallbackHelper callbackSignal = new CallbackHelper();
+        final int callCount = callbackSignal.getCallCount();
+
         MockTabModel.MockTabModelDelegate tabModelDelegate =
                 new MockTabModel.MockTabModelDelegate() {
                     @Override
@@ -101,21 +104,29 @@
                         return tab;
                     }
                 };
-        final MockTabModel normalTabModel = new MockTabModel(false, tabModelDelegate);
-        final MockTabModel incognitoTabModel = new MockTabModel(true, tabModelDelegate);
-        TabbedModeTabModelOrchestrator orchestrator = new TabbedModeTabModelOrchestrator(false);
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> orchestrator.createTabModels(new ChromeTabbedActivity(), null, null, 0));
-        TabModelSelector selector = orchestrator.getTabModelSelector();
-        ((MockTabModelSelector) selector).initializeTabModels(normalTabModel, incognitoTabModel);
-        final CallbackHelper callbackSignal = new CallbackHelper();
-        final int callCount = callbackSignal.getCallCount();
-        TabPersistentStore store = orchestrator.getTabPersistentStoreForTesting();
-        store.addObserver(new TabPersistentStoreObserver() {
-            @Override
-            public void onMetadataSavedAsynchronously(TabModelSelectorMetadata metadata) {
-                callbackSignal.notifyCalled();
-            }
+
+        final MockTabModel normalTabModel = TestThreadUtils.runOnUiThreadBlocking(
+                () -> new MockTabModel(false, tabModelDelegate));
+        final MockTabModel incognitoTabModel = TestThreadUtils.runOnUiThreadBlocking(
+                () -> new MockTabModel(true, tabModelDelegate));
+        TabbedModeTabModelOrchestrator orchestrator = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TabbedModeTabModelOrchestrator tmpOrchestrator =
+                    new TabbedModeTabModelOrchestrator(false);
+            tmpOrchestrator.createTabModels(new ChromeTabbedActivity(), null, null, 0);
+            TabModelSelector selector = tmpOrchestrator.getTabModelSelector();
+            ((MockTabModelSelector) selector)
+                    .initializeTabModels(normalTabModel, incognitoTabModel);
+            return tmpOrchestrator;
+        });
+        TabPersistentStore store = TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TabPersistentStore tmpStore = orchestrator.getTabPersistentStoreForTesting();
+            tmpStore.addObserver(new TabPersistentStoreObserver() {
+                @Override
+                public void onMetadataSavedAsynchronously(TabModelSelectorMetadata metadata) {
+                    callbackSignal.notifyCalled();
+                }
+            });
+            return tmpStore;
         });
 
         // Adding tabs results in writing to disk running on AsyncTasks. Run on the main thread
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarIntegrationTest.java
index ae7a2fb..8ac15c2bd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBarIntegrationTest.java
@@ -68,7 +68,8 @@
 
         final WebContents webContents = mActivityTestRule.getWebContents();
 
-        TestWebContentsObserver observer = new TestWebContentsObserver(webContents);
+        TestWebContentsObserver observer = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> new TestWebContentsObserver(webContents));
         // Start and stop load events are carefully tracked; there should be two start-stop pairs
         // that do not overlap.
         OnPageStartedHelper startHelper = observer.getOnPageStartedHelper();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerTest.java
index e8aad005..35b9b5c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerTest.java
@@ -164,9 +164,11 @@
             public void onDismiss(PropertyModel model, int dismissalCause) {}
         };
 
-        PropertyModel dialogModel = (new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
-                                             .with(ModalDialogProperties.CONTROLLER, controller)
-                                             .build());
+        PropertyModel dialogModel = TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
+            return new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
+                    .with(ModalDialogProperties.CONTROLLER, controller)
+                    .build();
+        });
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             sActivityTestRule.getActivity().getModalDialogManager().showDialog(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateCompactInfoBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateCompactInfoBarTest.java
index 864076b..7238bbd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateCompactInfoBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/translate/TranslateCompactInfoBarTest.java
@@ -54,16 +54,18 @@
     @Before
     public void setUp() throws Exception {
         mActivityTestRule.startMainActivityOnBlankPage();
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> TranslateBridge.setIgnoreMissingKeyForTesting(true));
-        mInfoBarContainer = mActivityTestRule.getInfoBarContainer();
-        mListener = new InfoBarTestAnimationListener();
-        mInfoBarContainer.addAnimationListener(mListener);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            TranslateBridge.setIgnoreMissingKeyForTesting(true);
+            mInfoBarContainer = mActivityTestRule.getInfoBarContainer();
+            mListener = new InfoBarTestAnimationListener();
+            mInfoBarContainer.addAnimationListener(mListener);
+        });
     }
 
     @After
     public void tearDown() {
-        mInfoBarContainer.removeAnimationListener(mListener);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mInfoBarContainer.removeAnimationListener(mListener));
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/video/FullscreenVideoTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/video/FullscreenVideoTest.java
index 2aefb5ae..8556045 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/video/FullscreenVideoTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/video/FullscreenVideoTest.java
@@ -31,6 +31,7 @@
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.KeyUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
 import org.chromium.media.MediaSwitches;
 import org.chromium.net.test.EmbeddedTestServerRule;
@@ -84,7 +85,8 @@
         mActivityTestRule.loadUrl(url);
         Tab tab = mActivity.getActivityTab();
         FullscreenManager.Observer listener = new FullscreenToggleListener();
-        mActivity.getFullscreenManager().addObserver(listener);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivity.getFullscreenManager().addObserver(listener));
 
         TestTouchUtils.singleClickView(
                 InstrumentationRegistry.getInstrumentation(), tab.getView(), 500, 500);
@@ -113,7 +115,8 @@
 
         final Tab tab = mActivity.getActivityTab();
         FullscreenManager.Observer listener = new FullscreenToggleListener();
-        mActivity.getFullscreenManager().addObserver(listener);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivity.getFullscreenManager().addObserver(listener));
 
         // Start playback to guarantee it's properly loaded.
         WebContents webContents = mActivity.getCurrentWebContents();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetTest.java
index 8c4c9f3..940a047f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetTest.java
@@ -40,6 +40,7 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
 import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
 import org.chromium.components.browser_ui.bottomsheet.TestBottomSheetContent;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.test.util.UiRestriction;
 
 import java.util.concurrent.ExecutionException;
@@ -151,7 +152,9 @@
     public void testTabObscuringState() throws TimeoutException {
         CallbackHelper obscuringStateChangedHelper = new CallbackHelper();
         TabObscuringHandler handler = mTestRule.getActivity().getTabObscuringHandler();
-        handler.addObserver((isObscured) -> obscuringStateChangedHelper.notifyCalled());
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            handler.addObserver((isObscured) -> obscuringStateChangedHelper.notifyCalled());
+        });
         mHighPriorityContent.setHasCustomScrimLifecycle(false);
 
         assertFalse("The tab should not yet be obscured.", handler.areAllTabsObscured());
@@ -173,7 +176,9 @@
     public void testTabObscuringState_customScrim() throws ExecutionException {
         CallbackHelper obscuringStateChangedHelper = new CallbackHelper();
         TabObscuringHandler handler = mTestRule.getActivity().getTabObscuringHandler();
-        handler.addObserver((isObscured) -> obscuringStateChangedHelper.notifyCalled());
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            handler.addObserver((isObscured) -> obscuringStateChangedHelper.notifyCalled());
+        });
         mHighPriorityContent.setHasCustomScrimLifecycle(true);
 
         assertFalse("The tab should not be obscured.", handler.areAllTabsObscured());
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index b26d2b08..e772e37d 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -786,6 +786,8 @@
     "metrics/metrics_memory_details.h",
     "metrics/metrics_reporting_state.cc",
     "metrics/metrics_reporting_state.h",
+    "metrics/metrics_services_web_contents_observer.cc",
+    "metrics/metrics_services_web_contents_observer.h",
     "metrics/network_quality_estimator_provider_impl.cc",
     "metrics/network_quality_estimator_provider_impl.h",
     "metrics/oom/out_of_memory_reporter.cc",
@@ -2390,6 +2392,7 @@
       "//chrome/browser/ash/system_extensions",
       "//chrome/browser/nearby_sharing/common",
       "//chrome/browser/ui/webui/chromeos/add_supervision:mojo_bindings",
+      "//chrome/browser/ui/webui/chromeos/audio:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/crostini_installer:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/crostini_upgrader:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/emoji:mojo_bindings",
@@ -4063,6 +4066,7 @@
       "sharesheet/sharesheet_service_delegate.h",
       "sharesheet/sharesheet_service_factory.cc",
       "sharesheet/sharesheet_service_factory.h",
+      "sharesheet/sharesheet_ui_delegate.h",
       "sharing/click_to_call/click_to_call_context_menu_observer.cc",
       "sharing/click_to_call/click_to_call_context_menu_observer.h",
       "sharing/click_to_call/click_to_call_metrics.cc",
diff --git a/chrome/browser/ash/arc/wallpaper/arc_wallpaper_service.cc b/chrome/browser/ash/arc/wallpaper/arc_wallpaper_service.cc
index ca3f656..f4ef505 100644
--- a/chrome/browser/ash/arc/wallpaper/arc_wallpaper_service.cc
+++ b/chrome/browser/ash/arc/wallpaper/arc_wallpaper_service.cc
@@ -169,12 +169,10 @@
                                              int32_t android_id) {
   const AccountId account_id =
       UserManager::Get()->GetPrimaryUser()->GetAccountId();
-  const std::string wallpaper_files_id =
-      WallpaperControllerClientImpl::Get()->GetFilesId(account_id);
 
   const bool result =
       WallpaperControllerClientImpl::Get()->SetThirdPartyWallpaper(
-          account_id, wallpaper_files_id, kAndroidWallpaperFilename,
+          account_id, kAndroidWallpaperFilename,
           ash::WALLPAPER_LAYOUT_CENTER_CROPPED, image);
 
   // Notify the Android side whether the request is going through or not.
diff --git a/chrome/browser/ash/full_restore/full_restore_app_launch_handler.cc b/chrome/browser/ash/full_restore/full_restore_app_launch_handler.cc
index d6244af..c9ea900 100644
--- a/chrome/browser/ash/full_restore/full_restore_app_launch_handler.cc
+++ b/chrome/browser/ash/full_restore/full_restore_app_launch_handler.cc
@@ -160,8 +160,6 @@
     // active profile path is set when switch users.
     ::full_restore::SetActiveProfilePath(profile_->GetPath());
   }
-
-  MaybePostRestore();
 }
 
 void FullRestoreAppLaunchHandler::MaybePostRestore() {
diff --git a/chrome/browser/ash/full_restore/full_restore_app_launch_handler_browsertest.cc b/chrome/browser/ash/full_restore/full_restore_app_launch_handler_browsertest.cc
index 429072ba..53c065f 100644
--- a/chrome/browser/ash/full_restore/full_restore_app_launch_handler_browsertest.cc
+++ b/chrome/browser/ash/full_restore/full_restore_app_launch_handler_browsertest.cc
@@ -303,6 +303,12 @@
   }
   ~FullRestoreAppLaunchHandlerBrowserTest() override = default;
 
+  void SetShouldRestore(FullRestoreAppLaunchHandler* app_launch_handler) {
+    content::RunAllTasksUntilIdle();
+    app_launch_handler->SetShouldRestore();
+    content::RunAllTasksUntilIdle();
+  }
+
   void CreateWebApp() {
     auto web_application_info = std::make_unique<WebApplicationInfo>();
     web_application_info->start_url = GURL("https://example.org");
@@ -381,9 +387,7 @@
   // Create FullRestoreAppLaunchHandler, and set should restore.
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
-  app_launch_handler->SetShouldRestore();
-
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   // Verify there is no new browser launched.
   EXPECT_EQ(count, BrowserList::GetInstance()->size());
@@ -405,7 +409,7 @@
   // Create FullRestoreAppLaunchHandler, and set should restore.
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
-  app_launch_handler->SetShouldRestore();
+  SetShouldRestore(app_launch_handler.get());
 
   CreateWebApp();
 
@@ -437,7 +441,7 @@
   // Create FullRestoreAppLaunchHandler, and set should restore.
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
-  app_launch_handler->SetShouldRestore();
+  SetShouldRestore(app_launch_handler.get());
 
   // The web app window should be attainable.
   CreateWebApp();
@@ -475,10 +479,7 @@
 
   CreateWebApp();
 
-  // Set should restore.
-  app_launch_handler->SetShouldRestore();
-
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   EXPECT_TRUE(FindWebAppWindow());
 }
@@ -556,10 +557,7 @@
   // Create FullRestoreAppLaunchHandler.
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
-
-  // Set should restore.
-  app_launch_handler->SetShouldRestore();
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   app_launch_handler->LaunchBrowserWhenReady(/*first_run_full_restore=*/false);
   content::RunAllTasksUntilIdle();
@@ -590,10 +588,7 @@
   // Create FullRestoreAppLaunchHandler.
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
-
-  // Set should restore.
-  app_launch_handler->SetShouldRestore();
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   app_launch_handler->LaunchBrowserWhenReady(/*first_run_full_restore=*/false);
   content::RunAllTasksUntilIdle();
@@ -658,11 +653,9 @@
   // Create FullRestoreAppLaunchHandler, and set should restore.
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
-  app_launch_handler->SetShouldRestore();
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   app_launch_handler->LaunchBrowserWhenReady(/*first_run_full_restore=*/false);
-  content::RunAllTasksUntilIdle();
 
   CreateWebApp();
   content::RunAllTasksUntilIdle();
@@ -695,14 +688,10 @@
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
 
   app_launch_handler->LaunchBrowserWhenReady(/*first_run_full_restore=*/false);
-  content::RunAllTasksUntilIdle();
 
   CreateWebApp();
-  content::RunAllTasksUntilIdle();
 
-  // Set should restore.
-  app_launch_handler->SetShouldRestore();
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   // Verify there is new browser launched.
   EXPECT_EQ(count + 2, BrowserList::GetInstance()->size());
@@ -726,8 +715,7 @@
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
   app_launch_handler->LaunchBrowserWhenReady(/*first_run_full_restore=*/false);
-  app_launch_handler->SetShouldRestore();
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   ASSERT_EQ(count + 1u, BrowserList::GetInstance()->size());
 
@@ -786,8 +774,7 @@
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
   app_launch_handler->LaunchBrowserWhenReady(/*first_run_full_restore=*/false);
-  app_launch_handler->SetShouldRestore();
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   ASSERT_EQ(count + 1u, browser_list->size());
   ASSERT_EQ(1u, browser_list->size());
@@ -852,8 +839,7 @@
   // Read from the restore data.
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
-  app_launch_handler->SetShouldRestore();
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   // Verify the restore window id.
   app_window = CreateAppWindow(browser()->profile(), extension);
@@ -922,8 +908,7 @@
   // Read from the restore data.
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
-  app_launch_handler->SetShouldRestore();
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   // Tests that the created window is minimized.
   app_window = CreateAppWindow(browser()->profile(), extension);
@@ -959,8 +944,7 @@
   // Read from the restore data.
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
-  app_launch_handler->SetShouldRestore();
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   // Verify the restore window id;
   app_window1 = CreateAppWindow(browser()->profile(), extension);
@@ -1023,8 +1007,7 @@
   // Read from the restore data.
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
-  app_launch_handler->SetShouldRestore();
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   // Tests that the created window is not fullscreen.
   app_window = CreateAppWindow(browser()->profile(), extension);
@@ -1139,9 +1122,7 @@
 
     app_launch_handler_ =
         std::make_unique<FullRestoreAppLaunchHandler>(profile());
-    app_launch_handler_->SetShouldRestore();
-
-    content::RunAllTasksUntilIdle();
+    SetShouldRestore(app_launch_handler_.get());
   }
 
   void ForceLaunchApp(const std::string& app_id, int32_t window_id) {
@@ -2255,9 +2236,7 @@
   // Simulate the user clicks the `restore` button.
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
-  app_launch_handler->SetShouldRestore();
-
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   widget1->CloseNow();
 
@@ -2347,6 +2326,12 @@
                  false /* should_notify_initialized */);
   }
 
+  void SetShouldRestore(FullRestoreAppLaunchHandler* app_launch_handler) {
+    content::RunAllTasksUntilIdle();
+    app_launch_handler->SetShouldRestore();
+    content::RunAllTasksUntilIdle();
+  }
+
   bool HasWindowInfo(int32_t restore_window_id) {
     return ::full_restore::FullRestoreReadHandler::GetInstance()->HasWindowInfo(
         restore_window_id);
@@ -2377,11 +2362,8 @@
 
   ASSERT_FALSE(HasWindowInfo(window_id));
 
-  // Set should restore.
-  app_launch_handler->SetShouldRestore();
+  SetShouldRestore(app_launch_handler.get());
 
-  // Wait for the restoration.
-  content::RunAllTasksUntilIdle();
   ASSERT_TRUE(HasWindowInfo(window_id));
 
   // Get the restored browser for the system web app.
@@ -2458,11 +2440,7 @@
   auto app_launch_handler =
       std::make_unique<FullRestoreAppLaunchHandler>(profile());
 
-  // Set should restore.
-  app_launch_handler->SetShouldRestore();
-
-  // Wait for the restoration.
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   // Verify the app is not restored because the app is not installed.
   Browser* restore_app_browser = GetBrowserForWindowId(window_id);
@@ -2518,13 +2496,7 @@
   // Close |app_browser| so that the SWA can be relaunched.
   web_app::CloseAndWait(app_browser);
 
-  // Set should restore.
-  app_launch_handler->SetShouldRestore();
-
-  // Wait for the restoration.
-  // TODO(chinsenj|nancylingwang): Look into using a more specific signal to
-  // detect when restoration is done.
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   // Get the restored browser for the system web app.
   Browser* restore_app_browser = GetBrowserForWindowId(window_id);
@@ -2584,11 +2556,7 @@
   web_app::CloseAndWait(app1_browser);
   web_app::CloseAndWait(app2_browser);
 
-  // Set should restore.
-  app_launch_handler->SetShouldRestore();
-
-  // Wait for the restoration.
-  content::RunAllTasksUntilIdle();
+  SetShouldRestore(app_launch_handler.get());
 
   aura::Window* restore_app1_window = nullptr;
   aura::Window* restore_app2_window = nullptr;
diff --git a/chrome/browser/ash/guest_os/guest_os_registry_service.cc b/chrome/browser/ash/guest_os/guest_os_registry_service.cc
index d1c7666..658a90c 100644
--- a/chrome/browser/ash/guest_os/guest_os_registry_service.cc
+++ b/chrome/browser/ash/guest_os/guest_os_registry_service.cc
@@ -58,6 +58,9 @@
 constexpr char kPluginVmAppsInstalledHistogram[] =
     "PluginVm.AppsInstalledAtLogin";
 
+constexpr char kBorealisAppsInstalledHistogram[] =
+    "Borealis.AppsInstalledAtLogin";
+
 base::Value ProtoToDictionary(const App::LocaleString& locale_string) {
   base::Value result(base::Value::Type::DICTIONARY);
   for (const App::LocaleString::Entry& entry : locale_string.values()) {
@@ -354,14 +357,10 @@
 
 GuestOsRegistryService::VmType GuestOsRegistryService::Registration::VmType()
     const {
-  absl::optional<int> vm_type =
-      pref_.FindIntKey(guest_os::prefs::kAppVmTypeKey);
   // The VmType field is new, existing Apps that do not include it must be
-  // TERMINA Apps, as Plugin VM apps are not yet in production.
-  if (!vm_type) {
-    return GuestOsRegistryService::VmType::ApplicationList_VmType_TERMINA;
-  }
-  return static_cast<GuestOsRegistryService::VmType>(*vm_type);
+  // TERMINA (0) Apps, as Plugin VM apps are not yet in production.
+  return static_cast<GuestOsRegistryService::VmType>(
+      pref_.FindIntKey(guest_os::prefs::kAppVmTypeKey).value_or(0));
 }
 
 std::string GuestOsRegistryService::Registration::VmName() const {
@@ -378,8 +377,7 @@
 }
 
 std::string GuestOsRegistryService::Registration::Name() const {
-  if (VmType() ==
-      GuestOsRegistryService::VmType::ApplicationList_VmType_PLUGIN_VM) {
+  if (VmType() == VmType::ApplicationList_VmType_PLUGIN_VM) {
     return l10n_util::GetStringFUTF8(
         IDS_PLUGIN_VM_APP_NAME_WINDOWS_SUFFIX,
         base::UTF8ToUTF16(LocalizedString(guest_os::prefs::kAppNameKey)));
@@ -643,18 +641,7 @@
   const base::DictionaryValue* apps =
       prefs_->GetDictionary(guest_os::prefs::kGuestOsRegistry);
 
-  bool crostini_enabled =
-      crostini::CrostiniFeatures::Get()->IsEnabled(profile_);
-  bool plugin_vm_enabled =
-      plugin_vm::PluginVmFeatures::Get()->IsEnabled(profile_);
-  bool borealis_enabled = borealis::BorealisService::GetForProfile(profile_)
-                              ->Features()
-                              .IsEnabled();
-  if (!crostini_enabled && !plugin_vm_enabled && !borealis_enabled)
-    return;
-
-  int num_crostini_apps = 0;
-  int num_plugin_vm_apps = 0;
+  base::flat_map<int, int> num_apps;
 
   for (const auto item : apps->DictItems()) {
     if (item.first == crostini::kCrostiniTerminalSystemAppId)
@@ -665,28 +652,27 @@
     if (no_display && no_display.value())
       continue;
 
-    absl::optional<int> vm_type =
-        item.second.FindIntKey(guest_os::prefs::kAppVmTypeKey);
-    if (!vm_type ||
-        vm_type ==
-            GuestOsRegistryService::VmType::ApplicationList_VmType_TERMINA) {
-      num_crostini_apps++;
-    } else if (vm_type == GuestOsRegistryService::VmType::
-                              ApplicationList_VmType_PLUGIN_VM) {
-      num_plugin_vm_apps++;
-    } else {
-      NOTREACHED();
-    }
+    int vm_type =
+        item.second.FindIntKey(guest_os::prefs::kAppVmTypeKey).value_or(0);
+    num_apps[vm_type]++;
   }
 
-  if (crostini_enabled)
+  if (crostini::CrostiniFeatures::Get()->IsEnabled(profile_)) {
     UMA_HISTOGRAM_COUNTS_1000(kCrostiniAppsInstalledHistogram,
-                              num_crostini_apps);
-  if (plugin_vm_enabled)
-    UMA_HISTOGRAM_COUNTS_1000(kPluginVmAppsInstalledHistogram,
-                              num_plugin_vm_apps);
-
-  // TODO(b/166691285): borealis launch metrics.
+                              num_apps[VmType::ApplicationList_VmType_TERMINA]);
+  }
+  if (plugin_vm::PluginVmFeatures::Get()->IsEnabled(profile_)) {
+    UMA_HISTOGRAM_COUNTS_1000(
+        kPluginVmAppsInstalledHistogram,
+        num_apps[VmType::ApplicationList_VmType_PLUGIN_VM]);
+  }
+  if (borealis::BorealisService::GetForProfile(profile_)
+          ->Features()
+          .IsEnabled()) {
+    UMA_HISTOGRAM_COUNTS_1000(
+        kBorealisAppsInstalledHistogram,
+        num_apps[VmType::ApplicationList_VmType_BOREALIS]);
+  }
 }
 
 base::FilePath GuestOsRegistryService::GetAppPath(
diff --git a/chrome/browser/ash/sharesheet/OWNERS b/chrome/browser/ash/sharesheet/OWNERS
deleted file mode 100644
index d1aabab..0000000
--- a/chrome/browser/ash/sharesheet/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file://chrome/browser/sharesheet/OWNERS
diff --git a/chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.h b/chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.h
deleted file mode 100644
index 6bff6e0..0000000
--- a/chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2021 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_ASH_SHARESHEET_CROS_SHARESHEET_SERVICE_DELEGATE_H_
-#define CHROME_BROWSER_ASH_SHARESHEET_CROS_SHARESHEET_SERVICE_DELEGATE_H_
-
-#include "chrome/browser/sharesheet/sharesheet_controller.h"
-#include "chrome/browser/sharesheet/sharesheet_service_delegate.h"
-#include "chrome/browser/sharesheet/sharesheet_types.h"
-#include "components/services/app_service/public/mojom/types.mojom.h"
-#include "ui/gfx/native_widget_types.h"
-
-namespace ash {
-namespace sharesheet {
-
-class SharesheetBubbleView;
-
-// The Chrome OS only SharesheetServiceDelegate class.
-// CrosSharesheetServiceDelegate is the interface through which the business
-// logic in the SharesheetService communicates with the UI
-// (SharesheetBubbleView).
-class CrosSharesheetServiceDelegate
-    : public ::sharesheet::SharesheetServiceDelegate {
- public:
-  CrosSharesheetServiceDelegate(
-      gfx::NativeWindow native_window,
-      ::sharesheet::SharesheetService* sharesheet_service);
-  ~CrosSharesheetServiceDelegate() override = default;
-  CrosSharesheetServiceDelegate(const CrosSharesheetServiceDelegate&) = delete;
-  CrosSharesheetServiceDelegate& operator=(
-      const CrosSharesheetServiceDelegate&) = delete;
-
-  // ::sharesheet::SharesheetServiceDelegate:
-  void ShowBubble(std::vector<::sharesheet::TargetInfo> targets,
-                  apps::mojom::IntentPtr intent,
-                  ::sharesheet::DeliveredCallback delivered_callback,
-                  ::sharesheet::CloseCallback close_callback) override;
-  void ShowNearbyShareBubbleForArc(
-      apps::mojom::IntentPtr intent,
-      ::sharesheet::DeliveredCallback delivered_callback,
-      ::sharesheet::CloseCallback close_callback) override;
-  void OnActionLaunched() override;
-
-  // ::sharesheet::SharesheetController:
-  void SetBubbleSize(int width, int height) override;
-  void CloseBubble(::sharesheet::SharesheetResult result) override;
-
- private:
-  bool IsBubbleVisible() const;
-
-  // Owned by views.
-  SharesheetBubbleView* sharesheet_bubble_view_;
-};
-
-}  // namespace sharesheet
-}  // namespace ash
-
-#endif  // CHROME_BROWSER_ASH_SHARESHEET_CROS_SHARESHEET_SERVICE_DELEGATE_H_
diff --git a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc
index 2064301..a9afdbc 100644
--- a/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/chrome_personalization_app_ui_delegate.cc
@@ -265,12 +265,11 @@
   DCHECK(user);
 
   auto* controller = ash::WallpaperController::Get();
-  auto* client = WallpaperControllerClientImpl::Get();
 
   const auto& account_id = user->GetAccountId();
 
   controller->SetCustomWallpaper(
-      account_id, client->GetFilesId(account_id), it->second.path,
+      account_id, it->second.path,
       ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
       /*preview_mode=*/false, std::move(callback));
 }
diff --git a/chrome/browser/browsing_data/browsing_data_quota_helper_impl.cc b/chrome/browser/browsing_data/browsing_data_quota_helper_impl.cc
index 6d06b8b..07fd1ac 100644
--- a/chrome/browser/browsing_data/browsing_data_quota_helper_impl.cc
+++ b/chrome/browser/browsing_data/browsing_data_quota_helper_impl.cc
@@ -78,18 +78,18 @@
                      base::Owned(pending_hosts)));
 
   for (const StorageType& type : types) {
-    quota_manager_->GetStorageKeysModifiedBetween(
-        type, base::Time(), base::Time::Max(),
-        base::BindOnce(&BrowsingDataQuotaHelperImpl::GotStorageKeys,
-                       weak_factory_.GetWeakPtr(), pending_hosts, completion));
+    quota_manager_->GetStorageKeysForType(
+        type, base::BindOnce(&BrowsingDataQuotaHelperImpl::GotStorageKeys,
+                             weak_factory_.GetWeakPtr(), pending_hosts,
+                             completion, type));
   }
 }
 
 void BrowsingDataQuotaHelperImpl::GotStorageKeys(
     PendingHosts* pending_hosts,
     base::OnceClosure completion,
-    const std::set<blink::StorageKey>& storage_keys,
-    StorageType type) {
+    StorageType type,
+    const std::set<blink::StorageKey>& storage_keys) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   for (const blink::StorageKey& storage_key : storage_keys) {
     if (!browsing_data::IsWebScheme(storage_key.origin().scheme()))
diff --git a/chrome/browser/browsing_data/browsing_data_quota_helper_impl.h b/chrome/browser/browsing_data/browsing_data_quota_helper_impl.h
index 0fecffc6..6d648c1 100644
--- a/chrome/browser/browsing_data/browsing_data_quota_helper_impl.h
+++ b/chrome/browser/browsing_data/browsing_data_quota_helper_impl.h
@@ -46,11 +46,11 @@
   // Calls QuotaManager::GetStorageKeysModifiedBetween for each storage type.
   void FetchQuotaInfoOnIOThread(FetchResultCallback callback);
 
-  // Callback function for QuotaManager::GetStorageKeysModifiedBetween.
+  // Callback function for QuotaManager::GetStorageKeysForType.
   void GotStorageKeys(PendingHosts* pending_hosts,
                       base::OnceClosure completion,
-                      const std::set<blink::StorageKey>& storage_keys,
-                      blink::mojom::StorageType type);
+                      blink::mojom::StorageType type,
+                      const std::set<blink::StorageKey>& storage_keys);
 
   // Calls QuotaManager::GetHostUsage for each (origin, type) pair.
   void OnGetOriginsComplete(FetchResultCallback callback,
diff --git a/chrome/browser/browsing_data/counters/site_data_counting_helper.cc b/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
index 0714dd3..590a4bd 100644
--- a/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
+++ b/chrome/browser/browsing_data/counters/site_data_counting_helper.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "components/browsing_data/content/browsing_data_helper.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/services/storage/public/cpp/buckets/bucket_info.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/dom_storage_context.h"
@@ -62,9 +63,9 @@
   if (quota_manager) {
     // Count storage keys with filesystem, websql, appcache, indexeddb,
     // serviceworkers and cachestorage using quota manager.
-    auto storage_keys_callback = base::BindRepeating(
-        &SiteDataCountingHelper::GetQuotaStorageKeysCallback,
-        base::Unretained(this));
+    auto buckets_callback =
+        base::BindRepeating(&SiteDataCountingHelper::GetQuotaBucketsCallback,
+                            base::Unretained(this));
     const blink::mojom::StorageType types[] = {
         blink::mojom::StorageType::kTemporary,
         blink::mojom::StorageType::kPersistent,
@@ -73,9 +74,8 @@
       tasks_ += 1;
       content::GetIOThreadTaskRunner({})->PostTask(
           FROM_HERE,
-          base::BindOnce(&storage::QuotaManager::GetStorageKeysModifiedBetween,
-                         quota_manager, type, begin_, end_,
-                         storage_keys_callback));
+          base::BindOnce(&storage::QuotaManager::GetBucketsModifiedBetween,
+                         quota_manager, type, begin_, end_, buckets_callback));
     }
   }
 
@@ -156,17 +156,17 @@
                                 base::Unretained(this), origins));
 }
 
-void SiteDataCountingHelper::GetQuotaStorageKeysCallback(
-    const std::set<blink::StorageKey>& storage_keys,
+void SiteDataCountingHelper::GetQuotaBucketsCallback(
+    const std::set<storage::BucketInfo>& buckets,
     blink::mojom::StorageType type) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  std::vector<GURL> urls;
-  urls.resize(storage_keys.size());
-  for (const blink::StorageKey& storage_key : storage_keys)
-    urls.push_back(storage_key.origin().GetURL());
+  std::set<GURL> urls;
+  for (const storage::BucketInfo& bucket : buckets)
+    urls.insert(bucket.storage_key.origin().GetURL());
   content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(&SiteDataCountingHelper::Done,
-                                base::Unretained(this), std::move(urls)));
+      FROM_HERE,
+      base::BindOnce(&SiteDataCountingHelper::Done, base::Unretained(this),
+                     std::vector<GURL>(urls.begin(), urls.end())));
 }
 
 void SiteDataCountingHelper::GetLocalStorageUsageInfoCallback(
diff --git a/chrome/browser/browsing_data/counters/site_data_counting_helper.h b/chrome/browser/browsing_data/counters/site_data_counting_helper.h
index 1d5cfd7..3ee94de3 100644
--- a/chrome/browser/browsing_data/counters/site_data_counting_helper.h
+++ b/chrome/browser/browsing_data/counters/site_data_counting_helper.h
@@ -19,16 +19,13 @@
 class Profile;
 class HostContentSettingsMap;
 
-namespace blink {
-class StorageKey;
-}
-
 namespace content {
 struct StorageUsageInfo;
 }
 
 namespace storage {
 class SpecialStoragePolicy;
+struct BucketInfo;
 }
 
 // Helper class that counts the number of unique origins, that are affected by
@@ -52,9 +49,8 @@
       const scoped_refptr<storage::SpecialStoragePolicy>&
           special_storage_policy,
       const std::vector<content::StorageUsageInfo>& infos);
-  void GetQuotaStorageKeysCallback(
-      const std::set<blink::StorageKey>& storage_keys,
-      blink::mojom::StorageType type);
+  void GetQuotaBucketsCallback(const std::set<storage::BucketInfo>& buckets,
+                               blink::mojom::StorageType type);
   void SitesWithMediaLicensesCallback(
       const std::list<BrowsingDataMediaLicenseHelper::MediaLicenseInfo>&
           media_license_info_list);
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index 53048514..aba20152 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -183,6 +183,8 @@
 #include "chrome/browser/ui/webui/app_management/app_management.mojom.h"
 #include "chrome/browser/ui/webui/chromeos/add_supervision/add_supervision.mojom.h"
 #include "chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_ui.h"
+#include "chrome/browser/ui/webui/chromeos/audio/audio.mojom.h"
+#include "chrome/browser/ui/webui/chromeos/audio/audio_ui.h"
 #include "chrome/browser/ui/webui/chromeos/crostini_installer/crostini_installer.mojom.h"
 #include "chrome/browser/ui/webui/chromeos/crostini_installer/crostini_installer_ui.h"
 #include "chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader.mojom.h"
@@ -918,6 +920,10 @@
         chromeos::bluetooth_config::mojom::CrosBluetoothConfig,
         chromeos::settings::OSSettingsUI>(map);
   }
+
+  RegisterWebUIControllerInterfaceBinder<audio::mojom::PageHandlerFactory,
+                                         chromeos::AudioUI>(map);
+
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OFFICIAL_BUILD)
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index ffc78361..9c1ef9f 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -4026,7 +4026,7 @@
       std::make_unique<CertificateReportingServiceCertReporter>(web_contents),
       base::BindOnce(&HandleSSLErrorWrapper), base::BindOnce(&IsInHostedApp),
       base::BindOnce(
-          &ShouldIgnoreInterstitialBecauseNavigationDefaultedToHttps)));
+          &ShouldIgnoreSslInterstitialBecauseNavigationDefaultedToHttps)));
 
   throttles.push_back(std::make_unique<LoginNavigationThrottle>(handle));
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index a445e13..28ed0fc 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -271,7 +271,6 @@
     "//chromeos/services/device_sync:stub_device_sync",
     "//chromeos/services/device_sync/public/cpp",
     "//chromeos/services/ime:constants",
-    "//chromeos/services/ime/public/cpp:buildflags",
     "//chromeos/services/ime/public/cpp:structs",
     "//chromeos/services/ime/public/mojom",
     "//chromeos/services/machine_learning/public/cpp",
@@ -2547,8 +2546,6 @@
     "../ash/settings/supervised_user_cros_settings_provider.h",
     "../ash/settings/token_encryptor.cc",
     "../ash/settings/token_encryptor.h",
-    "../ash/sharesheet/cros_sharesheet_service_delegate.cc",
-    "../ash/sharesheet/cros_sharesheet_service_delegate.h",
     "../ash/smb_client/discovery/host_locator.h",
     "../ash/smb_client/discovery/in_memory_host_locator.cc",
     "../ash/smb_client/discovery/in_memory_host_locator.h",
diff --git a/chrome/browser/chromeos/extensions/wallpaper_api.cc b/chrome/browser/chromeos/extensions/wallpaper_api.cc
index efc263c..dc248b9 100644
--- a/chrome/browser/chromeos/extensions/wallpaper_api.cc
+++ b/chrome/browser/chromeos/extensions/wallpaper_api.cc
@@ -156,8 +156,6 @@
   // Gets account id from the caller, ensuring multiprofile compatibility.
   const user_manager::User* user = GetUserFromBrowserContext(browser_context());
   account_id_ = user->GetAccountId();
-  wallpaper_files_id_ =
-      WallpaperControllerClientImpl::Get()->GetFilesId(account_id_);
 
   if (params_->details.data) {
     StartDecode(*params_->details.data);
@@ -188,7 +186,7 @@
   const std::string file_name =
       base::FilePath(params_->details.filename).BaseName().value();
   WallpaperControllerClientImpl::Get()->SetCustomWallpaper(
-      account_id_, wallpaper_files_id_, file_name, layout, image,
+      account_id_, file_name, layout, image,
       /*preview_mode=*/false);
   unsafe_wallpaper_decoder_ = nullptr;
 
diff --git a/chrome/browser/chromeos/extensions/wallpaper_api.h b/chrome/browser/chromeos/extensions/wallpaper_api.h
index 44d3ecc..9448877 100644
--- a/chrome/browser/chromeos/extensions/wallpaper_api.h
+++ b/chrome/browser/chromeos/extensions/wallpaper_api.h
@@ -45,9 +45,6 @@
 
   // User id of the user who initiate this API call.
   AccountId account_id_ = EmptyAccountId();
-
-  // Id used to identify user wallpaper files on hard drive.
-  std::string wallpaper_files_id_;
 };
 
 #endif  // CHROME_BROWSER_CHROMEOS_EXTENSIONS_WALLPAPER_API_H_
diff --git a/chrome/browser/chromeos/extensions/wallpaper_private_api.cc b/chrome/browser/chromeos/extensions/wallpaper_private_api.cc
index 9284c88..f9c0e32 100644
--- a/chrome/browser/chromeos/extensions/wallpaper_private_api.cc
+++ b/chrome/browser/chromeos/extensions/wallpaper_private_api.cc
@@ -425,8 +425,6 @@
   // Gets account id from the caller, ensuring multiprofile compatibility.
   const user_manager::User* user = GetUserFromBrowserContext(browser_context());
   account_id_ = user->GetAccountId();
-  wallpaper_files_id_ =
-      WallpaperControllerClientImpl::Get()->GetFilesId(account_id_);
 
   StartDecode(params->wallpaper);
 
@@ -442,8 +440,7 @@
   const std::string file_name =
       base::FilePath(params->file_name).BaseName().value();
   WallpaperControllerClientImpl::Get()->SetCustomWallpaper(
-      account_id_, wallpaper_files_id_, file_name, layout, image,
-      params->preview_mode);
+      account_id_, file_name, layout, image, params->preview_mode);
   unsafe_wallpaper_decoder_ = nullptr;
 
   if (params->generate_thumbnail) {
diff --git a/chrome/browser/chromeos/extensions/wallpaper_private_api.h b/chrome/browser/chromeos/extensions/wallpaper_private_api.h
index 25c1d001..f0cb515 100644
--- a/chrome/browser/chromeos/extensions/wallpaper_private_api.h
+++ b/chrome/browser/chromeos/extensions/wallpaper_private_api.h
@@ -135,9 +135,6 @@
 
   // User account id of the active user when this api is been called.
   AccountId account_id_ = EmptyAccountId();
-
-  // User id hash of the logged in user.
-  std::string wallpaper_files_id_;
 };
 
 class WallpaperPrivateSetCustomWallpaperLayoutFunction
diff --git a/chrome/browser/download/android/download_open_source.h b/chrome/browser/download/android/download_open_source.h
index 61a7f27..cf529af2 100644
--- a/chrome/browser/download/android/download_open_source.h
+++ b/chrome/browser/download/android/download_open_source.h
@@ -38,7 +38,9 @@
   kOfflineIndicator = 11,
   // Android notification for offline content.
   kOfflineContentNotification = 12,
-  kMaxValue = kOfflineContentNotification
+  // Download progress message.
+  kDownloadProgressMessage = 13,
+  kMaxValue = kDownloadProgressMessage
 };
 
 #endif  // CHROME_BROWSER_DOWNLOAD_ANDROID_DOWNLOAD_OPEN_SOURCE_H_
diff --git a/chrome/browser/extensions/navigation_observer_browsertest.cc b/chrome/browser/extensions/navigation_observer_browsertest.cc
index eb5135b..aaa2414 100644
--- a/chrome/browser/extensions/navigation_observer_browsertest.cc
+++ b/chrome/browser/extensions/navigation_observer_browsertest.cc
@@ -223,17 +223,23 @@
   // The SiteInstance of the disabled extension frame should be different from
   // the SiteInstance of the enabled extension subframe. It should reference the
   // invalid extension ID or the error page URL.
-  EXPECT_NE(subframe->GetSiteInstance(), extension_site_instance);
-  if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled(false)) {
-    EXPECT_EQ(subframe->GetSiteInstance()->GetSiteURL(),
-              GURL(content::kUnreachableWebDataURL));
-  } else {
-    EXPECT_EQ(subframe->GetSiteInstance()->GetSiteURL(),
-              GURL(chrome::kExtensionInvalidRequestURL));
-    // The disabled extension process should only be locked if strict extension
-    // isolation is enabled.
-    EXPECT_EQ(IsStrictExtensionIsolationEnabled(),
-              subframe->GetProcess()->IsProcessLockedToSiteForTesting());
+  // TODO(crbug.com/1234637): remove the exception for the
+  // SubframeShutdownDelay experiment below. It is temporary, intended to allow
+  // the experiment to proceed while the reason for it causing
+  // |extension_site_instance| to be reused is addressed separately.
+  if (!base::FeatureList::IsEnabled(features::kSubframeShutdownDelay)) {
+    EXPECT_NE(subframe->GetSiteInstance(), extension_site_instance);
+    if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled(false)) {
+      EXPECT_EQ(subframe->GetSiteInstance()->GetSiteURL(),
+                GURL(content::kUnreachableWebDataURL));
+    } else {
+      EXPECT_EQ(subframe->GetSiteInstance()->GetSiteURL(),
+                GURL(chrome::kExtensionInvalidRequestURL));
+      // The disabled extension process should only be locked if strict
+      // extension isolation is enabled.
+      EXPECT_EQ(IsStrictExtensionIsolationEnabled(),
+                subframe->GetProcess()->IsProcessLockedToSiteForTesting());
+    }
   }
 
   // Re-enable the extension.
diff --git a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
index 28970f4..24afa01 100644
--- a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
+++ b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
@@ -106,17 +106,6 @@
 const char kPathTypeKey[] = "path-type";
 const char kTimestampKey[] = "timestamp";
 
-// TODO(https://crbug.com/1177334): Remove migration logic.
-// Deprecated 2/2021. Former schema (per origin):
-// {
-//  ...
-//   "default-path" : <path>,
-//   "default-path-type" : <type>,
-//  ...
-// }
-const char kDeprecatedLastPickedDirectoryKey[] = "default-path";
-const char kDeprecatedLastPickedDirectoryTypeKey[] = "default-path-type";
-
 void ShowFileSystemAccessRestrictedDirectoryDialogOnUIThread(
     content::GlobalRenderFrameHostId frame_id,
     const url::Origin& origin,
@@ -1162,45 +1151,6 @@
                      std::move(result_callback)));
 }
 
-// TODO(https://crbug.com/1177334): Remove migration logic.
-void ChromeFileSystemAccessPermissionContext::MaybeMigrateOriginToNewSchema(
-    const url::Origin& origin) {
-  std::unique_ptr<base::Value> value = content_settings()->GetWebsiteSetting(
-      origin.GetURL(), origin.GetURL(),
-      ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, /*info=*/nullptr);
-
-  if (!value)
-    return;
-
-  auto* default_path_value = value->FindKey(kDeprecatedLastPickedDirectoryKey);
-  if (!default_path_value)
-    return;
-
-  auto default_path =
-      base::ValueToFilePath(default_path_value).value_or(base::FilePath());
-  auto default_type =
-      value->FindIntKey(kDeprecatedLastPickedDirectoryTypeKey) ==
-              static_cast<int>(PathType::kExternal)
-          ? PathType::kExternal
-          : PathType::kLocal;
-
-  // Remove old keys.
-  value->RemoveKey(kDeprecatedLastPickedDirectoryKey);
-  value->RemoveKey(kDeprecatedLastPickedDirectoryTypeKey);
-
-  // Set this information as the default.
-  base::Value entry(base::Value::Type::DICTIONARY);
-  entry.SetKey(kPathKey, base::FilePathToValue(default_path));
-  entry.SetIntKey(kPathTypeKey, static_cast<int>(default_type));
-
-  value->SetKey(GenerateLastPickedDirectoryKey(std::string()),
-                std::move(entry));
-
-  content_settings_->SetWebsiteSettingDefaultScope(
-      origin.GetURL(), origin.GetURL(),
-      ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, std::move(value));
-}
-
 void ChromeFileSystemAccessPermissionContext::MaybeEvictEntries(
     std::unique_ptr<base::Value>& value) {
   if (!value->is_dict()) {
@@ -1235,8 +1185,6 @@
     const std::string& id,
     const base::FilePath& path,
     const PathType type) {
-  MaybeMigrateOriginToNewSchema(origin);
-
   std::unique_ptr<base::Value> value = content_settings()->GetWebsiteSetting(
       origin.GetURL(), origin.GetURL(),
       ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, /*info=*/nullptr);
@@ -1262,8 +1210,6 @@
 ChromeFileSystemAccessPermissionContext::GetLastPickedDirectory(
     const url::Origin& origin,
     const std::string& id) {
-  MaybeMigrateOriginToNewSchema(origin);
-
   std::unique_ptr<base::Value> value = content_settings()->GetWebsiteSetting(
       origin.GetURL(), origin.GetURL(),
       ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, /*info=*/nullptr);
diff --git a/chrome/browser/file_system_access/chrome_file_system_access_permission_context_unittest.cc b/chrome/browser/file_system_access/chrome_file_system_access_permission_context_unittest.cc
index 270ed18..fbfa2a8 100644
--- a/chrome/browser/file_system_access/chrome_file_system_access_permission_context_unittest.cc
+++ b/chrome/browser/file_system_access/chrome_file_system_access_permission_context_unittest.cc
@@ -645,45 +645,6 @@
             new_path);
 }
 
-// TODO(https://crbug.com/1177334): Remove test when removing migration logic.
-TEST_F(ChromeFileSystemAccessPermissionContextTest,
-       Migrate_LastPickedDirectory) {
-  EXPECT_EQ(permission_context()
-                ->GetLastPickedDirectory(kTestOrigin, kTestStartingDirectoryId)
-                .path,
-            base::FilePath());
-
-  // Set keys using the old method.
-  const char kDeprecatedLastPickedDirectoryKey[] = "default-path";
-  const char kDeprecatedLastPickedDirectoryTypeKey[] = "default-path-type";
-  const base::FilePath path = base::FilePath(FILE_PATH_LITERAL("/baz/bar"));
-  const auto type = PathType::kExternal;
-  base::Value dict(base::Value::Type::DICTIONARY);
-  dict.SetKey(kDeprecatedLastPickedDirectoryKey, base::FilePathToValue(path));
-  dict.SetIntKey(kDeprecatedLastPickedDirectoryTypeKey, static_cast<int>(type));
-  HostContentSettingsMapFactory::GetForProfile(&profile_)
-      ->SetWebsiteSettingDefaultScope(
-          kTestOrigin.GetURL(), kTestOrigin.GetURL(),
-          ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY,
-          base::Value::ToUniquePtrValue(std::move(dict)));
-
-  // Retrieve key using the new method. Information should have been migrated.
-  auto result = permission_context()->GetLastPickedDirectory(
-      kTestOrigin, /*id=*/std::string());
-  EXPECT_EQ(result.path, path);
-  EXPECT_EQ(result.type, type);
-
-  // Confirm that the old keys have been removed.
-  std::unique_ptr<base::Value> value =
-      HostContentSettingsMapFactory::GetForProfile(&profile_)
-          ->GetWebsiteSetting(
-              kTestOrigin.GetURL(), kTestOrigin.GetURL(),
-              ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY,
-              /*info=*/nullptr);
-  EXPECT_FALSE(value->FindKey(kDeprecatedLastPickedDirectoryKey));
-  EXPECT_FALSE(value->FindIntKey(kDeprecatedLastPickedDirectoryKey));
-}
-
 TEST_F(ChromeFileSystemAccessPermissionContextTest,
        GetWellKnownDirectoryPath_Base_OK) {
   base::ScopedPathOverride user_desktop_override(
diff --git a/chrome/browser/language/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java b/chrome/browser/language/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java
index 3f0d0e8..155d96f 100644
--- a/chrome/browser/language/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java
+++ b/chrome/browser/language/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java
@@ -28,7 +28,10 @@
     }
 
     /**
-     * Initates a translation on the given tab to the given target language.
+     * Initates a translation on the given tab to the given target language. All metrics reported
+     * for this translation assume that this translation was initiated from the Translate UI. If the
+     * translation from the Context Menu or by some other means, then an extra signal needs to be
+     * passed through.
      */
     public static void translateTabToLanguage(Tab tab, String targetLanguageCode) {
         TranslateBridgeJni.get().translateToLanguage(tab.getWebContents(), targetLanguageCode);
diff --git a/chrome/browser/metrics/chrome_metrics_service_client.cc b/chrome/browser/metrics/chrome_metrics_service_client.cc
index a261bebf..bde4ce2 100644
--- a/chrome/browser/metrics/chrome_metrics_service_client.cc
+++ b/chrome/browser/metrics/chrome_metrics_service_client.cc
@@ -929,10 +929,6 @@
 }
 
 bool ChromeMetricsServiceClient::RegisterForNotifications() {
-  registrar_.Add(this, content::NOTIFICATION_LOAD_START,
-                 content::NotificationService::AllSources());
-  registrar_.Add(this, content::NOTIFICATION_LOAD_STOP,
-                 content::NotificationService::AllSources());
   registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
                  content::NotificationService::AllSources());
   registrar_.Add(this, content::NOTIFICATION_RENDER_WIDGET_HOST_HANG,
@@ -1021,8 +1017,6 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   switch (type) {
-    case content::NOTIFICATION_LOAD_STOP:
-    case content::NOTIFICATION_LOAD_START:
     case content::NOTIFICATION_RENDERER_PROCESS_CLOSED:
     case content::NOTIFICATION_RENDER_WIDGET_HOST_HANG:
       metrics_service_->OnApplicationNotIdle();
@@ -1045,6 +1039,10 @@
   }
 }
 
+void ChromeMetricsServiceClient::LoadingStateChanged(bool /*is_loading*/) {
+  metrics_service_->OnApplicationNotIdle();
+}
+
 void ChromeMetricsServiceClient::OnURLOpenedFromOmnibox(OmniboxLog* log) {
   metrics_service_->OnApplicationNotIdle();
 }
diff --git a/chrome/browser/metrics/chrome_metrics_service_client.h b/chrome/browser/metrics/chrome_metrics_service_client.h
index 1e5a8ab..59983177 100644
--- a/chrome/browser/metrics/chrome_metrics_service_client.h
+++ b/chrome/browser/metrics/chrome_metrics_service_client.h
@@ -78,6 +78,7 @@
       const metrics::MetricsLogUploader::UploadCallback& on_upload_complete)
       override;
   base::TimeDelta GetStandardUploadInterval() override;
+  void LoadingStateChanged(bool is_loading) override;
   void OnPluginLoadingError(const base::FilePath& plugin_path) override;
   bool IsReportingPolicyManaged() override;
   metrics::EnableMetricsDefault GetMetricsReportingDefaultState() override;
diff --git a/chrome/browser/metrics/metrics_services_web_contents_observer.cc b/chrome/browser/metrics/metrics_services_web_contents_observer.cc
new file mode 100644
index 0000000..e76b293
--- /dev/null
+++ b/chrome/browser/metrics/metrics_services_web_contents_observer.cc
@@ -0,0 +1,33 @@
+// Copyright 2021 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/metrics/metrics_services_web_contents_observer.h"
+
+#include "chrome/browser/browser_process.h"
+#include "components/metrics_services_manager/metrics_services_manager.h"
+
+namespace metrics {
+
+MetricsServicesWebContentsObserver::MetricsServicesWebContentsObserver(
+    content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents) {}
+
+MetricsServicesWebContentsObserver::~MetricsServicesWebContentsObserver() =
+    default;
+
+void MetricsServicesWebContentsObserver::DidStartLoading() {
+  auto* manager = g_browser_process->GetMetricsServicesManager();
+  if (manager)
+    manager->LoadingStateChanged(/*is_loading=*/true);
+}
+
+void MetricsServicesWebContentsObserver::DidStopLoading() {
+  auto* manager = g_browser_process->GetMetricsServicesManager();
+  if (manager)
+    manager->LoadingStateChanged(/*is_loading=*/false);
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(MetricsServicesWebContentsObserver)
+
+}  // namespace metrics
diff --git a/chrome/browser/metrics/metrics_services_web_contents_observer.h b/chrome/browser/metrics/metrics_services_web_contents_observer.h
new file mode 100644
index 0000000..5ef607f
--- /dev/null
+++ b/chrome/browser/metrics/metrics_services_web_contents_observer.h
@@ -0,0 +1,37 @@
+// Copyright 2021 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_METRICS_METRICS_SERVICES_WEB_CONTENTS_OBSERVER_H_
+#define CHROME_BROWSER_METRICS_METRICS_SERVICES_WEB_CONTENTS_OBSERVER_H_
+
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace metrics {
+
+class MetricsServicesWebContentsObserver
+    : public content::WebContentsObserver,
+      public content::WebContentsUserData<MetricsServicesWebContentsObserver> {
+ public:
+  ~MetricsServicesWebContentsObserver() override;
+
+ private:
+  explicit MetricsServicesWebContentsObserver(
+      content::WebContents* web_contents);
+  MetricsServicesWebContentsObserver(
+      const MetricsServicesWebContentsObserver&) = delete;
+  MetricsServicesWebContentsObserver& operator=(
+      const MetricsServicesWebContentsObserver&) = delete;
+  friend class content::WebContentsUserData<MetricsServicesWebContentsObserver>;
+
+  // content::WebContentsObserver overrides:
+  void DidStartLoading() override;
+  void DidStopLoading() override;
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+};
+
+}  // namespace metrics
+
+#endif  // CHROME_BROWSER_METRICS_METRICS_SERVICES_WEB_CONTENTS_OBSERVER_H_
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
index 25a180c7..9013206 100644
--- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
+++ b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/nearby_sharing/logging/logging.h"
 #include "chrome/browser/nearby_sharing/nearby_sharing_service.h"
 #include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/sharesheet/sharesheet_types.h"
 #include "chrome/browser/ui/browser_navigator.h"
@@ -118,7 +119,7 @@
 
 }  // namespace
 
-NearbyShareAction::NearbyShareAction() = default;
+NearbyShareAction::NearbyShareAction(Profile* profile) : profile_(profile) {}
 
 NearbyShareAction::~NearbyShareAction() = default;
 
@@ -137,8 +138,7 @@
   gfx::Size size = ComputeSize();
   controller->SetBubbleSize(size.width(), size.height());
 
-  auto* profile = controller->GetProfile();
-  auto view = std::make_unique<views::WebView>(profile);
+  auto view = std::make_unique<views::WebView>(profile_);
   // If this is not done, we don't see anything in our view.
   view->SetPreferredSize(size);
   web_view_ = root_view->AddChildView(std::move(view));
@@ -162,7 +162,7 @@
 
   nearby_ui->SetSharesheetController(controller);
   nearby_ui->SetAttachments(
-      CreateAttachmentsFromIntent(profile, std::move(intent)));
+      CreateAttachmentsFromIntent(profile_, std::move(intent)));
 }
 
 bool NearbyShareAction::ShouldShowAction(const apps::mojom::IntentPtr& intent,
@@ -196,13 +196,8 @@
   if (nearby_share_disabled_by_policy_for_testing_.has_value()) {
     return *nearby_share_disabled_by_policy_for_testing_;
   }
-
-  Profile* profile = ProfileManager::GetActiveUserProfile();
-  if (!profile) {
-    return false;
-  }
   NearbySharingService* nearby_sharing_service =
-      NearbySharingServiceFactory::GetForBrowserContext(profile);
+      NearbySharingServiceFactory::GetForBrowserContext(profile_);
   if (!nearby_sharing_service) {
     return false;
   }
@@ -236,13 +231,8 @@
   if (callback.is_null()) {
     return;
   }
-  Profile* profile = ProfileManager::GetActiveUserProfile();
-  if (!profile) {
-    std::move(callback).Run();
-    return;
-  }
   NearbySharingService* nearby_sharing_service =
-      NearbySharingServiceFactory::GetForBrowserContext(profile);
+      NearbySharingServiceFactory::GetForBrowserContext(profile_);
   if (!nearby_sharing_service) {
     std::move(callback).Run();
     return;
@@ -264,8 +254,7 @@
     const std::string& frame_name,
     const GURL& target_url,
     content::WebContents* new_contents) {
-  chrome::ScopedTabbedBrowserDisplayer displayer(
-      Profile::FromBrowserContext(web_view_->GetBrowserContext()));
+  chrome::ScopedTabbedBrowserDisplayer displayer(profile_);
   NavigateParams nav_params(displayer.browser(), target_url,
                             ui::PageTransition::PAGE_TRANSITION_LINK);
   Navigate(&nav_params);
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h
index 5a1baa0..bb9b286 100644
--- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h
+++ b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action.h
@@ -10,6 +10,8 @@
 #include "content/public/browser/web_contents_delegate.h"
 #include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
 
+class Profile;
+
 namespace views {
 class WebView;
 }  // namespace views
@@ -17,7 +19,7 @@
 class NearbyShareAction : public sharesheet::ShareAction,
                           content::WebContentsDelegate {
  public:
-  NearbyShareAction();
+  explicit NearbyShareAction(Profile* profile);
   ~NearbyShareAction() override;
   NearbyShareAction(const NearbyShareAction&) = delete;
   NearbyShareAction& operator=(const NearbyShareAction&) = delete;
@@ -57,6 +59,7 @@
  private:
   bool IsNearbyShareDisabledByPolicy();
 
+  Profile* profile_;
   absl::optional<bool> nearby_share_disabled_by_policy_for_testing_ =
       absl::nullopt;
   views::WebView* web_view_;
diff --git a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action_unittest.cc b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action_unittest.cc
index e7abab8..3f78d30 100644
--- a/chrome/browser/nearby_sharing/sharesheet/nearby_share_action_unittest.cc
+++ b/chrome/browser/nearby_sharing/sharesheet/nearby_share_action_unittest.cc
@@ -57,10 +57,11 @@
     ASSERT_TRUE(profile_manager_->SetUp());
     profile_ = profile_manager_->CreateTestingProfile("testing_profile");
 
-    nearby_share_action_.SetNearbyShareDisabledByPolicyForTesting(false);
+    nearby_share_action_ = std::make_unique<NearbyShareAction>(profile_);
+    nearby_share_action_->SetNearbyShareDisabledByPolicyForTesting(false);
     // Calling the cleanup callback means an error occurred in the function.
     ASSERT_DEATH(ActionCleanupCallbackStub(), "");
-    nearby_share_action_.SetActionCleanupCallbackForArc(
+    nearby_share_action_->SetActionCleanupCallbackForArc(
         base::BindOnce(&ActionCleanupCallbackStub));
   }
 
@@ -126,13 +127,13 @@
   content::BrowserTaskEnvironment task_environment_;
   std::unique_ptr<TestingProfileManager> profile_manager_;
   Profile* profile_;
-  NearbyShareAction nearby_share_action_;
+  std::unique_ptr<NearbyShareAction> nearby_share_action_;
 };
 
 TEST_F(NearbyShareActionTest, ShouldShowAction) {
   for (auto& test_case : GetIntentTestCases()) {
     EXPECT_EQ(
-        nearby_share_action_.ShouldShowAction(
+        nearby_share_action_->ShouldShowAction(
             std::move(test_case.intent), test_case.contains_hosted_document),
         test_case.should_show_action);
   }
diff --git a/chrome/browser/optimization_guide/blink/blink_optimization_guide_browsertest.cc b/chrome/browser/optimization_guide/blink/blink_optimization_guide_browsertest.cc
index f8313d9..c92deff 100644
--- a/chrome/browser/optimization_guide/blink/blink_optimization_guide_browsertest.cc
+++ b/chrome/browser/optimization_guide/blink/blink_optimization_guide_browsertest.cc
@@ -16,6 +16,7 @@
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/notification_types.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/prerender_test_util.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -355,4 +356,73 @@
   EXPECT_FALSE(GetObserverForActiveWebContents());
 }
 
+class BlinkOptimizationGuidePrerenderBrowserTest
+    : public BlinkOptimizationGuideBrowserTest {
+ public:
+  BlinkOptimizationGuidePrerenderBrowserTest() = default;
+  ~BlinkOptimizationGuidePrerenderBrowserTest() override = default;
+  BlinkOptimizationGuidePrerenderBrowserTest(
+      const BlinkOptimizationGuidePrerenderBrowserTest&) = delete;
+
+  BlinkOptimizationGuidePrerenderBrowserTest& operator=(
+      const BlinkOptimizationGuidePrerenderBrowserTest&) = delete;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    prerender_helper_ = std::make_unique<content::test::PrerenderTestHelper>(
+        base::BindRepeating(
+            &BlinkOptimizationGuidePrerenderBrowserTest::GetWebContents,
+            base::Unretained(this)));
+  }
+
+  void SetUpOnMainThread() override {
+    prerender_helper_->SetUpOnMainThread(embedded_test_server());
+    BlinkOptimizationGuideBrowserTest::SetUpOnMainThread();
+  }
+
+  content::test::PrerenderTestHelper& prerender_test_helper() {
+    return *prerender_helper_;
+  }
+
+  content::WebContents* GetWebContents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
+ private:
+  std::unique_ptr<content::test::PrerenderTestHelper> prerender_helper_;
+};
+
+// Instantiates test cases for each optimization type.
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    BlinkOptimizationGuidePrerenderBrowserTest,
+    testing::Combine(
+        // The optimization type.
+        testing::Values(
+            proto::OptimizationType::DELAY_ASYNC_SCRIPT_EXECUTION,
+            proto::OptimizationType::DELAY_COMPETING_LOW_PRIORITY_REQUESTS),
+        // Whether the feature flag for the optimization type is enabled.
+        testing::Bool()));
+
+IN_PROC_BROWSER_TEST_P(BlinkOptimizationGuidePrerenderBrowserTest,
+                       PrerenderingDontCreateBlinkOptimizationGuideInquirer) {
+  GURL initial_url = GetURLWithMockHost("/empty.html");
+  GURL prerender_url = GetURLWithMockHost("/simple.html");
+  ASSERT_NE(ui_test_utils::NavigateToURL(browser(), initial_url), nullptr);
+  auto weak_prev_inquirer = GetCurrentInquirer()->GetWeakPtrForTesting();
+  EXPECT_TRUE(weak_prev_inquirer);
+
+  // Load a page in the prerender. This shouldn't create a new inquirer yet.
+  int host_id = prerender_test_helper().AddPrerender(prerender_url);
+  content::test::PrerenderHostObserver host_observer(*GetWebContents(),
+                                                     host_id);
+  EXPECT_FALSE(host_observer.was_activated());
+  EXPECT_TRUE(weak_prev_inquirer);
+
+  // Activate the prerendered page. This should create a new inquirer.
+  prerender_test_helper().NavigatePrimaryPage(prerender_url);
+  EXPECT_TRUE(host_observer.was_activated());
+  EXPECT_FALSE(weak_prev_inquirer);
+  EXPECT_TRUE(GetCurrentInquirer()->GetWeakPtrForTesting());
+}
+
 }  // namespace optimization_guide
diff --git a/chrome/browser/optimization_guide/blink/blink_optimization_guide_inquirer.h b/chrome/browser/optimization_guide/blink/blink_optimization_guide_inquirer.h
index 84dcefe..b8d13a6 100644
--- a/chrome/browser/optimization_guide/blink/blink_optimization_guide_inquirer.h
+++ b/chrome/browser/optimization_guide/blink/blink_optimization_guide_inquirer.h
@@ -44,6 +44,10 @@
     return optimization_guide_hints_.Clone();
   }
 
+  base::WeakPtr<BlinkOptimizationGuideInquirer> GetWeakPtrForTesting() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
  private:
   BlinkOptimizationGuideInquirer();
 
diff --git a/chrome/browser/optimization_guide/blink/blink_optimization_guide_web_contents_observer.cc b/chrome/browser/optimization_guide/blink/blink_optimization_guide_web_contents_observer.cc
index e8bcae8..0cccd55 100644
--- a/chrome/browser/optimization_guide/blink/blink_optimization_guide_web_contents_observer.cc
+++ b/chrome/browser/optimization_guide/blink/blink_optimization_guide_web_contents_observer.cc
@@ -27,10 +27,8 @@
     content::NavigationHandle* navigation_handle) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  // Currently the optimization guide supports only the main frame navigation.
-  // TODO(https://crbug.com/1218946): With MPArch there may be multiple main
-  // frames. This caller was converted automatically to the primary main frame
-  // to preserve its semantics. Follow up to confirm correctness.
+  // Currently the optimization guide supports only the primary main frame
+  // navigation.
   if (!navigation_handle->IsInPrimaryMainFrame() ||
       navigation_handle->IsSameDocument()) {
     return;
diff --git a/chrome/browser/optimization_guide/optimization_guide_hints_manager.cc b/chrome/browser/optimization_guide/optimization_guide_hints_manager.cc
index 3600c826..af0a6ae5 100644
--- a/chrome/browser/optimization_guide/optimization_guide_hints_manager.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_hints_manager.cc
@@ -54,7 +54,6 @@
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/navigation_handle.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_source.h"
@@ -210,12 +209,10 @@
 class ScopedHintsManagerRaceNavigationHintsFetchAttemptRecorder {
  public:
   explicit ScopedHintsManagerRaceNavigationHintsFetchAttemptRecorder(
-      content::NavigationHandle* navigation_handle)
+      OptimizationGuideNavigationData* navigation_data)
       : race_attempt_status_(
             optimization_guide::RaceNavigationFetchAttemptStatus::kUnknown),
-        navigation_data_(
-            OptimizationGuideKeyedService::
-                GetNavigationDataFromNavigationHandle(navigation_handle)) {}
+        navigation_data_(navigation_data) {}
 
   ~ScopedHintsManagerRaceNavigationHintsFetchAttemptRecorder() {
     DCHECK_NE(race_attempt_status_,
@@ -833,12 +830,10 @@
       last_attempt_time.ToDeltaSinceWindowsEpoch().InMicroseconds());
 }
 
-void OptimizationGuideHintsManager::LoadHintForNavigation(
-    content::NavigationHandle* navigation_handle,
-    base::OnceClosure callback) {
+void OptimizationGuideHintsManager::LoadHintForURL(const GURL& url,
+                                                   base::OnceClosure callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  const auto& url = navigation_handle->GetURL();
   if (!url.has_host()) {
     std::move(callback).Run();
     return;
@@ -1284,37 +1279,33 @@
 }
 
 void OptimizationGuideHintsManager::OnNavigationStartOrRedirect(
-    content::NavigationHandle* navigation_handle,
+    OptimizationGuideNavigationData* navigation_data,
     base::OnceClosure callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  LoadHintForNavigation(navigation_handle, std::move(callback));
+  LoadHintForURL(navigation_data->navigation_url(), std::move(callback));
 
   if (optimization_guide::switches::
           DisableFetchingHintsAtNavigationStartForTesting()) {
     return;
   }
 
-  MaybeFetchHintsForNavigation(navigation_handle);
+  MaybeFetchHintsForNavigation(navigation_data);
 }
 
 void OptimizationGuideHintsManager::MaybeFetchHintsForNavigation(
-    content::NavigationHandle* navigation_handle) {
+    OptimizationGuideNavigationData* navigation_data) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   if (registered_optimization_types_.empty())
     return;
 
-  const GURL url = navigation_handle->GetURL();
+  const GURL url = navigation_data->navigation_url();
   if (!IsAllowedToFetchNavigationHints(url))
     return;
 
   ScopedHintsManagerRaceNavigationHintsFetchAttemptRecorder
-      race_navigation_recorder(navigation_handle);
-
-  OptimizationGuideNavigationData* navigation_data =
-      OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle(
-          navigation_handle);
+      race_navigation_recorder(navigation_data);
 
   // We expect that if the URL is being fetched for, we have already run through
   // the logic to decide if we also require fetching hints for the host.
@@ -1325,8 +1316,7 @@
 
     // Just set the hints fetch start to the start of the navigation, so we can
     // track whether the hint came back before commit or not.
-    navigation_data->set_hints_fetch_start(
-        navigation_handle->NavigationStart());
+    navigation_data->set_hints_fetch_start(navigation_data->navigation_start());
     return;
   }
 
diff --git a/chrome/browser/optimization_guide/optimization_guide_hints_manager.h b/chrome/browser/optimization_guide/optimization_guide_hints_manager.h
index 3d0dc404..b7ed3fc 100644
--- a/chrome/browser/optimization_guide/optimization_guide_hints_manager.h
+++ b/chrome/browser/optimization_guide/optimization_guide_hints_manager.h
@@ -28,10 +28,6 @@
 #include "components/optimization_guide/proto/models.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
-namespace content {
-class NavigationHandle;
-}  // namespace content
-
 namespace network {
 class SharedURLLoaderFactory;
 }  // namespace network
@@ -143,12 +139,13 @@
   // Overrides |clock_| for testing.
   void SetClockForTesting(const base::Clock* clock);
 
-  // Notifies |this| that a navigation with |navigation_handle| has started.
+  // Notifies |this| that a navigation with |navigation_data| started.
   // |callback| is run when the request has finished regardless of whether there
   // was actually a hint for that load or not. The callback can be used as a
   // signal for tests.
-  void OnNavigationStartOrRedirect(content::NavigationHandle* navigation_handle,
-                                   base::OnceClosure callback);
+  void OnNavigationStartOrRedirect(
+      OptimizationGuideNavigationData* navigation_data,
+      base::OnceClosure callback);
 
   // Notifies |this| that a navigation with redirect chain
   // |navigation_redirect_chain| has finished.
@@ -298,12 +295,11 @@
   // |url|.
   bool IsAllowedToFetchNavigationHints(const GURL& url);
 
-  // Loads the hint if available.
+  // Loads the hint if available for navigation to |url|.
   // |callback| is run when the request has finished regardless of whether there
   // was actually a hint for that load or not. The callback can be used as a
   // signal for tests.
-  void LoadHintForNavigation(content::NavigationHandle* navigation_handle,
-                             base::OnceClosure callback);
+  void LoadHintForURL(const GURL& url, base::OnceClosure callback);
 
   // Loads the hint for |host| if available.
   // |callback| is run when the request has finished regardless of whether there
@@ -330,11 +326,12 @@
   // optimization types are covered by optimization filters.
   bool HasOptimizationTypeToFetchFor();
 
-  // Creates a hints fetch for |navigation_handle| if it is allowed. The
-  // fetch will include the host and URL of the |navigation_handle| if the
-  // associated hints for each are not already in the cache.
+  // Creates a hints fetch for navigation represented by |navigation_data|, if
+  // it is allowed. The fetch will include the host and URL of the
+  // |navigation_data| if the associated hints for each are not already in the
+  // cache.
   void MaybeFetchHintsForNavigation(
-      content::NavigationHandle* navigation_handle);
+      OptimizationGuideNavigationData* navigation_data);
 
   // If an entry for |navigation_url| is contained in |registered_callbacks_|,
   // it will load the hint for |navigation_url|'s host and upon completion, will
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 f104d83..171dda2 100644
--- a/chrome/browser/optimization_guide/optimization_guide_hints_manager_unittest.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_hints_manager_unittest.cc
@@ -16,7 +16,6 @@
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
 #include "chrome/browser/optimization_guide/optimization_guide_tab_url_provider.h"
 #include "chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h"
 #include "chrome/test/base/testing_profile.h"
@@ -426,6 +425,28 @@
     return navigation_handle;
   }
 
+  void CallOnNavigationStartOrRedirect(
+      content::NavigationHandle* navigation_handle,
+      base::OnceClosure callback) {
+    OptimizationGuideNavigationData* navigation_data =
+        GetNavigationDataFromNavigationHandle(navigation_handle);
+    navigation_data->set_navigation_url(navigation_handle->GetURL());
+    hints_manager()->OnNavigationStartOrRedirect(navigation_data,
+                                                 std::move(callback));
+  }
+
+  static OptimizationGuideNavigationData* GetNavigationDataFromNavigationHandle(
+      content::NavigationHandle* navigation_handle) {
+    OptimizationGuideWebContentsObserver*
+        optimization_guide_web_contents_observer =
+            OptimizationGuideWebContentsObserver::FromWebContents(
+                navigation_handle->GetWebContents());
+    if (!optimization_guide_web_contents_observer)
+      return nullptr;
+    return optimization_guide_web_contents_observer
+        ->GetOrCreateOptimizationGuideNavigationData(navigation_handle);
+  }
+
   void SetConnectionOffline() {
     network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
         network::mojom::ConnectionType::CONNECTION_NONE);
@@ -481,7 +502,6 @@
   }
 
   content::BrowserTaskEnvironment task_environment_{
-      base::test::TaskEnvironment::MainThreadType::UI,
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   base::test::ScopedFeatureList scoped_feature_list_;
   TestingProfile testing_profile_;
@@ -854,8 +874,8 @@
   navigation_handle->set_has_committed(true);
 
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   histogram_tester.ExpectUniqueSample("OptimizationGuide.LoadedHint.Result",
@@ -871,8 +891,8 @@
           url_with_hints());
 
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   histogram_tester.ExpectUniqueSample("OptimizationGuide.LoadedHint.Result",
@@ -888,8 +908,8 @@
           GURL("https://notinhints.com"));
 
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   histogram_tester.ExpectUniqueSample("OptimizationGuide.LoadedHint.Result",
@@ -905,8 +925,8 @@
           GURL("blargh"));
 
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   histogram_tester.ExpectTotalCount("OptimizationGuide.LoadedHint.Result", 0);
@@ -1305,8 +1325,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   optimization_guide::OptimizationMetadata optimization_metadata;
@@ -1338,8 +1358,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   ukm::TestAutoSetUkmRecorder ukm_recorder;
@@ -1386,8 +1406,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   ukm::TestAutoSetUkmRecorder ukm_recorder;
@@ -1436,8 +1456,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   ukm::TestAutoSetUkmRecorder ukm_recorder;
@@ -1486,8 +1506,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   ukm::TestAutoSetUkmRecorder ukm_recorder;
@@ -1533,8 +1553,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   ukm::TestAutoSetUkmRecorder ukm_recorder;
@@ -1572,8 +1592,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   ukm::TestAutoSetUkmRecorder ukm_recorder;
@@ -1602,8 +1622,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   optimization_guide::OptimizationTypeDecision optimization_type_decision =
@@ -1642,8 +1662,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   optimization_guide::OptimizationMetadata optimization_metadata;
@@ -1679,8 +1699,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   optimization_guide::OptimizationMetadata optimization_metadata;
@@ -1718,8 +1738,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   optimization_guide::OptimizationMetadata optimization_metadata;
@@ -1759,8 +1779,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   optimization_guide::OptimizationMetadata optimization_metadata;
@@ -1819,8 +1839,8 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           GURL("https://somedomain.org/nomatch"));
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   hints_manager()->RegisterOptimizationTypes(
@@ -1911,8 +1931,8 @@
   ProcessHints(config, "1.0.0.0");
 
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   optimization_guide::OptimizationTypeDecision optimization_type_decision =
@@ -1957,8 +1977,8 @@
   ProcessHints(config, "1.0.0.0");
 
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   optimization_guide::OptimizationTypeDecision optimization_type_decision =
@@ -2044,8 +2064,8 @@
           url_with_hints());
   // Wait for hint to be loaded.
   base::RunLoop run_loop;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
   run_loop.Run();
 
   hints_manager()->CanApplyOptimizationAsync(
@@ -2614,8 +2634,7 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_without_hints());
   base::HistogramTester histogram_tester;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   histogram_tester.ExpectTotalCount(
@@ -2668,8 +2687,7 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_without_hints());
   base::HistogramTester histogram_tester;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   histogram_tester.ExpectUniqueSample(
@@ -2699,8 +2717,7 @@
   hints_manager()->SetHintsFetcherFactoryForTesting(
       BuildTestHintsFetcherFactory({HintsFetcherEndState::kFetchFailed}));
   base::HistogramTester histogram_tester;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   histogram_tester.ExpectTotalCount(
@@ -2731,8 +2748,7 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_hints());
   base::HistogramTester histogram_tester;
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
   histogram_tester.ExpectTotalCount(
       "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount", 0);
@@ -2763,8 +2779,7 @@
         CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
             url_with_hints());
 
-    hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                                 base::DoNothing());
+    CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
     RunUntilIdle();
 
     histogram_tester.ExpectTotalCount(
@@ -2772,8 +2787,7 @@
 
     // Make sure navigation data is populated correctly.
     OptimizationGuideNavigationData* navigation_data =
-        OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle(
-            navigation_handle.get());
+        GetNavigationDataFromNavigationHandle(navigation_handle.get());
     EXPECT_TRUE(navigation_data->hints_fetch_latency().has_value());
     EXPECT_EQ(navigation_data->hints_fetch_attempt_status(),
               optimization_guide::RaceNavigationFetchAttemptStatus::
@@ -2796,8 +2810,7 @@
     std::unique_ptr<content::MockNavigationHandle> navigation_handle =
         CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
             url_with_hints());
-    hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                                 base::DoNothing());
+    CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
     RunUntilIdle();
 
     histogram_tester.ExpectBucketCount(
@@ -2808,8 +2821,7 @@
 
     // Make sure navigation data is populated correctly.
     OptimizationGuideNavigationData* navigation_data =
-        OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle(
-            navigation_handle.get());
+        GetNavigationDataFromNavigationHandle(navigation_handle.get());
     EXPECT_FALSE(navigation_data->hints_fetch_latency().has_value());
     EXPECT_EQ(navigation_data->hints_fetch_attempt_status(),
               optimization_guide::RaceNavigationFetchAttemptStatus::
@@ -2841,16 +2853,14 @@
         CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
             url_without_hints());
 
-    hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                                 base::DoNothing());
+    CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
     RunUntilIdle();
     histogram_tester.ExpectTotalCount(
         "OptimizationGuide.HintsFetcher.GetHintsRequest.HostCount", 0);
 
     // Make sure navigation data is populated correctly.
     OptimizationGuideNavigationData* navigation_data =
-        OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle(
-            navigation_handle.get());
+        GetNavigationDataFromNavigationHandle(navigation_handle.get());
     EXPECT_TRUE(navigation_data->hints_fetch_latency().has_value());
     EXPECT_EQ(navigation_data->hints_fetch_attempt_status(),
               optimization_guide::RaceNavigationFetchAttemptStatus::
@@ -2872,8 +2882,7 @@
     navigation_handle =
         CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
             url_without_hints());
-    hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                                 base::DoNothing());
+    CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
     RunUntilIdle();
 
     histogram_tester.ExpectBucketCount(
@@ -2882,8 +2891,7 @@
             kRaceNavigationFetchHost,
         1);
     OptimizationGuideNavigationData* navigation_data =
-        OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle(
-            navigation_handle.get());
+        GetNavigationDataFromNavigationHandle(navigation_handle.get());
     EXPECT_TRUE(navigation_data->hints_fetch_latency().has_value());
     EXPECT_EQ(navigation_data->hints_fetch_attempt_status(),
               optimization_guide::RaceNavigationFetchAttemptStatus::
@@ -2904,8 +2912,7 @@
   std::unique_ptr<content::MockNavigationHandle> navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_without_hints());
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   optimization_guide::OptimizationTypeDecision optimization_type_decision =
       hints_manager()->CanApplyOptimization(
           navigation_handle->GetURL(), /*navigation_id=*/absl::nullopt,
@@ -2934,8 +2941,7 @@
   std::unique_ptr<content::MockNavigationHandle> navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_without_hints());
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   optimization_guide::OptimizationTypeDecision optimization_type_decision =
@@ -2964,8 +2970,7 @@
   std::unique_ptr<content::MockNavigationHandle> navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_without_hints());
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   optimization_guide::OptimizationTypeDecision optimization_type_decision =
@@ -2996,8 +3001,7 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_url_keyed_hint());
   // Make sure URL-keyed hint is fetched and processed.
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   optimization_guide::OptimizationMetadata optimization_metadata;
@@ -3033,8 +3037,7 @@
   std::unique_ptr<content::MockNavigationHandle> navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_url_keyed_hint());
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   optimization_guide::OptimizationMetadata optimization_metadata;
@@ -3066,8 +3069,7 @@
   std::unique_ptr<content::MockNavigationHandle> navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_url_keyed_hint());
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   optimization_guide::OptimizationMetadata optimization_metadata;
@@ -3100,8 +3102,7 @@
           url_without_hints());
 
   // Attempt to fetch a hint but ensure nothing comes back.
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   optimization_guide::OptimizationMetadata optimization_metadata;
@@ -3135,8 +3136,7 @@
   hints_manager()->SetHintsFetcherFactoryForTesting(
       BuildTestHintsFetcherFactory(
           {HintsFetcherEndState::kFetchSuccessWithHostHints}));
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   optimization_guide::OptimizationMetadata optimization_metadata;
   optimization_guide::OptimizationTypeDecision optimization_type_decision =
       hints_manager()->CanApplyOptimization(
@@ -3171,8 +3171,7 @@
     hints_manager()->SetHintsFetcherFactoryForTesting(
         BuildTestHintsFetcherFactory(
             {HintsFetcherEndState::kFetchSuccessWithHostHints}));
-    hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                                 base::DoNothing());
+    CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
     histogram_tester.ExpectUniqueSample(
         "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus",
         optimization_guide::RaceNavigationFetchAttemptStatus::
@@ -3183,17 +3182,11 @@
   }
 
   {
-    ON_CALL(*navigation_handle, NavigationStart)
-        .WillByDefault(Return(base::TimeTicks::Now()));
-
-    EXPECT_CALL(*navigation_handle, NavigationStart);
-
     base::HistogramTester histogram_tester;
     hints_manager()->SetHintsFetcherFactoryForTesting(
         BuildTestHintsFetcherFactory(
             {HintsFetcherEndState::kFetchSuccessWithHostHints}));
-    hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                                 base::DoNothing());
+    CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
     histogram_tester.ExpectUniqueSample(
         "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus",
         optimization_guide::RaceNavigationFetchAttemptStatus::
@@ -3204,8 +3197,7 @@
         "OptimizationGuide.HintsManager.ConcurrentPageNavigationFetches", 0);
 
     OptimizationGuideNavigationData* navigation_data =
-        OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle(
-            navigation_handle.get());
+        GetNavigationDataFromNavigationHandle(navigation_handle.get());
     // Set hints fetch end.so we can figure out if hints fetch start was set.
     navigation_data->set_hints_fetch_end(base::TimeTicks::Now());
     EXPECT_TRUE(navigation_data->hints_fetch_latency().has_value());
@@ -3237,8 +3229,7 @@
           url_without_hints());
   {
     base::HistogramTester histogram_tester;
-    hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                                 base::DoNothing());
+    CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
     histogram_tester.ExpectUniqueSample(
         "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus",
         optimization_guide::RaceNavigationFetchAttemptStatus::
@@ -3253,8 +3244,7 @@
 
   {
     base::HistogramTester histogram_tester;
-    hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                                 base::DoNothing());
+    CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
     histogram_tester.ExpectUniqueSample(
         "OptimizationGuide.HintsManager.RaceNavigationFetchAttemptStatus",
         optimization_guide::RaceNavigationFetchAttemptStatus::
@@ -3295,12 +3285,12 @@
   // Attempt to fetch a hint but initiate the next navigations right away to
   // simulate being mid-fetch.
   base::HistogramTester histogram_tester;
-  hints_manager()->OnNavigationStartOrRedirect(
-      navigation_handle_with_hints.get(), base::DoNothing());
-  hints_manager()->OnNavigationStartOrRedirect(
-      navigation_handle_without_hints.get(), base::DoNothing());
-  hints_manager()->OnNavigationStartOrRedirect(
-      navigation_handle_without_hints2.get(), base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle_with_hints.get(),
+                                  base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle_without_hints.get(),
+                                  base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle_without_hints2.get(),
+                                  base::DoNothing());
 
   // The third one is over the max and should evict another one.
   histogram_tester.ExpectTotalCount(
@@ -3330,8 +3320,7 @@
   std::unique_ptr<content::MockNavigationHandle> navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_url_keyed_hint());
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   hints_manager()->CanApplyOptimizationAsync(
       url_with_url_keyed_hint(), navigation_handle->GetNavigationId(),
       optimization_guide::proto::COMPRESS_PUBLIC_IMAGES,
@@ -3388,8 +3377,7 @@
                       decision);
             EXPECT_TRUE(metadata.public_image_metadata().has_value());
           }));
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   histogram_tester.ExpectUniqueSample(
@@ -3417,8 +3405,7 @@
   std::unique_ptr<content::MockNavigationHandle> navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_url_keyed_hint());
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   hints_manager()->CanApplyOptimizationAsync(
       url_with_url_keyed_hint(), navigation_handle->GetNavigationId(),
       optimization_guide::proto::RESOURCE_LOADING,
@@ -3463,8 +3450,7 @@
             EXPECT_EQ(optimization_guide::OptimizationGuideDecision::kFalse,
                       decision);
           }));
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   histogram_tester.ExpectUniqueSample(
@@ -3492,8 +3478,7 @@
   std::unique_ptr<content::MockNavigationHandle> navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_url_keyed_hint());
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   hints_manager()->CanApplyOptimizationAsync(
@@ -3533,8 +3518,7 @@
   std::unique_ptr<content::MockNavigationHandle> navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_url_keyed_hint());
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   hints_manager()->CanApplyOptimizationAsync(
@@ -3573,8 +3557,7 @@
   std::unique_ptr<content::MockNavigationHandle> navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_without_hints());
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   hints_manager()->CanApplyOptimizationAsync(
       url_without_hints(), navigation_handle->GetNavigationId(),
       optimization_guide::proto::PERFORMANCE_HINTS,
@@ -3647,8 +3630,7 @@
   std::unique_ptr<content::MockNavigationHandle> navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_url_keyed_hint());
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   hints_manager()->CanApplyOptimizationAsync(
       url_with_url_keyed_hint(), navigation_handle->GetNavigationId(),
       optimization_guide::proto::COMPRESS_PUBLIC_IMAGES,
@@ -3762,8 +3744,7 @@
   std::unique_ptr<content::MockNavigationHandle> navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(
           url_with_url_keyed_hint());
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   hints_manager()->CanApplyOptimizationAsync(
       url_with_url_keyed_hint(), navigation_handle->GetNavigationId(),
       optimization_guide::proto::COMPRESS_PUBLIC_IMAGES,
@@ -3817,8 +3798,7 @@
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(url);
 
   // Attempt to fetch a hint but ensure nothing comes back.
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               base::DoNothing());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(), base::DoNothing());
   RunUntilIdle();
 
   optimization_guide::OptimizationMetadata optimization_metadata;
@@ -3847,8 +3827,8 @@
 
   navigation_handle =
       CreateMockNavigationHandleWithOptimizationGuideWebContentsObserver(url);
-  hints_manager()->OnNavigationStartOrRedirect(navigation_handle.get(),
-                                               run_loop.QuitClosure());
+  CallOnNavigationStartOrRedirect(navigation_handle.get(),
+                                  run_loop.QuitClosure());
 
   run_loop.Run();
 
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
index ba9cebc..afe0628 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "chrome/browser/optimization_guide/optimization_guide_hints_manager.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
-#include "chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h"
 #include "chrome/browser/optimization_guide/prediction/prediction_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
@@ -216,16 +215,14 @@
 }
 
 void OptimizationGuideKeyedService::OnNavigationStartOrRedirect(
-    content::NavigationHandle* navigation_handle) {
+    OptimizationGuideNavigationData* navigation_data) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  OptimizationGuideNavigationData* navigation_data =
-      GetNavigationDataFromNavigationHandle(navigation_handle);
   base::flat_set<optimization_guide::proto::OptimizationType>
       registered_optimization_types =
           hints_manager_->registered_optimization_types();
   if (!registered_optimization_types.empty()) {
-    hints_manager_->OnNavigationStartOrRedirect(navigation_handle,
+    hints_manager_->OnNavigationStartOrRedirect(navigation_data,
                                                 base::DoNothing());
   }
 
@@ -259,20 +256,6 @@
       optimization_targets_and_metadata);
 }
 
-// static
-OptimizationGuideNavigationData*
-OptimizationGuideKeyedService::GetNavigationDataFromNavigationHandle(
-    content::NavigationHandle* navigation_handle) {
-  OptimizationGuideWebContentsObserver*
-      optimization_guide_web_contents_observer =
-          OptimizationGuideWebContentsObserver::FromWebContents(
-              navigation_handle->GetWebContents());
-  if (!optimization_guide_web_contents_observer)
-    return nullptr;
-  return optimization_guide_web_contents_observer
-      ->GetOrCreateOptimizationGuideNavigationData(navigation_handle);
-}
-
 void OptimizationGuideKeyedService::ShouldTargetNavigationAsync(
     content::NavigationHandle* navigation_handle,
     optimization_guide::proto::OptimizationTarget optimization_target,
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
index b4127eb..b92605c 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
@@ -100,11 +100,6 @@
       optimization_guide::proto::OptimizationTarget optimization_target,
       std::unique_ptr<optimization_guide::ModelInfo> model_info);
 
-  // Returns the OptimizationGuideNavigationData for |navigation_handle|. Will
-  // return nullptr if one cannot be created for it for any reason.
-  static OptimizationGuideNavigationData* GetNavigationDataFromNavigationHandle(
-      content::NavigationHandle* navigation_handle);
-
  private:
   friend class ChromeBrowsingDataRemoverDelegate;
   friend class HintsFetcherBrowserTest;
@@ -131,9 +126,9 @@
   }
 
   // Notifies |hints_manager_| that the navigation associated with
-  // |navigation_handle| has started or redirected.
+  // |navigation_data| has started or redirected.
   void OnNavigationStartOrRedirect(
-      content::NavigationHandle* navigation_handle);
+      OptimizationGuideNavigationData* navigation_data);
 
   // Notifies |hints_manager_| that the navigation associated with
   // |navigation_redirect_chain| has finished.
diff --git a/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.cc b/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.cc
index 7fe5ec5..ea1fc4bf 100644
--- a/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.cc
@@ -47,13 +47,15 @@
     GetOrCreateOptimizationGuideNavigationData(
         content::NavigationHandle* navigation_handle) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_EQ(web_contents(), navigation_handle->GetWebContents());
 
   int64_t navigation_id = navigation_handle->GetNavigationId();
   if (inflight_optimization_guide_navigation_datas_.find(navigation_id) ==
       inflight_optimization_guide_navigation_datas_.end()) {
     // We do not have one already - create one.
     inflight_optimization_guide_navigation_datas_[navigation_id] =
-        std::make_unique<OptimizationGuideNavigationData>(navigation_id);
+        std::make_unique<OptimizationGuideNavigationData>(
+            navigation_id, navigation_handle->NavigationStart());
   }
 
   DCHECK(inflight_optimization_guide_navigation_datas_.find(navigation_id) !=
@@ -80,8 +82,11 @@
   if (!optimization_guide_keyed_service_)
     return;
 
+  OptimizationGuideNavigationData* navigation_data =
+      GetOrCreateOptimizationGuideNavigationData(navigation_handle);
+  navigation_data->set_navigation_url(navigation_handle->GetURL());
   optimization_guide_keyed_service_->OnNavigationStartOrRedirect(
-      navigation_handle);
+      navigation_data);
 }
 
 void OptimizationGuideWebContentsObserver::DidRedirectNavigation(
@@ -94,8 +99,11 @@
   if (!optimization_guide_keyed_service_)
     return;
 
+  OptimizationGuideNavigationData* navigation_data =
+      GetOrCreateOptimizationGuideNavigationData(navigation_handle);
+  navigation_data->set_navigation_url(navigation_handle->GetURL());
   optimization_guide_keyed_service_->OnNavigationStartOrRedirect(
-      navigation_handle);
+      navigation_data);
 }
 
 void OptimizationGuideWebContentsObserver::ClearHintsToFetchBasedOnPredictions(
diff --git a/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h b/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h
index 7941f8e..b94b4bb 100644
--- a/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h
+++ b/chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h
@@ -31,12 +31,6 @@
  public:
   ~OptimizationGuideWebContentsObserver() override;
 
-  // Gets the OptimizationGuideNavigationData associated with
-  // |navigation_handle|. If one does not exist already, one will be created for
-  // it.
-  OptimizationGuideNavigationData* GetOrCreateOptimizationGuideNavigationData(
-      content::NavigationHandle* navigation_handle);
-
   // Notifies |this| to flush |last_navigation_data| so metrics are recorded.
   void FlushLastNavigationData();
 
@@ -48,6 +42,7 @@
 
  private:
   friend class OptimizationGuideHintsManagerFetchingTest;
+  friend class OptimizationGuideHintsManagerTest;
 
   friend class content::WebContentsUserData<
       OptimizationGuideWebContentsObserver>;
@@ -55,6 +50,12 @@
   explicit OptimizationGuideWebContentsObserver(
       content::WebContents* web_contents);
 
+  // Gets the OptimizationGuideNavigationData associated with the
+  // |navigation_handle|. If one does not exist already, one will be created for
+  // it.
+  OptimizationGuideNavigationData* GetOrCreateOptimizationGuideNavigationData(
+      content::NavigationHandle* navigation_handle);
+
   // Clears the state related to hints to be fetched at onload due to navigation
   // predictions.
   void ClearHintsToFetchBasedOnPredictions(
diff --git a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc
index 5e211bf4..aad70d89 100644
--- a/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/translate_page_load_metrics_observer_unittest.cc
@@ -112,8 +112,10 @@
     mock_translate_metrics_logger_->LogUIInteraction(ui_interaction);
   }
 
-  translate::TranslationType GetNextManualTranslationType() override {
-    return mock_translate_metrics_logger_->GetNextManualTranslationType();
+  translate::TranslationType GetNextManualTranslationType(
+      bool is_context_menu_initiated_translation) override {
+    return mock_translate_metrics_logger_->GetNextManualTranslationType(
+        is_context_menu_initiated_translation);
   }
 
   void SetHasHrefTranslateTarget(bool has_href_translate_target) override {
diff --git a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/StartupPaintPreviewTest.java b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/StartupPaintPreviewTest.java
index f783085..f783503 100644
--- a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/StartupPaintPreviewTest.java
+++ b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/StartupPaintPreviewTest.java
@@ -163,7 +163,9 @@
         showAndWaitForInflation(startupPaintPreview, tabbedPaintPreview, dismissCallback);
         assertAttachedAndShown(tabbedPaintPreview, true, true);
         // Should be removed on PageLoadFinished signal.
-        startupPaintPreview.getTabObserverForTesting().onPageLoadFinished(tab, null);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            startupPaintPreview.getTabObserverForTesting().onPageLoadFinished(tab, null);
+        });
         assertAttachedAndShown(tabbedPaintPreview, false, false);
         Assert.assertEquals(
                 "Dismiss callback should have been called.", 1, dismissCallback.getCallCount());
diff --git a/chrome/browser/password_check/android/javatests/src/org/chromium/chrome/browser/password_check/PasswordCheckIntegrationTest.java b/chrome/browser/password_check/android/javatests/src/org/chromium/chrome/browser/password_check/PasswordCheckIntegrationTest.java
index 427a064..5a483b7 100644
--- a/chrome/browser/password_check/android/javatests/src/org/chromium/chrome/browser/password_check/PasswordCheckIntegrationTest.java
+++ b/chrome/browser/password_check/android/javatests/src/org/chromium/chrome/browser/password_check/PasswordCheckIntegrationTest.java
@@ -28,6 +28,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.components.browser_ui.settings.SettingsLauncher;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Integration test for the Password Check component, testing the interaction between sub-components
@@ -59,7 +60,8 @@
     @Test
     @MediumTest
     public void testDestroysComponentIfFirstInSettingsStack() {
-        PasswordCheckFactory.getOrCreate(mMockSettingsLauncher);
+        TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> PasswordCheckFactory.getOrCreate(mMockSettingsLauncher));
         Activity activity = setUpUiLaunchedFromDialog();
         activity.finish();
         CriteriaHelper.pollUiThread(() -> activity.isDestroyed());
@@ -69,13 +71,14 @@
     @Test
     @MediumTest
     public void testDoesNotDestroyComponentIfNotFirstInSettingsStack() {
-        PasswordCheckFactory.getOrCreate(mMockSettingsLauncher);
+        TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> PasswordCheckFactory.getOrCreate(mMockSettingsLauncher));
         Activity activity = setUpUiLaunchedFromSettings();
         activity.finish();
         CriteriaHelper.pollUiThread(() -> activity.isDestroyed());
         assertNotNull(PasswordCheckFactory.getPasswordCheckInstance());
         // Clean up the password check component.
-        PasswordCheckFactory.destroy();
+        TestThreadUtils.runOnUiThreadBlocking(PasswordCheckFactory::destroy);
     }
 
     private Activity setUpUiLaunchedFromSettings() {
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/options.html b/chrome/browser/resources/chromeos/accessibility/select_to_speak/options.html
index 7dfe0cf..0f04a8a 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/options.html
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/options.html
@@ -60,7 +60,8 @@
             <span class="i18n" msgid="options_natural_voices_explanation">
             </span>
             <!-- TODO(https://crbug.com/1227547): Change to correct link -->
-            <a href="https://support.google.com/chromebook/?p=accessibility">
+            <a target="_blank" rel="noreferrer"
+               href="https://support.google.com/chromebook/?p=accessibility">
               <span class="i18n"
                     msgid="options_natural_voices_explanation_more">
               </span>
diff --git a/chrome/browser/resources/chromeos/audio/BUILD.gn b/chrome/browser/resources/chromeos/audio/BUILD.gn
index 952a4e7a0..8144397 100644
--- a/chrome/browser/resources/chromeos/audio/BUILD.gn
+++ b/chrome/browser/resources/chromeos/audio/BUILD.gn
@@ -6,11 +6,34 @@
 import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
 
+copy("copy_audio") {
+  sources = [
+    "audio.ts",
+    "audio_broker.ts",
+  ]
+  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
+}
+
+copy("copy_mojo") {
+  deps = [ "//chrome/browser/ui/webui/chromeos/audio:mojo_bindings_webui_js" ]
+  sources = [ "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/chromeos/audio/audio.mojom-webui.js" ]
+  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
+}
+
 ts_library("build_ts") {
   deps = [ "//third_party/polymer/v3_0:library" ]
-  root_dir = "./"
+  extra_deps = [
+    ":copy_audio",
+    ":copy_mojo",
+  ]
+  root_dir = "$target_gen_dir"
   out_dir = "$target_gen_dir/tsc"
-  in_files = [ "audio.ts" ]
+  tsconfig_base = "tsconfig_base.json"
+  in_files = [
+    "audio_broker.ts",
+    "audio.ts",
+    "audio.mojom-webui.js",
+  ]
 }
 
 resources_grd_file = "$target_gen_dir/resources.grd"
diff --git a/chrome/browser/resources/chromeos/audio/audio.ts b/chrome/browser/resources/chromeos/audio/audio.ts
index 2443d9f..7423680 100644
--- a/chrome/browser/resources/chromeos/audio/audio.ts
+++ b/chrome/browser/resources/chromeos/audio/audio.ts
@@ -2,7 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {AudioBroker} from './audio_broker.js';
+
 function initialize() {
+  const handler = AudioBroker.getInstance().handler;
+  handler.getAudioDeviceInfo().then(({deviceName}) => {
+    console.log('mock device name output: ' + deviceName);
+  });
   console.log('welcome to the audio page.');
 }
 
diff --git a/chrome/browser/resources/chromeos/audio/audio_broker.ts b/chrome/browser/resources/chromeos/audio/audio_broker.ts
new file mode 100644
index 0000000..92361a5
--- /dev/null
+++ b/chrome/browser/resources/chromeos/audio/audio_broker.ts
@@ -0,0 +1,27 @@
+// Copyright 2021 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 {PageCallbackRouter, PageHandlerFactory, PageHandlerRemote} from './audio.mojom-webui.js';
+
+export class AudioBroker {
+  callbackRouter: PageCallbackRouter;
+  handler: PageHandlerRemote;
+
+  constructor() {
+    this.callbackRouter = new PageCallbackRouter();
+
+    this.handler = new PageHandlerRemote();
+
+    const factory = PageHandlerFactory.getRemote();
+    factory.createPageHandler(
+        this.callbackRouter.$.bindNewPipeAndPassRemote(),
+        this.handler.$.bindNewPipeAndPassReceiver());
+  }
+
+  static getInstance() {
+    return instance || (instance = new AudioBroker());
+  }
+}
+
+let instance: AudioBroker|null = null;
diff --git a/chrome/browser/resources/chromeos/audio/tsconfig_base.json b/chrome/browser/resources/chromeos/audio/tsconfig_base.json
new file mode 100644
index 0000000..b98fa52
--- /dev/null
+++ b/chrome/browser/resources/chromeos/audio/tsconfig_base.json
@@ -0,0 +1,6 @@
+{
+  "extends": "../../../../../tools/typescript/tsconfig_base.json",
+  "compilerOptions": {
+    "allowJs": true
+  }
+}
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_search.html b/chrome/browser/resources/chromeos/emoji_picker/emoji_search.html
index 80769858..78e2b3c 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_search.html
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_search.html
@@ -98,6 +98,10 @@
     overflow-y: scroll;
     padding-bottom: var(--emoji-picker-bottom-padding);
   }
+
+  #no-emoji-image {
+    display: block;
+  }
 </style>
 
 <div id="search-shadow">
@@ -120,7 +124,7 @@
     </template>
     <template is="dom-if" if="[[!results.length]]">
       <div class="no-result">
-        <img src="no_results.svg">
+        <img src="no_results.svg" id = "no-emoji-image">
         No emoji found
         </div>
     </template>
diff --git a/chrome/browser/resources/settings/BUILD.gn b/chrome/browser/resources/settings/BUILD.gn
index 55909d5..e80c5f2 100644
--- a/chrome/browser/resources/settings/BUILD.gn
+++ b/chrome/browser/resources/settings/BUILD.gn
@@ -146,7 +146,7 @@
     "route.js",
     "router.js",
     "safety_check_page/safety_check_browser_proxy.js",
-    "search_engines_page/search_engines_browser_proxy.js",
+    "search_engines_page/search_engines_browser_proxy.ts",
     "search_settings.js",
     "setting_id_param_util.ts",
     "settings.ts",
@@ -273,13 +273,13 @@
     "safety_check_page/safety_check_passwords_child.js",
     "safety_check_page/safety_check_safe_browsing_child.js",
     "safety_check_page/safety_check_updates_child.js",
-    "search_engines_page/omnibox_extension_entry.js",
-    "search_engines_page/search_engine_dialog.js",
-    "search_engines_page/search_engine_entry_css.js",
-    "search_engines_page/search_engine_entry.js",
-    "search_engines_page/search_engines_list.js",
-    "search_engines_page/search_engines_page.js",
-    "search_page/search_page.js",
+    "search_engines_page/omnibox_extension_entry.ts",
+    "search_engines_page/search_engine_dialog.ts",
+    "search_engines_page/search_engine_entry_css.ts",
+    "search_engines_page/search_engine_entry.ts",
+    "search_engines_page/search_engines_list.ts",
+    "search_engines_page/search_engines_page.ts",
+    "search_page/search_page.ts",
     "settings_main/settings_main.js",
     "settings_page/settings_animated_pages.js",
     "settings_page/settings_section.js",
@@ -383,8 +383,6 @@
     "privacy_page/privacy_review:closure_compile",
     "privacy_sandbox:closure_compile",
     "safety_check_page:closure_compile",
-    "search_engines_page:closure_compile",
-    "search_page:closure_compile",
     "settings_main:closure_compile",
     "settings_menu:closure_compile",
     "settings_page:closure_compile",
@@ -455,7 +453,6 @@
     "privacy_page:privacy_page",
     "privacy_page:privacy_page_browser_proxy",
     "safety_check_page:safety_check_browser_proxy",
-    "search_engines_page:search_engines_browser_proxy",
     "settings_ui:settings_ui",
   ]
   extra_deps = [ ":build_ts" ]
@@ -779,14 +776,14 @@
     "safety_check_page/safety_check_passwords_child.js",
     "safety_check_page/safety_check_safe_browsing_child.js",
     "safety_check_page/safety_check_updates_child.js",
-    "search_engines_page/omnibox_extension_entry.js",
-    "search_engines_page/search_engine_dialog.js",
-    "search_engines_page/search_engine_entry_css.js",
-    "search_engines_page/search_engine_entry.js",
-    "search_engines_page/search_engines_browser_proxy.js",
-    "search_engines_page/search_engines_list.js",
-    "search_engines_page/search_engines_page.js",
-    "search_page/search_page.js",
+    "search_engines_page/omnibox_extension_entry.ts",
+    "search_engines_page/search_engine_dialog.ts",
+    "search_engines_page/search_engine_entry_css.ts",
+    "search_engines_page/search_engine_entry.ts",
+    "search_engines_page/search_engines_browser_proxy.ts",
+    "search_engines_page/search_engines_list.ts",
+    "search_engines_page/search_engines_page.ts",
+    "search_page/search_page.ts",
     "search_settings.js",
     "setting_id_param_util.ts",
     "settings.ts",
@@ -891,7 +888,10 @@
         [ "//ui/webui/resources/cr_components/certificate_manager:build_ts" ]
   }
 
-  definitions = [ "//tools/typescript/definitions/chrome_send.d.ts" ]
+  definitions = [
+    "//tools/typescript/definitions/chrome_send.d.ts",
+    "//tools/typescript/definitions/settings_private.d.ts",
+  ]
 
   extra_deps = [
     ":preprocess",
diff --git a/chrome/browser/resources/settings/basic_page/BUILD.gn b/chrome/browser/resources/settings/basic_page/BUILD.gn
index 7a463f9..5d6159f7 100644
--- a/chrome/browser/resources/settings/basic_page/BUILD.gn
+++ b/chrome/browser/resources/settings/basic_page/BUILD.gn
@@ -22,7 +22,6 @@
     "../prefs:prefs_behavior",
     "../privacy_page:privacy_page",
     "../safety_check_page:safety_check_page",
-    "../search_page",
     "../settings_page:main_page_mixin",
     "//ui/webui/resources/js:load_time_data.m",
   ]
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index b6b918b..5d5a5da9 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -238,6 +238,7 @@
     "chromeos/os_printing_page/cups_printers_entry_manager.js",
     "chromeos/os_search_page/search_engines_browser_proxy.js",
     "chromeos/bluetooth_page/bluetooth_page_browser_proxy.js",
+    "chromeos/os_about_page/device_name_browser_proxy.js",
     "chromeos/os_reset_page/os_reset_browser_proxy.js",
     "chromeos/os_settings.js",
     "chromeos/personalization_page/change_picture_browser_proxy.js",
@@ -391,6 +392,8 @@
     "chromeos/os_bluetooth_page/os_bluetooth_page.js",
     "chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js",
     "chromeos/os_bluetooth_page/os_bluetooth_summary.js",
+    "chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.js",
+    "chromeos/os_bluetooth_page/os_paired_bluetooth_list.js",
     "chromeos/os_a11y_page/change_dictation_locale_dialog.js",
     "chromeos/os_a11y_page/manage_a11y_page.m.js",
     "chromeos/os_a11y_page/os_a11y_page.m.js",
@@ -405,12 +408,11 @@
     "chromeos/os_a11y_page/manage_a11y_page_browser_proxy.m.js",
     "chromeos/os_a11y_page/tts_subpage.m.js",
     "chromeos/os_a11y_page/tts_subpage_browser_proxy.m.js",
-    "chromeos/os_about_page/channel_switcher_dialog.m.js",
-    "chromeos/os_about_page/detailed_build_info.m.js",
-    "chromeos/os_about_page/device_name_browser_proxy.m.js",
-    "chromeos/os_about_page/edit_hostname_dialog.m.js",
-    "chromeos/os_about_page/os_about_page.m.js",
-    "chromeos/os_about_page/update_warning_dialog.m.js",
+    "chromeos/os_about_page/channel_switcher_dialog.js",
+    "chromeos/os_about_page/detailed_build_info.js",
+    "chromeos/os_about_page/edit_hostname_dialog.js",
+    "chromeos/os_about_page/os_about_page.js",
+    "chromeos/os_about_page/update_warning_dialog.js",
     "chromeos/os_apps_page/android_apps_browser_proxy.m.js",
     "chromeos/os_apps_page/android_apps_subpage.m.js",
     "chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.js",
@@ -725,7 +727,7 @@
     "nearby_share_page:polymer3_elements",
     "os_a11y_page:polymer3_elements",
     "os_a11y_page:web_components",
-    "os_about_page:polymer3_elements",
+    "os_about_page:web_components",
     "os_apps_page:polymer3_elements",
     "os_apps_page/app_management_page:polymer3_elements",
     "os_apps_page/app_management_page/borealis_page:polymer3_elements",
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_about_page/BUILD.gn
index b1b4c16..32c4417 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/BUILD.gn
@@ -3,39 +3,35 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/polymer.gni")
-import("//ui/webui/resources/tools/js_modulizer.gni")
+import("//tools/polymer/html_to_js.gni")
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
   closure_flags = os_settings_closure_flags
   is_polymer3 = true
   deps = [
-    ":channel_switcher_dialog.m",
-    ":detailed_build_info.m",
-    ":device_name_browser_proxy.m",
-    ":edit_hostname_dialog.m",
-    ":os_about_page.m",
-    ":update_warning_dialog.m",
+    ":channel_switcher_dialog",
+    ":detailed_build_info",
+    ":device_name_browser_proxy",
+    ":edit_hostname_dialog",
+    ":os_about_page",
+    ":update_warning_dialog",
   ]
 }
 
-js_library("channel_switcher_dialog.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.m.js" ]
+js_library("channel_switcher_dialog") {
   deps = [
     "../../about_page:about_page_browser_proxy",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:load_time_data.m",
   ]
-  extra_deps = [ ":channel_switcher_dialog_module" ]
 }
 
-js_library("detailed_build_info.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.m.js" ]
+js_library("detailed_build_info") {
   deps = [
-    ":channel_switcher_dialog.m",
-    ":edit_hostname_dialog.m",
+    ":channel_switcher_dialog",
+    ":edit_hostname_dialog",
     "..:deep_linking_behavior.m",
     "..:os_route.m",
     "../..:router",
@@ -45,31 +41,25 @@
     "//ui/webui/resources/cr_elements/policy:cr_tooltip_icon.m",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
-  extra_deps = [ ":detailed_build_info_module" ]
 }
 
-js_library("device_name_browser_proxy.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.m.js" ]
+js_library("device_name_browser_proxy") {
   deps = [ "//ui/webui/resources/js:cr.m" ]
   externs_list = [ "$externs_path/chrome_send.js" ]
-  extra_deps = [ ":modulize" ]
 }
 
-js_library("edit_hostname_dialog.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.m.js" ]
+js_library("edit_hostname_dialog") {
   deps = [
     "../../about_page:about_page_browser_proxy",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:load_time_data.m",
   ]
-  extra_deps = [ ":edit_hostname_dialog_module" ]
 }
 
-js_library("os_about_page.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.m.js" ]
+js_library("os_about_page") {
   deps = [
-    ":device_name_browser_proxy.m",
+    ":device_name_browser_proxy",
     "..:deep_linking_behavior.m",
     "..:os_route.m",
     "../..:i18n_setup",
@@ -83,75 +73,22 @@
     "//ui/webui/resources/js:parse_html_subset.m",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
-  extra_deps = [ ":os_about_page_module" ]
 }
 
-js_library("update_warning_dialog.m") {
-  sources = [ "$root_gen_dir/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.m.js" ]
+js_library("update_warning_dialog") {
   deps = [
     "../../about_page:about_page_browser_proxy",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
-  extra_deps = [ ":update_warning_dialog_module" ]
 }
 
-group("polymer3_elements") {
-  public_deps = [
-    ":channel_switcher_dialog_module",
-    ":detailed_build_info_module",
-    ":edit_hostname_dialog_module",
-    ":modulize",
-    ":os_about_page_module",
-    ":update_warning_dialog_module",
+html_to_js("web_components") {
+  js_files = [
+    "channel_switcher_dialog.js",
+    "detailed_build_info.js",
+    "edit_hostname_dialog.js",
+    "os_about_page.js",
+    "update_warning_dialog.js",
   ]
 }
-
-polymer_modulizer("channel_switcher_dialog") {
-  js_file = "channel_switcher_dialog.js"
-  html_file = "channel_switcher_dialog.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("detailed_build_info") {
-  js_file = "detailed_build_info.js"
-  html_file = "detailed_build_info.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("edit_hostname_dialog") {
-  js_file = "edit_hostname_dialog.js"
-  html_file = "edit_hostname_dialog.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("os_about_page") {
-  js_file = "os_about_page.js"
-  html_file = "os_about_page.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-polymer_modulizer("update_warning_dialog") {
-  js_file = "update_warning_dialog.js"
-  html_file = "update_warning_dialog.html"
-  html_type = "dom-module"
-  migrated_imports = os_settings_migrated_imports
-  namespace_rewrites = os_settings_namespace_rewrites
-  auto_imports = os_settings_auto_imports
-}
-
-js_modulizer("modulize") {
-  input_files = [ "device_name_browser_proxy.js" ]
-}
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.html b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.html
index 36ccecd55..348b21d 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.html
@@ -1,73 +1,56 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-selector/iron-selector.html">
-<link rel="import" href="../../about_page/about_page_browser_proxy.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-
-<dom-module id="settings-channel-switcher-dialog">
-  <template>
-    <style include="settings-shared">
-      #warningSelector > :not(.iron-selected) {
-        display: none;
-      }
-    </style>
-    <cr-dialog id="dialog" close-text="$i18n{close}">
-      <div slot="title">$i18n{aboutChangeChannel}</div>
-      <div slot="body">
-        <!-- TODO(dbeam): this can be policy-controlled. Show this in the UI.
-             https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ChromeOsReleaseChannel
-        -->
-        <cr-radio-group on-selected-changed="onChannelSelectionChanged_">
-          <cr-radio-button name="[[browserChannelEnum_.STABLE]]">
-            $i18n{aboutChannelDialogStable}
-          </cr-radio-button>
-          <cr-radio-button name="[[browserChannelEnum_.BETA]]">
-            $i18n{aboutChannelDialogBeta}
-          </cr-radio-button>
-          <cr-radio-button name="[[browserChannelEnum_.DEV]]">
-            $i18n{aboutChannelDialogDev}
-          </cr-radio-button>
-        </cr-radio-group>
-        <iron-selector id="warningSelector">
-          <div>
-            <h2>$i18n{aboutDelayedWarningTitle}</h2>
-            <div>[[substituteString_(
-                '$i18nPolymer{aboutDelayedWarningMessage}',
-                '$i18nPolymer{aboutOsProductTitle}')]]</div>
-          </div>
-          <div>
-            <h2>$i18n{aboutPowerwashWarningTitle}</h2>
-            <div>$i18n{aboutPowerwashWarningMessage}</div>
-          </div>
-          <div>
-            <h2>$i18n{aboutUnstableWarningTitle}</h2>
-            <div>[[substituteString_(
-                '$i18nPolymer{aboutUnstableWarningMessage}',
-                '$i18nPolymer{aboutOsProductTitle}')]]</div>
-          </div>
-        </iron-selector>
+<style include="settings-shared">
+  #warningSelector > :not(.iron-selected) {
+    display: none;
+  }
+</style>
+<cr-dialog id="dialog" close-text="$i18n{close}">
+  <div slot="title">$i18n{aboutChangeChannel}</div>
+  <div slot="body">
+    <!-- TODO(dbeam): this can be policy-controlled. Show this in the UI.
+         https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ChromeOsReleaseChannel
+    -->
+    <cr-radio-group on-selected-changed="onChannelSelectionChanged_">
+      <cr-radio-button name="[[browserChannelEnum_.STABLE]]">
+        $i18n{aboutChannelDialogStable}
+      </cr-radio-button>
+      <cr-radio-button name="[[browserChannelEnum_.BETA]]">
+        $i18n{aboutChannelDialogBeta}
+      </cr-radio-button>
+      <cr-radio-button name="[[browserChannelEnum_.DEV]]">
+        $i18n{aboutChannelDialogDev}
+      </cr-radio-button>
+    </cr-radio-group>
+    <iron-selector id="warningSelector">
+      <div>
+        <h2>$i18n{aboutDelayedWarningTitle}</h2>
+        <div>[[substituteString_(
+            '$i18nPolymer{aboutDelayedWarningMessage}',
+            '$i18nPolymer{aboutOsProductTitle}')]]</div>
       </div>
-      <div slot="button-container">
-        <cr-button class="cancel-button" on-click="onCancelTap_"
-            id="cancel">$i18n{cancel}</cr-button>
-        <cr-button id="changeChannel" class="action-button"
-            on-click="onChangeChannelTap_"
-            hidden="[[!shouldShowButtons_.changeChannel]]">
-          $i18n{aboutChangeChannel}
-        </cr-button>
-        <cr-button id="changeChannelAndPowerwash" class="action-button"
-            on-click="onChangeChannelAndPowerwashTap_"
-            hidden="[[!shouldShowButtons_.changeChannelAndPowerwash]]">
-          $i18n{aboutChangeChannelAndPowerwash}
-        </cr-button>
+      <div>
+        <h2>$i18n{aboutPowerwashWarningTitle}</h2>
+        <div>$i18n{aboutPowerwashWarningMessage}</div>
       </div>
-    </cr-dialog>
-  </template>
-  <script src="channel_switcher_dialog.js"></script>
-</dom-module>
+      <div>
+        <h2>$i18n{aboutUnstableWarningTitle}</h2>
+        <div>[[substituteString_(
+            '$i18nPolymer{aboutUnstableWarningMessage}',
+            '$i18nPolymer{aboutOsProductTitle}')]]</div>
+      </div>
+    </iron-selector>
+  </div>
+  <div slot="button-container">
+    <cr-button class="cancel-button" on-click="onCancelTap_"
+        id="cancel">$i18n{cancel}</cr-button>
+    <cr-button id="changeChannel" class="action-button"
+        on-click="onChangeChannelTap_"
+        hidden="[[!shouldShowButtons_.changeChannel]]">
+      $i18n{aboutChangeChannel}
+    </cr-button>
+    <cr-button id="changeChannelAndPowerwash" class="action-button"
+        on-click="onChangeChannelAndPowerwashTap_"
+        hidden="[[!shouldShowButtons_.changeChannelAndPowerwash]]">
+      $i18n{aboutChangeChannelAndPowerwash}
+    </cr-button>
+  </div>
+</cr-dialog>
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js
index e39e438..3e30edc5 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.js
@@ -2,7 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-(function() {
+import '//resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
+import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import '//resources/polymer/v3_0/iron-selector/iron-selector.js';
+import '../../settings_shared_css.js';
+
+import {assert, assertNotReached} from '//resources/js/assert.m.js';
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo, BrowserChannel, browserChannelToI18nId, ChannelInfo, isTargetChannelMoreStable, RegulatoryInfo, TPMFirmwareUpdateStatusChangedEvent, UpdateStatus, UpdateStatusChangedEvent, VersionInfo} from '../../about_page/about_page_browser_proxy.js';
+
 
 /**
  */
@@ -20,6 +32,7 @@
  * release channel to notify parents of this dialog.
  */
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-channel-switcher-dialog',
 
   properties: {
@@ -45,12 +58,12 @@
     },
   },
 
-  /** @private {?settings.AboutPageBrowserProxy} */
+  /** @private {?AboutPageBrowserProxy} */
   browserProxy_: null,
 
   /** @override */
   ready() {
-    this.browserProxy_ = settings.AboutPageBrowserProxyImpl.getInstance();
+    this.browserProxy_ = AboutPageBrowserProxyImpl.getInstance();
     this.browserProxy_.getChannelInfo().then(info => {
       this.currentChannel_ = info.currentChannel;
       this.targetChannel_ = info.targetChannel;
@@ -125,8 +138,7 @@
       return;
     }
 
-    if (settings.isTargetChannelMoreStable(
-            this.currentChannel_, selectedChannel)) {
+    if (isTargetChannelMoreStable(this.currentChannel_, selectedChannel)) {
       // More stable channel selected. For non managed devices, notify the user
       // about powerwash.
       if (loadTimeData.getBoolean('aboutEnterpriseManaged')) {
@@ -157,4 +169,3 @@
     return loadTimeData.substituteString(format, replacement);
   },
 });
-})();
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.html b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.html
index 8c1d26fe..70c8d24 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.html
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.html
@@ -1,133 +1,108 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared">
+  cr-policy-indicator {
+    margin-inline-start: var(--cr-controlled-by-spacing);
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator_behavior.html">
-<link rel="import" href="chrome://resources/cr_elements/policy/cr_tooltip_icon.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="../../about_page/about_page_browser_proxy.html">
-<link rel="import" href="../../i18n_setup.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../deep_linking_behavior.html">
-<link rel="import" href="../localized_link/localized_link.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="channel_switcher_dialog.html">
-<link rel="import" href="device_name_browser_proxy.html">
-<link rel="import" href="edit_hostname_dialog.html">
+  /* The command line string can contain very long substrings that
+   * don't have any spaces, need to force a line break in such cases. */
+  #command-line {
+    overflow-wrap: break-word;
+    width: 100%;
+  }
 
-<dom-module id="settings-detailed-build-info">
-  <template>
-    <style include="settings-shared">
-      cr-policy-indicator {
-        margin-inline-start: var(--cr-controlled-by-spacing);
-      }
-
-      /* The command line string can contain very long substrings that
-       * don't have any spaces, need to force a line break in such cases. */
-      #command-line {
-        overflow-wrap: break-word;
-        width: 100%;
-      }
-
-      #managedEolTooltipIcon {
-        margin-inline-end: 54px;
-      }
-    </style>
-    <div class="settings-box two-line">
-      <div class="start">
-        <div role="heading" aria-level="2">$i18n{aboutChannelLabel}</div>
-        <div id="currentlyOnChannelText" aria-hidden="true" class="secondary">
-          [[currentlyOnChannelText_]]
-        </div>
-      </div>
-      <div class="separator"></div>
-      <cr-button on-click="onChangeChannelTap_"
-          aria-describedby="currentlyOnChannelText"
-          disabled="[[!canChangeChannel_]]"
-          deep-link-focus-id$="[[Setting.kChangeChromeChannel]]">
-        $i18n{aboutChangeChannel}
-      </cr-button>
-      <template is="dom-if" if="[[!canChangeChannel_]]">
-        <cr-policy-indicator
-            indicator-source-name="[[getChangeChannelIndicatorSourceName_(
-                canChangeChannel_)]]"
-            indicator-type="[[getChangeChannelIndicatorType_(
-                canChangeChannel_)]]">
-        </cr-policy-indicator>
-      </template>
-      <template is="dom-if" if="[[showChannelSwitcherDialog_]]" restamp>
-        <settings-channel-switcher-dialog
-            on-close="onChannelSwitcherDialogClosed_">
-        </settings-channel-switcher-dialog>
-      </template>
+  #managedEolTooltipIcon {
+    margin-inline-end: 54px;
+  }
+</style>
+<div class="settings-box two-line">
+  <div class="start">
+    <div role="heading" aria-level="2">$i18n{aboutChannelLabel}</div>
+    <div id="currentlyOnChannelText" aria-hidden="true" class="secondary">
+      [[currentlyOnChannelText_]]
     </div>
-    <div id="endOfLifeSectionContainer"
-        class="settings-box two-line single-column"
-        hidden="[[shouldHideEolInfo_]]">
-      <div role="heading" aria-level="2">$i18n{aboutEndOfLifeTitle}</div>
-      <settings-localized-link class="secondary"
-          localized-string="[[eolMessageWithMonthAndYear]]">
-      </settings-localized-link>
-    </div>
-    <template is="dom-if" if="[[isManaged_]]">
-      <div class="settings-box two-line">
-        <div class="start" aria-hidden="true">
-          <div role="heading">$i18n{aboutEndOfLifeTitle}</div>
-          <div class="secondary">
-            $i18n{aboutManagedEndOfLifeSubtitle}
-          </div>
-        </div>
-        <cr-tooltip-icon
-            id="managedEolTooltipIcon"
-            icon-class="cr20:domain"
-            tooltip-text="$i18n{aboutManagedEndOfLifeSubtitle}"
-            icon-aria-label="$i18n{aboutManagedEndOfLifeSubtitle}"
-            tooltip-position="bottom">
-        </cr-tooltip-icon>
-      </div>
-    </template>
-    <template is="dom-if" if="[[isHostnameSettingEnabled_]]">
-      <div class="settings-box two-line">
-        <div class="start">
-          <div role="heading">$i18n{aboutDeviceName}</div>
-          <div id="deviceName" aria-hidden="true" class="secondary">
-            [[deviceNameText_]]
-          </div>
-        </div>
-        <cr-icon-button class="icon-edit" on-click="onEditHostnameTap_">
-        </cr-icon-button>
-      </div>
-      <template is="dom-if" if="[[showEditHostnameDialog_]]" restamp>
-        <edit-hostname-dialog
-            on-close="onEditHostnameDialogClosed_">
-        </edit-hostname-dialog>
-      </template>
-    </template>
-    <div id="buildDetailsLinkContainer" class="settings-box">
-      <div class="start" id="aboutBuildDetailsTitle">
-        $i18n{aboutBuildDetailsTitle}
-      </div>
-      <cr-icon-button id="copyBuildDetailsButton" class="icon-copy-content"
-          aria-labelledby="copyBuildDetailsButtonToolTip"
-          on-click="onCopyBuildDetailsToClipBoardTap_"
-          disabled="[[!copyToClipBoardEnabled_(versionInfo_, channelInfo_)]]"
-          deep-link-focus-id$="[[Setting.kCopyDetailedBuildInfo]]">
-      </cr-icon-button>
-      <paper-tooltip id="copyBuildDetailsButtonToolTip"
-          for="copyBuildDetailsButton"
-          position="bottom" fit-to-visible-bounds>
-        $i18n{aboutBuildDetailsCopyTooltipLabel}
-      </paper-tooltip>
-      <div class="separator"></div>
-      <cr-icon-button on-click="onVisitBuildDetailsPageTap_"
-          aria-labelledby="aboutBuildDetailsTitle"
-          class="icon-external">
-      </cr-icon-button>
-    </div>
-    <div class="hr"></div>
+  </div>
+  <div class="separator"></div>
+  <cr-button on-click="onChangeChannelTap_"
+      aria-describedby="currentlyOnChannelText"
+      disabled="[[!canChangeChannel_]]"
+      deep-link-focus-id$="[[Setting.kChangeChromeChannel]]">
+    $i18n{aboutChangeChannel}
+  </cr-button>
+  <template is="dom-if" if="[[!canChangeChannel_]]">
+    <cr-policy-indicator
+        indicator-source-name="[[getChangeChannelIndicatorSourceName_(
+            canChangeChannel_)]]"
+        indicator-type="[[getChangeChannelIndicatorType_(
+            canChangeChannel_)]]">
+    </cr-policy-indicator>
   </template>
-  <script src="detailed_build_info.js"></script>
-</dom-module>
+  <template is="dom-if" if="[[showChannelSwitcherDialog_]]" restamp>
+    <settings-channel-switcher-dialog
+        on-close="onChannelSwitcherDialogClosed_">
+    </settings-channel-switcher-dialog>
+  </template>
+</div>
+<div id="endOfLifeSectionContainer"
+    class="settings-box two-line single-column"
+    hidden="[[shouldHideEolInfo_]]">
+  <div role="heading" aria-level="2">$i18n{aboutEndOfLifeTitle}</div>
+  <settings-localized-link class="secondary"
+      localized-string="[[eolMessageWithMonthAndYear]]">
+  </settings-localized-link>
+</div>
+<template is="dom-if" if="[[isManaged_]]">
+  <div class="settings-box two-line">
+    <div class="start" aria-hidden="true">
+      <div role="heading">$i18n{aboutEndOfLifeTitle}</div>
+      <div class="secondary">
+        $i18n{aboutManagedEndOfLifeSubtitle}
+      </div>
+    </div>
+    <cr-tooltip-icon
+        id="managedEolTooltipIcon"
+        icon-class="cr20:domain"
+        tooltip-text="$i18n{aboutManagedEndOfLifeSubtitle}"
+        icon-aria-label="$i18n{aboutManagedEndOfLifeSubtitle}"
+        tooltip-position="bottom">
+    </cr-tooltip-icon>
+  </div>
+</template>
+<template is="dom-if" if="[[isHostnameSettingEnabled_]]">
+  <div class="settings-box two-line">
+    <div class="start">
+      <div role="heading">$i18n{aboutDeviceName}</div>
+      <div id="deviceName" aria-hidden="true" class="secondary">
+        [[deviceNameText_]]
+      </div>
+    </div>
+    <cr-icon-button class="icon-edit" on-click="onEditHostnameTap_">
+    </cr-icon-button>
+  </div>
+  <template is="dom-if" if="[[showEditHostnameDialog_]]" restamp>
+    <edit-hostname-dialog
+        on-close="onEditHostnameDialogClosed_">
+    </edit-hostname-dialog>
+  </template>
+</template>
+<div id="buildDetailsLinkContainer" class="settings-box">
+  <div class="start" id="aboutBuildDetailsTitle">
+    $i18n{aboutBuildDetailsTitle}
+  </div>
+  <cr-icon-button id="copyBuildDetailsButton" class="icon-copy-content"
+      aria-labelledby="copyBuildDetailsButtonToolTip"
+      on-click="onCopyBuildDetailsToClipBoardTap_"
+      disabled="[[!copyToClipBoardEnabled_(versionInfo_, channelInfo_)]]"
+      deep-link-focus-id$="[[Setting.kCopyDetailedBuildInfo]]">
+  </cr-icon-button>
+  <paper-tooltip id="copyBuildDetailsButtonToolTip"
+      for="copyBuildDetailsButton"
+      position="bottom" fit-to-visible-bounds>
+    $i18n{aboutBuildDetailsCopyTooltipLabel}
+  </paper-tooltip>
+  <div class="separator"></div>
+  <cr-icon-button on-click="onVisitBuildDetailsPageTap_"
+      aria-labelledby="aboutBuildDetailsTitle"
+      class="icon-external">
+  </cr-icon-button>
+</div>
+<div class="hr"></div>
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js
index d77ddbd..3da862b 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.js
@@ -7,13 +7,36 @@
  * information for ChromeOS.
  */
 
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/policy/cr_policy_indicator.m.js';
+import '//resources/cr_elements/policy/cr_tooltip_icon.m.js';
+import '../../settings_shared_css.js';
+import '../localized_link/localized_link.js';
+import './channel_switcher_dialog.js';
+import './edit_hostname_dialog.js';
+
+import {CrPolicyIndicatorType} from '//resources/cr_elements/policy/cr_policy_indicator_behavior.m.js';
+import {assert, assertNotReached} from '//resources/js/assert.m.js';
+import {focusWithoutInk} from '//resources/js/cr/ui/focus_without_ink.m.js';
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo, BrowserChannel, browserChannelToI18nId, ChannelInfo, isTargetChannelMoreStable, RegulatoryInfo, TPMFirmwareUpdateStatusChangedEvent, UpdateStatus, UpdateStatusChangedEvent, VersionInfo} from '../../about_page/about_page_browser_proxy.js';
+import {loadTimeData} from '../../i18n_setup.js';
+import {Route, RouteObserverBehavior, Router} from '../../router.js';
+import {DeepLinkingBehavior} from '../deep_linking_behavior.m.js';
+import {routes} from '../os_route.m.js';
+
+import {DeviceNameBrowserProxy, DeviceNameBrowserProxyImpl} from './device_name_browser_proxy.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-detailed-build-info',
 
   behaviors: [
     DeepLinkingBehavior,
     I18nBehavior,
-    settings.RouteObserverBehavior,
+    RouteObserverBehavior,
   ],
 
   properties: {
@@ -86,7 +109,7 @@
 
   /** @override */
   ready() {
-    const browserProxy = settings.AboutPageBrowserProxyImpl.getInstance();
+    const browserProxy = AboutPageBrowserProxyImpl.getInstance();
     browserProxy.pageReady();
 
     browserProxy.getVersionInfo().then(versionInfo => {
@@ -101,12 +124,12 @@
   },
 
   /**
-   * @param {!settings.Route} route
-   * @param {!settings.Route} oldRoute
+   * @param {!Route} route
+   * @param {!Route} oldRoute
    */
   currentRouteChanged(route, oldRoute) {
     // Does not apply to this page.
-    if (route !== settings.routes.DETAILED_BUILD_INFO) {
+    if (route !== routes.DETAILED_BUILD_INFO) {
       return;
     }
 
@@ -123,7 +146,7 @@
 
   /** @private */
   updateChannelInfo_() {
-    const browserProxy = settings.AboutPageBrowserProxyImpl.getInstance();
+    const browserProxy = AboutPageBrowserProxyImpl.getInstance();
 
     // canChangeChannel() call is expected to be low-latency, so fetch this
     // value by itself to ensure UI consistency (see https://crbug.com/848750).
@@ -138,8 +161,7 @@
       // Display the target channel for the 'Currently on' message.
       this.currentlyOnChannelText_ = this.i18n(
           'aboutCurrentlyOnChannel',
-          this.i18n(
-              settings.browserChannelToI18nId(info.targetChannel, info.isLts)));
+          this.i18n(browserChannelToI18nId(info.targetChannel, info.isLts)));
     });
   },
 
@@ -238,14 +260,14 @@
   /** @private */
   onChannelSwitcherDialogClosed_() {
     this.showChannelSwitcherDialog_ = false;
-    cr.ui.focusWithoutInk(assert(this.$$('cr-button')));
+    focusWithoutInk(assert(this.$$('cr-button')));
     this.updateChannelInfo_();
   },
 
   /** @private */
   onEditHostnameDialogClosed_() {
     this.showEditHostnameDialog_ = false;
-    cr.ui.focusWithoutInk(assert(this.$$('cr-button')));
+    focusWithoutInk(assert(this.$$('cr-button')));
     // TODO(jhawkins): Verify hostname property updated at this point.
   },
 });
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.js b/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.js
index df0e1f1..f8cfff17 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 // clang-format off
-// #import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
 // clang-format on
 
 /**
@@ -11,10 +11,10 @@
  *   deviceName: string,
  * }}
  */
-/* #export */ let DeviceNameMetadata;
+export let DeviceNameMetadata;
 
 /** @interface */
-/* #export */ class DeviceNameBrowserProxy {
+export class DeviceNameBrowserProxy {
   /**
    * Queries the system for metadata about the device name.
    * @return {!Promise<!DeviceNameMetadata>}
@@ -25,13 +25,13 @@
 /**
  * @implements {DeviceNameBrowserProxy}
  */
-/* #export */ class DeviceNameBrowserProxyImpl {
+export class DeviceNameBrowserProxyImpl {
   /** @override */
   getDeviceNameMetadata() {
-    return cr.sendWithPromise('getDeviceNameMetadata');
+    return sendWithPromise('getDeviceNameMetadata');
   }
 }
 
 // The singleton instance_ is replaced with a test version of this wrapper
 // during testing.
-cr.addSingletonGetter(DeviceNameBrowserProxyImpl);
+addSingletonGetter(DeviceNameBrowserProxyImpl);
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.html b/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.html
index 8a64870..251350654 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.html
@@ -1,50 +1,32 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared">
+  cr-input {
+    --cr-input-error-display: none;
+  }
 
-<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_input/cr_input.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/load_time_data.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-selector/iron-selector.html">
-<link rel="import" href="../../about_page/about_page_browser_proxy.html">
-<link rel="import" href="../../settings_shared_css.html">
+  #input-subtext {
+    display: flex;
+  }
 
-<dom-module id="edit-hostname-dialog">
-  <template>
-    <style include="settings-shared">
-      cr-input {
-        --cr-input-error-display: none;
-      }
-
-      #input-subtext {
-        display: flex;
-      }
-
-      #input-subtext span:last-child {
-        margin-inline-start: auto;
-      }
-    </style>
-    <cr-dialog id="dialog" show-on-attach>
-      <div slot="title">$i18n{aboutEditDeviceName}</div>
-      <div slot="body">
-        <div>$i18n{aboutDeviceNameInfo}</div>
-        <cr-input></cr-input>
-        <div id="input-subtext">
-          <span>$i18n{aboutDeviceNameConstraints}</span>
-          <span>1/15</span>
-        </div>
-      </div>
-      <div slot="button-container">
-        <cr-button class="cancel-button" on-click="onCancelTap_">
-          $i18n{cancel}
-        </cr-button>
-        <cr-button class="action-button" on-click="onDoneTap_">
-          $i18n{done}
-        </cr-button>
-      </div>
-    </cr-dialog>
-  </template>
-  <script src="edit_hostname_dialog.js"></script>
-</dom-module>
+  #input-subtext span:last-child {
+    margin-inline-start: auto;
+  }
+</style>
+<cr-dialog id="dialog" show-on-attach>
+  <div slot="title">$i18n{aboutEditDeviceName}</div>
+  <div slot="body">
+    <div>$i18n{aboutDeviceNameInfo}</div>
+    <cr-input></cr-input>
+    <div id="input-subtext">
+      <span>$i18n{aboutDeviceNameConstraints}</span>
+      <span>1/15</span>
+    </div>
+  </div>
+  <div slot="button-container">
+    <cr-button class="cancel-button" on-click="onCancelTap_">
+      $i18n{cancel}
+    </cr-button>
+    <cr-button class="action-button" on-click="onDoneTap_">
+      $i18n{done}
+    </cr-button>
+  </div>
+</cr-dialog>
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.js b/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.js
index fa76e09..d27796e0 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.js
@@ -6,7 +6,22 @@
  * @fileoverview 'edit-hostname-dialog' is a component allowing the
  * user to edit the device hostname.
  */
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import '//resources/cr_elements/cr_input/cr_input.m.js';
+import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
+import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import '//resources/polymer/v3_0/iron-selector/iron-selector.js';
+import '../../settings_shared_css.js';
+
+import {assert, assertNotReached} from '//resources/js/assert.m.js';
+import {loadTimeData} from '//resources/js/load_time_data.m.js';
+import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo, BrowserChannel, browserChannelToI18nId, ChannelInfo, isTargetChannelMoreStable, RegulatoryInfo, TPMFirmwareUpdateStatusChangedEvent, UpdateStatus, UpdateStatusChangedEvent, VersionInfo} from '../../about_page/about_page_browser_proxy.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'edit-hostname-dialog',
 
   /** @private */
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html
index ea71d9f..e29a1478 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html
@@ -1,260 +1,224 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="settings-shared settings-page-styles">
+  :host {
+    --about-page-image-space: 10px;
+  }
 
-<link rel="import" href="chrome://resources/html/parse_html_subset.html">
-<link rel="import" href="../../about_page/about_page_browser_proxy.html">
-<link rel="import" href="../../i18n_setup.html">
-<link rel="import" href="../../icons.html">
-<link rel="import" href="../../lifetime_browser_proxy.html">
-<link rel="import" href="../../prefs/prefs.html">
-<link rel="import" href="../os_settings_page/main_page_behavior.html">
-<link rel="import" href="../../settings_page/settings_animated_pages.html">
-<link rel="import" href="../../settings_page/settings_section.html">
-<link rel="import" href="../../settings_page/settings_subpage.html">
-<link rel="import" href="../../settings_page_css.html">
-<link rel="import" href="../../settings_shared_css.html">
-<link rel="import" href="../deep_linking_behavior.html">
-<link rel="import" href="../metrics_recorder.html">
-<link rel="import" href="../os_icons.html">
-<link rel="import" href="../os_route.html">
-<link rel="import" href="../../router.html">
-<link rel="import" href="../os_reset_page/os_powerwash_dialog.html">
-<link rel="import" href="../localized_link/localized_link.html">
-<link rel="import" href="detailed_build_info.html">
-<link rel="import" href="update_warning_dialog.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_link_row/cr_link_row.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/html/assert.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-icon/iron-icon.html">
+  .info-section {
+    margin-bottom: 12px;
+  }
 
-<dom-module id="os-settings-about-page">
-  <template>
-    <style include="settings-shared settings-page-styles">
-      :host {
-        --about-page-image-space: 10px;
-      }
+  .padded {
+    padding-bottom: 10px;
+    padding-top: 10px;
+  }
 
-      .info-section {
-        margin-bottom: 12px;
-      }
+  .product-title {
+    font-size: 153.85%;  /* 20px / 13px */
+    font-weight: 400;
+    margin-bottom: auto;
+    margin-top: auto;
+  }
 
-      .padded {
-        padding-bottom: 10px;
-        padding-top: 10px;
-      }
+  img {
+    margin-inline-end: var(--about-page-image-space);
+  }
 
-      .product-title {
-        font-size: 153.85%;  /* 20px / 13px */
-        font-weight: 400;
-        margin-bottom: auto;
-        margin-top: auto;
-      }
+  .icon-container {
+    margin-inline-end: var(--about-page-image-space);
+    min-width: 32px;  /* The width of the product-logo img. */
+    text-align: center;
+  }
 
-      img {
-        margin-inline-end: var(--about-page-image-space);
-      }
+  /* TODO(crbug.com/986596): Don't use browser icons here. Fork them. */
+  iron-icon[icon='settings:check-circle'] {
+    fill: var(--cros-icon-color-prominent);
+  }
 
-      .icon-container {
-        margin-inline-end: var(--about-page-image-space);
-        min-width: 32px;  /* The width of the product-logo img. */
-        text-align: center;
-      }
+  iron-icon[icon='cr:error-outline'] {
+    fill: var(--cros-text-color-alert);
+  }
 
-      /* TODO(crbug.com/986596): Don't use browser icons here. Fork them. */
-      iron-icon[icon='settings:check-circle'] {
-        fill: var(--cros-icon-color-prominent);
-      }
+  .settings-box .start {
+    overflow-x: auto;
+  }
 
-      iron-icon[icon='cr:error-outline'] {
-        fill: var(--cros-text-color-alert);
-      }
+  cr-button {
+    white-space: nowrap;
+  }
 
-      .settings-box .start {
-        overflow-x: auto;
-      }
-
-      cr-button {
-        white-space: nowrap;
-      }
-
-      #regulatoryInfo img {
-        width: 330px;
-      }
-    </style>
-    <settings-section page-title="$i18n{aboutOsPageTitle}" section="about">
-      <settings-animated-pages id="pages" section="about"
-          focus-config="[[focusConfig_]]">
-        <div route-path="default">
-          <div class="settings-box two-line first">
-            <img id="product-logo" on-click="onProductLogoClick_"
-                srcset="chrome://theme/current-channel-logo@1x 1x,
-                        chrome://theme/current-channel-logo@2x 2x"
-                alt="$i18n{aboutProductLogoAlt}"
-                role="presentation">
-            <h1 class="product-title">$i18n{aboutOsProductTitle}</h1>
-          </div>
-          <div class="settings-box two-line">
-            <!-- Set the icon from the iconset (when it's obsolete/EOL and
-              when update is done) or set the src (when it's updating). -->
-            <div class="icon-container"
-                hidden="[[!shouldShowIcons_(showUpdateStatus_)]]">
-              <!-- TODO(crbug.com/986596): Don't use browser icons here. Fork them. -->
-              <iron-icon
-                  icon$="[[getUpdateStatusIcon_(
-                      hasEndOfLife_, currentUpdateStatusEvent_)]]"
-                  src="[[getThrobberSrcIfUpdating_(
-                      hasEndOfLife_, currentUpdateStatusEvent_)]]">
-              </iron-icon>
-            </div>
-            <div class="start padded">
-              <div id="updateStatusMessage" hidden="[[!showUpdateStatus_]]">
-                <div
-                    inner-h-t-m-l="[[getUpdateStatusMessage_(
-                        currentUpdateStatusEvent_, targetChannel_)]]"></div>
-                <a hidden$="[[!shouldShowLearnMoreLink_(
-                    currentUpdateStatusEvent_)]]" target="_blank"
-                    href="https://support.google.com/chrome?p=update_error">
-                  $i18n{learnMore}
-                </a>
-              </div>
-              <settings-localized-link
-                  id="endOfLifeMessageContainer" hidden="[[!hasEndOfLife_]]"
-                  localized-string="$i18n{endOfLifeMessage}">
-              </settings-localized-link>
-              <div class="secondary">$i18n{aboutBrowserVersion}</div>
-            </div>
-            <div class="separator" hidden="[[!showButtonContainer_]]"></div>
-            <span id="buttonContainer" hidden="[[!showButtonContainer_]]">
-              <cr-button id="relaunch" hidden$="[[!showRelaunch_]]"
-                         on-click="onRelaunchClick_">
-                  [[getRelaunchButtonText_(
-                                  currentUpdateStatusEvent_)]]
-              </cr-button>
-              <cr-button id="checkForUpdates" hidden="[[!showCheckUpdates_]]"
-                  on-click="onCheckUpdatesClick_"
-                  deep-link-focus-id$="[[Setting.kCheckForOsUpdate]]">
-                $i18n{aboutCheckForUpdates}
-              </cr-button>
-            </span>
-          </div>
-          <cr-link-row
-              id="aboutTPMFirmwareUpdate"
-              class="hr"
-              hidden$="[[!showTPMFirmwareUpdateLineItem_]]"
-              label="$i18n{aboutTPMFirmwareUpdateTitle}"
-              on-click="onTPMFirmwareUpdateClick_">
-            <div slot="sub-label">
-              $i18n{aboutTPMFirmwareUpdateDescription}
-              <a href="$i18n{aboutTPMFirmwareUpdateLearnMoreURL}"
-                  target="_blank" on-click="onLearnMoreClick_">
-                $i18n{learnMore}
-              </a>
-            </div>
-          </cr-link-row>
-          <template is="dom-if" if="[[hasInternetConnection_]]">
-            <cr-link-row class="hr" id="releaseNotesOnline"
-                on-click="onReleaseNotesTap_"
-                label="$i18n{aboutShowReleaseNotes}" external
-                deep-link-focus-id$="[[Setting.kSeeWhatsNew]]">
-            </cr-link-row>
-          </template>
-          <template is="dom-if" if="[[!hasInternetConnection_]]">
-            <cr-link-row class="hr" id="releaseNotesOffline"
-                on-click="onReleaseNotesTap_"
-                label="$i18n{aboutShowReleaseNotes}"
-                title="$i18n{aboutReleaseNotesOffline}" external
-                deep-link-focus-id$="[[Setting.kSeeWhatsNew]]">
-            </cr-link-row>
-          </template>
-          <cr-link-row class="hr" id="help" on-click="onHelpClick_"
-              label="$i18n{aboutGetHelpUsingChromeOs}" external
-              deep-link-focus-id$="[[Setting.kGetHelpWithChromeOs]]">
-          </cr-link-row>
-<if expr="_google_chrome">
-          <cr-link-row class="hr" id="reportIssue"
-              on-click="onReportIssueClick_"
-              hidden="[[!prefs.feedback_allowed.value]]"
-              label="$i18n{aboutReportAnIssue}" external
-              deep-link-focus-id$="[[Setting.kReportAnIssue]]">
-          </cr-link-row>
-</if>
-          <cr-link-row class="hr" id="diagnostics"
-              on-click="onDiagnosticsClick_"
-              hidden$="[[!showDiagnosticsApp_]]"
-              label="$i18n{aboutDiagnostics}" external
-              deep-link-focus-id$="[[Setting.kDiagnostics]]">
-          </cr-link-row>
-          <cr-link-row class="hr" id="detailed-build-info-trigger"
-              on-click="onDetailedBuildInfoClick_"
-              label="$i18n{aboutDetailedBuildInfo}"
-              role-description="$i18n{subpageArrowRoleDescription}">
-          </cr-link-row>
-          <cr-link-row class="hr" on-click="onManagementPageClick_"
-              start-icon="cr:domain" label="$i18n{managementPage}"
-              hidden$="[[!isManaged_]]" external>
-          </cr-link-row>
+  #regulatoryInfo img {
+    width: 330px;
+  }
+</style>
+<settings-section page-title="$i18n{aboutOsPageTitle}" section="about">
+  <settings-animated-pages id="pages" section="about"
+      focus-config="[[focusConfig_]]">
+    <div route-path="default">
+      <div class="settings-box two-line first">
+        <img id="product-logo" on-click="onProductLogoClick_"
+            srcset="chrome://theme/current-channel-logo@1x 1x,
+                    chrome://theme/current-channel-logo@2x 2x"
+            alt="$i18n{aboutProductLogoAlt}"
+            role="presentation">
+        <h1 class="product-title">$i18n{aboutOsProductTitle}</h1>
+      </div>
+      <div class="settings-box two-line">
+        <!-- Set the icon from the iconset (when it's obsolete/EOL and
+          when update is done) or set the src (when it's updating). -->
+        <div class="icon-container"
+            hidden="[[!shouldShowIcons_(showUpdateStatus_)]]">
+          <!-- TODO(crbug.com/986596): Don't use browser icons here. Fork
+            them. -->
+          <iron-icon
+              icon$="[[getUpdateStatusIcon_(
+                  hasEndOfLife_, currentUpdateStatusEvent_)]]"
+              src="[[getThrobberSrcIfUpdating_(
+                  hasEndOfLife_, currentUpdateStatusEvent_)]]">
+          </iron-icon>
         </div>
-        <template is="dom-if" route-path="/help/details">
-          <settings-subpage page-title="$i18n{aboutDetailedBuildInfo}">
-            <settings-detailed-build-info
-                eol-message-with-month-and-year=
-                    "[[eolMessageWithMonthAndYear_]]">
-            </settings-detailed-build-info>
-          </settings-subpage>
-        </template>
-      </settings-animated-pages>
-    </settings-section>
-
-    <settings-section>
-      <div class="settings-box padded block first">
-        <div class="info-section">
-          <div class="secondary">$i18n{aboutOsProductTitle}</div>
-          <div class="secondary">$i18n{aboutProductCopyright}</div>
-        </div>
-
-        <div class="info-section">
-          <div class="secondary">$i18nRaw{aboutProductLicense}</div>
-          <div class="secondary"
-              inner-h-t-m-l="[[getAboutProductOsLicense_(
-                  showCrostiniLicense_)]]">
+        <div class="start padded">
+          <div id="updateStatusMessage" hidden="[[!showUpdateStatus_]]">
+            <div
+                inner-h-t-m-l="[[getUpdateStatusMessage_(
+                    currentUpdateStatusEvent_, targetChannel_)]]"></div>
+            <a hidden$="[[!shouldShowLearnMoreLink_(
+                currentUpdateStatusEvent_)]]" target="_blank"
+                href="https://support.google.com/chrome?p=update_error">
+              $i18n{learnMore}
+            </a>
           </div>
+          <settings-localized-link
+              id="endOfLifeMessageContainer" hidden="[[!hasEndOfLife_]]"
+              localized-string="$i18n{endOfLifeMessage}">
+          </settings-localized-link>
+          <div class="secondary">$i18n{aboutBrowserVersion}</div>
         </div>
-<if expr="_google_chrome">
-        <div class="secondary">
-          <a id="aboutProductTos" href="$i18n{aboutTermsURL}" target="_blank"
-              deep-link-focus-id$="[[Setting.kTermsOfService]]">
-            $i18n{aboutProductTos}
+        <div class="separator" hidden="[[!showButtonContainer_]]"></div>
+        <span id="buttonContainer" hidden="[[!showButtonContainer_]]">
+          <cr-button id="relaunch" hidden$="[[!showRelaunch_]]"
+                     on-click="onRelaunchClick_">
+              [[getRelaunchButtonText_(
+                              currentUpdateStatusEvent_)]]
+          </cr-button>
+          <cr-button id="checkForUpdates" hidden="[[!showCheckUpdates_]]"
+              on-click="onCheckUpdatesClick_"
+              deep-link-focus-id$="[[Setting.kCheckForOsUpdate]]">
+            $i18n{aboutCheckForUpdates}
+          </cr-button>
+        </span>
+      </div>
+      <cr-link-row
+          id="aboutTPMFirmwareUpdate"
+          class="hr"
+          hidden$="[[!showTPMFirmwareUpdateLineItem_]]"
+          label="$i18n{aboutTPMFirmwareUpdateTitle}"
+          on-click="onTPMFirmwareUpdateClick_">
+        <div slot="sub-label">
+          $i18n{aboutTPMFirmwareUpdateDescription}
+          <a href="$i18n{aboutTPMFirmwareUpdateLearnMoreURL}"
+              target="_blank" on-click="onLearnMoreClick_">
+            $i18n{learnMore}
           </a>
         </div>
-</if>
-      </div>
-      <div class="settings-box padded block" id="regulatoryInfo"
-          hidden$="[[!shouldShowRegulatoryOrSafetyInfo_(regulatoryInfo_)]]">
+      </cr-link-row>
+      <template is="dom-if" if="[[hasInternetConnection_]]">
+        <cr-link-row class="hr" id="releaseNotesOnline"
+            on-click="onReleaseNotesTap_"
+            label="$i18n{aboutShowReleaseNotes}" external
+            deep-link-focus-id$="[[Setting.kSeeWhatsNew]]">
+        </cr-link-row>
+      </template>
+      <template is="dom-if" if="[[!hasInternetConnection_]]">
+        <cr-link-row class="hr" id="releaseNotesOffline"
+            on-click="onReleaseNotesTap_"
+            label="$i18n{aboutShowReleaseNotes}"
+            title="$i18n{aboutReleaseNotesOffline}" external
+            deep-link-focus-id$="[[Setting.kSeeWhatsNew]]">
+        </cr-link-row>
+      </template>
+      <cr-link-row class="hr" id="help" on-click="onHelpClick_"
+          label="$i18n{aboutGetHelpUsingChromeOs}" external
+          deep-link-focus-id$="[[Setting.kGetHelpWithChromeOs]]">
+      </cr-link-row>
 <if expr="_google_chrome">
-        <div class="secondary" hidden$="[[!shouldShowSafetyInfo_()]]">
-          <a target="_blank" href="$i18n{aboutProductSafetyURL}">
-            $i18nRaw{aboutProductSafety}
-          </a>
-        </div>
+      <cr-link-row class="hr" id="reportIssue"
+          on-click="onReportIssueClick_"
+          hidden="[[!prefs.feedback_allowed.value]]"
+          label="$i18n{aboutReportAnIssue}" external
+          deep-link-focus-id$="[[Setting.kReportAnIssue]]">
+      </cr-link-row>
 </if>
-        <img src="[[regulatoryInfo_.url]]" alt="[[regulatoryInfo_.text]]"
-            hidden$="[[!shouldShowRegulatoryInfo_(regulatoryInfo_)]]">
+      <cr-link-row class="hr" id="diagnostics"
+          on-click="onDiagnosticsClick_"
+          hidden$="[[!showDiagnosticsApp_]]"
+          label="$i18n{aboutDiagnostics}" external
+          deep-link-focus-id$="[[Setting.kDiagnostics]]">
+      </cr-link-row>
+      <cr-link-row class="hr" id="detailed-build-info-trigger"
+          on-click="onDetailedBuildInfoClick_"
+          label="$i18n{aboutDetailedBuildInfo}"
+          role-description="$i18n{subpageArrowRoleDescription}">
+      </cr-link-row>
+      <cr-link-row class="hr" on-click="onManagementPageClick_"
+          start-icon="cr:domain" label="$i18n{managementPage}"
+          hidden$="[[!isManaged_]]" external>
+      </cr-link-row>
+    </div>
+    <template is="dom-if" route-path="/help/details">
+      <settings-subpage page-title="$i18n{aboutDetailedBuildInfo}">
+        <settings-detailed-build-info
+            eol-message-with-month-and-year=
+                "[[eolMessageWithMonthAndYear_]]">
+        </settings-detailed-build-info>
+      </settings-subpage>
+    </template>
+  </settings-animated-pages>
+</settings-section>
+
+<settings-section>
+  <div class="settings-box padded block first">
+    <div class="info-section">
+      <div class="secondary">$i18n{aboutOsProductTitle}</div>
+      <div class="secondary">$i18n{aboutProductCopyright}</div>
+    </div>
+
+    <div class="info-section">
+      <div class="secondary">$i18nRaw{aboutProductLicense}</div>
+      <div class="secondary"
+          inner-h-t-m-l="[[getAboutProductOsLicense_(
+              showCrostiniLicense_)]]">
       </div>
-    </settings-section>
-    <template is="dom-if" if="[[showUpdateWarningDialog_]]" restamp>
-      <settings-update-warning-dialog update-info="[[updateInfo_]]"
-          on-close="onUpdateWarningDialogClose_">
-      </settings-update-warning-dialog>
-    </template>
-    <template is="dom-if" if="[[showTPMFirmwareUpdateDialog_]]"
-        restamp>
-      <os-settings-powerwash-dialog request-tpm-firmware-update
-          on-close="onPowerwashDialogClose_">
-      </os-settings-powerwash-dialog>
-    </template>
-  </template>
-  <script src="os_about_page.js"></script>
-</dom-module>
+    </div>
+<if expr="_google_chrome">
+    <div class="secondary">
+      <a id="aboutProductTos" href="$i18n{aboutTermsURL}" target="_blank"
+          deep-link-focus-id$="[[Setting.kTermsOfService]]">
+        $i18n{aboutProductTos}
+      </a>
+    </div>
+</if>
+  </div>
+  <div class="settings-box padded block" id="regulatoryInfo"
+      hidden$="[[!shouldShowRegulatoryOrSafetyInfo_(regulatoryInfo_)]]">
+<if expr="_google_chrome">
+    <div class="secondary" hidden$="[[!shouldShowSafetyInfo_()]]">
+      <a target="_blank" href="$i18n{aboutProductSafetyURL}">
+        $i18nRaw{aboutProductSafety}
+      </a>
+    </div>
+</if>
+    <img src="[[regulatoryInfo_.url]]" alt="[[regulatoryInfo_.text]]"
+        hidden$="[[!shouldShowRegulatoryInfo_(regulatoryInfo_)]]">
+  </div>
+</settings-section>
+<template is="dom-if" if="[[showUpdateWarningDialog_]]" restamp>
+  <settings-update-warning-dialog update-info="[[updateInfo_]]"
+      on-close="onUpdateWarningDialogClose_">
+  </settings-update-warning-dialog>
+</template>
+<template is="dom-if" if="[[showTPMFirmwareUpdateDialog_]]"
+    restamp>
+  <os-settings-powerwash-dialog request-tpm-firmware-update
+      on-close="onPowerwashDialogClose_">
+  </os-settings-powerwash-dialog>
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js
index 14bf0b5..e9f1b73 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.js
@@ -7,14 +7,48 @@
  * information.
  */
 
+import '../../icons.js';
+import '../../prefs/prefs.js';
+import '../../settings_page/settings_animated_pages.js';
+import '../../settings_page/settings_section.js';
+import '../../settings_page/settings_subpage.js';
+import '../../settings_page_css.js';
+import '../../settings_shared_css.js';
+import '../os_icons.m.js';
+import '../os_reset_page/os_powerwash_dialog.js';
+import '../localized_link/localized_link.js';
+import './detailed_build_info.js';
+import './update_warning_dialog.js';
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
+import '//resources/cr_elements/cr_link_row/cr_link_row.js';
+import '//resources/cr_elements/icons.m.js';
+import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+
+import {assert, assertNotReached} from '//resources/js/assert.m.js';
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {parseHtmlSubset} from '//resources/js/parse_html_subset.m.js';
+import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js';
+import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo, BrowserChannel, browserChannelToI18nId, ChannelInfo, isTargetChannelMoreStable, RegulatoryInfo, TPMFirmwareUpdateStatusChangedEvent, UpdateStatus, UpdateStatusChangedEvent, VersionInfo} from '../../about_page/about_page_browser_proxy.js';
+import {loadTimeData} from '../../i18n_setup.js';
+import {LifetimeBrowserProxyImpl} from '../../lifetime_browser_proxy.js';
+import {Route, RouteObserverBehavior, Router} from '../../router.js';
+import {DeepLinkingBehavior} from '../deep_linking_behavior.m.js';
+import {recordClick, recordNavigation, recordPageBlur, recordPageFocus, recordSearch, recordSettingChange, setUserActionRecorderForTesting} from '../metrics_recorder.m.js';
+import {routes} from '../os_route.m.js';
+import {MainPageBehavior} from '../os_settings_page/main_page_behavior.m.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'os-settings-about-page',
 
   behaviors: [
     DeepLinkingBehavior,
     WebUIListenerBehavior,
-    settings.MainPageBehavior,
-    settings.RouteObserverBehavior,
+    MainPageBehavior,
+    RouteObserverBehavior,
     I18nBehavior,
   ],
 
@@ -127,10 +161,9 @@
       type: Object,
       value() {
         const map = new Map();
-        if (settings.routes.DETAILED_BUILD_INFO) {
+        if (routes.DETAILED_BUILD_INFO) {
           map.set(
-              settings.routes.DETAILED_BUILD_INFO.path,
-              '#detailed-build-info-trigger');
+              routes.DETAILED_BUILD_INFO.path, '#detailed-build-info-trigger');
         }
         return map;
       },
@@ -190,12 +223,12 @@
     'handleCrostiniEnabledChanged_(prefs.crostini.enabled.value)',
   ],
 
-  /** @private {?settings.AboutPageBrowserProxy} */
+  /** @private {?AboutPageBrowserProxy} */
   aboutBrowserProxy_: null,
 
   /** @override */
   attached() {
-    this.aboutBrowserProxy_ = settings.AboutPageBrowserProxyImpl.getInstance();
+    this.aboutBrowserProxy_ = AboutPageBrowserProxyImpl.getInstance();
     this.aboutBrowserProxy_.pageReady();
 
     this.addEventListener('target-channel-changed', e => {
@@ -222,22 +255,21 @@
       this.hasInternetConnection_ = result;
     });
 
-    if (settings.Router.getInstance().getQueryParameters().get(
-            'checkForUpdate') === 'true') {
+    if (Router.getInstance().getQueryParameters().get('checkForUpdate') ===
+        'true') {
       this.onCheckUpdatesClick_();
     }
   },
 
   /**
-   * @param {!settings.Route} newRoute
-   * @param {settings.Route} oldRoute
+   * @param {!Route} newRoute
+   * @param {Route} oldRoute
    */
   currentRouteChanged(newRoute, oldRoute) {
-    settings.MainPageBehavior.currentRouteChanged.call(
-        this, newRoute, oldRoute);
+    MainPageBehavior.currentRouteChanged.call(this, newRoute, oldRoute);
 
     // Does not apply to this page.
-    if (newRoute !== settings.routes.ABOUT_ABOUT) {
+    if (newRoute !== routes.ABOUT_ABOUT) {
       return;
     }
 
@@ -253,9 +285,9 @@
     });
   },
 
-  // Override settings.MainPageBehavior method.
+  // Override MainPageBehavior method.
   containsRoute(route) {
-    return !route || settings.routes.ABOUT.contains(route);
+    return !route || routes.ABOUT.contains(route);
   },
 
   /** @private */
@@ -307,13 +339,13 @@
   onDiagnosticsClick_() {
     assert(this.showDiagnosticsApp_);
     this.aboutBrowserProxy_.openDiagnostics();
-    settings.recordSettingChange(chromeos.settings.mojom.Setting.kDiagnostics);
+    recordSettingChange(chromeos.settings.mojom.Setting.kDiagnostics);
   },
 
   /** @private */
   onRelaunchClick_() {
-    settings.recordSettingChange();
-    settings.LifetimeBrowserProxyImpl.getInstance().relaunch();
+    recordSettingChange();
+    LifetimeBrowserProxyImpl.getInstance().relaunch();
   },
 
   /** @private */
@@ -403,8 +435,8 @@
         if (this.currentChannel_ !== this.targetChannel_) {
           return this.i18nAdvanced('aboutUpgradeUpdatingChannelSwitch', {
             substitutions: [
-              this.i18nAdvanced(settings.browserChannelToI18nId(
-                  this.targetChannel_, this.isLts_)),
+              this.i18nAdvanced(
+                  browserChannelToI18nId(this.targetChannel_, this.isLts_)),
               progressPercent
             ]
           });
@@ -518,8 +550,7 @@
 
   /** @private */
   onDetailedBuildInfoClick_() {
-    settings.Router.getInstance().navigateTo(
-        settings.routes.DETAILED_BUILD_INFO);
+    Router.getInstance().navigateTo(routes.DETAILED_BUILD_INFO);
   },
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.html b/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.html
index 3080573a..bcb0f7d9 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.html
@@ -1,28 +1,15 @@
-<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/html/i18n_behavior.html">
-<link rel="import" href="../../about_page/about_page_browser_proxy.html">
-<link rel="import" href="../../settings_shared_css.html">
-
-<dom-module id="settings-update-warning-dialog">
-  <template>
-    <style include="settings-shared"></style>
-    <cr-dialog id="dialog" close-text="$i18n{close}">
-      <div slot="title">$i18n{aboutUpdateWarningTitle}</div>
-      <div slot="body">
-        <div id="update-warning-message"></div>
-      </div>
-      <div slot="button-container">
-        <cr-button id="cancel" class="cancel-button"
-            on-click="onCancelTap_">$i18n{cancel}</cr-button>
-        <cr-button id="continue" class="action-button"
-            on-click="onContinueTap_">
-          $i18n{continue}
-        </cr-button>
-      </div>
-    </cr-dialog>
-  </template>
-  <script src="update_warning_dialog.js"></script>
-</dom-module>
+<style include="settings-shared"></style>
+<cr-dialog id="dialog" close-text="$i18n{close}">
+  <div slot="title">$i18n{aboutUpdateWarningTitle}</div>
+  <div slot="body">
+    <div id="update-warning-message"></div>
+  </div>
+  <div slot="button-container">
+    <cr-button id="cancel" class="cancel-button"
+        on-click="onCancelTap_">$i18n{cancel}</cr-button>
+    <cr-button id="continue" class="action-button"
+        on-click="onContinueTap_">
+      $i18n{continue}
+    </cr-button>
+  </div>
+</cr-dialog>
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.js b/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.js
index e679770..e0e90a3 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.js
@@ -7,7 +7,17 @@
  * user about update over mobile data. By clicking 'Continue', the user
  * agrees to download update using mobile data.
  */
+import '//resources/cr_elements/cr_button/cr_button.m.js';
+import '//resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import '../../settings_shared_css.js';
+
+import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
+import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, AboutPageUpdateInfo, BrowserChannel, browserChannelToI18nId, ChannelInfo, isTargetChannelMoreStable, RegulatoryInfo, TPMFirmwareUpdateStatusChangedEvent, UpdateStatus, UpdateStatusChangedEvent, VersionInfo} from '../../about_page/about_page_browser_proxy.js';
+
 Polymer({
+  _template: html`{__html_template__}`,
   is: 'settings-update-warning-dialog',
 
   behaviors: [I18nBehavior],
@@ -20,12 +30,12 @@
     },
   },
 
-  /** @private {?settings.AboutPageBrowserProxy} */
+  /** @private {?AboutPageBrowserProxy} */
   browserProxy_: null,
 
   /** @override */
   ready() {
-    this.browserProxy_ = settings.AboutPageBrowserProxyImpl.getInstance();
+    this.browserProxy_ = AboutPageBrowserProxyImpl.getInstance();
   },
 
   /** @override */
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
index 5956dfe01..a34f989 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
@@ -13,6 +13,8 @@
     ":os_bluetooth_devices_subpage",
     ":os_bluetooth_page",
     ":os_bluetooth_summary",
+    ":os_paired_bluetooth_list",
+    ":os_paired_bluetooth_list_item",
   ]
 }
 
@@ -29,7 +31,9 @@
 
 js_library("os_bluetooth_devices_subpage") {
   deps = [
+    ":os_paired_bluetooth_list",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:i18n_behavior.m",
   ]
 }
 
@@ -43,8 +47,24 @@
   ]
 }
 
+js_library("os_paired_bluetooth_list") {
+  deps = [
+    ":os_paired_bluetooth_list_item",
+    "//third_party/polymer/v3_0/components-chromium/iron-list:iron-list",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+  ]
+}
+
+js_library("os_paired_bluetooth_list_item") {
+  deps = [
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+  ]
+}
+
 html_to_js("web_components") {
   js_files = [
+    "os_paired_bluetooth_list_item.js",
+    "os_paired_bluetooth_list.js",
     "os_bluetooth_devices_subpage.js",
     "os_bluetooth_page.js",
     "os_bluetooth_summary.js",
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.html b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.html
index a769441..767e2b0 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.html
@@ -1,5 +1,29 @@
 <style include="settings-shared">
+  #container {
+    padding-inline-end: calc(var(--cr-section-padding) -
+        var(--cr-icon-ripple-padding));
+    padding-inline-start: var(--cr-section-padding);
+  }
+
+  .device-list {
+    margin-inline-start: 32px;
+  }
 </style>
 <div id="container">
-   <!-- TODO(crbug.com/1010321): Add bluetooth devices list -->
+  <!-- TODO(crbug.com/1010321): Add toggle and pair new device button. -->
+  <!-- TODO(crbug.com/1010321): Populate with real data. -->
+  <div class="settings-box-text">
+    $i18n{bluetoothDeviceListCurrentlyConnected}
+  </div>
+  <div class="device-list">
+    <os-settings-paired-bluetooth-list>
+    </os-settings-paired-bluetooth-list>
+  </div>
+  <div class="settings-box-text">
+    $i18n{bluetoothDeviceListPreviouslyConnected}
+  </div>
+  <div class="device-list">
+    <os-settings-paired-bluetooth-list>
+    </os-settings-paired-bluetooth-list>
+  </div>
 </div>
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js
index f6db680..7270b3a 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.js
@@ -8,11 +8,22 @@
  */
 
 import '../../settings_shared_css.js';
+import './os_paired_bluetooth_list.js';
 
-import {html, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {I18nBehavior, I18nBehaviorInterface} from '//resources/js/i18n_behavior.m.js';
+import {html, mixinBehaviors, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ * @implements {I18nBehaviorInterface}
+ */
+const SettingsBluetoothDevicesSubpageElementBase =
+    mixinBehaviors([I18nBehavior], PolymerElement);
 
 /** @polymer */
-class SettingsBluetoothDevicesSubpageElement extends PolymerElement {
+class SettingsBluetoothDevicesSubpageElement extends
+    SettingsBluetoothDevicesSubpageElementBase {
   static get is() {
     return 'os-settings-bluetooth-devices-subpage';
   }
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.html b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.html
new file mode 100644
index 0000000..de4e45a9
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.html
@@ -0,0 +1,17 @@
+<style include="settings-shared">
+  iron-list > *:not(:first-of-type) {
+    border-top: var(--cr-separator-line);
+  }
+</style>
+<!-- TODO(crbug.com/1010321): Populate with real data. -->
+<div id="container" class="layout vertical flex" scrollable
+    no-bottom-scroll-border>
+  <iron-list items="[[devices_]]" scroll-target="container" preserve-focus>
+    <template>
+      <!-- TODO(crbug.com/1010321): Add focus behavior,
+           fix not all elements rendering in large lists. -->
+      <os-settings-paired-bluetooth-list-item>
+      </os-settings-paired-bluetooth-list-item>
+    </template>
+  </iron-list>
+</div>
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.js b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.js
new file mode 100644
index 0000000..086cb74d
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.js
@@ -0,0 +1,43 @@
+// Copyright 2021 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
+ * UI element for displaying paired Bluetooth devices.
+ */
+
+import '../../settings_shared_css.js';
+import './os_paired_bluetooth_list_item.js';
+
+import '//resources/polymer/v3_0/iron-list/iron-list.js';
+import {html, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+/** @polymer */
+class SettingsPairedBluetoothListElement extends PolymerElement {
+  static get is() {
+    return 'os-settings-paired-bluetooth-list';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+  static get properties() {
+    return {
+      /**
+       * TODO(crbug.com/1010321): Use actual Device objects.
+       * @private {Array<Object>}
+       */
+      devices_: {
+        type: Array,
+        value() {
+          return [{}, {}, {}];
+        }
+      }
+    };
+  }
+}
+
+customElements.define(
+    SettingsPairedBluetoothListElement.is, SettingsPairedBluetoothListElement);
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.html b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.html
new file mode 100644
index 0000000..22d9b88
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.html
@@ -0,0 +1,34 @@
+<style include="settings-shared">
+  .battery {
+    color: var(--google-green-500);
+  }
+</style>
+<div focus-row-container>
+  <!-- TODO(crbug.com/1010321): Populate with dynamic data. -->
+  <!-- TODO(crbug.com/1010321): Add a11y label. -->
+  <div class="list-item"
+      focus-row-control
+      focus-type="rowWrapper"
+      role="button"
+      selectable>
+    <!-- TODO(crbug.com/1010321): Add updated device type icon. -->
+    <iron-icon icon="os-settings:headset">
+    </iron-icon>
+    <div class="middle" aria-hidden="true">
+      <div>
+        Device Name
+      </div>
+      <!-- TODO(crbug.com/1010321): Add battery icon. -->
+      <div class="battery secondary">
+        96%
+      </div>
+    </div>
+    <div>
+      <!-- TODO(crbug.com/1010321): Add click listener. -->
+      <cr-icon-button class="subpage-arrow"
+          focus-row-control
+          focus-type="subpageButton">
+      </cr-icon-button>
+    </div>
+  </div>
+</div>
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.js b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.js
new file mode 100644
index 0000000..82f5df18
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.js
@@ -0,0 +1,30 @@
+// Copyright 2021 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
+ * Item in <os-paired_bluetooth-list> that displays information for a paired
+ * Bluetooth device.
+ */
+
+import '../../settings_shared_css.js';
+import '//resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
+import '../os_icons.m.js';
+
+import {html, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+/** @polymer */
+class SettingsPairedBluetoothListItemElement extends PolymerElement {
+  static get is() {
+    return 'os-settings-paired-bluetooth-list-item';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+}
+
+customElements.define(
+    SettingsPairedBluetoothListItemElement.is,
+    SettingsPairedBluetoothListItemElement);
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index 3c325cc..26187c9 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -332,11 +332,19 @@
                                  "chrome/browser/resources/settings/chromeos/internet_page/tether_connection_dialog.html",
                                  "chrome/browser/resources/settings/chromeos/localized_link/localized_link.html",
                                  "chrome/browser/resources/settings/chromeos/on_startup_page/on_startup_page.html",
+                                 "chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.html",
+                                 "chrome/browser/resources/settings/chromeos/os_about_page/detailed_build_info.html",
+                                 "chrome/browser/resources/settings/chromeos/os_about_page/device_name_browser_proxy.html",
+                                 "chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.html",
+                                 "chrome/browser/resources/settings/chromeos/os_about_page/os_about_page.html",
+                                 "chrome/browser/resources/settings/chromeos/os_about_page/update_warning_dialog.html",
                                  "chrome/browser/resources/settings/chromeos/os_a11y_page/change_dictation_locale_dialog.html",
                                  "chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.html",
                                  "chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_page.html",
                                  "chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_devices_subpage.html",
                                  "chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_summary.html",
+                                 "chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.html",
+                                 "chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list_item.html",
                                  "chrome/browser/resources/settings/chromeos/os_files_page/os_files_page.html",
                                  "chrome/browser/resources/settings/chromeos/os_files_page/smb_shares_page.html",
                                  "chrome/browser/resources/settings/chromeos/os_languages_page/add_spellcheck_languages_dialog.html",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js
index 9b874cf..3eb427fb 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -43,10 +43,10 @@
 import './personalization_page/dark_mode_subpage.m.js';
 import './personalization_page/personalization_page.m.js';
 import './os_a11y_page/change_dictation_locale_dialog.js';
-import './os_about_page/channel_switcher_dialog.m.js';
-import './os_about_page/detailed_build_info.m.js';
-import './os_about_page/os_about_page.m.js';
-import './os_about_page/update_warning_dialog.m.js';
+import './os_about_page/channel_switcher_dialog.js';
+import './os_about_page/detailed_build_info.js';
+import './os_about_page/os_about_page.js';
+import './os_about_page/update_warning_dialog.js';
 import './os_apps_page/android_apps_subpage.m.js';
 import './os_apps_page/app_notifications_page/app_notifications_subpage.js';
 import './os_apps_page/app_management_page/app_item.m.js';
@@ -72,6 +72,8 @@
 import './os_bluetooth_page/os_bluetooth_devices_subpage.js';
 import './os_bluetooth_page/os_bluetooth_page.js';
 import './os_bluetooth_page/os_bluetooth_summary.js';
+import './os_bluetooth_page/os_paired_bluetooth_list.js';
+import './os_bluetooth_page/os_paired_bluetooth_list_item.js';
 import './os_icons.m.js';
 import './os_people_page/account_manager.m.js';
 import './os_people_page/os_people_page.m.js';
@@ -120,7 +122,7 @@
 export {OsA11yPageBrowserProxy, OsA11yPageBrowserProxyImpl} from './os_a11y_page/os_a11y_page_browser_proxy.m.js';
 export {SwitchAccessSubpageBrowserProxy, SwitchAccessSubpageBrowserProxyImpl} from './os_a11y_page/switch_access_subpage_browser_proxy.m.js';
 export {TtsSubpageBrowserProxy, TtsSubpageBrowserProxyImpl} from './os_a11y_page/tts_subpage_browser_proxy.m.js';
-export {DeviceNameBrowserProxy, DeviceNameBrowserProxyImpl} from './os_about_page/device_name_browser_proxy.m.js';
+export {DeviceNameBrowserProxy, DeviceNameBrowserProxyImpl} from './os_about_page/device_name_browser_proxy.js';
 export {AndroidAppsBrowserProxyImpl} from './os_apps_page/android_apps_browser_proxy.m.js';
 export {addApp, changeApp, removeApp, updateSelectedAppId} from './os_apps_page/app_management_page/actions.m.js';
 export {BrowserProxy} from './os_apps_page/app_management_page/browser_proxy.m.js';
diff --git a/chrome/browser/resources/settings/extension_control_browser_proxy.ts b/chrome/browser/resources/settings/extension_control_browser_proxy.ts
index d317784..3e330d69 100644
--- a/chrome/browser/resources/settings/extension_control_browser_proxy.ts
+++ b/chrome/browser/resources/settings/extension_control_browser_proxy.ts
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
-
 export interface ExtensionControlBrowserProxy {
   // TODO(dbeam): should be be returning !Promise<boolean> to indicate whether
   // it succeeded?
@@ -21,6 +19,14 @@
   manageExtension(extensionId: string) {
     window.open('chrome://extensions?id=' + extensionId);
   }
+
+  static getInstance(): ExtensionControlBrowserProxy {
+    return instance || (instance = new ExtensionControlBrowserProxyImpl());
+  }
+
+  static setInstance(obj: ExtensionControlBrowserProxy) {
+    instance = obj;
+  }
 }
 
-addSingletonGetter(ExtensionControlBrowserProxyImpl);
+let instance: ExtensionControlBrowserProxy|null = null;
diff --git a/chrome/browser/resources/settings/search_engines_page/BUILD.gn b/chrome/browser/resources/settings/search_engines_page/BUILD.gn
index 93123272..e0761b74 100644
--- a/chrome/browser/resources/settings/search_engines_page/BUILD.gn
+++ b/chrome/browser/resources/settings/search_engines_page/BUILD.gn
@@ -2,84 +2,15 @@
 # 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")
 import("//tools/polymer/html_to_js.gni")
-import("../settings.gni")
-
-js_type_check("closure_compile") {
-  is_polymer3 = true
-  closure_flags = settings_closure_flags
-  deps = [
-    ":omnibox_extension_entry",
-    ":search_engine_dialog",
-    ":search_engine_entry",
-    ":search_engines_browser_proxy",
-    ":search_engines_list",
-    ":search_engines_page",
-  ]
-}
-
-js_library("omnibox_extension_entry") {
-  deps = [
-    ":search_engines_browser_proxy",
-    "..:extension_control_browser_proxy",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js/cr/ui:focus_row_behavior.m",
-  ]
-}
-
-js_library("search_engine_dialog") {
-  deps = [
-    ":search_engines_browser_proxy",
-    "..:i18n_setup",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m",
-    "//ui/webui/resources/js:web_ui_listener_behavior.m",
-  ]
-}
-
-js_library("search_engine_entry") {
-  deps = [
-    ":search_engines_browser_proxy",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js/cr/ui:focus_row_behavior.m",
-  ]
-}
-
-js_library("search_engines_browser_proxy") {
-  deps = [ "//ui/webui/resources/js:cr.m" ]
-}
-
-js_library("search_engines_list") {
-  deps = [
-    ":search_engines_browser_proxy",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-  ]
-}
-
-js_library("search_engines_page") {
-  deps = [
-    ":search_engines_browser_proxy",
-    "..:global_scroll_target_mixin",
-    "..:settings_routes",
-    "//third_party/polymer/v3_0/components-chromium/iron-list",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:web_ui_listener_behavior.m",
-  ]
-}
 
 html_to_js("web_components") {
   js_files = [
-    "omnibox_extension_entry.js",
-    "search_engine_dialog.js",
-    "search_engine_entry_css.js",
-    "search_engine_entry.js",
-    "search_engines_list.js",
-    "search_engines_page.js",
+    "omnibox_extension_entry.ts",
+    "search_engine_dialog.ts",
+    "search_engine_entry_css.ts",
+    "search_engine_entry.ts",
+    "search_engines_list.ts",
+    "search_engines_page.ts",
   ]
 }
diff --git a/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.js b/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.js
deleted file mode 100644
index 440d44f..0000000
--- a/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.js
+++ /dev/null
@@ -1,88 +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.
-
-/**
- * @fileoverview 'settings-omnibox-extension-entry' is a component for showing
- * an omnibox extension with its name and keyword.
- */
-import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
-import 'chrome://resources/cr_elements/icons.m.js';
-import './search_engine_entry_css.js';
-import '../settings_shared_css.js';
-import '../site_favicon.js';
-
-import {AnchorAlignment, CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
-import {assert} from 'chrome://resources/js/assert.m.js';
-import {FocusRowBehavior, FocusRowBehaviorInterface} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-import {ExtensionControlBrowserProxyImpl} from '../extension_control_browser_proxy.js';
-
-import {SearchEngine} from './search_engines_browser_proxy.js';
-
-
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {FocusRowBehaviorInterface}
- */
-const SettingsOmniboxExtensionEntryElementBase =
-    mixinBehaviors([FocusRowBehavior], PolymerElement);
-
-/** @polymer */
-class SettingsOmniboxExtensionEntryElement extends
-    SettingsOmniboxExtensionEntryElementBase {
-  static get is() {
-    return 'settings-omnibox-extension-entry';
-  }
-
-  static get template() {
-    return html`{__html_template__}`;
-  }
-
-  static get properties() {
-    return {
-      /** @type {!SearchEngine} */
-      engine: Object,
-    };
-  }
-
-  /** @override */
-  constructor() {
-    super();
-
-    /** @private {!ExtensionControlBrowserProxyImpl} */
-    this.browserProxy_ = ExtensionControlBrowserProxyImpl.getInstance();
-  }
-
-  /** @private */
-  onManageTap_() {
-    this.closePopupMenu_();
-    this.browserProxy_.manageExtension(this.engine.extension.id);
-  }
-
-  /** @private */
-  onDisableTap_() {
-    this.closePopupMenu_();
-    this.browserProxy_.disableExtension(this.engine.extension.id);
-  }
-
-  /** @private */
-  closePopupMenu_() {
-    this.shadowRoot.querySelector('cr-action-menu').close();
-  }
-
-  /** @private */
-  onDotsTap_() {
-    /** @type {!CrActionMenuElement} */ (
-        this.shadowRoot.querySelector('cr-action-menu'))
-        .showAt(assert(this.shadowRoot.querySelector('cr-icon-button')), {
-          anchorAlignmentY: AnchorAlignment.AFTER_END,
-        });
-  }
-}
-
-customElements.define(
-    SettingsOmniboxExtensionEntryElement.is,
-    SettingsOmniboxExtensionEntryElement);
diff --git a/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.ts b/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.ts
new file mode 100644
index 0000000..2e15701f
--- /dev/null
+++ b/chrome/browser/resources/settings/search_engines_page/omnibox_extension_entry.ts
@@ -0,0 +1,72 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview 'settings-omnibox-extension-entry' is a component for showing
+ * an omnibox extension with its name and keyword.
+ */
+import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
+import 'chrome://resources/cr_elements/icons.m.js';
+import './search_engine_entry_css.js';
+import '../settings_shared_css.js';
+import '../site_favicon.js';
+
+import {AnchorAlignment} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {FocusRowBehavior} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js';
+import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {ExtensionControlBrowserProxy, ExtensionControlBrowserProxyImpl} from '../extension_control_browser_proxy.js';
+
+import {SearchEngine} from './search_engines_browser_proxy.js';
+
+const SettingsOmniboxExtensionEntryElementBase =
+    mixinBehaviors([FocusRowBehavior], PolymerElement) as
+    {new (): PolymerElement & FocusRowBehavior};
+
+class SettingsOmniboxExtensionEntryElement extends
+    SettingsOmniboxExtensionEntryElementBase {
+  static get is() {
+    return 'settings-omnibox-extension-entry';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+  static get properties() {
+    return {
+      engine: Object,
+    };
+  }
+
+  engine: SearchEngine;
+  private browserProxy_: ExtensionControlBrowserProxy =
+      ExtensionControlBrowserProxyImpl.getInstance();
+
+  private onManageTap_() {
+    this.closePopupMenu_();
+    this.browserProxy_.manageExtension(this.engine.extension!.id);
+  }
+
+  private onDisableTap_() {
+    this.closePopupMenu_();
+    this.browserProxy_.disableExtension(this.engine.extension!.id);
+  }
+
+  private closePopupMenu_() {
+    this.shadowRoot!.querySelector('cr-action-menu')!.close();
+  }
+
+  private onDotsTap_() {
+    this.shadowRoot!.querySelector('cr-action-menu')!.showAt(
+        assert(this.shadowRoot!.querySelector('cr-icon-button')!), {
+          anchorAlignmentY: AnchorAlignment.AFTER_END,
+        });
+  }
+}
+
+customElements.define(
+    SettingsOmniboxExtensionEntryElement.is,
+    SettingsOmniboxExtensionEntryElement);
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.js b/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.ts
similarity index 74%
rename from chrome/browser/resources/settings/search_engines_page/search_engine_dialog.js
rename to chrome/browser/resources/settings/search_engines_page/search_engine_dialog.ts
index 6a7cd58..1295670 100644
--- a/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.js
+++ b/chrome/browser/resources/settings/search_engines_page/search_engine_dialog.ts
@@ -10,23 +10,30 @@
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 
-import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
+import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
+import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
+import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
 import {html, microTask, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../i18n_setup.js';
 
 import {SearchEngine, SearchEnginesBrowserProxy, SearchEnginesBrowserProxyImpl, SearchEnginesInfo} from './search_engines_browser_proxy.js';
 
+interface SettingsSearchEngineDialogElement {
+  $: {
+    actionButton: CrButtonElement,
+    dialog: CrDialogElement,
+    keyword: CrInputElement,
+    queryUrl: CrInputElement,
+    searchEngine: CrInputElement,
+  };
+}
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {WebUIListenerBehaviorInterface}
- */
 const SettingsSearchEngineDialogElementBase =
-    mixinBehaviors([WebUIListenerBehavior], PolymerElement);
+    mixinBehaviors([WebUIListenerBehavior], PolymerElement) as
+    {new (): PolymerElement & WebUIListenerBehavior};
 
-/** @polymer */
 class SettingsSearchEngineDialogElement extends
     SettingsSearchEngineDialogElementBase {
   static get is() {
@@ -42,45 +49,38 @@
       /**
        * The search engine to be edited. If not populated a new search engine
        * should be added.
-       * @type {?SearchEngine}
        */
       model: Object,
 
-      /** @private {string} */
       searchEngine_: String,
-
-      /** @private {string} */
       keyword_: String,
-
-      /** @private {string} */
       queryUrl_: String,
-
-      /** @private {string} */
       dialogTitle_: String,
-
-      /** @private {string} */
       actionButtonText_: String,
-
     };
   }
 
-  /** @override */
+  model: SearchEngine|null;
+  private searchEngine_: string;
+  private keyword_: string;
+  private queryUrl_: string;
+  private dialogTitle_: string;
+  private actionButtonText_: string;
+  private browserProxy_: SearchEnginesBrowserProxy =
+      SearchEnginesBrowserProxyImpl.getInstance();
+  DEFAULT_MODEL_INDEX: number;
+
   constructor() {
     super();
 
-    /** @private {SearchEnginesBrowserProxy} */
-    this.browserProxy_ = SearchEnginesBrowserProxyImpl.getInstance();
-
     /**
      * The |modelIndex| to use when a new search engine is added. Must match
      * with kNewSearchEngineIndex constant specified at
      * chrome/browser/ui/webui/settings/search_engines_handler.cc
-     * @type {number}
      */
     this.DEFAULT_MODEL_INDEX = -1;
   }
 
-  /** @override */
   ready() {
     super.ready();
 
@@ -107,7 +107,6 @@
         'search-engines-changed', this.enginesChanged_.bind(this));
   }
 
-  /** @override */
   connectedCallback() {
     super.connectedCallback();
 
@@ -117,16 +116,12 @@
     this.$.dialog.showModal();
   }
 
-  /**
-   * @param {!SearchEnginesInfo} searchEnginesInfo
-   * @private
-   */
-  enginesChanged_(searchEnginesInfo) {
+  private enginesChanged_(searchEnginesInfo: SearchEnginesInfo) {
     if (this.model) {
       const engineWasRemoved =
           ['defaults', 'actives', 'others', 'extensions'].every(
               engineType => searchEnginesInfo[engineType].every(
-                  e => e.id !== this.model.id));
+                  e => e.id !== this.model!.id));
       if (engineWasRemoved) {
         this.cancel_();
         return;
@@ -137,23 +132,17 @@
         element => this.validateElement_(element));
   }
 
-  /** @private */
-  cancel_() {
-    /** @type {!CrDialogElement} */ (this.$.dialog).cancel();
+  private cancel_() {
+    this.$.dialog.cancel();
   }
 
-  /** @private */
-  onActionButtonTap_() {
+  private onActionButtonTap_() {
     this.browserProxy_.searchEngineEditCompleted(
         this.searchEngine_, this.keyword_, this.queryUrl_);
     this.$.dialog.close();
   }
 
-  /**
-   * @param {!Element} inputElement
-   * @private
-   */
-  validateElement_(inputElement) {
+  private validateElement_(inputElement: CrInputElement) {
     // If element is empty, disable the action button, but don't show the red
     // invalid message.
     if (inputElement.value === '') {
@@ -170,17 +159,12 @@
         });
   }
 
-  /**
-   * @param {!Event} event
-   * @private
-   */
-  validate_(event) {
-    const inputElement = /** @type {!Element} */ (event.target);
+  private validate_(event: Event) {
+    const inputElement = event.target as CrInputElement;
     this.validateElement_(inputElement);
   }
 
-  /** @private */
-  updateActionButtonState_() {
+  private updateActionButtonState_() {
     const allValid = [
       this.$.searchEngine, this.$.keyword, this.$.queryUrl
     ].every(function(inputElement) {
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engine_entry.js b/chrome/browser/resources/settings/search_engines_page/search_engine_entry.ts
similarity index 61%
rename from chrome/browser/resources/settings/search_engines_page/search_engine_entry.js
rename to chrome/browser/resources/settings/search_engines_page/search_engine_entry.ts
index 613d3b4..ab954b7c 100644
--- a/chrome/browser/resources/settings/search_engines_page/search_engine_entry.js
+++ b/chrome/browser/resources/settings/search_engines_page/search_engine_entry.ts
@@ -13,23 +13,17 @@
 import '../settings_shared_css.js';
 import '../site_favicon.js';
 
-import {AnchorAlignment, CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
+import {AnchorAlignment} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {FocusRowBehavior, FocusRowBehaviorInterface} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js';
+import {FocusRowBehavior} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {SearchEngine, SearchEnginesBrowserProxy, SearchEnginesBrowserProxyImpl} from './search_engines_browser_proxy.js';
 
-
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {FocusRowBehaviorInterface}
- */
 const SettingsSearchEngineEntryElementBase =
-    mixinBehaviors([FocusRowBehavior], PolymerElement);
+    mixinBehaviors([FocusRowBehavior], PolymerElement) as
+    {new (): PolymerElement & FocusRowBehavior};
 
-/** @polymer */
 class SettingsSearchEngineEntryElement extends
     SettingsSearchEngineEntryElementBase {
   static get is() {
@@ -42,10 +36,8 @@
 
   static get properties() {
     return {
-      /** @type {!SearchEngine} */
       engine: Object,
 
-      /** @type {boolean} */
       isDefault: {
         reflectToAttribute: true,
         type: Boolean,
@@ -55,47 +47,32 @@
     };
   }
 
-  /** @override */
-  constructor() {
-    super();
+  engine: SearchEngine;
+  isDefault: boolean;
+  private browserProxy_: SearchEnginesBrowserProxy =
+      SearchEnginesBrowserProxyImpl.getInstance();
 
-    /** @private {!SearchEnginesBrowserProxy} */
-    this.browserProxy_ = SearchEnginesBrowserProxyImpl.getInstance();
+  private closePopupMenu_() {
+    this.shadowRoot!.querySelector('cr-action-menu')!.close();
   }
 
-  /** @private */
-  closePopupMenu_() {
-    this.shadowRoot.querySelector('cr-action-menu').close();
-  }
-
-  /**
-   * @return {boolean}
-   * @private
-   */
-  computeIsDefault_() {
+  private computeIsDefault_(): boolean {
     return this.engine.default;
   }
 
-  /** @private */
-  onDeleteTap_() {
+  private onDeleteTap_() {
     this.browserProxy_.removeSearchEngine(this.engine.modelIndex);
     this.closePopupMenu_();
   }
 
-  /** @private */
-  onDotsTap_() {
-    /** @type {!CrActionMenuElement} */ (
-        this.shadowRoot.querySelector('cr-action-menu'))
-        .showAt(assert(this.shadowRoot.querySelector('cr-icon-button')), {
+  private onDotsTap_() {
+    this.shadowRoot!.querySelector('cr-action-menu')!.showAt(
+        assert(this.shadowRoot!.querySelector('cr-icon-button')!), {
           anchorAlignmentY: AnchorAlignment.AFTER_END,
         });
   }
 
-  /**
-   * @param {!Event} e
-   * @private
-   */
-  onEditTap_(e) {
+  private onEditTap_(e: Event) {
     e.preventDefault();
     this.closePopupMenu_();
     this.dispatchEvent(new CustomEvent('edit-search-engine', {
@@ -103,13 +80,13 @@
       composed: true,
       detail: {
         engine: this.engine,
-        anchorElement: assert(this.shadowRoot.querySelector('cr-icon-button')),
+        anchorElement:
+            assert(this.shadowRoot!.querySelector('cr-icon-button')!),
       },
     }));
   }
 
-  /** @private */
-  onMakeDefaultTap_() {
+  private onMakeDefaultTap_() {
     this.closePopupMenu_();
     this.browserProxy_.setDefaultSearchEngine(this.engine.modelIndex);
   }
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.js b/chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.ts
similarity index 96%
rename from chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.js
rename to chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.ts
index ffec81b..1b80523e 100644
--- a/chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.js
+++ b/chrome/browser/resources/settings/search_engines_page/search_engine_entry_css.ts
@@ -7,3 +7,4 @@
 <dom-module id="search-engine-entry">{__html_template__}</dom-module>
 `;
 document.body.appendChild(template.content.cloneNode(true));
+export {};
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.js b/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.js
deleted file mode 100644
index 6c99744..0000000
--- a/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.js
+++ /dev/null
@@ -1,123 +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.
-
-// clang-format off
-import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
-// clang-format on
-
-/**
- * @fileoverview A helper object used from the "Manage search engines" section
- * to interact with the browser.
- */
-
-/**
- * @typedef {{canBeDefault: boolean,
- *            canBeEdited: boolean,
- *            canBeRemoved: boolean,
- *            default: boolean,
- *            displayName: string,
- *            extension: ({id: string,
- *                         name: string,
- *                         canBeDisabled: boolean,
- *                         icon: string}|undefined),
- *            iconURL: (string|undefined),
- *            id: number,
- *            isOmniboxExtension: boolean,
- *            keyword: string,
- *            modelIndex: number,
- *            name: string,
- *            url: string,
- *            urlLocked: boolean}}
- * @see chrome/browser/ui/webui/settings/search_engine_manager_handler.cc
- */
-export let SearchEngine;
-
-/**
- * @typedef {{
- *   defaults: !Array<!SearchEngine>,
- *   actives: !Array<!SearchEngine>,
- *   others: !Array<!SearchEngine>,
- *   extensions: !Array<!SearchEngine>
- * }}
- */
-export let SearchEnginesInfo;
-
-/** @interface */
-export class SearchEnginesBrowserProxy {
-  /** @param {number} modelIndex */
-  setDefaultSearchEngine(modelIndex) {}
-
-  /** @param {number} modelIndex */
-  removeSearchEngine(modelIndex) {}
-
-  /** @param {number} modelIndex */
-  searchEngineEditStarted(modelIndex) {}
-
-  searchEngineEditCancelled() {}
-
-  /**
-   * @param {string} searchEngine
-   * @param {string} keyword
-   * @param {string} queryUrl
-   */
-  searchEngineEditCompleted(searchEngine, keyword, queryUrl) {}
-
-  /** @return {!Promise<!SearchEnginesInfo>} */
-  getSearchEnginesList() {}
-
-  /**
-   * @param {string} fieldName
-   * @param {string} fieldValue
-   * @return {!Promise<boolean>}
-   */
-  validateSearchEngineInput(fieldName, fieldValue) {}
-}
-
-/**
- * @implements {SearchEnginesBrowserProxy}
- */
-export class SearchEnginesBrowserProxyImpl {
-  /** @override */
-  setDefaultSearchEngine(modelIndex) {
-    chrome.send('setDefaultSearchEngine', [modelIndex]);
-  }
-
-  /** @override */
-  removeSearchEngine(modelIndex) {
-    chrome.send('removeSearchEngine', [modelIndex]);
-  }
-
-  /** @override */
-  searchEngineEditStarted(modelIndex) {
-    chrome.send('searchEngineEditStarted', [modelIndex]);
-  }
-
-  /** @override */
-  searchEngineEditCancelled() {
-    chrome.send('searchEngineEditCancelled');
-  }
-
-  /** @override */
-  searchEngineEditCompleted(searchEngine, keyword, queryUrl) {
-    chrome.send('searchEngineEditCompleted', [
-      searchEngine,
-      keyword,
-      queryUrl,
-    ]);
-  }
-
-  /** @override */
-  getSearchEnginesList() {
-    return sendWithPromise('getSearchEnginesList');
-  }
-
-  /** @override */
-  validateSearchEngineInput(fieldName, fieldValue) {
-    return sendWithPromise('validateSearchEngineInput', fieldName, fieldValue);
-  }
-}
-
-  // The singleton instance_ is replaced with a test version of this wrapper
-  // during testing.
-addSingletonGetter(SearchEnginesBrowserProxyImpl);
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.ts b/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.ts
new file mode 100644
index 0000000..fec9a57
--- /dev/null
+++ b/chrome/browser/resources/settings/search_engines_page/search_engines_browser_proxy.ts
@@ -0,0 +1,104 @@
+// 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.
+
+// clang-format off
+import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
+// clang-format on
+
+/**
+ * @fileoverview A helper object used from the "Manage search engines" section
+ * to interact with the browser.
+ */
+
+/**
+ * @see chrome/browser/ui/webui/settings/search_engine_manager_handler.cc
+ */
+export type SearchEngine = {
+  canBeDefault: boolean,
+  canBeEdited: boolean,
+  canBeRemoved: boolean,
+  default: boolean,
+  displayName: string,
+  extension?: {id: string, name: string, canBeDisabled: boolean, icon: string},
+  iconURL?: string,
+  id: number,
+  isOmniboxExtension: boolean,
+  keyword: string,
+  modelIndex: number,
+  name: string,
+  url: string,
+  urlLocked: boolean,
+};
+
+export type SearchEnginesInfo = {
+  defaults: Array<SearchEngine>,
+  actives: Array<SearchEngine>,
+  others: Array<SearchEngine>,
+  extensions: Array<SearchEngine>,
+  [key: string]: Array<SearchEngine>,
+};
+
+export interface SearchEnginesBrowserProxy {
+  setDefaultSearchEngine(modelIndex: number): void;
+
+  removeSearchEngine(modelIndex: number): void;
+
+  searchEngineEditStarted(modelIndex: number): void;
+
+  searchEngineEditCancelled(): void;
+
+  searchEngineEditCompleted(
+      searchEngine: string, keyword: string, queryUrl: string): void;
+
+  getSearchEnginesList(): Promise<SearchEnginesInfo>;
+
+  validateSearchEngineInput(fieldName: string, fieldValue: string):
+      Promise<boolean>
+}
+
+export class SearchEnginesBrowserProxyImpl implements
+    SearchEnginesBrowserProxy {
+  setDefaultSearchEngine(modelIndex: number) {
+    chrome.send('setDefaultSearchEngine', [modelIndex]);
+  }
+
+  removeSearchEngine(modelIndex: number) {
+    chrome.send('removeSearchEngine', [modelIndex]);
+  }
+
+  searchEngineEditStarted(modelIndex: number) {
+    chrome.send('searchEngineEditStarted', [modelIndex]);
+  }
+
+  searchEngineEditCancelled() {
+    chrome.send('searchEngineEditCancelled');
+  }
+
+  searchEngineEditCompleted(
+      searchEngine: string, keyword: string, queryUrl: string) {
+    chrome.send('searchEngineEditCompleted', [
+      searchEngine,
+      keyword,
+      queryUrl,
+    ]);
+  }
+
+  getSearchEnginesList() {
+    return sendWithPromise('getSearchEnginesList');
+  }
+
+  validateSearchEngineInput(fieldName: string, fieldValue: string) {
+    return sendWithPromise('validateSearchEngineInput', fieldName, fieldValue);
+  }
+
+  static getInstance(): SearchEnginesBrowserProxy {
+    return instance || (instance = new SearchEnginesBrowserProxyImpl());
+  }
+
+  static setInstance(obj: SearchEnginesBrowserProxy) {
+    instance = obj;
+  }
+}
+
+let instance: SearchEnginesBrowserProxy|null = null;
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engines_list.js b/chrome/browser/resources/settings/search_engines_page/search_engines_list.ts
similarity index 83%
rename from chrome/browser/resources/settings/search_engines_page/search_engines_list.js
rename to chrome/browser/resources/settings/search_engines_page/search_engines_list.ts
index 127f5cb..d7100d31 100644
--- a/chrome/browser/resources/settings/search_engines_page/search_engines_list.js
+++ b/chrome/browser/resources/settings/search_engines_page/search_engines_list.ts
@@ -16,9 +16,7 @@
 
 import {SearchEngine} from './search_engines_browser_proxy.js';
 
-
-/** @polymer */
-class SettingsSearchEnginesListElement extends PolymerElement {
+export class SettingsSearchEnginesListElement extends PolymerElement {
   static get is() {
     return 'settings-search-engines-list';
   }
@@ -29,22 +27,18 @@
 
   static get properties() {
     return {
-      /** @type {!Array<!SearchEngine>} */
       engines: Array,
 
       /**
        * The scroll target that this list should use.
-       * @type {?HTMLElement}
        */
       scrollTarget: Object,
 
       /** Used to fix scrolling glitch when list is not top most element. */
       scrollOffset: Number,
 
-      /** @private {Object}*/
       lastFocused_: Object,
 
-      /** @private */
       listBlurred_: Boolean,
 
       fixedHeight: {
@@ -52,10 +46,16 @@
         value: false,
         reflectToAttribute: true,
       },
-
     };
   }
+
+  engines: Array<SearchEngine>;
+  scrollTarget: HTMLElement|null;
+  scrollOffset: number;
+  fixedHeight: boolean;
+  private lastFocused_: HTMLElement;
+  private listBlurred_: boolean;
 }
 
 customElements.define(
-    SettingsSearchEnginesListElement.is, SettingsSearchEnginesListElement);
\ No newline at end of file
+    SettingsSearchEnginesListElement.is, SettingsSearchEnginesListElement);
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engines_page.js b/chrome/browser/resources/settings/search_engines_page/search_engines_page.ts
similarity index 68%
rename from chrome/browser/resources/settings/search_engines_page/search_engines_page.js
rename to chrome/browser/resources/settings/search_engines_page/search_engines_page.ts
index 9fd6e7d0..a08d6397 100644
--- a/chrome/browser/resources/settings/search_engines_page/search_engines_page.js
+++ b/chrome/browser/resources/settings/search_engines_page/search_engines_page.ts
@@ -21,7 +21,8 @@
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
-import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
+import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
+import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import {afterNextRender, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {GlobalScrollTargetMixin} from '../global_scroll_target_mixin.js';
@@ -29,25 +30,26 @@
 import {routes} from '../route.js';
 
 import {SearchEngine, SearchEnginesBrowserProxyImpl, SearchEnginesInfo} from './search_engines_browser_proxy.js';
+import {SettingsSearchEnginesListElement} from './search_engines_list.js';
 
-/**
- * @typedef {!CustomEvent<!{
- *     engine: !SearchEngine,
- *     anchorElement: !HTMLElement
- * }>}
- */
-let SearchEngineEditEvent;
+type SearchEngineEditEvent = CustomEvent<{
+  engine: SearchEngine,
+  anchorElement: HTMLElement,
+}>;
 
+interface SettingsSearchEnginesPageElement {
+  $: {
+    addSearchEngine: HTMLElement,
+    extensions: IronListElement,
+    otherEngines: SettingsSearchEnginesListElement,
+  };
+}
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {WebUIListenerBehaviorInterface}
- */
-const SettingsSearchEnginesPageElementBase = mixinBehaviors(
-    [WebUIListenerBehavior], GlobalScrollTargetMixin(PolymerElement));
+const SettingsSearchEnginesPageElementBase =
+    mixinBehaviors(
+        [WebUIListenerBehavior], GlobalScrollTargetMixin(PolymerElement)) as
+    {new (): PolymerElement & WebUIListenerBehavior};
 
-/** @polymer */
 class SettingsSearchEnginesPageElement extends
     SettingsSearchEnginesPageElementBase {
   static get is() {
@@ -68,28 +70,19 @@
         notify: true,
       },
 
-      /** @type {!Array<!SearchEngine>} */
       defaultEngines: Array,
-
-      /** @type {!Array<!SearchEngine>} */
       activeEngines: Array,
-
-      /** @type {!Array<!SearchEngine>} */
       otherEngines: Array,
-
-      /** @type {!Array<!SearchEngine>} */
       extensions: Array,
 
       /**
        * Needed by GlobalScrollTargetMixin.
-       * @override
        */
       subpageRoute: {
         type: Object,
         value: routes.SEARCH_ENGINES,
       },
 
-      /** @private {boolean} */
       showExtensionsList_: {
         type: Boolean,
         computed: 'computeShowExtensionsList_(extensions)',
@@ -101,66 +94,53 @@
         value: '',
       },
 
-      /** @private {!Array<!SearchEngine>} */
       matchingDefaultEngines_: {
         type: Array,
         computed: 'computeMatchingEngines_(defaultEngines, filter)',
       },
 
-      /** @private {!Array<!SearchEngine>} */
       matchingActiveEngines_: {
         type: Array,
         computed: 'computeMatchingEngines_(activeEngines, filter)',
       },
 
-      /** @private {!Array<!SearchEngine>} */
       matchingOtherEngines_: {
         type: Array,
         computed: 'computeMatchingEngines_(otherEngines, filter)',
       },
 
-      /** @private {!Array<!SearchEngine>} */
       matchingExtensions_: {
         type: Array,
         computed: 'computeMatchingEngines_(extensions, filter)',
       },
 
-      /** @private {HTMLElement} */
       omniboxExtensionlastFocused_: Object,
-
-      /** @private {boolean} */
       omniboxExtensionListBlurred_: Boolean,
 
-      /** @private {?SearchEngine} */
       dialogModel_: {
         type: Object,
         value: null,
       },
 
-      /** @private {?HTMLElement} */
       dialogAnchorElement_: {
         type: Object,
         value: null,
       },
 
-      /** @private */
       showDialog_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private */
       showKeywordTriggerSetting_: {
         type: Boolean,
         value: () => loadTimeData.getBoolean('showKeywordTriggerSetting'),
       },
 
-      /** @private */
       showActiveSearchEngines_: {
         type: Boolean,
         value: () => loadTimeData.getBoolean('showActiveSearchEngines'),
       },
-
     };
   }
 
@@ -168,7 +148,24 @@
     return ['extensionsChanged_(extensions, showExtensionsList_)'];
   }
 
-  /** @override */
+  defaultEngines: Array<SearchEngine>;
+  activeEngines: Array<SearchEngine>;
+  otherEngines: Array<SearchEngine>;
+  extensions: Array<SearchEngine>;
+  private showExtensionsList_: boolean;
+  filter: string;
+  private matchingDefaultEngines_: Array<SearchEngine>;
+  private matchingActiveEngines_: Array<SearchEngine>;
+  private matchingOtherEngines_: Array<SearchEngine>;
+  private matchingExtensions_: Array<SearchEngine>;
+  private omniboxExtensionlastFocused_: HTMLElement;
+  private omniboxExtensionListBlurred_: boolean;
+  private dialogModel_: SearchEngine|null;
+  private dialogAnchorElement_: HTMLElement|null;
+  private showDialog_: boolean;
+  private showKeywordTriggerSetting_: boolean;
+  private showActiveSearchEngines_: boolean;
+
   ready() {
     super.ready();
 
@@ -178,56 +175,40 @@
         'search-engines-changed', this.enginesChanged_.bind(this));
 
     // Sets offset in iron-list that uses the page as a scrollTarget.
-    afterNextRender(this, function() {
+    afterNextRender(this, () => {
       this.$.otherEngines.scrollOffset = this.$.otherEngines.offsetTop;
     });
 
     this.addEventListener(
         'edit-search-engine',
-        e => this.onEditSearchEngine_(
-            /** @type {!SearchEngineEditEvent} */ (e)));
+        e => this.onEditSearchEngine_(e as SearchEngineEditEvent));
   }
 
-  /**
-   * @param {?SearchEngine} searchEngine
-   * @param {!HTMLElement} anchorElement
-   * @private
-   */
-  openDialog_(searchEngine, anchorElement) {
+  private openDialog_(
+      searchEngine: SearchEngine|null, anchorElement: HTMLElement) {
     this.dialogModel_ = searchEngine;
     this.dialogAnchorElement_ = anchorElement;
     this.showDialog_ = true;
   }
 
-  /** @private */
-  onCloseDialog_() {
+  private onCloseDialog_() {
     this.showDialog_ = false;
-    const anchor = /** @type {!HTMLElement} */ (this.dialogAnchorElement_);
-    focusWithoutInk(anchor);
+    focusWithoutInk(this.dialogAnchorElement_ as HTMLElement);
     this.dialogModel_ = null;
     this.dialogAnchorElement_ = null;
   }
 
-  /**
-   * @param {!SearchEngineEditEvent} e
-   * @private
-   */
-  onEditSearchEngine_(e) {
+  private onEditSearchEngine_(e: SearchEngineEditEvent) {
     this.openDialog_(e.detail.engine, e.detail.anchorElement);
   }
 
-  /** @private */
-  extensionsChanged_() {
+  private extensionsChanged_() {
     if (this.showExtensionsList_ && this.$.extensions) {
-      /** @type {!IronListElement} */ (this.$.extensions).notifyResize();
+      this.$.extensions.notifyResize();
     }
   }
 
-  /**
-   * @param {!SearchEnginesInfo} searchEnginesInfo
-   * @private
-   */
-  enginesChanged_(searchEnginesInfo) {
+  private enginesChanged_(searchEnginesInfo: SearchEnginesInfo) {
     this.defaultEngines = searchEnginesInfo.defaults;
 
     // Sort |activeEngines| and |otherEngines| in alphabetical order.
@@ -241,28 +222,20 @@
     this.extensions = searchEnginesInfo.extensions;
   }
 
-  /**
-   * @param {!Event} e
-   * @private
-   */
-  onAddSearchEngineTap_(e) {
+  private onAddSearchEngineTap_(e: Event) {
     e.preventDefault();
-    this.openDialog_(
-        null, assert(/** @type {HTMLElement} */ (this.$.addSearchEngine)));
+    this.openDialog_(null, this.$.addSearchEngine);
   }
 
-  /** @private */
-  computeShowExtensionsList_() {
+  private computeShowExtensionsList_(): boolean {
     return this.extensions.length > 0;
   }
 
   /**
    * Filters the given list based on the currently existing filter string.
-   * @param {!Array<!SearchEngine>} list
-   * @return {!Array<!SearchEngine>}
-   * @private
    */
-  computeMatchingEngines_(list) {
+  private computeMatchingEngines_(list: Array<SearchEngine>):
+      Array<SearchEngine> {
     if (this.filter === '') {
       return list;
     }
@@ -275,12 +248,12 @@
   }
 
   /**
-   * @param {!Array<!SearchEngine>} list The original list.
-   * @param {!Array<!SearchEngine>} filteredList The filtered list.
-   * @return {boolean} Whether to show the "no results" message.
-   * @private
+   * @param list The original list.
+   * @param filteredList The filtered list.
+   * @return Whether to show the "no results" message.
    */
-  showNoResultsMessage_(list, filteredList) {
+  private showNoResultsMessage_(
+      list: Array<SearchEngine>, filteredList: Array<SearchEngine>): boolean {
     return list.length > 0 && filteredList.length === 0;
   }
 }
diff --git a/chrome/browser/resources/settings/search_page/BUILD.gn b/chrome/browser/resources/settings/search_page/BUILD.gn
index a9d3bdc9..9d88e41 100644
--- a/chrome/browser/resources/settings/search_page/BUILD.gn
+++ b/chrome/browser/resources/settings/search_page/BUILD.gn
@@ -2,28 +2,8 @@
 # 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")
 import("//tools/polymer/html_to_js.gni")
-import("../settings.gni")
-
-js_type_check("closure_compile") {
-  is_polymer3 = true
-  closure_flags = settings_closure_flags
-  deps = [ ":search_page" ]
-}
-
-js_library("search_page") {
-  deps = [
-    "..:base_mixin",
-    "..:route",
-    "..:router",
-    "../search_engines_page:search_engines_browser_proxy",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:cr.m",
-  ]
-  externs_list = [ "$externs_path/settings_private.js" ]
-}
 
 html_to_js("web_components") {
-  js_files = [ "search_page.js" ]
+  js_files = [ "search_page.ts" ]
 }
diff --git a/chrome/browser/resources/settings/search_page/search_page.js b/chrome/browser/resources/settings/search_page/search_page.ts
similarity index 74%
rename from chrome/browser/resources/settings/search_page/search_page.js
rename to chrome/browser/resources/settings/search_page/search_page.ts
index 20f2337..b227481a8 100644
--- a/chrome/browser/resources/settings/search_page/search_page.js
+++ b/chrome/browser/resources/settings/search_page/search_page.ts
@@ -24,7 +24,7 @@
 import {BaseMixin, BaseMixinInterface} from '../base_mixin.js';
 import {routes} from '../route.js';
 import {Router} from '../router.js';
-import {SearchEngine, SearchEnginesBrowserProxy, SearchEnginesBrowserProxyImpl} from '../search_engines_page/search_engines_browser_proxy.js';
+import {SearchEngine, SearchEnginesBrowserProxy, SearchEnginesBrowserProxyImpl, SearchEnginesInfo} from '../search_engines_page/search_engines_browser_proxy.js';
 
 
 /**
@@ -33,7 +33,8 @@
  * @appliesMixin {BaseMixin}
  * @implements {BaseMixinInterface}
  */
-const SettingsSearchPageElementBase = BaseMixin(PolymerElement);
+const SettingsSearchPageElementBase =
+    BaseMixin(PolymerElement) as unknown as {new (): PolymerElement};
 
 /** @polymer */
 export class SettingsSearchPageElement extends SettingsSearchPageElementBase {
@@ -51,7 +52,6 @@
 
       /**
        * List of default search engines available.
-       * @private {!Array<!SearchEngine>}
        */
       searchEngines_: {
         type: Array,
@@ -60,27 +60,25 @@
         }
       },
 
-      /** @private Filter applied to search engines. */
+      /** Filter applied to search engines. */
       searchEnginesFilter_: String,
 
-      /** @type {?Map<string, string>} */
       focusConfig_: Object,
     };
   }
 
-  constructor() {
-    super();
-
-    /** @private {!SearchEnginesBrowserProxy} */
-    this.browserProxy_ = SearchEnginesBrowserProxyImpl.getInstance();
-  }
+  private searchEngines_: Array<SearchEngine>;
+  private searchEnginesFilter_: string;
+  private focusConfig_: Map<string, string>|null;
+  private browserProxy_: SearchEnginesBrowserProxy =
+      SearchEnginesBrowserProxyImpl.getInstance();
 
   /** @override */
   ready() {
     super.ready();
 
     // Omnibox search engine
-    const updateSearchEngines = searchEngines => {
+    const updateSearchEngines = (searchEngines: SearchEnginesInfo) => {
       this.set('searchEngines_', searchEngines.defaults);
     };
     this.browserProxy_.getSearchEnginesList().then(updateSearchEngines);
@@ -93,16 +91,13 @@
     }
   }
 
-  /** @private */
-  onChange_() {
-    const select = /** @type {!HTMLSelectElement} */ (
-        this.shadowRoot.querySelector('select'));
+  private onChange_() {
+    const select = this.shadowRoot!.querySelector('select')!;
     const searchEngine = this.searchEngines_[select.selectedIndex];
     this.browserProxy_.setDefaultSearchEngine(searchEngine.modelIndex);
   }
 
-  /** @private */
-  onDisableExtension_() {
+  private onDisableExtension_() {
     this.dispatchEvent(new CustomEvent('refresh-pref', {
       bubbles: true,
       composed: true,
@@ -110,27 +105,18 @@
     }));
   }
 
-  /** @private */
-  onManageSearchEnginesTap_() {
+  private onManageSearchEnginesTap_() {
     Router.getInstance().navigateTo(routes.SEARCH_ENGINES);
   }
 
-  /**
-   * @param {!chrome.settingsPrivate.PrefObject} pref
-   * @return {boolean}
-   * @private
-   */
-  isDefaultSearchControlledByPolicy_(pref) {
+  private isDefaultSearchControlledByPolicy_(
+      pref: chrome.settingsPrivate.PrefObject): boolean {
     return pref.controlledBy ===
         chrome.settingsPrivate.ControlledBy.USER_POLICY;
   }
 
-  /**
-   * @param {!chrome.settingsPrivate.PrefObject} pref
-   * @return {boolean}
-   * @private
-   */
-  isDefaultSearchEngineEnforced_(pref) {
+  private isDefaultSearchEngineEnforced_(
+      pref: chrome.settingsPrivate.PrefObject): boolean {
     return pref.enforcement === chrome.settingsPrivate.Enforcement.ENFORCED;
   }
 }
diff --git a/chrome/browser/resources/settings/settings_page_css.ts b/chrome/browser/resources/settings/settings_page_css.ts
index ddfa4807..a498575e 100644
--- a/chrome/browser/resources/settings/settings_page_css.ts
+++ b/chrome/browser/resources/settings/settings_page_css.ts
@@ -7,3 +7,4 @@
 <dom-module id="settings-page-styles">{__html_template__}</dom-module>
 `;
 document.body.appendChild(template.content.cloneNode(true));
+export {};
diff --git a/chrome/browser/safe_browsing/download_protection/file_analyzer.cc b/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
index 6146346..f88d9e52 100644
--- a/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
+++ b/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
@@ -239,7 +239,6 @@
                           weakptr_factory_.GetWeakPtr()),
       LaunchFileUtilService());
   dmg_analyzer_->Start();
-  dmg_analysis_start_time_ = base::TimeTicks::Now();
 }
 
 void FileAnalyzer::ExtractFileOrDmgFeatures(
@@ -278,9 +277,6 @@
     results_.type = ClientDownloadRequest::MAC_ARCHIVE_FAILED_PARSING;
   }
 
-  UMA_HISTOGRAM_MEDIUM_TIMES("SBClientDownload.ExtractDmgFeaturesTimeMedium",
-                             base::TimeTicks::Now() - dmg_analysis_start_time_);
-
   std::move(callback_).Run(std::move(results_));
 }
 #endif  // defined(OS_MAC)
diff --git a/chrome/browser/safe_browsing/download_protection/file_analyzer.h b/chrome/browser/safe_browsing/download_protection/file_analyzer.h
index 37dbffe..939fd40 100644
--- a/chrome/browser/safe_browsing/download_protection/file_analyzer.h
+++ b/chrome/browser/safe_browsing/download_protection/file_analyzer.h
@@ -117,7 +117,6 @@
 
 #if defined(OS_MAC)
   scoped_refptr<SandboxedDMGAnalyzer> dmg_analyzer_;
-  base::TimeTicks dmg_analysis_start_time_;
 #endif
 
   base::WeakPtrFactory<FileAnalyzer> weakptr_factory_{this};
diff --git a/chrome/browser/sharesheet/drive_share_action.cc b/chrome/browser/sharesheet/drive_share_action.cc
index 3defccf..61729e1 100644
--- a/chrome/browser/sharesheet/drive_share_action.cc
+++ b/chrome/browser/sharesheet/drive_share_action.cc
@@ -9,6 +9,8 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sharesheet/sharesheet_controller.h"
 #include "chrome/browser/sharesheet/sharesheet_types.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
@@ -21,7 +23,7 @@
 
 namespace sharesheet {
 
-DriveShareAction::DriveShareAction() = default;
+DriveShareAction::DriveShareAction(Profile* profile) : profile_(profile) {}
 
 DriveShareAction::~DriveShareAction() = default;
 
@@ -38,8 +40,7 @@
                                     apps::mojom::IntentPtr intent) {
   controller_ = controller;
   DCHECK(intent->drive_share_url.has_value());
-  NavigateParams params(controller_->GetProfile(),
-                        intent->drive_share_url.value(),
+  NavigateParams params(profile_, intent->drive_share_url.value(),
                         ui::PAGE_TRANSITION_LINK);
   params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
   Navigate(&params);
diff --git a/chrome/browser/sharesheet/drive_share_action.h b/chrome/browser/sharesheet/drive_share_action.h
index 39338f8..0daede5 100644
--- a/chrome/browser/sharesheet/drive_share_action.h
+++ b/chrome/browser/sharesheet/drive_share_action.h
@@ -7,11 +7,13 @@
 
 #include "chrome/browser/sharesheet/share_action.h"
 
+class Profile;
+
 namespace sharesheet {
 
 class DriveShareAction : public ShareAction {
  public:
-  DriveShareAction();
+  explicit DriveShareAction(Profile* profile);
   ~DriveShareAction() override;
   DriveShareAction(const DriveShareAction&) = delete;
   DriveShareAction& operator=(const DriveShareAction&) = delete;
@@ -27,6 +29,7 @@
                         bool contains_hosted_document) override;
 
  private:
+  Profile* profile_;
   SharesheetController* controller_ = nullptr;
 };
 
diff --git a/chrome/browser/sharesheet/example_action.cc b/chrome/browser/sharesheet/example_action.cc
index a54c729..e5c5212 100644
--- a/chrome/browser/sharesheet/example_action.cc
+++ b/chrome/browser/sharesheet/example_action.cc
@@ -3,8 +3,10 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/sharesheet/example_action.h"
+
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/sharesheet/sharesheet_controller.h"
 #include "chrome/browser/sharesheet/sharesheet_types.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 
diff --git a/chrome/browser/sharesheet/share_action.h b/chrome/browser/sharesheet/share_action.h
index 18584ef..349284d 100644
--- a/chrome/browser/sharesheet/share_action.h
+++ b/chrome/browser/sharesheet/share_action.h
@@ -8,17 +8,18 @@
 #include <string>
 
 #include "base/callback.h"
-#include "chrome/browser/sharesheet/sharesheet_controller.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/views/view.h"
 
 namespace gfx {
 struct VectorIcon;
-}
+}  // namespace gfx
 
 namespace sharesheet {
 
+class SharesheetController;
+
 // An interface implemented by each ShareAction.
 class ShareAction {
  public:
diff --git a/chrome/browser/sharesheet/sharesheet_action_cache.cc b/chrome/browser/sharesheet/sharesheet_action_cache.cc
index 6150a18d..1569ae3 100644
--- a/chrome/browser/sharesheet/sharesheet_action_cache.cc
+++ b/chrome/browser/sharesheet/sharesheet_action_cache.cc
@@ -26,9 +26,9 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (NearbySharingServiceFactory::IsNearbyShareSupportedForBrowserContext(
           profile)) {
-    AddShareAction(std::make_unique<NearbyShareAction>());
+    AddShareAction(std::make_unique<NearbyShareAction>(profile));
   }
-  AddShareAction(std::make_unique<DriveShareAction>());
+  AddShareAction(std::make_unique<DriveShareAction>(profile));
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
diff --git a/chrome/browser/sharesheet/sharesheet_controller.h b/chrome/browser/sharesheet/sharesheet_controller.h
index 02a217f..cd7a640 100644
--- a/chrome/browser/sharesheet/sharesheet_controller.h
+++ b/chrome/browser/sharesheet/sharesheet_controller.h
@@ -7,8 +7,6 @@
 
 #include "chrome/browser/sharesheet/sharesheet_types.h"
 
-class Profile;
-
 namespace sharesheet {
 
 // The SharesheetController allows ShareActions to request changes to the state
@@ -17,8 +15,6 @@
  public:
   virtual ~SharesheetController() = default;
 
-  virtual Profile* GetProfile() = 0;
-
   // When called will set the bubble size to |width| and |height|.
   // |width| and |height| must be set to a positive int.
   virtual void SetBubbleSize(int width, int height) = 0;
diff --git a/chrome/browser/sharesheet/sharesheet_service.cc b/chrome/browser/sharesheet/sharesheet_service.cc
index ace63b1b..b3feb7c 100644
--- a/chrome/browser/sharesheet/sharesheet_service.cc
+++ b/chrome/browser/sharesheet/sharesheet_service.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/app_service/launch_utils.h"
 #include "chrome/browser/sharesheet/share_action.h"
+#include "chrome/browser/sharesheet/sharesheet_service_delegate.h"
 #include "chrome/browser/sharesheet/sharesheet_types.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
@@ -26,10 +27,7 @@
 #include "ui/views/view.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.h"
 #include "ui/chromeos/strings/grit/ui_chromeos_strings.h"
-#else
-#include "chrome/browser/sharesheet/sharesheet_service_delegate.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 namespace sharesheet {
@@ -126,7 +124,7 @@
         ShareAction* share_action =
             sharesheet_action_cache_->GetActionFromName(active_action);
         if (share_action != nullptr)
-          share_action->OnClosing(iter->get());
+          share_action->OnClosing(iter->get()->GetSharesheetController());
       }
       active_delegates_.erase(iter);
       break;
@@ -151,7 +149,8 @@
     if (share_action == nullptr)
       return;
     delegate->OnActionLaunched();
-    share_action->LaunchAction(delegate, share_action_view, std::move(intent));
+    share_action->LaunchAction(delegate->GetSharesheetController(),
+                               share_action_view, std::move(intent));
   } else if (type == TargetType::kArcApp || type == TargetType::kWebApp) {
     DCHECK(intent);
     LaunchApp(target_name, std::move(intent));
@@ -177,12 +176,7 @@
   auto* delegate = GetDelegate(native_window);
   if (delegate == nullptr) {
     auto new_delegate =
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-        std::make_unique<ash::sharesheet::CrosSharesheetServiceDelegate>(
-            native_window, this);
-#else
         std::make_unique<SharesheetServiceDelegate>(native_window, this);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
     delegate = new_delegate.get();
     active_delegates_.push_back(std::move(new_delegate));
   }
diff --git a/chrome/browser/sharesheet/sharesheet_service_delegate.cc b/chrome/browser/sharesheet/sharesheet_service_delegate.cc
index 003508f..51bdfa9 100644
--- a/chrome/browser/sharesheet/sharesheet_service_delegate.cc
+++ b/chrome/browser/sharesheet/sharesheet_service_delegate.cc
@@ -4,8 +4,12 @@
 
 #include "chrome/browser/sharesheet/sharesheet_service_delegate.h"
 
+#include <utility>
+
+#include "base/callback.h"
 #include "chrome/browser/sharesheet/sharesheet_service.h"
-#include "chrome/browser/sharesheet/sharesheet_types.h"
+#include "chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.h"
+#include "ui/base/accelerators/accelerator.h"
 #include "ui/views/view.h"
 
 namespace sharesheet {
@@ -13,22 +17,60 @@
 SharesheetServiceDelegate::SharesheetServiceDelegate(
     gfx::NativeWindow native_window,
     SharesheetService* sharesheet_service)
-    : native_window_(native_window), sharesheet_service_(sharesheet_service) {}
+    : native_window_(native_window), sharesheet_service_(sharesheet_service) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  sharesheet_controller_ =
+      std::make_unique<ash::sharesheet::SharesheetBubbleViewDelegate>(
+          native_window_, this);
+#else
+  NOTIMPLEMENTED();
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+}
+
+SharesheetServiceDelegate::~SharesheetServiceDelegate() = default;
 
 gfx::NativeWindow SharesheetServiceDelegate::GetNativeWindow() {
   return native_window_;
 }
 
-void SharesheetServiceDelegate::ShowBubble(
-    std::vector<TargetInfo> targets,
-    apps::mojom::IntentPtr intent,
-    sharesheet::DeliveredCallback delivered_callback,
-    sharesheet::CloseCallback close_callback) {
-  NOTIMPLEMENTED();
+SharesheetController* SharesheetServiceDelegate::GetSharesheetController() {
+  return sharesheet_controller_.get();
 }
 
+Profile* SharesheetServiceDelegate::GetProfile() {
+  return sharesheet_service_->GetProfile();
+}
+
+void SharesheetServiceDelegate::ShowBubble(std::vector<TargetInfo> targets,
+                                           apps::mojom::IntentPtr intent,
+                                           DeliveredCallback delivered_callback,
+                                           CloseCallback close_callback) {
+  sharesheet_controller_->ShowBubble(std::move(targets), std::move(intent),
+                                     std::move(delivered_callback),
+                                     std::move(close_callback));
+}
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// Skips the generic Sharesheet bubble and directly displays the
+// NearbyShare bubble dialog.
+void SharesheetServiceDelegate::ShowNearbyShareBubbleForArc(
+    apps::mojom::IntentPtr intent,
+    DeliveredCallback delivered_callback,
+    CloseCallback close_callback) {
+  sharesheet_controller_->ShowNearbyShareBubbleForArc(
+      std::move(intent), std::move(delivered_callback),
+      std::move(close_callback));
+}
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+// Invoked immediately after an action has launched in the event that UI
+// changes need to occur at this point.
 void SharesheetServiceDelegate::OnActionLaunched() {
-  NOTIMPLEMENTED();
+  sharesheet_controller_->OnActionLaunched();
+}
+
+void SharesheetServiceDelegate::CloseBubble(SharesheetResult result) {
+  sharesheet_controller_->CloseBubble(result);
 }
 
 void SharesheetServiceDelegate::OnBubbleClosed(
@@ -57,12 +99,4 @@
   return sharesheet_service_->GetVectorIcon(display_name);
 }
 
-Profile* SharesheetServiceDelegate::GetProfile() {
-  return sharesheet_service_->GetProfile();
-}
-
-void SharesheetServiceDelegate::SetBubbleSize(int width, int height) {}
-
-void SharesheetServiceDelegate::CloseBubble(SharesheetResult result) {}
-
 }  // namespace sharesheet
diff --git a/chrome/browser/sharesheet/sharesheet_service_delegate.h b/chrome/browser/sharesheet/sharesheet_service_delegate.h
index e56b817b..8f16d39 100644
--- a/chrome/browser/sharesheet/sharesheet_service_delegate.h
+++ b/chrome/browser/sharesheet/sharesheet_service_delegate.h
@@ -5,11 +5,12 @@
 #ifndef CHROME_BROWSER_SHARESHEET_SHARESHEET_SERVICE_DELEGATE_H_
 #define CHROME_BROWSER_SHARESHEET_SHARESHEET_SERVICE_DELEGATE_H_
 
+#include <memory>
 #include <string>
 
 #include "base/callback.h"
-#include "chrome/browser/sharesheet/sharesheet_controller.h"
 #include "chrome/browser/sharesheet/sharesheet_types.h"
+#include "chrome/browser/sharesheet/sharesheet_ui_delegate.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/gfx/native_widget_types.h"
@@ -20,6 +21,10 @@
 struct VectorIcon;
 }  // namespace gfx
 
+namespace ui {
+class Accelerator;
+}  // namespace ui
+
 namespace views {
 class View;
 }  // namespace views
@@ -30,34 +35,49 @@
 
 // The SharesheetServiceDelegate is the interface through which the business
 // logic in SharesheetService communicates with the UI.
-class SharesheetServiceDelegate : public ::sharesheet::SharesheetController {
+class SharesheetServiceDelegate {
  public:
   SharesheetServiceDelegate(gfx::NativeWindow native_window,
                             SharesheetService* sharesheet_service);
-  ~SharesheetServiceDelegate() override = default;
+  ~SharesheetServiceDelegate();
   SharesheetServiceDelegate(const SharesheetServiceDelegate&) = delete;
   SharesheetServiceDelegate& operator=(const SharesheetServiceDelegate&) =
       delete;
 
   gfx::NativeWindow GetNativeWindow();
+  SharesheetController* GetSharesheetController();
+
+  // TODO(crbug.com/1233830) : Remove after business logic is moved
+  // out of SharesheetHeaderView.
+  Profile* GetProfile();
+
+  // ==========================================================================
+  // ======================== SHARESHEET SERVICE TO UI ========================
+  // ==========================================================================
 
   // The following are called by the ShareService to communicate with the UI.
-  virtual void ShowBubble(std::vector<TargetInfo> targets,
-                          apps::mojom::IntentPtr intent,
-                          sharesheet::DeliveredCallback delivered_callback,
-                          sharesheet::CloseCallback close_callback);
+  void ShowBubble(std::vector<TargetInfo> targets,
+                  apps::mojom::IntentPtr intent,
+                  DeliveredCallback delivered_callback,
+                  CloseCallback close_callback);
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  virtual void ShowNearbyShareBubbleForArc(
-      apps::mojom::IntentPtr intent,
-      sharesheet::DeliveredCallback delivered_callback,
-      sharesheet::CloseCallback close_callback) = 0;
+  // Skips the generic Sharesheet bubble and directly displays the
+  // NearbyShare bubble dialog.
+  void ShowNearbyShareBubbleForArc(apps::mojom::IntentPtr intent,
+                                   DeliveredCallback delivered_callback,
+                                   CloseCallback close_callback);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
   // Invoked immediately after an action has launched in the event that UI
   // changes need to occur at this point.
-  virtual void OnActionLaunched();
+  void OnActionLaunched();
 
+  void CloseBubble(SharesheetResult result);
+
+  // ==========================================================================
+  // ======================== UI TO SHARESHEET SERVICE ========================
+  // ==========================================================================
   // The following are called by the UI to communicate with the ShareService.
   void OnBubbleClosed(const std::u16string& active_action);
   void OnTargetSelected(const std::u16string& target_name,
@@ -68,20 +88,14 @@
                             const std::u16string& active_action);
   const gfx::VectorIcon* GetVectorIcon(const std::u16string& display_name);
 
-  // SharesheetController:
-  Profile* GetProfile() override;
-  // Default implementation does nothing. Override as needed.
-  void SetBubbleSize(int width, int height) override;
-  // Default implementation does nothing. Override as needed.
-  void CloseBubble(SharesheetResult result) override;
-
  private:
   // Only used for ID purposes. NativeWindow will always outlive the
   // SharesheetServiceDelegate.
   gfx::NativeWindow native_window_;
 
-  // Owned by views.
   SharesheetService* sharesheet_service_;
+
+  std::unique_ptr<SharesheetUiDelegate> sharesheet_controller_;
 };
 
 }  // namespace sharesheet
diff --git a/chrome/browser/sharesheet/sharesheet_ui_delegate.h b/chrome/browser/sharesheet/sharesheet_ui_delegate.h
new file mode 100644
index 0000000..b42fa06
--- /dev/null
+++ b/chrome/browser/sharesheet/sharesheet_ui_delegate.h
@@ -0,0 +1,43 @@
+// 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_SHARESHEET_SHARESHEET_UI_DELEGATE_H_
+#define CHROME_BROWSER_SHARESHEET_SHARESHEET_UI_DELEGATE_H_
+
+#include <vector>
+
+#include "chrome/browser/sharesheet/sharesheet_controller.h"
+#include "chrome/browser/sharesheet/sharesheet_types.h"
+#include "components/services/app_service/public/mojom/types.mojom.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace sharesheet {
+
+// The SharesheetUiDelegate is the interface for the controller used by
+// the ShareService to request changes to the UI. This class needs to be
+// implemented by each share bubble.
+class SharesheetUiDelegate : public SharesheetController {
+ public:
+  ~SharesheetUiDelegate() override = default;
+
+  virtual void ShowBubble(std::vector<TargetInfo> targets,
+                          apps::mojom::IntentPtr intent,
+                          DeliveredCallback delivered_callback,
+                          CloseCallback close_callback) = 0;
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Skips the generic Sharesheet bubble and directly displays the
+  // NearbyShare bubble dialog.
+  virtual void ShowNearbyShareBubbleForArc(apps::mojom::IntentPtr intent,
+                                           DeliveredCallback delivered_callback,
+                                           CloseCallback close_callback) = 0;
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+  // Invoked immediately after an action has launched in the event that UI
+  // changes need to occur at this point.
+  virtual void OnActionLaunched() {}
+};
+
+}  // namespace sharesheet
+
+#endif  // CHROME_BROWSER_SHARESHEET_SHARESHEET_UI_DELEGATE_H_
diff --git a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetTest.java b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetTest.java
index 97546d7..0f289ed 100644
--- a/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetTest.java
+++ b/chrome/browser/signin/ui/android/java/src/org/chromium/chrome/browser/signin/ui/account_picker/AccountPickerBottomSheetTest.java
@@ -82,7 +82,7 @@
 public class AccountPickerBottomSheetTest {
     private static class CustomFakeAccountInfoService extends FakeAccountInfoService {
         int getNumberOfObservers() {
-            return mObservers.size();
+            return TestThreadUtils.runOnUiThreadBlockingNoException(mObservers::size);
         }
     }
 
diff --git a/chrome/browser/ssl/https_defaulted_callbacks.cc b/chrome/browser/ssl/https_defaulted_callbacks.cc
index d3ec8210..d771ae6 100644
--- a/chrome/browser/ssl/https_defaulted_callbacks.cc
+++ b/chrome/browser/ssl/https_defaulted_callbacks.cc
@@ -4,16 +4,18 @@
 
 #include "chrome/browser/ssl/https_defaulted_callbacks.h"
 
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ssl/https_only_mode_tab_helper.h"
 #include "chrome/browser/ssl/typed_navigation_upgrade_throttle.h"
 #include "chrome/common/chrome_features.h"
 #include "components/omnibox/common/omnibox_features.h"
+#include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
 #include "url/gurl.h"
 #include "url/url_constants.h"
 
-bool ShouldIgnoreInterstitialBecauseNavigationDefaultedToHttps(
+bool ShouldIgnoreSslInterstitialBecauseNavigationDefaultedToHttps(
     content::NavigationHandle* handle) {
   DCHECK_EQ(url::kHttpsScheme, handle->GetURL().scheme());
 
@@ -25,12 +27,31 @@
   }
 
   // Check HTTPS-Only Mode upgrade status.
-  auto* https_only_mode_helper =
-      HttpsOnlyModeTabHelper::FromWebContents(handle->GetWebContents());
-  if (base::FeatureList::IsEnabled(features::kHttpsOnlyMode) &&
-      https_only_mode_helper->is_navigation_upgraded()) {
-    return true;
+  // Suppress any SSL errors if the navigation was upgraded to HTTPS by
+  // HTTPS-First Mode but it has not yet fallen back to HTTP. If the user
+  // already clicked through the HTTPS-First Mode interstitial then the SSL
+  // error should no longer be suppressed.
+  if (!base::FeatureList::IsEnabled(features::kHttpsOnlyMode)) {
+    return false;
   }
 
-  return false;
+  auto* https_only_mode_helper =
+      HttpsOnlyModeTabHelper::FromWebContents(handle->GetWebContents());
+  bool is_upgraded = https_only_mode_helper &&
+                     https_only_mode_helper->is_navigation_upgraded();
+
+  Profile* profile = Profile::FromBrowserContext(
+      handle->GetWebContents()->GetBrowserContext());
+  if (!profile) {
+    // In the edge case of there not being a Profile associated with this
+    // navigation, fail safe by not suppressing SSL interstitials.
+    return false;
+  }
+  StatefulSSLHostStateDelegate* state =
+      static_cast<StatefulSSLHostStateDelegate*>(
+          profile->GetSSLHostStateDelegate());
+  bool is_allowlisted =
+      state && state->IsHttpAllowedForHost(handle->GetURL().host());
+
+  return is_upgraded && !is_allowlisted;
 }
diff --git a/chrome/browser/ssl/https_defaulted_callbacks.h b/chrome/browser/ssl/https_defaulted_callbacks.h
index a34b7e9..551d074 100644
--- a/chrome/browser/ssl/https_defaulted_callbacks.h
+++ b/chrome/browser/ssl/https_defaulted_callbacks.h
@@ -14,7 +14,7 @@
 // it should not trigger SSL error interstitials. These features that upgrade
 // navigations to HTTPS have special handling for error cases -- see
 // `TypedNavigationUpgradeThrottle` and `HttpsOnlyModeNavigationThrottle`.
-bool ShouldIgnoreInterstitialBecauseNavigationDefaultedToHttps(
+bool ShouldIgnoreSslInterstitialBecauseNavigationDefaultedToHttps(
     content::NavigationHandle* handle);
 
 #endif  // CHROME_BROWSER_SSL_HTTPS_DEFAULTED_CALLBACKS_H_
diff --git a/chrome/browser/ssl/https_only_mode_browsertest.cc b/chrome/browser/ssl/https_only_mode_browsertest.cc
index 5c58404..ed47f9c 100644
--- a/chrome/browser/ssl/https_only_mode_browsertest.cc
+++ b/chrome/browser/ssl/https_only_mode_browsertest.cc
@@ -442,3 +442,62 @@
   ProceedThroughInterstitial(contents);
   EXPECT_EQ(security_state::WARNING, helper->GetSecurityLevel());
 }
+
+// Regression test for crbug.com/1233207.
+// Tests the case where the HTTP version of a site redirects to HTTPS, but the
+// HTTPS version of the site has a cert error. If the user initially navigates
+// to the HTTP URL, then HTTPS-First Mode should upgrade the navigation to HTTPS
+// and trigger the HTTPS-First Mode interstitial when that fails, but if the
+// user clicks through the HTTPS-First Mode interstitial and falls back into the
+// HTTP->HTTPS redirect back to the cert error, then the SSL interstitial should
+// be shown and the user should be able to click through the SSL interstitial to
+// visit the HTTPS version of the site (but in a DANGEROUS security level
+// state).
+IN_PROC_BROWSER_TEST_F(HttpsOnlyModeBrowserTest,
+                       HttpsUpgradeWithBrokenSSL_ShouldTriggerSSLInterstitial) {
+  // Set up a new test server instance so it can have a custom handler that
+  // redirects to the HTTPS server.
+  net::EmbeddedTestServer upgrading_server{net::EmbeddedTestServer::TYPE_HTTP};
+  upgrading_server.RegisterRequestHandler(base::BindLambdaForTesting(
+      [&](const net::test_server::HttpRequest& request)
+          -> std::unique_ptr<net::test_server::HttpResponse> {
+        auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+        response->set_code(net::HTTP_TEMPORARY_REDIRECT);
+        response->AddCustomHeader(
+            "Location",
+            "https://bad-https.test:" +
+                base::NumberToString(
+                    HttpsOnlyModeUpgradeInterceptor::GetHttpsPortForTesting()) +
+                "/simple.html");
+        return response;
+      }));
+  HttpsOnlyModeUpgradeInterceptor::SetHttpPortForTesting(
+      upgrading_server.port());
+  ASSERT_TRUE(upgrading_server.Start());
+
+  GURL http_url = upgrading_server.GetURL("bad-https.test", "/simple.html");
+  // HTTPS server will have a cert error.
+  GURL https_url = https_server()->GetURL("bad-https.test", "/simple.html");
+
+  auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
+  EXPECT_FALSE(content::NavigateToURL(contents, http_url));
+  EXPECT_EQ(https_url, contents->GetLastCommittedURL());
+
+  // The HTTPS-First Mode interstitial should trigger first.
+  EXPECT_TRUE(chrome_browser_interstitials::IsInterstitialDisplayingText(
+      contents->GetMainFrame(),
+      l10n_util::GetStringUTF8(IDS_HTTPS_ONLY_MODE_PRIMARY_PARAGRAPH)));
+
+  // Proceeding through the HTTPS-First Mode interstitial will hit the upgrading
+  // server's HTTP->HTTPS redirect. This should result in an SSL interstitial
+  // (not an HTTPS-First Mode interstitial).
+  ProceedThroughInterstitial(contents);
+  EXPECT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
+
+  // Proceeding through the SSL interstitial should navigate to the HTTPS
+  // version of the site but with the DANGEROUS security level.
+  ProceedThroughInterstitial(contents);
+  EXPECT_EQ(https_url, contents->GetLastCommittedURL());
+  auto* helper = SecurityStateTabHelper::FromWebContents(contents);
+  EXPECT_EQ(security_state::DANGEROUS, helper->GetSecurityLevel());
+}
diff --git a/chrome/browser/ssl/ssl_prerender_browsertest.cc b/chrome/browser/ssl/ssl_prerender_browsertest.cc
new file mode 100644
index 0000000..cae9fe2
--- /dev/null
+++ b/chrome/browser/ssl/ssl_prerender_browsertest.cc
@@ -0,0 +1,192 @@
+// Copyright 2021 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/bind.h"
+#include "base/files/file_path.h"
+#include "chrome/browser/interstitials/security_interstitial_page_test_utils.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/ssl_host_state_delegate.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/prerender_test_util.h"
+#include "content/public/test/url_loader_interceptor.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using chrome_browser_interstitials::IsShowingSSLInterstitial;
+using content::EvalJs;
+using content::RenderFrameHost;
+using content::SSLHostStateDelegate;
+using content::TestNavigationManager;
+using content::URLLoaderInterceptor;
+using content::WebContents;
+using net::EmbeddedTestServer;
+using ui_test_utils::NavigateToURL;
+
+namespace {
+
+std::unique_ptr<net::EmbeddedTestServer> CreateExpiredCertServer(
+    const base::FilePath& data_dir) {
+  auto server =
+      std::make_unique<EmbeddedTestServer>(EmbeddedTestServer::TYPE_HTTPS);
+  server->SetSSLConfig(EmbeddedTestServer::CERT_EXPIRED);
+  server->ServeFilesFromSourceDirectory(data_dir);
+  return server;
+}
+
+}  // namespace
+
+class SSLPrerenderTest : public InProcessBrowserTest {
+ public:
+  SSLPrerenderTest()
+      : prerender_helper_(base::BindRepeating(&SSLPrerenderTest::web_contents,
+                                              base::Unretained(this))) {}
+  ~SSLPrerenderTest() override = default;
+
+ protected:
+  content::WebContents* web_contents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
+  content::test::PrerenderTestHelper prerender_helper_;
+};
+
+// Verifies that a certificate error in a prerendered page causes cancelation
+// of prerendering without showing an interstitial.
+// TODO(bokan): In the future, when prerendering supports cross origin
+// triggering, this test can be more straightforward by using one server for
+// the initial page and another, with bad certs, for the prerendering page.
+IN_PROC_BROWSER_TEST_F(SSLPrerenderTest, TestNoInterstitialInPrerender) {
+  auto server = CreateExpiredCertServer(GetChromeTestDataDir());
+  ASSERT_TRUE(server->Start());
+
+  const GURL kPrerenderUrl = server->GetURL("/empty.html?prerender");
+  const GURL kInitialUrl = server->GetURL("/empty.html");
+
+  // Use an interceptor to load the initial page. This is done because the
+  // server has certificate errors. If the initial URL is loaded from the test
+  // server, this will trigger an interstitial before the prerender can be
+  // triggered. Since the prerender must be same origin with the initial page,
+  // proceeding through that interstitial would add an exception for the URL,
+  // and so the error won't be visible to the prerender load. Since this test
+  // is trying to make sure that interstitials on prerender loads abort the
+  // prerender, this interceptor ensures the initial load won't have an
+  // interstitial, but the prerender will.
+  {
+    auto url_loader_interceptor =
+        content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
+            GetChromeTestDataDir().MaybeAsASCII(), kInitialUrl.GetOrigin());
+
+    // Navigate to the initial page.
+    NavigateToURL(browser(), kInitialUrl);
+    ASSERT_FALSE(IsShowingSSLInterstitial(web_contents()));
+
+    // Make sure there is no exception for the prerendering URL, so that an SSL
+    // error will not be ignored.
+    Profile* profile =
+        Profile::FromBrowserContext(web_contents()->GetBrowserContext());
+    SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();
+    ASSERT_FALSE(
+        state->HasAllowException(kPrerenderUrl.host(), web_contents()));
+  }
+
+  // Trigger a prerender. Unlike the initial navigation, this will hit the
+  // server, so it'll respond with a bad certificate. If this request was a
+  // normal navigation, an interstitial would be shown, but because it is a
+  // prerender request, the prerender should be canceled and no interstitial
+  // shown.
+  {
+    TestNavigationManager observer(web_contents(), kPrerenderUrl);
+
+    // Trigger the prerender. The PrerenderHost starts the request when it is
+    // created so it should be available after WaitForRequestStart.
+    prerender_helper_.AddPrerenderAsync(kPrerenderUrl);
+    ASSERT_TRUE(observer.WaitForRequestStart());
+    ASSERT_NE(prerender_helper_.GetHostForUrl(kPrerenderUrl),
+              RenderFrameHost::kNoFrameTreeNodeId);
+
+    // The prerender navigation should be canceled as part of the response.
+    // Ensure the prerender host is destroyed and no interstitial is showing.
+    EXPECT_FALSE(observer.WaitForResponse());
+    EXPECT_EQ(prerender_helper_.GetHostForUrl(kPrerenderUrl),
+              RenderFrameHost::kNoFrameTreeNodeId);
+    EXPECT_FALSE(IsShowingSSLInterstitial(web_contents()));
+  }
+}
+
+// Verifies that a certificate error in a prerendered page fetched via service
+// worker causes cancelation of prerendering without showing an interstitial.
+// TODO(bokan): In the future, when prerendering supports cross origin
+// triggering, this test can be more straightforward by using one server for
+// the initial page and another, with bad certs, for the prerendering page.
+IN_PROC_BROWSER_TEST_F(SSLPrerenderTest, TestNoInterstitialInPrerenderSW) {
+  auto server = CreateExpiredCertServer(GetChromeTestDataDir());
+  ASSERT_TRUE(server->Start());
+
+  const GURL kPrerenderUrl = server->GetURL("/service_worker/blank.html");
+  const GURL kInitialUrl =
+      server->GetURL("/service_worker/create_service_worker.html");
+
+  // Use an interceptor to load the initial URL. This is done because the
+  // server has certificate errors. If the initial URL is loaded from the test
+  // server, this will trigger an interstitial before the prerender can be
+  // triggered. Since the prerender must be same origin with the initial page,
+  // proceeding through that interstitial would add an exception for the URL,
+  // and so the error won't be visible to the prerender load. Since this test
+  // is trying to make sure that interstitials on prerender loads abort the
+  // prerender, this interceptor ensures the initial load won't have an
+  // interstitial, but the prerender will.
+  {
+    auto url_loader_interceptor =
+        content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
+            GetChromeTestDataDir().MaybeAsASCII(), kInitialUrl.GetOrigin());
+
+    // Navigate to the initial page and register a service worker that will
+    // relay the fetch.
+    NavigateToURL(browser(), kInitialUrl);
+    ASSERT_EQ("DONE", EvalJs(web_contents(),
+                             "register('fetch_event_respond_with_fetch.js');"));
+    ASSERT_FALSE(IsShowingSSLInterstitial(web_contents()));
+
+    // Make sure there is no exception for the prerendering URL, so that an SSL
+    // error will not be ignored.
+    Profile* profile =
+        Profile::FromBrowserContext(web_contents()->GetBrowserContext());
+    SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();
+    ASSERT_FALSE(
+        state->HasAllowException(kPrerenderUrl.host(), web_contents()));
+  }
+
+  // Trigger a prerender. Unlike the initial navigation, this will hit the
+  // server, so it'll respond with a bad certificate. If this request was a
+  // normal navigation, an interstitial would be shown, but because it is a
+  // prerender request, the prerender should be canceled and no interstitial
+  // shown.
+  {
+    TestNavigationManager observer(web_contents(), kPrerenderUrl);
+
+    // Trigger the prerender. The PrerenderHost starts the request when it is
+    // created so it should be available after WaitForRequestStart.
+    prerender_helper_.AddPrerenderAsync(kPrerenderUrl);
+    ASSERT_TRUE(observer.WaitForRequestStart());
+    ASSERT_NE(prerender_helper_.GetHostForUrl(kPrerenderUrl),
+              RenderFrameHost::kNoFrameTreeNodeId);
+
+    // The prerender navigation should be canceled as part of the response.
+    // Ensure the prerender host is destroyed and no interstitial is showing.
+    EXPECT_FALSE(observer.WaitForResponse());
+    EXPECT_EQ(prerender_helper_.GetHostForUrl(kPrerenderUrl),
+              RenderFrameHost::kNoFrameTreeNodeId);
+    EXPECT_FALSE(IsShowingSSLInterstitial(web_contents()));
+  }
+}
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModelTest.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModelTest.java
index 07c8fb3..58b4dbd 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModelTest.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/IncognitoTabModelTest.java
@@ -73,7 +73,9 @@
     public void testCloseAllDuringAddTabDoesNotCrash() {
         createTabOnUiThread();
         Assert.assertEquals(1, mTabModel.getCount());
-        mTabModel.addObserver(new CloseAllDuringAddTabTabModelObserver());
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mTabModel.addObserver(new CloseAllDuringAddTabTabModelObserver()));
+
         createTabOnUiThread();
         Assert.assertEquals(1, mTabModel.getCount());
     }
@@ -103,16 +105,18 @@
         CallbackHelper didAddTabCallbackHelper = new CallbackHelper();
         CallbackHelper tabRemovedCallbackHelper = new CallbackHelper();
 
-        mTabModel.addObserver(new TabModelObserver() {
-            @Override
-            public void tabRemoved(Tab tab) {
-                tabRemovedCallbackHelper.notifyCalled();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTabModel.addObserver(new TabModelObserver() {
+                @Override
+                public void tabRemoved(Tab tab) {
+                    tabRemovedCallbackHelper.notifyCalled();
+                }
 
-            @Override
-            public void didAddTab(Tab tab, int type, int creationState) {
-                didAddTabCallbackHelper.notifyCalled();
-            }
+                @Override
+                public void didAddTab(Tab tab, int type, int creationState) {
+                    didAddTabCallbackHelper.notifyCalled();
+                }
+            });
         });
 
         createTabOnUiThread();
diff --git a/chrome/browser/translate/android/translate_bridge.cc b/chrome/browser/translate/android/translate_bridge.cc
index 9164253..2fa6087 100644
--- a/chrome/browser/translate/android/translate_bridge.cc
+++ b/chrome/browser/translate/android/translate_bridge.cc
@@ -97,11 +97,13 @@
   } else {
     // We don't check for source_language_code support because TranslatePage
     // handles that case already.
-    manager->TranslatePage(source_language_code, target_language_code,
-                           /*triggered_from_menu=*/false,
-                           /*translation_type=*/
-                           manager->GetActiveTranslateMetricsLogger()
-                               ->GetNextManualTranslationType());
+    manager->TranslatePage(
+        source_language_code, target_language_code,
+        /*triggered_from_menu=*/false,
+        /*translation_type=*/
+        manager->GetActiveTranslateMetricsLogger()
+            ->GetNextManualTranslationType(
+                /*is_context_menu_initiated_translation=*/false));
   }
 }
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 9a98777..4f2c3d7 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2083,6 +2083,8 @@
       "ash/session_util.h",
       "ash/sharesheet/sharesheet_bubble_view.cc",
       "ash/sharesheet/sharesheet_bubble_view.h",
+      "ash/sharesheet/sharesheet_bubble_view_delegate.cc",
+      "ash/sharesheet/sharesheet_bubble_view_delegate.h",
       "ash/sharesheet/sharesheet_constants.h",
       "ash/sharesheet/sharesheet_expand_button.cc",
       "ash/sharesheet/sharesheet_expand_button.h",
@@ -2291,6 +2293,8 @@
       "webui/chromeos/assistant_optin/assistant_optin_ui.h",
       "webui/chromeos/assistant_optin/assistant_optin_utils.cc",
       "webui/chromeos/assistant_optin/assistant_optin_utils.h",
+      "webui/chromeos/audio/audio_handler.cc",
+      "webui/chromeos/audio/audio_handler.h",
       "webui/chromeos/audio/audio_ui.cc",
       "webui/chromeos/audio/audio_ui.h",
       "webui/chromeos/bluetooth_pairing_dialog.cc",
@@ -2772,6 +2776,7 @@
       "//chrome/browser/ui/app_list/search/search_result_ranker:recurrence_ranker_proto",
       "//chrome/browser/ui/webui/app_management:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/add_supervision:mojo_bindings",
+      "//chrome/browser/ui/webui/chromeos/audio:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/crostini_upgrader:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/enterprise_casting:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/launcher_internals:mojo_bindings",
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 3d1710f..844bed9 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -2018,6 +2018,42 @@
         You’ll see a notification when this download starts at <ph name="time">%1$s<ex>8:30PM</ex></ph>.
       </message>
 
+      <!-- Download progress message UI -->
+      <message name="IDS_DOWNLOAD_MESSAGE_DOWNLOAD_IN_PROGRESS_DESCRIPTION" desc="Download message text describing that user can find about their download status from notifications.">
+        You can see the download status in your notifications
+      </message>
+      <message name="IDS_DOWNLOAD_MESSAGE_DOWNLOAD_COMPLETE_DESCRIPTION" desc="Download message description text about downloaded file name and their size.">
+        (<ph name="megabytes">%1$s<ex>100 MB</ex></ph>)\n<ph name="url">%2$s<ex>https://...example.com</ex></ph>
+      </message>
+      <message name="IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_IN_PROGRESS" desc="Download message text describing that chrome is downloading multiple files.">
+        {FILE_COUNT, plural,
+            =1 {Downloading file…}
+            other {Downloading # files…}}
+      </message>
+      <message name="IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_COMPLETE" desc="Download message label describing that one or more downloads have completed">
+        {FILE_COUNT, plural,
+          =1 {File downloaded}
+          other {# downloads complete}}
+      </message>
+      <message name="IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_FAILED" desc="Download message label describing that one or more downloads have failed">
+        {FILE_COUNT, plural,
+          =1 {1 download failed}
+          other {# downloads failed}}
+      </message>
+      <message name="IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_PENDING" desc="Download message label describing that one or more downloads are pending">
+        {FILE_COUNT, plural,
+          =1 {1 download pending}
+          other {# downloads pending}}
+      </message>
+      <message name="IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_SCHEDULED" desc="Download message label describing that one or more downloads are scheduled">
+        {FILE_COUNT, plural,
+          =1 {1 download scheduled}
+          other {# downloads scheduled}}
+      </message>
+      <message name="IDS_DOWNLOAD_MESSAGE_DOWNLOAD_SCHEDULED_DESCRIPTION" desc="Download message text describing that user can find about their download status from notifications.">
+        Download will start when on Wi-Fi
+      </message>
+
       <!-- Inline Update Infobar -->
       <message name="IDS_INLINE_UPDATE_INFOBAR_READY_MESSAGE" desc="Text showing that the update process is almost ready.">
         Almost finished!
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_COMPLETE_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_COMPLETE_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..18a46f1
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_COMPLETE_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+436eebd2b3a112e6556c98dc9c8d00ba618b5ead
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_IN_PROGRESS_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_IN_PROGRESS_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..94a9732
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_IN_PROGRESS_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+6c18810ecd01392a1a749d75490a1d2d619af5d1
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_SCHEDULED_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_SCHEDULED_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..57e5263
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_DOWNLOAD_SCHEDULED_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+615c3db108752ad5ccd1f6022e19ece79dcf992c
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_COMPLETE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_COMPLETE.png.sha1
new file mode 100644
index 0000000..2bfe1db
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_COMPLETE.png.sha1
@@ -0,0 +1 @@
+a320dadee28c239e3de9bc06e68a9856f90c7ffa
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_FAILED.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_FAILED.png.sha1
new file mode 100644
index 0000000..1f4282e
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_FAILED.png.sha1
@@ -0,0 +1 @@
+480a8a6ea29a695efd32bbce890fd7b5966a89f4
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_IN_PROGRESS.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_IN_PROGRESS.png.sha1
new file mode 100644
index 0000000..897ef61e
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_IN_PROGRESS.png.sha1
@@ -0,0 +1 @@
+373e2e982101719158b6a71dac294d861f73324d
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_PENDING.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_PENDING.png.sha1
new file mode 100644
index 0000000..1f4282e
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_PENDING.png.sha1
@@ -0,0 +1 @@
+480a8a6ea29a695efd32bbce890fd7b5966a89f4
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_SCHEDULED.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_SCHEDULED.png.sha1
new file mode 100644
index 0000000..f0a8844
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DOWNLOAD_MESSAGE_MULTIPLE_DOWNLOAD_SCHEDULED.png.sha1
@@ -0,0 +1 @@
+795499cb3552abce6563e4ef6184487b3c1a7075
\ No newline at end of file
diff --git a/chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.cc b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.cc
similarity index 74%
rename from chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.cc
rename to chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.cc
index 8613927..6acbd61 100644
--- a/chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.cc
+++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.cc
@@ -2,25 +2,27 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ash/sharesheet/cros_sharesheet_service_delegate.h"
+#include "chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.h"
 
 #include <utility>
 
 #include "base/bind.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sharesheet/sharesheet_service_delegate.h"
 #include "chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
 namespace sharesheet {
 
-CrosSharesheetServiceDelegate::CrosSharesheetServiceDelegate(
+SharesheetBubbleViewDelegate::SharesheetBubbleViewDelegate(
     gfx::NativeWindow native_window,
-    ::sharesheet::SharesheetService* sharesheet_service)
-    : ::sharesheet::SharesheetServiceDelegate(native_window,
-                                              sharesheet_service),
-      sharesheet_bubble_view_(new SharesheetBubbleView(native_window, this)) {}
+    ::sharesheet::SharesheetServiceDelegate* sharesheet_service_delegate)
+    : sharesheet_bubble_view_(
+          new SharesheetBubbleView(native_window,
+                                   sharesheet_service_delegate)) {}
 
-void CrosSharesheetServiceDelegate::ShowBubble(
+void SharesheetBubbleViewDelegate::ShowBubble(
     std::vector<::sharesheet::TargetInfo> targets,
     apps::mojom::IntentPtr intent,
     ::sharesheet::DeliveredCallback delivered_callback,
@@ -40,7 +42,7 @@
                                       std::move(close_callback));
 }
 
-void CrosSharesheetServiceDelegate::ShowNearbyShareBubbleForArc(
+void SharesheetBubbleViewDelegate::ShowNearbyShareBubbleForArc(
     apps::mojom::IntentPtr intent,
     ::sharesheet::DeliveredCallback delivered_callback,
     ::sharesheet::CloseCallback close_callback) {
@@ -59,17 +61,17 @@
       std::move(close_callback));
 }
 
-void CrosSharesheetServiceDelegate::OnActionLaunched() {
+void SharesheetBubbleViewDelegate::OnActionLaunched() {
   sharesheet_bubble_view_->ShowActionView();
 }
 
-void CrosSharesheetServiceDelegate::SetBubbleSize(int width, int height) {
+void SharesheetBubbleViewDelegate::SetBubbleSize(int width, int height) {
   DCHECK_GT(width, 0);
   DCHECK_GT(height, 0);
   sharesheet_bubble_view_->ResizeBubble(width, height);
 }
 
-void CrosSharesheetServiceDelegate::CloseBubble(
+void SharesheetBubbleViewDelegate::CloseBubble(
     ::sharesheet::SharesheetResult result) {
   views::Widget::ClosedReason reason =
       views::Widget::ClosedReason::kUnspecified;
@@ -83,7 +85,7 @@
   sharesheet_bubble_view_->CloseBubble(reason);
 }
 
-bool CrosSharesheetServiceDelegate::IsBubbleVisible() const {
+bool SharesheetBubbleViewDelegate::IsBubbleVisible() const {
   return sharesheet_bubble_view_->GetWidget() &&
          sharesheet_bubble_view_->GetWidget()->IsVisible();
 }
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.h b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.h
new file mode 100644
index 0000000..d3860a2
--- /dev/null
+++ b/chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view_delegate.h
@@ -0,0 +1,60 @@
+// Copyright 2021 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_ASH_SHARESHEET_SHARESHEET_BUBBLE_VIEW_DELEGATE_H_
+#define CHROME_BROWSER_UI_ASH_SHARESHEET_SHARESHEET_BUBBLE_VIEW_DELEGATE_H_
+
+#include "chrome/browser/sharesheet/sharesheet_ui_delegate.h"
+
+namespace sharesheet {
+class SharesheetServiceDelegate;
+}  // namespace sharesheet
+
+namespace ash {
+namespace sharesheet {
+
+class SharesheetBubbleView;
+
+// SharesheetBubbleViewDelegate is the SharesheetUiDelegate for
+// SharesheetBubbleView.
+class SharesheetBubbleViewDelegate : public ::sharesheet::SharesheetUiDelegate {
+ public:
+  SharesheetBubbleViewDelegate(
+      gfx::NativeWindow native_window,
+      ::sharesheet::SharesheetServiceDelegate* sharesheet_service_delegate);
+  ~SharesheetBubbleViewDelegate() override = default;
+  SharesheetBubbleViewDelegate(const SharesheetBubbleViewDelegate&) = delete;
+  SharesheetBubbleViewDelegate& operator=(const SharesheetBubbleViewDelegate&) =
+      delete;
+
+  // ::sharesheet::SharesheetUiDelegate:
+  void ShowBubble(std::vector<::sharesheet::TargetInfo> targets,
+                  apps::mojom::IntentPtr intent,
+                  ::sharesheet::DeliveredCallback delivered_callback,
+                  ::sharesheet::CloseCallback close_callback) override;
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  void ShowNearbyShareBubbleForArc(
+      apps::mojom::IntentPtr intent,
+      ::sharesheet::DeliveredCallback delivered_callback,
+      ::sharesheet::CloseCallback close_callback) override;
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+  void OnActionLaunched() override;
+
+  // ::sharesheet::SharesheetController:
+  void SetBubbleSize(int width, int height) override;
+  void CloseBubble(::sharesheet::SharesheetResult result) override;
+
+ private:
+  bool IsBubbleVisible() const;
+
+  // Owned by views.
+  SharesheetBubbleView* sharesheet_bubble_view_;
+};
+
+}  // namespace sharesheet
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_UI_ASH_SHARESHEET_SHARESHEET_BUBBLE_VIEW_DELEGATE_H_
diff --git a/chrome/browser/ui/ash/sharesheet/sharesheet_header_view.h b/chrome/browser/ui/ash/sharesheet/sharesheet_header_view.h
index c03c8bb..03babcd 100644
--- a/chrome/browser/ui/ash/sharesheet/sharesheet_header_view.h
+++ b/chrome/browser/ui/ash/sharesheet/sharesheet_header_view.h
@@ -60,6 +60,7 @@
   std::vector<std::u16string> ExtractShareText();
   const gfx::VectorIcon& GetTextVectorIcon();
 
+  // TODO(crbug.com/1233830): Move business logic out of UI code.
   void ResolveImages();
   void ResolveImage(size_t index);
   void LoadImage(const base::FilePath& file_path,
diff --git a/chrome/browser/ui/ash/test_wallpaper_controller.cc b/chrome/browser/ui/ash/test_wallpaper_controller.cc
index 3645f95..9ee3522 100644
--- a/chrome/browser/ui/ash/test_wallpaper_controller.cc
+++ b/chrome/browser/ui/ash/test_wallpaper_controller.cc
@@ -41,7 +41,6 @@
 
 void TestWallpaperController::SetCustomWallpaper(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const base::FilePath& file_path,
     ash::WallpaperLayout layout,
     bool preview_mode,
@@ -52,7 +51,6 @@
 
 void TestWallpaperController::SetCustomWallpaper(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const std::string& file_name,
     ash::WallpaperLayout layout,
     const gfx::ImageSkia& image,
@@ -81,7 +79,6 @@
 
 void TestWallpaperController::SetDefaultWallpaper(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     bool show_wallpaper) {
   ++set_default_wallpaper_count_;
 }
@@ -94,7 +91,6 @@
 
 void TestWallpaperController::SetPolicyWallpaper(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const std::string& data) {
   NOTIMPLEMENTED();
 }
@@ -106,7 +102,6 @@
 
 bool TestWallpaperController::SetThirdPartyWallpaper(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const std::string& file_name,
     ash::WallpaperLayout layout,
     const gfx::ImageSkia& image) {
@@ -150,15 +145,12 @@
   ++remove_always_on_top_wallpaper_count_;
 }
 
-void TestWallpaperController::RemoveUserWallpaper(
-    const AccountId& account_id,
-    const std::string& wallpaper_files_id) {
+void TestWallpaperController::RemoveUserWallpaper(const AccountId& account_id) {
   ++remove_user_wallpaper_count_;
 }
 
 void TestWallpaperController::RemovePolicyWallpaper(
-    const AccountId& account_id,
-    const std::string& wallpaper_files_id) {
+    const AccountId& account_id) {
   NOTIMPLEMENTED();
 }
 
diff --git a/chrome/browser/ui/ash/test_wallpaper_controller.h b/chrome/browser/ui/ash/test_wallpaper_controller.h
index 6428e1f8..6bfe331b 100644
--- a/chrome/browser/ui/ash/test_wallpaper_controller.h
+++ b/chrome/browser/ui/ash/test_wallpaper_controller.h
@@ -47,13 +47,11 @@
             const base::FilePath& custom_wallpapers,
             const base::FilePath& device_policy_wallpaper) override;
   void SetCustomWallpaper(const AccountId& account_id,
-                          const std::string& wallpaper_files_id,
                           const base::FilePath& file_path,
                           ash::WallpaperLayout layout,
                           bool preview_mode,
                           SetCustomWallpaperCallback callback) override;
   void SetCustomWallpaper(const AccountId& account_id,
-                          const std::string& wallpaper_files_id,
                           const std::string& file_name,
                           ash::WallpaperLayout layout,
                           const gfx::ImageSkia& image,
@@ -66,18 +64,15 @@
                                   const std::string& image_data,
                                   SetOnlineWallpaperCallback callback) override;
   void SetDefaultWallpaper(const AccountId& account_id,
-                           const std::string& wallpaper_files_id,
                            bool show_wallpaper) override;
   void SetCustomizedDefaultWallpaperPaths(
       const base::FilePath& customized_default_small_path,
       const base::FilePath& customized_default_large_path) override;
   void SetPolicyWallpaper(const AccountId& account_id,
-                          const std::string& wallpaper_files_id,
                           const std::string& data) override;
   void SetDevicePolicyWallpaperPath(
       const base::FilePath& device_policy_wallpaper_path) override;
   bool SetThirdPartyWallpaper(const AccountId& account_id,
-                              const std::string& wallpaper_files_id,
                               const std::string& file_name,
                               ash::WallpaperLayout layout,
                               const gfx::ImageSkia& image) override;
@@ -90,10 +85,8 @@
   void ShowOneShotWallpaper(const gfx::ImageSkia& image) override;
   void ShowAlwaysOnTopWallpaper(const base::FilePath& image_path) override;
   void RemoveAlwaysOnTopWallpaper() override;
-  void RemoveUserWallpaper(const AccountId& account_id,
-                           const std::string& wallpaper_files_id) override;
-  void RemovePolicyWallpaper(const AccountId& account_id,
-                             const std::string& wallpaper_files_id) override;
+  void RemoveUserWallpaper(const AccountId& account_id) override;
+  void RemovePolicyWallpaper(const AccountId& account_id) override;
   void GetOfflineWallpaperList(
       GetOfflineWallpaperListCallback callback) override;
   void SetAnimationDuration(base::TimeDelta animation_duration) override;
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc b/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
index 6a3bc078..922a49af 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
+++ b/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
@@ -115,13 +115,22 @@
          chromeos::SystemSaltGetter::Get()->GetRawSalt();
 }
 
-// Calls |callback| when system salt is ready. (|CanGetFilesId| returns true.)
-void AddCanGetFilesIdCallback(base::OnceClosure callback) {
-  // System salt may not be initialized in tests.
-  if (chromeos::SystemSaltGetter::IsInitialized()) {
-    chromeos::SystemSaltGetter::Get()->AddOnSystemSaltReady(
-        std::move(callback));
+void GetFilesIdSaltReady(
+    const AccountId& account_id,
+    base::OnceCallback<void(const std::string&)> files_id_callback) {
+  DCHECK(CanGetFilesId());
+  std::string stored_value;
+  if (user_manager::known_user::GetStringPref(account_id, kWallpaperFilesId,
+                                              &stored_value)) {
+    std::move(files_id_callback).Run(stored_value);
+    return;
   }
+
+  const std::string wallpaper_files_id =
+      HashWallpaperFilesIdStr(account_id.GetUserEmail());
+  user_manager::known_user::SetStringPref(account_id, kWallpaperFilesId,
+                                          wallpaper_files_id);
+  std::move(files_id_callback).Run(wallpaper_files_id);
 }
 
 // Returns true if |users| contains users other than device local accounts.
@@ -282,33 +291,16 @@
   return g_wallpaper_controller_client_instance;
 }
 
-std::string WallpaperControllerClientImpl::GetFilesId(
-    const AccountId& account_id) const {
-  DCHECK(CanGetFilesId());
-  std::string stored_value;
-  if (user_manager::known_user::GetStringPref(account_id, kWallpaperFilesId,
-                                              &stored_value)) {
-    return stored_value;
-  }
-
-  const std::string wallpaper_files_id =
-      HashWallpaperFilesIdStr(account_id.GetUserEmail());
-  user_manager::known_user::SetStringPref(account_id, kWallpaperFilesId,
-                                          wallpaper_files_id);
-  return wallpaper_files_id;
-}
-
 void WallpaperControllerClientImpl::SetCustomWallpaper(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const std::string& file_name,
     ash::WallpaperLayout layout,
     const gfx::ImageSkia& image,
     bool preview_mode) {
   if (!IsKnownUser(account_id))
     return;
-  wallpaper_controller_->SetCustomWallpaper(
-      account_id, wallpaper_files_id, file_name, layout, image, preview_mode);
+  wallpaper_controller_->SetCustomWallpaper(account_id, file_name, layout,
+                                            image, preview_mode);
 }
 
 void WallpaperControllerClientImpl::SetOnlineWallpaper(
@@ -352,28 +344,17 @@
   if (!data || !IsKnownUser(account_id))
     return;
 
-  // Postpone setting the wallpaper until we can get files id. See
-  // https://crbug.com/615239.
-  if (!CanGetFilesId()) {
-    AddCanGetFilesIdCallback(base::BindOnce(
-        &WallpaperControllerClientImpl::SetPolicyWallpaper,
-        weak_factory_.GetWeakPtr(), account_id, std::move(data)));
-    return;
-  }
-
-  wallpaper_controller_->SetPolicyWallpaper(account_id, GetFilesId(account_id),
-                                            *data);
+  wallpaper_controller_->SetPolicyWallpaper(account_id, *data);
 }
 
 bool WallpaperControllerClientImpl::SetThirdPartyWallpaper(
     const AccountId& account_id,
-    const std::string& wallpaper_files_id,
     const std::string& file_name,
     ash::WallpaperLayout layout,
     const gfx::ImageSkia& image) {
   return IsKnownUser(account_id) &&
-         wallpaper_controller_->SetThirdPartyWallpaper(
-             account_id, wallpaper_files_id, file_name, layout, image);
+         wallpaper_controller_->SetThirdPartyWallpaper(account_id, file_name,
+                                                       layout, image);
 }
 
 void WallpaperControllerClientImpl::ConfirmPreviewWallpaper() {
@@ -415,19 +396,7 @@
   if (!IsKnownUser(account_id))
     return;
 
-  // Postpone removing the wallpaper until we can get files id.
-  if (!CanGetFilesId()) {
-    LOG(WARNING)
-        << "Cannot get wallpaper files id in RemoveUserWallpaper. This "
-           "should never happen under normal circumstances.";
-    AddCanGetFilesIdCallback(
-        base::BindOnce(&WallpaperControllerClientImpl::RemoveUserWallpaper,
-                       weak_factory_.GetWeakPtr(), account_id));
-    return;
-  }
-
-  wallpaper_controller_->RemoveUserWallpaper(account_id,
-                                             GetFilesId(account_id));
+  wallpaper_controller_->RemoveUserWallpaper(account_id);
 }
 
 void WallpaperControllerClientImpl::RemovePolicyWallpaper(
@@ -435,19 +404,7 @@
   if (!IsKnownUser(account_id))
     return;
 
-  // Postpone removing the wallpaper until we can get files id.
-  if (!CanGetFilesId()) {
-    LOG(WARNING)
-        << "Cannot get wallpaper files id in RemovePolicyWallpaper. This "
-           "should never happen under normal circumstances.";
-    AddCanGetFilesIdCallback(
-        base::BindOnce(&WallpaperControllerClientImpl::RemovePolicyWallpaper,
-                       weak_factory_.GetWeakPtr(), account_id));
-    return;
-  }
-
-  wallpaper_controller_->RemovePolicyWallpaper(account_id,
-                                               GetFilesId(account_id));
+  wallpaper_controller_->RemovePolicyWallpaper(account_id);
 }
 
 void WallpaperControllerClientImpl::GetOfflineWallpaperList(
@@ -540,6 +497,13 @@
   return success;
 }
 
+void WallpaperControllerClientImpl::GetFilesId(
+    const AccountId& account_id,
+    base::OnceCallback<void(const std::string&)> files_id_callback) const {
+  chromeos::SystemSaltGetter::Get()->AddOnSystemSaltReady(base::BindOnce(
+      &GetFilesIdSaltReady, account_id, std::move(files_id_callback)));
+}
+
 void WallpaperControllerClientImpl::ActiveUserChanged(
     user_manager::User* active_user) {
   active_user->AddProfileCreatedObserver(base::BindOnce(
@@ -643,19 +607,7 @@
   if (!IsKnownUser(account_id))
     return;
 
-  // Postpone setting the wallpaper until we can get files id.
-  if (!CanGetFilesId()) {
-    LOG(WARNING)
-        << "Cannot get wallpaper files id in SetDefaultWallpaper. This "
-           "should never happen under normal circumstances.";
-    AddCanGetFilesIdCallback(
-        base::BindOnce(&WallpaperControllerClient::SetDefaultWallpaper,
-                       weak_factory_.GetWeakPtr(), account_id, show_wallpaper));
-    return;
-  }
-
-  wallpaper_controller_->SetDefaultWallpaper(account_id, GetFilesId(account_id),
-                                             show_wallpaper);
+  wallpaper_controller_->SetDefaultWallpaper(account_id, show_wallpaper);
 }
 
 void WallpaperControllerClientImpl::MigrateCollectionIdFromChromeApp() {
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_impl.h b/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
index 02e8fdd..5d4be10 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
+++ b/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
@@ -50,9 +50,6 @@
 
   static WallpaperControllerClientImpl* Get();
 
-  // Returns files identifier for the |account_id|.
-  std::string GetFilesId(const AccountId& account_id) const;
-
   // ash::WallpaperControllerClient:
   void OpenWallpaperPicker() override;
   void MaybeClosePreviewWallpaper() override;
@@ -64,6 +61,9 @@
       DailyWallpaperUrlFetchedCallback callback) override;
   bool SaveWallpaperToDriveFs(const AccountId& account_id,
                               const base::FilePath& origin) override;
+  void GetFilesId(const AccountId& account_id,
+                  base::OnceCallback<void(const std::string&)>
+                      files_id_callback) const override;
 
   // user_manager::UserManager::UserSessionStateObserver:
   void ActiveUserChanged(user_manager::User* active_user) override;
@@ -74,7 +74,6 @@
 
   // Wrappers around the ash::WallpaperController interface.
   void SetCustomWallpaper(const AccountId& account_id,
-                          const std::string& wallpaper_files_id,
                           const std::string& file_name,
                           ash::WallpaperLayout layout,
                           const gfx::ImageSkia& image,
@@ -95,7 +94,6 @@
   void SetPolicyWallpaper(const AccountId& account_id,
                           std::unique_ptr<std::string> data);
   bool SetThirdPartyWallpaper(const AccountId& account_id,
-                              const std::string& wallpaper_files_id,
                               const std::string& file_name,
                               ash::WallpaperLayout layout,
                               const gfx::ImageSkia& image);
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index 18ef118..11b5d97 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -37,6 +37,7 @@
 #include "chrome/browser/media/history/media_history_contents_observer.h"
 #include "chrome/browser/media/media_engagement_service.h"
 #include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_observer.h"
+#include "chrome/browser/metrics/metrics_services_web_contents_observer.h"
 #include "chrome/browser/metrics/oom/out_of_memory_reporter.h"
 #include "chrome/browser/navigation_predictor/navigation_predictor_preconnect_client.h"
 #include "chrome/browser/net/net_error_tab_helper.h"
@@ -281,6 +282,8 @@
     MediaEngagementService::CreateWebContentsObserver(web_contents);
   if (base::FeatureList::IsEnabled(media::kUseMediaHistoryStore))
     MediaHistoryContentsObserver::CreateForWebContents(web_contents);
+  metrics::MetricsServicesWebContentsObserver::CreateForWebContents(
+      web_contents);
   MixedContentSettingsTabHelper::CreateForWebContents(web_contents);
   NavigationMetricsRecorder::CreateForWebContents(web_contents);
   NavigationPredictorPreconnectClient::CreateForWebContents(web_contents);
diff --git a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc
index 9f4d6e6..5f2f4e9d 100644
--- a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc
+++ b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc
@@ -4,11 +4,19 @@
 
 #include "chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h"
 
+#include "base/strings/strcat.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/grit/generated_resources.h"
+#include "content/public/browser/download_manager.h"
+#include "content/public/browser/download_request_utils.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/webui/web_ui_util.h"
+#include "ui/resources/grit/ui_resources.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/button/md_text_button.h"
@@ -45,7 +53,9 @@
     views::View* anchor_view,
     content::WebContents* web_contents,
     const gfx::Image& image)
-    : LocationBarBubbleDelegateView(anchor_view, nullptr), image_(image) {
+    : LocationBarBubbleDelegateView(anchor_view, nullptr),
+      image_(image),
+      web_contents_(web_contents) {
   SetButtons(ui::DIALOG_BUTTON_NONE);
   SetTitle(IDS_BROWSER_SHARING_SCREENSHOT_POST_CAPTURE_TITLE);
 }
@@ -112,7 +122,7 @@
 
   // Edit button.
   auto edit_button = std::make_unique<views::MdTextButton>(
-      base::BindRepeating(&ScreenshotCapturedBubble::DownloadButtonPressed,
+      base::BindRepeating(&ScreenshotCapturedBubble::EditButtonPressed,
                           base::Unretained(this)),
       l10n_util::GetStringUTF16(
           IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_EDIT_BUTTON_LABEL));
@@ -120,7 +130,7 @@
 
   // Share button.
   auto share_button = std::make_unique<views::MdTextButton>(
-      base::BindRepeating(&ScreenshotCapturedBubble::DownloadButtonPressed,
+      base::BindRepeating(&ScreenshotCapturedBubble::ShareButtonPressed,
                           base::Unretained(this)),
       l10n_util::GetStringUTF16(
           IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_SHARE_BUTTON_LABEL));
@@ -173,8 +183,57 @@
   // End controls row
 }
 
+/*static*/
+const std::u16string ScreenshotCapturedBubble::GetFilenameForURL(
+    const GURL& url) {
+  if (!url.has_host() || url.HostIsIPAddress())
+    return u"chrome_screenshot.png";
+
+  return base::ASCIIToUTF16(
+      base::StrCat({"chrome_screenshot_", url.host(), ".png"}));
+}
+
 void ScreenshotCapturedBubble::DownloadButtonPressed() {
-  NOTIMPLEMENTED();
+  // Returns closest scaling to parameter (1.0).
+  const gfx::ImageSkia& image_ref = image_view_->GetImage();
+  const gfx::ImageSkiaRep& image_rep = image_ref.GetRepresentation(1.0f);
+  const SkBitmap& bitmap = image_rep.GetBitmap();
+  const GURL data_url = GURL(webui::GetBitmapDataUrl(bitmap));
+
+  Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
+  content::DownloadManager* download_manager =
+      browser->profile()->GetDownloadManager();
+  // TODO(crbug.com/1186839): Update the annotation's |setting| and
+  // |chrome_policy| fields once the Sharing Hub is landed.
+  net::NetworkTrafficAnnotationTag traffic_annotation =
+      net::DefineNetworkTrafficAnnotation("desktop_screenshot_save", R"(
+      semantics {
+        sender: "Desktop Screenshots"
+        description:
+          "The user may capture a selection of the current page. This bubble "
+          "view has a download button to save the generated image to disk. "
+        trigger: "User clicks 'download' in a bubble view launched from the "
+          "omnibox after the 'Screenshot' option is selected in the sharing "
+          "hub and a selection is made on the page. "
+        data: "A capture of a portion of the current webpage."
+        destination: LOCAL
+      }
+      policy {
+        cookies_allowed: NO
+        setting:
+          "No user-visible setting for this feature. Experiment and rollout to "
+          "be coordinated via Chrome Variations."
+        policy_exception_justification:
+          "Not implemented, considered not required."
+      })");
+  std::unique_ptr<download::DownloadUrlParameters> params =
+      content::DownloadRequestUtils::CreateDownloadForWebContentsMainFrame(
+          web_contents_, data_url, traffic_annotation);
+  // Suggest a name incorporating the hostname. Protocol, TLD, etc are
+  // not taken into consideration. Duplicate names get automatic suffixes.
+  params->set_suggested_name(
+      GetFilenameForURL(web_contents_->GetLastCommittedURL()));
+  download_manager->DownloadUrl(std::move(params));
 }
 
 void ScreenshotCapturedBubble::ShareButtonPressed() {
diff --git a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h
index 90a9110..f495921 100644
--- a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h
+++ b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h
@@ -42,6 +42,8 @@
   bool ShouldShowCloseButton() const override;
   void WindowClosing() override;
 
+  static const std::u16string GetFilenameForURL(const GURL& url);
+
   // views::BubbleDialogDelegateView:
   void Init() override;
 
@@ -53,6 +55,8 @@
 
   const gfx::Image& image_;
 
+  content::WebContents* web_contents_;
+
   // Pointers to view widgets; weak.
   views::ImageView* image_view_ = nullptr;
   views::MdTextButton* download_button_ = nullptr;
diff --git a/chrome/browser/ui/views/toolbar/reload_button.cc b/chrome/browser/ui/views/toolbar/reload_button.cc
index fd2b95048..73d9b015 100644
--- a/chrome/browser/ui/views/toolbar/reload_button.cc
+++ b/chrome/browser/ui/views/toolbar/reload_button.cc
@@ -12,6 +12,7 @@
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/command_updater.h"
+#include "chrome/browser/external_protocol/external_protocol_handler.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/grit/generated_resources.h"
@@ -161,6 +162,15 @@
 }
 
 void ReloadButton::ButtonPressed(const ui::Event& event) {
+  // This is called in order to signal that external protocol dialogs are
+  // allowed to show due to a user action, which are likely to happen on the
+  // next page load after the reload button is clicked.
+  // Ideally, the browser UI's event system would notify ExternalProtocolHandler
+  // that a user action occurred and we are OK to open the dialog, but for some
+  // reason that isn't happening every time the reload button is clicked. See
+  // http://crbug.com/1206456
+  ExternalProtocolHandler::PermitLaunchUrl();
+
   ClearPendingMenu();
 
   if (visible_mode_ == Mode::kStop) {
diff --git a/chrome/browser/ui/views/toolbar/reload_button_browsertest.cc b/chrome/browser/ui/views/toolbar/reload_button_browsertest.cc
new file mode 100644
index 0000000..abee95f
--- /dev/null
+++ b/chrome/browser/ui/views/toolbar/reload_button_browsertest.cc
@@ -0,0 +1,42 @@
+// Copyright 2021 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/external_protocol/external_protocol_handler.h"
+#include "chrome/browser/ui/view_ids.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/toolbar/reload_button.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#include "content/public/test/browser_test.h"
+
+class ReloadButtonBrowserTest : public InProcessBrowserTest {
+ public:
+  ReloadButtonBrowserTest() = default;
+
+  content::WebContents* GetWebContents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(ReloadButtonBrowserTest, AllowExternalProtocols) {
+  const char fake_protocol[] = "fake";
+
+  // Call LaunchUrl once to trigger the blocked state.
+  GURL url("fake://example.test");
+  ExternalProtocolHandler::LaunchUrl(
+      url,
+      base::BindRepeating(&ReloadButtonBrowserTest::GetWebContents,
+                          base::Unretained(this)),
+      ui::PAGE_TRANSITION_LINK, true, url::Origin::Create(url));
+  ASSERT_EQ(ExternalProtocolHandler::BLOCK,
+            ExternalProtocolHandler::GetBlockState(fake_protocol, nullptr,
+                                                   browser()->profile()));
+
+  // Clicking the reload button should remove the blocked state.
+  ui_test_utils::ClickOnView(browser(), VIEW_ID_RELOAD_BUTTON);
+  ASSERT_NE(ExternalProtocolHandler::BLOCK,
+            ExternalProtocolHandler::GetBlockState(fake_protocol, nullptr,
+                                                   browser()->profile()));
+}
diff --git a/chrome/browser/ui/web_applications/app_browser_controller.cc b/chrome/browser/ui/web_applications/app_browser_controller.cc
index 45bc007..5cc3800 100644
--- a/chrome/browser/ui/web_applications/app_browser_controller.cc
+++ b/chrome/browser/ui/web_applications/app_browser_controller.cc
@@ -161,20 +161,20 @@
       browser_(browser),
       theme_provider_(
           ThemeService::CreateBoundThemeProvider(browser_->profile(), this)),
-      system_app_type_(
-          HasAppId()
-              ? GetSystemWebAppTypeForAppId(browser_->profile(), GetAppId())
-              : absl::nullopt),
-      provider_(system_app_type_
-                    ? WebAppProvider::GetForSystemWebApps(browser_->profile())
-                    : WebAppProvider::GetForWebApps(browser_->profile())),
+      system_app_type_(HasAppId() ? WebAppProvider::Get(browser->profile())
+                                        ->system_web_app_manager()
+                                        .GetSystemAppTypeForAppId(GetAppId())
+                                  : absl::nullopt),
       has_tab_strip_(
-          (system_app_type_ &&
-           provider_->system_web_app_manager().ShouldHaveTabStrip(
-               system_app_type_.value())) ||
+          (system_app_type_.has_value() &&
+           WebAppProvider::Get(browser->profile())
+               ->system_web_app_manager()
+               .ShouldHaveTabStrip(system_app_type_.value())) ||
           (base::FeatureList::IsEnabled(features::kDesktopPWAsTabStrip) &&
            HasAppId() &&
-           provider_->registrar().IsTabbedWindowModeEnabled(GetAppId()))) {
+           WebAppProvider::Get(browser->profile())
+               ->registrar()
+               .IsTabbedWindowModeEnabled(GetAppId()))) {
   browser->tab_strip_model()->AddObserver(this);
 }
 
@@ -330,8 +330,9 @@
   if (!system_app_type_)
     return true;
 
-  return provider_->system_web_app_manager().ShouldHaveReloadButtonInMinimalUi(
-      system_app_type_.value());
+  return WebAppProvider::Get(browser()->profile())
+      ->system_web_app_manager()
+      .ShouldHaveReloadButtonInMinimalUi(system_app_type_.value());
 }
 
 std::u16string AppBrowserController::GetLaunchFlashText() const {
@@ -367,8 +368,9 @@
 
 gfx::Rect AppBrowserController::GetDefaultBounds() const {
   if (system_app_type_.has_value()) {
-    return provider_->system_web_app_manager().GetDefaultBounds(
-        system_app_type_.value(), browser());
+    return WebAppProvider::Get(browser()->profile())
+        ->system_web_app_manager()
+        .GetDefaultBounds(system_app_type_.value(), browser());
   }
 
   return gfx::Rect();
@@ -464,7 +466,9 @@
     return raw_title;
 
   std::u16string app_name =
-      base::UTF8ToUTF16(provider_->registrar().GetAppShortName(GetAppId()));
+      base::UTF8ToUTF16(WebAppProvider::Get(browser()->profile())
+                            ->registrar()
+                            .GetAppShortName(GetAppId()));
   if (base::StartsWith(raw_title, app_name)) {
     return raw_title;
   } else if (raw_title.empty()) {
diff --git a/chrome/browser/ui/web_applications/app_browser_controller.h b/chrome/browser/ui/web_applications/app_browser_controller.h
index 9d1b129d..4bb72df57 100644
--- a/chrome/browser/ui/web_applications/app_browser_controller.h
+++ b/chrome/browser/ui/web_applications/app_browser_controller.h
@@ -36,7 +36,6 @@
 namespace web_app {
 
 class WebAppBrowserController;
-class WebAppProvider;
 enum class SystemAppType;
 
 // Returns true if |app_url| and |page_url| are the same origin. To avoid
@@ -245,7 +244,6 @@
   absl::optional<SkColor> last_background_color_;
 
   absl::optional<SystemAppType> system_app_type_;
-  WebAppProvider* provider_;
 
   const bool has_tab_strip_;
 
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index ff0037c..d1ae373 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -778,7 +778,7 @@
   if (url.host_piece() == chrome::kChromeUIAddSupervisionHost)
     return &NewWebUI<chromeos::AddSupervisionUI>;
   if (url.host_piece() == chrome::kChromeUIAudioHost)
-    return &NewWebUI<AudioUI>;
+    return &NewWebUI<chromeos::AudioUI>;
   if (url.host_piece() == chrome::kChromeUIBluetoothPairingHost)
     return &NewWebUI<chromeos::BluetoothPairingDialogUI>;
 // TODO(crbug.com/1147032): The certificates settings page is temporarily
diff --git a/chrome/browser/ui/webui/chromeos/audio/BUILD.gn b/chrome/browser/ui/webui/chromeos/audio/BUILD.gn
new file mode 100644
index 0000000..9b051c33
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/audio/BUILD.gn
@@ -0,0 +1,12 @@
+# Copyright 2021 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("//mojo/public/tools/bindings/mojom.gni")
+
+assert(is_chromeos, "Audio WebUI is Chrome OS only.")
+
+mojom("mojo_bindings") {
+  sources = [ "audio.mojom" ]
+  webui_module_path = "/"
+}
diff --git a/chrome/browser/ui/webui/chromeos/audio/OWNERS b/chrome/browser/ui/webui/chromeos/audio/OWNERS
index 2ac9a65..f6042c8 100644
--- a/chrome/browser/ui/webui/chromeos/audio/OWNERS
+++ b/chrome/browser/ui/webui/chromeos/audio/OWNERS
@@ -1,2 +1,5 @@
 yuhsuan@chromium.org
 hychao@chromium.org
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio.mojom b/chrome/browser/ui/webui/chromeos/audio/audio.mojom
new file mode 100644
index 0000000..b5460eb
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/audio/audio.mojom
@@ -0,0 +1,27 @@
+// Copyright 2021 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.
+
+module audio.mojom;
+
+// Interface for creating page handler
+interface PageHandlerFactory {
+    // Creates a page handler to update and respond to requests or
+    // audio device information
+    CreatePageHandler(pending_remote<Page> page,
+                      pending_receiver<PageHandler> handler);
+};
+
+// Interface for browser-side handler
+// Respond to calls from the WebUI page.
+interface PageHandler {
+    // Request to get audio device information from the browser side
+    GetAudioDeviceInfo() => (string device_name);
+};
+
+// Interface for the WebUI page
+// Respond to calls from the browser end
+interface Page {
+    // Updates the page with new audio device information
+    UpdateDeviceInfo(string device_name);
+};
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio_handler.cc b/chrome/browser/ui/webui/chromeos/audio/audio_handler.cc
new file mode 100644
index 0000000..679126e9
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/audio/audio_handler.cc
@@ -0,0 +1,25 @@
+// Copyright 2021 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 <string>
+#include <utility>
+
+#include "chrome/browser/ui/webui/chromeos/audio/audio_handler.h"
+
+namespace chromeos {
+
+AudioHandler::AudioHandler(
+    mojo::PendingReceiver<audio::mojom::PageHandler> receiver,
+    mojo::PendingRemote<audio::mojom::Page> page)
+    : page_(std::move(page)), receiver_(this, std::move(receiver)) {}
+
+AudioHandler::~AudioHandler() = default;
+
+void AudioHandler::GetAudioDeviceInfo(
+    audio::mojom::PageHandler::GetAudioDeviceInfoCallback callback) {
+  const std::string mock_device_name = "mock";
+  std::move(callback).Run(mock_device_name);
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio_handler.h b/chrome/browser/ui/webui/chromeos/audio/audio_handler.h
new file mode 100644
index 0000000..99ce62c
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/audio/audio_handler.h
@@ -0,0 +1,33 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_AUDIO_AUDIO_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_AUDIO_AUDIO_HANDLER_H_
+
+#include "chrome/browser/ui/webui/chromeos/audio/audio.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace chromeos {
+
+class AudioHandler : public audio::mojom::PageHandler {
+ public:
+  AudioHandler(mojo::PendingReceiver<audio::mojom::PageHandler> receiver,
+               mojo::PendingRemote<audio::mojom::Page> page);
+  AudioHandler(const AudioHandler&) = delete;
+  AudioHandler& operator=(const AudioHandler&) = delete;
+  ~AudioHandler() override;
+
+  void GetAudioDeviceInfo(GetAudioDeviceInfoCallback callback) override;
+
+ private:
+  mojo::Remote<audio::mojom::Page> page_;
+  mojo::Receiver<audio::mojom::PageHandler> receiver_;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_AUDIO_AUDIO_HANDLER_H_
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio_ui.cc b/chrome/browser/ui/webui/chromeos/audio/audio_ui.cc
index 2b219d9..df08426 100644
--- a/chrome/browser/ui/webui/chromeos/audio/audio_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/audio/audio_ui.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/browser/ui/webui/chromeos/audio/audio_ui.h"
 
+#include <memory>
+#include <utility>
+
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/audio_resources.h"
@@ -13,7 +16,9 @@
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_data_source.h"
 
-AudioUI::AudioUI(content::WebUI* web_ui) : content::WebUIController(web_ui) {
+namespace chromeos {
+
+AudioUI::AudioUI(content::WebUI* web_ui) : ui::MojoWebUIController(web_ui) {
   // Set up the chrome://audio source.
   content::WebUIDataSource* html_source =
       content::WebUIDataSource::Create(chrome::kChromeUIAudioHost);
@@ -27,4 +32,21 @@
   content::WebUIDataSource::Add(browser_context, html_source);
 }
 
+WEB_UI_CONTROLLER_TYPE_IMPL(AudioUI)
+
 AudioUI::~AudioUI() = default;
+
+void AudioUI::BindInterface(
+    mojo::PendingReceiver<audio::mojom::PageHandlerFactory> receiver) {
+  factory_receiver_.reset();
+  factory_receiver_.Bind(std::move(receiver));
+}
+
+void AudioUI::CreatePageHandler(
+    mojo::PendingRemote<audio::mojom::Page> page,
+    mojo::PendingReceiver<audio::mojom::PageHandler> receiver) {
+  page_handler_ =
+      std::make_unique<AudioHandler>(std::move(receiver), std::move(page));
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/audio/audio_ui.h b/chrome/browser/ui/webui/chromeos/audio/audio_ui.h
index 35583f6..4c9dc82 100644
--- a/chrome/browser/ui/webui/chromeos/audio/audio_ui.h
+++ b/chrome/browser/ui/webui/chromeos/audio/audio_ui.h
@@ -5,15 +5,37 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_AUDIO_AUDIO_UI_H_
 #define CHROME_BROWSER_UI_WEBUI_CHROMEOS_AUDIO_AUDIO_UI_H_
 
-#include "content/public/browser/web_ui_controller.h"
+#include "chrome/browser/ui/webui/chromeos/audio/audio.mojom.h"
+#include "chrome/browser/ui/webui/chromeos/audio/audio_handler.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "ui/webui/mojo_web_ui_controller.h"
 
-// The WebUI for chrome://audio
-class AudioUI : public content::WebUIController {
+namespace chromeos {
+
+// The WebUI Controller for chrome://audio
+class AudioUI : public ui::MojoWebUIController,
+                public audio::mojom::PageHandlerFactory {
  public:
   explicit AudioUI(content::WebUI* web_ui);
   AudioUI(const AudioUI&) = delete;
   AudioUI& operator=(const AudioUI&) = delete;
   ~AudioUI() override;
+
+  void BindInterface(
+      mojo::PendingReceiver<audio::mojom::PageHandlerFactory> receiver);
+
+ private:
+  // audio::mojom::PageHandlerFactory
+  void CreatePageHandler(
+      mojo::PendingRemote<audio::mojom::Page> page,
+      mojo::PendingReceiver<audio::mojom::PageHandler> receiver) override;
+  std::unique_ptr<AudioHandler> page_handler_;
+  mojo::Receiver<audio::mojom::PageHandlerFactory> factory_receiver_{this};
+  WEB_UI_CONTROLLER_TYPE_DECL();
 };
 
+}  // namespace chromeos
+
 #endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_AUDIO_AUDIO_UI_H_
diff --git a/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui_browsertest.cc b/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui_browsertest.cc
index 79b9676..c0b3138 100644
--- a/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui_browsertest.cc
@@ -4,7 +4,7 @@
 
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
-#include "chrome/browser/sharesheet/sharesheet_types.h"
+#include "chrome/browser/sharesheet/sharesheet_controller.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.h"
@@ -21,7 +21,6 @@
 class TestSharesheetController : public sharesheet::SharesheetController {
  public:
   // sharesheet::SharesheetController
-  Profile* GetProfile() override { return nullptr; }
   void SetBubbleSize(int width, int height) override {}
   void CloseBubble(::sharesheet::SharesheetResult result) override {
     // The NearbyShareDialogUI can only invoke this method with a cancellation
diff --git a/chrome/browser/ui/webui/settings/chromeos/bluetooth_section.cc b/chrome/browser/ui/webui/settings/chromeos/bluetooth_section.cc
index 002c53e..ab2e123 100644
--- a/chrome/browser/ui/webui/settings/chromeos/bluetooth_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/bluetooth_section.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "device/bluetooth/bluetooth_adapter_factory.h"
 #include "device/bluetooth/bluetooth_device.h"
@@ -201,6 +202,10 @@
        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_KEYBOARD_MOUSE_COMBO},
       {"bluetoothDeviceType_unknown",
        IDS_BLUETOOTH_ACCESSIBILITY_DEVICE_TYPE_UNKNOWN},
+      {"bluetoothDeviceListCurrentlyConnected",
+       IDS_BLUETOOTH_DEVICE_LIST_CURRENTLY_CONNECTED},
+      {"bluetoothDeviceListPreviouslyConnected",
+       IDS_BLUETOOTH_DEVICE_LIST_PREVIOUSLY_CONNECTED},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
   chromeos::bluetooth::AddLoadTimeData(html_source);
diff --git a/chrome/browser/ui/webui/whats_new/whats_new_handler.cc b/chrome/browser/ui/webui/whats_new/whats_new_handler.cc
index 978671ad..bcbd21c 100644
--- a/chrome/browser/ui/webui/whats_new/whats_new_handler.cc
+++ b/chrome/browser/ui/webui/whats_new/whats_new_handler.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/webui/whats_new/whats_new_handler.h"
 
 #include "base/bind.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
@@ -25,6 +26,28 @@
 
 const int64_t kMaxDownloadBytes = 1024 * 1024;
 
+namespace whats_new {
+
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class LoadEvent {
+  kLoadStart = 0,
+  kLoadSuccess = 1,
+  kLoadFailAndShowError = 2,
+  kLoadFailAndFallbackToNtp = 3,
+  kMaxValue = kLoadFailAndFallbackToNtp,
+};
+
+}  // namespace whats_new
+
+namespace {
+
+void LogLoadEvent(whats_new::LoadEvent event) {
+  base::UmaHistogramEnumeration("WhatsNew.LoadEvent", event);
+}
+
+}  // namespace
+
 WhatsNewHandler::WhatsNewHandler() = default;
 
 WhatsNewHandler::~WhatsNewHandler() = default;
@@ -63,6 +86,7 @@
 }
 
 void WhatsNewHandler::Fetch(const GURL& url, OnFetchResultCallback on_result) {
+  LogLoadEvent(whats_new::LoadEvent::kLoadStart);
   auto traffic_annotation =
       net::DefineNetworkTrafficAnnotation("whats_new_handler", R"(
         semantics {
@@ -124,12 +148,15 @@
     if (!browser)
       return;
 
+    LogLoadEvent(whats_new::LoadEvent::kLoadFailAndFallbackToNtp);
     content::OpenURLParams params(GURL(chrome::kChromeUINewTabPageURL),
                                   content::Referrer(),
                                   WindowOpenDisposition::CURRENT_TAB,
                                   ui::PAGE_TRANSITION_AUTO_BOOKMARK, false);
     browser->OpenURL(params);
   } else {
+    LogLoadEvent(success ? whats_new::LoadEvent::kLoadSuccess
+                         : whats_new::LoadEvent::kLoadFailAndShowError);
     // Update pref if successfully shown automatically.
     if (success && is_auto) {
       whats_new::SetLastVersion(g_browser_process->local_state());
diff --git a/chrome/browser/usb/web_usb_detector.cc b/chrome/browser/usb/web_usb_detector.cc
index 340754c..b35d1d1a 100644
--- a/chrome/browser/usb/web_usb_detector.cc
+++ b/chrome/browser/usb/web_usb_detector.cc
@@ -184,6 +184,14 @@
 WebUsbDetector::~WebUsbDetector() = default;
 
 void WebUsbDetector::Initialize() {
+#if defined(OS_WIN)
+  // The WebUSB device detector is disabled on Windows due to jank and hangs
+  // caused by enumerating devices. The new USB backend is designed to resolve
+  // these issues so enable it for testing. https://crbug.com/656702
+  if (!base::FeatureList::IsEnabled(device::kNewUsbBackend))
+    return;
+#endif  // defined(OS_WIN)
+
   // Tests may set a fake manager.
   if (!device_manager_) {
     // Receive mojo::Remote<UsbDeviceManager> from DeviceService.
diff --git a/chrome/services/file_util/public/cpp/zip_file_creator.cc b/chrome/services/file_util/public/cpp/zip_file_creator.cc
index 93461829..6b68aca 100644
--- a/chrome/services/file_util/public/cpp/zip_file_creator.cc
+++ b/chrome/services/file_util/public/cpp/zip_file_creator.cc
@@ -87,10 +87,12 @@
   remote_zip_file_creator_.set_disconnect_handler(
       base::BindOnce(&ZipFileCreator::ReportResult, this, kError));
 
-  remote_zip_file_creator_->CreateZipFile(
-      std::move(directory), src_relative_paths_, std::move(file),
-      listener_.BindNewPipeAndPassRemote(),
-      base::BindOnce(&ZipFileCreator::OnFinished, this));
+  remote_zip_file_creator_->CreateZipFile(std::move(directory),
+                                          src_relative_paths_, std::move(file),
+                                          listener_.BindNewPipeAndPassRemote());
+
+  listener_.set_disconnect_handler(
+      base::BindOnce(&ZipFileCreator::ReportResult, this, kError));
 }
 
 void ZipFileCreator::BindDirectory(
diff --git a/chrome/services/file_util/public/cpp/zip_file_creator.h b/chrome/services/file_util/public/cpp/zip_file_creator.h
index bf8d039..5e70f89 100644
--- a/chrome/services/file_util/public/cpp/zip_file_creator.h
+++ b/chrome/services/file_util/public/cpp/zip_file_creator.h
@@ -67,7 +67,7 @@
       mojo::PendingReceiver<filesystem::mojom::Directory> receiver) const;
 
   // Called when the ZipFileCreator service finished.
-  void OnFinished(bool success);
+  void OnFinished(bool success) override;
 
   // Notifies by calling |result_callback| specified in the constructor the end
   // of the ZIP operation.
diff --git a/chrome/services/file_util/public/mojom/zip_file_creator.mojom b/chrome/services/file_util/public/mojom/zip_file_creator.mojom
index e66e6378..c9c393d 100644
--- a/chrome/services/file_util/public/mojom/zip_file_creator.mojom
+++ b/chrome/services/file_util/public/mojom/zip_file_creator.mojom
@@ -16,6 +16,9 @@
   // Regularly called during ZIP creation operation to report progress, with the
   // total number of bytes, files and directories processed so far.
   OnProgress(uint64 bytes, uint32 files, uint32 directories);
+
+  // Called once after the ZIP operation finished.
+  OnFinished(bool success);
 };
 
 // Service that zips files and folders.
@@ -28,6 +31,5 @@
   CreateZipFile(pending_remote<filesystem.mojom.Directory> src_dir,
                 array<mojo_base.mojom.FilePath> relative_paths,
                 mojo_base.mojom.File zip_file,
-                pending_remote<ZipListener> listener)
-      => (bool success);
+                pending_remote<ZipListener> listener);
 };
diff --git a/chrome/services/file_util/zip_file_creator.cc b/chrome/services/file_util/zip_file_creator.cc
index 3a5f4d05..2281eb6 100644
--- a/chrome/services/file_util/zip_file_creator.cc
+++ b/chrome/services/file_util/zip_file_creator.cc
@@ -155,42 +155,42 @@
     PendingDirectory src_dir,
     const std::vector<base::FilePath>& relative_paths,
     base::File zip_file,
-    PendingListener listener,
-    CreateZipFileCallback callback) {
+    PendingListener listener) {
   DCHECK(zip_file.IsValid());
 
   for (const base::FilePath& path : relative_paths) {
     if (path.IsAbsolute() || path.ReferencesParent()) {
       // Paths are expected to be relative. If there are not, the API is used
       // incorrectly and this is an error.
-      std::move(callback).Run(/*success=*/false);
+      Listener(std::move(listener))->OnFinished(/*success=*/false);
       return;
     }
   }
 
-  runner_->PostTaskAndReplyWithResult(
-      FROM_HERE,
-      base::BindOnce(&ZipFileCreator::WriteZipFile, this, std::move(src_dir),
-                     std::move(relative_paths), std::move(zip_file),
-                     std::move(listener)),
-      std::move(callback));
+  runner_->PostTask(
+      FROM_HERE, base::BindOnce(&ZipFileCreator::WriteZipFile, this,
+                                std::move(src_dir), std::move(relative_paths),
+                                std::move(zip_file), std::move(listener)));
 }
 
-bool ZipFileCreator::WriteZipFile(
+void ZipFileCreator::WriteZipFile(
     PendingDirectory src_dir,
     const std::vector<base::FilePath>& relative_paths,
     base::File zip_file,
-    PendingListener listener) const {
+    PendingListener pending_listener) const {
   MojoFileAccessor file_accessor(std::move(src_dir));
-  return zip::Zip({
+  const Listener listener(std::move(pending_listener));
+  const bool success = zip::Zip({
       .file_accessor = &file_accessor,
       .dest_fd = zip_file.GetPlatformFile(),
       .src_files = relative_paths,
-      .progress_callback = base::BindRepeating(
-          &ZipFileCreator::OnProgress, this, Listener(std::move(listener))),
+      .progress_callback = base::BindRepeating(&ZipFileCreator::OnProgress,
+                                               this, std::cref(listener)),
       .progress_period = base::TimeDelta::FromMilliseconds(1000),
       .recursive = true,
   });
+
+  listener->OnFinished(success);
 }
 
 bool ZipFileCreator::OnProgress(const Listener& listener,
diff --git a/chrome/services/file_util/zip_file_creator.h b/chrome/services/file_util/zip_file_creator.h
index 92663fc..c8c1ada 100644
--- a/chrome/services/file_util/zip_file_creator.h
+++ b/chrome/services/file_util/zip_file_creator.h
@@ -47,12 +47,11 @@
   void CreateZipFile(PendingDirectory src_dir,
                      const std::vector<base::FilePath>& relative_paths,
                      base::File zip_file,
-                     PendingListener listener,
-                     CreateZipFileCallback callback) override;
+                     PendingListener listener) override;
 
   // Zips |src_dir| files given by |relative_paths| into |zip_file|.
   // Must be run in a separate task runner.
-  bool WriteZipFile(PendingDirectory src_dir,
+  void WriteZipFile(PendingDirectory src_dir,
                     const std::vector<base::FilePath>& relative_paths,
                     base::File zip_file,
                     PendingListener listener) const;
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 251019c..907532e 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1787,6 +1787,7 @@
       "../browser/ssl/ocsp_browsertest.cc",
       "../browser/ssl/security_state_tab_helper_browsertest.cc",
       "../browser/ssl/ssl_browsertest.cc",
+      "../browser/ssl/ssl_prerender_browsertest.cc",
       "../browser/ssl/stateful_ssl_host_state_delegate_test.cc",
       "../browser/storage/durable_storage_browsertest.cc",
       "../browser/storage_access_api/api_browsertest.cc",
@@ -8102,6 +8103,7 @@
         "../browser/ui/views/tabs/tab_hover_card_bubble_view_interactive_uitest.cc",
         "../browser/ui/views/test/view_event_test_base.cc",
         "../browser/ui/views/test/view_event_test_base.h",
+        "../browser/ui/views/toolbar/reload_button_browsertest.cc",
         "../browser/ui/views/toolbar/toolbar_view_interactive_uitest.cc",
         "../browser/ui/views/translate/translate_bubble_test_utils_views.cc",
         "../browser/ui/views/translate/translate_bubble_view_interactive_uitest.cc",
@@ -9448,6 +9450,10 @@
       "//chrome/test/chromedriver",
       "//testing:test_scripts_shared",
     ]
-    data = [ "//testing/scripts/run_variations_smoke_tests.py" ]
+    data = [
+      "//testing/scripts/run_variations_smoke_tests.py",
+      "//testing/scripts/variations_smoke_test_data/",
+      "//third_party/webdriver/pylib/",
+    ]
   }
 }
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ChromeTabUtils.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ChromeTabUtils.java
index c9d07cb..0266f38 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ChromeTabUtils.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ChromeTabUtils.java
@@ -747,15 +747,17 @@
         final CallbackHelper createdCallback = new CallbackHelper();
         final TabModel tabModel =
                 testRule.getActivity().getTabModelSelector().getModel(expectIncognito);
-        tabModel.addObserver(new TabModelObserver() {
-            @Override
-            public void didAddTab(
-                    Tab tab, @TabLaunchType int type, @TabCreationState int creationState) {
-                if (TextUtils.equals(expectedUrl, tab.getUrl().getSpec())) {
-                    createdCallback.notifyCalled();
-                    tabModel.removeObserver(this);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tabModel.addObserver(new TabModelObserver() {
+                @Override
+                public void didAddTab(
+                        Tab tab, @TabLaunchType int type, @TabCreationState int creationState) {
+                    if (TextUtils.equals(expectedUrl, tab.getUrl().getSpec())) {
+                        createdCallback.notifyCalled();
+                        tabModel.removeObserver(this);
+                    }
                 }
-            }
+            });
         });
 
         TestTouchUtils.performLongClickOnMainSync(
@@ -797,15 +799,17 @@
         final CallbackHelper createdCallback = new CallbackHelper();
         final TabModel tabModel =
                 backgroundActivity.getTabModelSelector().getModel(expectIncognito);
-        tabModel.addObserver(new TabModelObserver() {
-            @Override
-            public void didAddTab(
-                    Tab tab, @TabLaunchType int type, @TabCreationState int creationState) {
-                if (TextUtils.equals(expectedUrl, tab.getUrl().getSpec())) {
-                    createdCallback.notifyCalled();
-                    tabModel.removeObserver(this);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tabModel.addObserver(new TabModelObserver() {
+                @Override
+                public void didAddTab(
+                        Tab tab, @TabLaunchType int type, @TabCreationState int creationState) {
+                    if (TextUtils.equals(expectedUrl, tab.getUrl().getSpec())) {
+                        createdCallback.notifyCalled();
+                        tabModel.removeObserver(this);
+                    }
                 }
-            }
+            });
         });
 
         TestTouchUtils.performLongClickOnMainSync(
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/SadTabRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/SadTabRule.java
index ea87140..49b1f3dc8 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/SadTabRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/SadTabRule.java
@@ -36,26 +36,28 @@
         assert mTab != null;
 
         if (mSadTab == null) {
-            mSadTab = new SadTab(mTab) {
-                private boolean mShowing;
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                mSadTab = new SadTab(mTab) {
+                    private boolean mShowing;
 
-                @Override
-                public void show(
-                        Context context, Runnable suggestionAction, Runnable buttonAction) {
-                    mShowing = true;
-                }
+                    @Override
+                    public void show(
+                            Context context, Runnable suggestionAction, Runnable buttonAction) {
+                        mShowing = true;
+                    }
 
-                @Override
-                public void removeIfPresent() {
-                    mShowing = false;
-                }
+                    @Override
+                    public void removeIfPresent() {
+                        mShowing = false;
+                    }
 
-                @Override
-                public boolean isShowing() {
-                    return mShowing;
-                }
-            };
-            TestThreadUtils.runOnUiThreadBlocking(() -> SadTab.initForTesting(mTab, mSadTab));
+                    @Override
+                    public boolean isShowing() {
+                        return mShowing;
+                    }
+                };
+                SadTab.initForTesting(mTab, mSadTab);
+            });
         }
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             if (show) {
diff --git a/chrome/test/base/perf/performance_test.cc b/chrome/test/base/perf/performance_test.cc
index 8d402fe..43bd8293 100644
--- a/chrome/test/base/perf/performance_test.cc
+++ b/chrome/test/base/perf/performance_test.cc
@@ -69,9 +69,8 @@
   base::RunLoop run_loop;
   TestWallpaperObserver observer(run_loop.QuitClosure());
   WallpaperControllerClientImpl::Get()->SetCustomWallpaper(
-      user_manager::StubAccountId(), /*wallpaper_files_id=*/"dummyid",
-      /*file_name=*/"dummyfilename", ash::WALLPAPER_LAYOUT_CENTER_CROPPED,
-      image, /*preview_mode=*/false);
+      user_manager::StubAccountId(), /*file_name=*/"dummyfilename",
+      ash::WALLPAPER_LAYOUT_CENTER_CROPPED, image, /*preview_mode=*/false);
   run_loop.Run();
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/test/data/service_worker/blank.html b/chrome/test/data/service_worker/blank.html
new file mode 100644
index 0000000..ed8e56e
--- /dev/null
+++ b/chrome/test/data/service_worker/blank.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>An empty page</title>
+  </head>
+  <body>
+  </body>
+</html>
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 9e972ec..bb71d61 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -297,6 +297,7 @@
         "$root_gen_dir/chrome/test/data/webui/nearby_share/shared/nearby_onboarding_page_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/nearby_share/shared/nearby_page_template_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/nearby_share/shared/nearby_visibility_page_test.m.js",
+        "$root_gen_dir/chrome/test/data/webui/settings/chromeos/fake_bluetooth_config.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/add_users_tests.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/ambient_mode_page_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/ambient_mode_photos_page_test.m.js",
@@ -376,6 +377,9 @@
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_about_page_tests.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_bluetooth_page_tests.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_bluetooth_summary_tests.m.js",
+        "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_bluetooth_devices_subpage_tests.m.js",
+        "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_tests.m.js",
+        "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_item_tests.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_edit_dictionary_page_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_files_page_test.m.js",
         "$root_gen_dir/chrome/test/data/webui/settings/chromeos/os_languages_page_v2_tests.m.js",
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/BUILD.gn b/chrome/test/data/webui/chromeos/shortcut_customization/BUILD.gn
index 57bb59ff..b42ab68 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/BUILD.gn
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/BUILD.gn
@@ -18,12 +18,14 @@
     ":accelerator_view_test",
     ":fake_shortcut_provider_test",
     ":shortcut_customization_test",
+    ":shortcut_customization_test_util",
     ":shortcut_customization_unified_test",
   ]
 }
 
 js_library("accelerator_edit_dialog_test") {
   deps = [
+    ":shortcut_customization_test_util",
     "../..:chai_assert",
     "//ash/webui/shortcut_customization_ui/resources:accelerator_edit_dialog",
   ]
@@ -32,6 +34,7 @@
 
 js_library("accelerator_edit_view_test") {
   deps = [
+    ":shortcut_customization_test_util",
     "../..:chai_assert",
     "//ash/webui/shortcut_customization_ui/resources:accelerator_edit_view",
   ]
@@ -40,6 +43,7 @@
 
 js_library("accelerator_row_test") {
   deps = [
+    ":shortcut_customization_test_util",
     "../..:chai_assert",
     "//ash/webui/shortcut_customization_ui/resources:accelerator_row",
   ]
@@ -56,6 +60,7 @@
 
 js_library("accelerator_view_test") {
   deps = [
+    ":shortcut_customization_test_util",
     "../..:chai_assert",
     "//ash/webui/shortcut_customization_ui/resources:accelerator_view",
   ]
@@ -82,5 +87,9 @@
   externs_list = [ "$externs_path/mocha-2.5.js" ]
 }
 
+js_library("shortcut_customization_test_util") {
+  deps = []
+}
+
 js_library("shortcut_customization_unified_test") {
 }
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_dialog_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_dialog_test.js
index 6a1f11e..a1c6094 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_dialog_test.js
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_dialog_test.js
@@ -8,6 +8,8 @@
 
 import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
 
+import {CreateDefaultAccelerator} from './shortcut_customization_test_util.js';
+
 export function acceleratorEditDialogTest() {
   /** @type {?AcceleratorEditDialogElement} */
   let viewElement = null;
@@ -25,26 +27,16 @@
 
   test('LoadsBasicDialog', async () => {
     /** @type {!AcceleratorInfo} */
-    const acceleratorInfo1 = {
-      accelerator: /** @type {!AcceleratorKeys} */ ({
-        modifiers: Modifier.CONTROL | Modifier.SHIFT,
-        key: 71,
-        key_display: 'g',
-      }),
-      type: AcceleratorType.kDefault,
-      state: AcceleratorState.kEnabled,
-    };
+    const acceleratorInfo1 = CreateDefaultAccelerator(
+        Modifier.CONTROL | Modifier.SHIFT,
+        /*key=*/ 71,
+        /*key_display=*/ 'g');
 
     /** @type {!AcceleratorInfo} */
-    const acceleratorInfo2 = {
-      accelerator: /** @type {!AcceleratorKeys} */ ({
-        modifiers: Modifier.CONTROL,
-        key: 67,
-        key_display: 'c',
-      }),
-      type: AcceleratorType.kDefault,
-      state: AcceleratorState.kEnabled,
-    };
+    const acceleratorInfo2 = CreateDefaultAccelerator(
+        Modifier.CONTROL,
+        /*key=*/ 67,
+        /*key_display=*/ 'c');
 
     const accelerators = [acceleratorInfo1, acceleratorInfo2];
 
@@ -91,26 +83,16 @@
 
   test('AddShortcut', async () => {
     /** @type {!AcceleratorInfo} */
-    const acceleratorInfo1 = {
-      accelerator: /** @type {!AcceleratorKeys} */ ({
-        modifiers: Modifier.CONTROL | Modifier.SHIFT,
-        key: 71,
-        key_display: 'g',
-      }),
-      type: AcceleratorType.kDefault,
-      state: AcceleratorState.kEnabled,
-    };
+    const acceleratorInfo1 = CreateDefaultAccelerator(
+        Modifier.CONTROL | Modifier.SHIFT,
+        /*key=*/ 71,
+        /*key_display=*/ 'g');
 
     /** @type {!AcceleratorInfo} */
-    const acceleratorInfo2 = {
-      accelerator: /** @type {!AcceleratorKeys} */ ({
-        modifiers: Modifier.CONTROL,
-        key: 67,
-        key_display: 'c',
-      }),
-      type: AcceleratorType.kDefault,
-      state: AcceleratorState.kEnabled,
-    };
+    const acceleratorInfo2 = CreateDefaultAccelerator(
+        Modifier.CONTROL | Modifier.SHIFT,
+        /*key=*/ 67,
+        /*key_display=*/ 'c');
 
     const acceleratorInfos = [acceleratorInfo1, acceleratorInfo2];
     const description = 'test shortcut';
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_view_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_view_test.js
index 7314777..bab9b91 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_view_test.js
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_edit_view_test.js
@@ -8,6 +8,8 @@
 
 import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
 
+import {CreateDefaultAccelerator} from './shortcut_customization_test_util.js';
+
 export function acceleratorEditViewTest() {
   /** @type {?AcceleratorEditViewElement} */
   let editViewElement = null;
@@ -25,15 +27,10 @@
 
   test('LoadsBasicEditView', async () => {
     /** @type {!AcceleratorInfo} */
-    const acceleratorInfo = {
-      accelerator: /** @type {!AcceleratorKeys} */ ({
-        modifiers: Modifier.CONTROL | Modifier.SHIFT,
-        key: 71,
-        key_display: 'g',
-      }),
-      type: AcceleratorType.kDefault,
-      state: AcceleratorState.kEnabled,
-    };
+    const acceleratorInfo = CreateDefaultAccelerator(
+        Modifier.CONTROL | Modifier.SHIFT,
+        /*key=*/ 71,
+        /*key_display=*/ 'g');
 
     editViewElement.acceleratorInfo = acceleratorInfo;
     await flush();
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_row_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_row_test.js
index 69105dda..19e8433 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_row_test.js
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_row_test.js
@@ -8,6 +8,8 @@
 
 import {assertEquals} from '../../chai_assert.js';
 
+import {CreateDefaultAccelerator} from './shortcut_customization_test_util.js';
+
 export function acceleratorRowTest() {
   /** @type {?AcceleratorRowElement} */
   let rowElement = null;
@@ -24,27 +26,16 @@
   });
 
   test('LoadsBasicRow', async () => {
-    /** @type {!AcceleratorInfo} */
-    const acceleratorInfo1 = {
-      accelerator: /** @type {!AcceleratorKeys} */ ({
-        modifiers: Modifier.CONTROL | Modifier.SHIFT,
-        key: 71,
-        key_display: 'g',
-      }),
-      type: AcceleratorType.kDefault,
-      state: AcceleratorState.kEnabled,
-    };
+    const acceleratorInfo1 = CreateDefaultAccelerator(
+        Modifier.CONTROL | Modifier.SHIFT,
+        /*key=*/ 71,
+        /*key_display=*/ 'g');
 
     /** @type {!AcceleratorInfo} */
-    const acceleratorInfo2 = {
-      accelerator: /** @type {!AcceleratorKeys} */ ({
-        modifiers: Modifier.CONTROL,
-        key: 67,
-        key_display: 'c',
-      }),
-      type: AcceleratorType.kDefault,
-      state: AcceleratorState.kEnabled,
-    };
+    const acceleratorInfo2 = CreateDefaultAccelerator(
+        Modifier.CONTROL,
+        /*key=*/ 67,
+        /*key_display=*/ 'c');
 
     const accelerators = [acceleratorInfo1, acceleratorInfo2];
     const description = 'test shortcut';
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_subsection_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_subsection_test.js
index 58a6a77..36f03e54 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_subsection_test.js
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_subsection_test.js
@@ -6,9 +6,10 @@
 import {AcceleratorSubsectionElement} from 'chrome://shortcut-customization/accelerator_subsection.js';
 import {AcceleratorInfo, AcceleratorKeys, AcceleratorState, AcceleratorType, Modifier} from 'chrome://shortcut-customization/shortcut_types.js';
 
-
 import {assertEquals} from '../../chai_assert.js';
 
+import {CreateDefaultAccelerator} from './shortcut_customization_test_util.js';
+
 export function acceleratorSubsectionTest() {
   /** @type {?AcceleratorSubsectionElement} */
   let sectionElement = null;
@@ -27,29 +28,17 @@
   // TODO(jimmyxgong): Update this test after retrieving accelerators is
   // implemented for a subsection.
   test('LoadsBasicSection', async () => {
-    // TODO(jimmyxgong): Update the type of the test accelerator with the mojom
-    // version.
     /** @type {!AcceleratorInfo} */
-    const acceleratorInfo1 = {
-      accelerator: /** @type {!AcceleratorKeys} */ ({
-        modifiers: Modifier.CONTROL | Modifier.SHIFT,
-        key: 71,
-        key_display: 'g',
-      }),
-      type: AcceleratorType.kDefault,
-      state: AcceleratorState.kEnabled,
-    };
+    const acceleratorInfo1 = CreateDefaultAccelerator(
+        Modifier.CONTROL | Modifier.SHIFT,
+        /*key=*/ 71,
+        /*key_display=*/ 'g');
 
     /** @type {!AcceleratorInfo} */
-    const acceleratorInfo2 = {
-      accelerator: /** @type {!AcceleratorKeys} */ ({
-        modifiers: Modifier.CONTROL,
-        key: 67,
-        key_display: 'c',
-      }),
-      type: AcceleratorType.kDefault,
-      state: AcceleratorState.kEnabled,
-    };
+    const acceleratorInfo2 = CreateDefaultAccelerator(
+        Modifier.CONTROL | Modifier.SHIFT,
+        /*key=*/ 67,
+        /*key_display=*/ 'c');
 
     const accelerators = [acceleratorInfo1, acceleratorInfo2];
     const description = 'test shortcut';
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.js b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.js
index 16034e26..5b7ee4d 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.js
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.js
@@ -8,6 +8,8 @@
 
 import {assertEquals, assertTrue} from '../../chai_assert.js';
 
+import {CreateDefaultAccelerator} from './shortcut_customization_test_util.js';
+
 export function acceleratorViewTest() {
   /** @type {?AcceleratorViewElement} */
   let viewElement = null;
@@ -51,15 +53,10 @@
 
   test('EditableAccelerator', async () => {
     /** @type {!AcceleratorInfo} */
-    const acceleratorInfo = {
-      accelerator: /** @type {!AcceleratorKeys} */ ({
-        modifiers: Modifier.CONTROL | Modifier.SHIFT,
-        key: 71,
-        key_display: 'g',
-      }),
-      type: AcceleratorType.kDefault,
-      state: AcceleratorState.kEnabled,
-    };
+    const acceleratorInfo = CreateDefaultAccelerator(
+        Modifier.CONTROL | Modifier.SHIFT,
+        /*key=*/ 71,
+        /*key_display=*/ 'g');
 
     viewElement.acceleratorInfo = acceleratorInfo;
     await flush();
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test_util.js b/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test_util.js
new file mode 100644
index 0000000..501da9b
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_customization_test_util.js
@@ -0,0 +1,23 @@
+// Copyright 2021 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 {AcceleratorInfo, AcceleratorKeys, AcceleratorState, AcceleratorType} from 'chrome://shortcut-customization/shortcut_types.js';
+
+/**
+ * @param {number} modifier
+ * @param {number} keycode
+ * @param {string} key_display
+ * @return {!AcceleratorInfo}
+ */
+export function CreateDefaultAccelerator(modifier, keycode, key_display) {
+  return /** @type {!AcceleratorInfo} */ ({
+    accelerator: /** @type {!AcceleratorKeys} */ ({
+      modifiers: modifier,
+      key: keycode,
+      key_display: key_display,
+    }),
+    type: AcceleratorType.kDefault,
+    state: AcceleratorState.kEnabled,
+  });
+}
\ No newline at end of file
diff --git a/chrome/test/data/webui/settings/BUILD.gn b/chrome/test/data/webui/settings/BUILD.gn
index e3cec78..c658077 100644
--- a/chrome/test/data/webui/settings/BUILD.gn
+++ b/chrome/test/data/webui/settings/BUILD.gn
@@ -102,7 +102,7 @@
     ":safety_check_page_test",
 
     #":search_engines_page_test",
-    ":search_page_test",
+    #":search_page_test",
 
     #":search_settings_test",
     #":secure_dns_interactive_test",
@@ -157,7 +157,7 @@
 
     #":test_profile_info_browser_proxy",
     #":test_reset_browser_proxy",
-    ":test_search_engines_browser_proxy",
+    #":test_search_engines_browser_proxy",
     ":test_site_settings_prefs_browser_proxy",
     ":test_sync_browser_proxy",
     ":test_util",
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index 3b1e59b..9e1ad9e 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -76,6 +76,7 @@
     "esim_rename_dialog_test.js",
     "fake_bluetooth.js",
     "fake_bluetooth_private.js",
+    "fake_bluetooth_config.js",
     "fake_quick_unlock_private.js",
     "fake_quick_unlock_uma.js",
     "fake_receive_manager.js",
@@ -122,10 +123,13 @@
     "os_about_page_tests.js",
     "os_bluetooth_page_tests.js",
     "os_bluetooth_summary_tests.js",
+    "os_bluetooth_devices_subpage_tests.js",
     "os_edit_dictionary_page_test.js",
     "os_files_page_test.js",
     "os_languages_page_v2_tests.js",
     "os_reset_page_test.js",
+    "os_paired_bluetooth_list_tests.js",
+    "os_paired_bluetooth_list_item_tests.js",
     "os_people_page_test.js",
     "os_printing_page_tests.js",
     "os_privacy_page_test.js",
diff --git a/chrome/test/data/webui/settings/chromeos/fake_bluetooth_config.js b/chrome/test/data/webui/settings/chromeos/fake_bluetooth_config.js
new file mode 100644
index 0000000..2a50dcf
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/fake_bluetooth_config.js
@@ -0,0 +1,106 @@
+// Copyright 2021 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.
+
+// TODO(crbug.com/1010321): Use cros_bluetooth_config.mojom-webui.js instead
+// as non-module JS is deprecated.
+import 'chrome://resources/mojo/chromeos/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom-lite.js';
+
+/**
+ * @fileoverview Fake implementation of CrosBluetoothConfig for testing.
+ */
+
+/** @implements {chromeos.bluetoothConfig.mojom.CrosBluetoothConfigInterface} */
+export class FakeBluetoothConfig {
+  constructor() {
+    /** @private {!chromeos.bluetoothConfig.mojom.BluetoothSystemProperties} */
+    this.systemProperties_ = {
+      systemState:
+          chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled,
+    };
+
+    /**
+     * @private {!Array<
+     *     !chromeos.bluetoothConfig.mojom.SystemPropertiesObserver>}
+     */
+    this.observers_ = [];
+  }
+
+  /**
+   * @override
+   * @param {SystemPropertiesObserver} observer
+   */
+  observeSystemProperties(observer) {
+    this.observers_.push(observer);
+    this.notifyObserversPropertiesUpdated_();
+  }
+
+  /**
+   * @override
+   * Begins the operation to enable/disable Bluetooth. If the systemState is
+   * current disabled, transitions to enabling. If the systemState is
+   * currently enabled, transitions to disabled. Does nothing if already in the
+   * requested state. This method should be followed by a call to
+   * completeSetBluetoothEnabledStateForTest() to complete the operation.
+   * @param {boolean} enabled
+   */
+  setBluetoothEnabledState(enabled) {
+    const bluetoothSystemState =
+        chromeos.bluetoothConfig.mojom.BluetoothSystemState;
+    const systemState = this.systemProperties_.systemState;
+    if ((enabled && systemState === bluetoothSystemState.kEnabled) ||
+        (!enabled && systemState === bluetoothSystemState.kDisabled)) {
+      return;
+    }
+
+    this.setSystemState(
+        enabled ? bluetoothSystemState.kEnabling :
+                  bluetoothSystemState.kDisabling);
+  }
+
+  /**
+   * @param {chromeos.bluetoothConfig.mojom.BluetoothSystemState} systemState
+   */
+  setSystemState(systemState) {
+    const newSystemProperties = {...this.systemProperties_};
+    newSystemProperties.systemState = systemState;
+    this.systemProperties_ = newSystemProperties;
+    this.notifyObserversPropertiesUpdated_();
+  }
+
+  /**
+   * Completes the Bluetooth enable/disable operation. This method should be
+   * called after setBluetoothEnabledState(). If the systemState is already
+   * enabled or disabled, does nothing.
+   * @param {boolean} success Whether the operation should succeed or not.
+   */
+  completeSetBluetoothEnabledState(success) {
+    const bluetoothSystemState =
+        chromeos.bluetoothConfig.mojom.BluetoothSystemState;
+    const systemState = this.systemProperties_.systemState;
+    if (systemState === bluetoothSystemState.kDisabled ||
+        systemState === bluetoothSystemState.kEnabled) {
+      return;
+    }
+
+    if (success) {
+      this.setSystemState(
+          systemState === bluetoothSystemState.kDisabling ?
+              bluetoothSystemState.kDisabled :
+              bluetoothSystemState.kEnabled);
+    } else {
+      this.setSystemState(
+          systemState === bluetoothSystemState.kDisabling ?
+              bluetoothSystemState.kEnabled :
+              bluetoothSystemState.kDisabled);
+    }
+  }
+
+  /**
+   * @private
+   * Notifies the observer list that systemProperties_ has changed.
+   */
+  notifyObserversPropertiesUpdated_() {
+    this.observers_.forEach(o => o.onPropertiesUpdated(this.systemProperties_));
+  }
+}
\ No newline at end of file
diff --git a/chrome/test/data/webui/settings/chromeos/os_bluetooth_devices_subpage_tests.js b/chrome/test/data/webui/settings/chromeos/os_bluetooth_devices_subpage_tests.js
new file mode 100644
index 0000000..0cc758a0
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/os_bluetooth_devices_subpage_tests.js
@@ -0,0 +1,28 @@
+// Copyright 2021 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.
+
+// clang-format off
+// #import 'chrome://os-settings/chromeos/os_settings.js';
+
+// #import 'chrome://os-settings/strings.m.js';
+
+// #import {flush, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {assertTrue} from '../../../chai_assert.js';
+// clang-format on
+
+suite('OsBluetoothDevicesSubpageTest', function() {
+  /** @type {!SettingsBluetoothDevicesSubpageElement|undefined} */
+  let bluetoothDevicesSubpage;
+
+  setup(function() {
+    bluetoothDevicesSubpage =
+        document.createElement('os-settings-bluetooth-devices-subpage');
+    document.body.appendChild(bluetoothDevicesSubpage);
+    Polymer.dom.flush();
+  });
+
+  test('Base Test', function() {
+    assertTrue(!!bluetoothDevicesSubpage);
+  });
+});
\ No newline at end of file
diff --git a/chrome/test/data/webui/settings/chromeos/os_bluetooth_page_tests.js b/chrome/test/data/webui/settings/chromeos/os_bluetooth_page_tests.js
index 94dba62..539c7862 100644
--- a/chrome/test/data/webui/settings/chromeos/os_bluetooth_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/os_bluetooth_page_tests.js
@@ -9,20 +9,20 @@
 
 // #import {flush, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 // #import {assertTrue} from '../../../chai_assert.js';
+// #import {FakeBluetoothConfig} from './fake_bluetooth_config.m.js';
 // #import {setBluetoothConfigForTesting} from 'chrome://resources/cr_components/chromeos/bluetooth/cros_bluetooth_config.js';
 // clang-format on
 
 suite('OsBluetoothPageTest', function() {
+  /** @type {!FakeBluetoothConfig} */
+  let bluetoothConfig;
+
   /** @type {!SettingsBluetoothPageElement|undefined} */
   let bluetoothPage;
 
   setup(function() {
-    // TODO(crbug.com/1010321): Replace this with fake_cros_bluetooth_config
-    // when it is created.
-    setBluetoothConfigForTesting({
-      observeSystemProperties: (observer) => {},
-      setBluetoothEnabledState: (enabled) => {}
-    });
+    bluetoothConfig = new FakeBluetoothConfig();
+    setBluetoothConfigForTesting(bluetoothConfig);
     bluetoothPage = document.createElement('os-settings-bluetooth-page');
     document.body.appendChild(bluetoothPage);
     Polymer.dom.flush();
diff --git a/chrome/test/data/webui/settings/chromeos/os_bluetooth_summary_tests.js b/chrome/test/data/webui/settings/chromeos/os_bluetooth_summary_tests.js
index 7b274eb..0e4c63d 100644
--- a/chrome/test/data/webui/settings/chromeos/os_bluetooth_summary_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/os_bluetooth_summary_tests.js
@@ -10,20 +10,40 @@
 // #import {flush, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 // #import {Router, Route, routes} from 'chrome://os-settings/chromeos/os_settings.js';
 // #import {assertTrue} from '../../../chai_assert.js';
+// #import {FakeBluetoothConfig} from './fake_bluetooth_config.m.js';
 // #import {setBluetoothConfigForTesting} from 'chrome://resources/cr_components/chromeos/bluetooth/cros_bluetooth_config.js';
 // clang-format on
 
 suite('OsBluetoothSummaryTest', function() {
+  /** @type {!FakeBluetoothConfig} */
+  let bluetoothConfig;
+
   /** @type {!SettingsBluetoothSummaryElement|undefined} */
   let bluetoothSummary;
 
+  /**
+   * @type {!chromeos.bluetoothConfig.mojom.SystemPropertiesObserverInterface}
+   */
+  let propertiesObserver;
+
   setup(function() {
-    // TODO(crbug.com/1010321): Replace this with fake_cros_bluetooth_config
-    // when it is created.
-    setBluetoothConfigForTesting({setBluetoothEnabledState: (enabled) => {}});
+    bluetoothConfig = new FakeBluetoothConfig();
+    setBluetoothConfigForTesting(bluetoothConfig);
     bluetoothSummary = document.createElement('os-settings-bluetooth-summary');
     document.body.appendChild(bluetoothSummary);
     Polymer.dom.flush();
+
+    propertiesObserver = {
+      /**
+       * SystemPropertiesObserverInterface override
+       * @param {!chromeos.bluetoothConfig.mojom.BluetoothSystemProperties}
+       *     properties
+       */
+      onPropertiesUpdated(properties) {
+        bluetoothSummary.systemProperties = properties;
+      }
+    };
+    bluetoothConfig.observeSystemProperties(propertiesObserver);
   });
 
   function flushAsync() {
@@ -43,24 +63,42 @@
   });
 
   test('Toggle button states', async function() {
-    // TODO(crbug.com/1010321): Remove |mockSystemProperties| once
-    // fake_cros_bluetooth_config has been added.
-    const mockSystemProperties = {
-      systemState: chromeos.bluetoothConfig.mojom.BluetoothSystemState.kEnabled,
-    };
-    bluetoothSummary.systemProperties = {...mockSystemProperties};
+    const enableBluetoothToggle = bluetoothSummary.$$('#enableBluetoothToggle');
+    assertTrue(!!enableBluetoothToggle);
+    assertFalse(enableBluetoothToggle.checked);
 
-    const enableBluettonToggle = bluetoothSummary.$$('#enableBluetoothToggle');
-    assertTrue(!!enableBluettonToggle);
-    assertTrue(enableBluettonToggle.checked);
-
-    // Simulate disabled state.
-    mockSystemProperties.systemState =
-        chromeos.bluetoothConfig.mojom.BluetoothSystemState.kDisabled;
-    bluetoothSummary.systemProperties = {...mockSystemProperties};
+    // Simulate clicking toggle.
+    enableBluetoothToggle.click();
     await flushAsync();
 
-    assertFalse(enableBluettonToggle.checked);
-  });
+    // Toggle should be on since systemState is enabling.
+    assertTrue(enableBluetoothToggle.checked);
 
+    // Mock operation failing.
+    bluetoothConfig.completeSetBluetoothEnabledState(/*success=*/ false);
+    await flushAsync();
+
+    // Toggle should be off again.
+    assertFalse(enableBluetoothToggle.checked);
+
+    // Click again.
+    enableBluetoothToggle.click();
+    await flushAsync();
+
+    // Toggle should be on since systemState is enabling.
+    assertTrue(enableBluetoothToggle.checked);
+
+    // Mock operation success.
+    bluetoothConfig.completeSetBluetoothEnabledState(/*success=*/ true);
+    await flushAsync();
+
+    // Toggle should still be on.
+    assertTrue(enableBluetoothToggle.checked);
+
+    // Mock systemState becoming unavailable.
+    bluetoothConfig.setSystemState(
+        chromeos.bluetoothConfig.mojom.BluetoothSystemState.kUnavailable);
+    await flushAsync();
+    assertTrue(enableBluetoothToggle.disabled);
+  });
 });
\ No newline at end of file
diff --git a/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_item_tests.js b/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_item_tests.js
new file mode 100644
index 0000000..8e24ab187
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_item_tests.js
@@ -0,0 +1,28 @@
+// Copyright 2021 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.
+
+// clang-format off
+// #import 'chrome://os-settings/chromeos/os_settings.js';
+
+// #import 'chrome://os-settings/strings.m.js';
+
+// #import {flush, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {assertTrue} from '../../../chai_assert.js';
+// clang-format on
+
+suite('OsPairedBluetoothListItemTest', function() {
+  /** @type {!SettingsPairedBluetoothListItemElement|undefined} */
+  let pairedBluetoothListItem;
+
+  setup(function() {
+    pairedBluetoothListItem =
+        document.createElement('os-settings-paired-bluetooth-list-item');
+    document.body.appendChild(pairedBluetoothListItem);
+    Polymer.dom.flush();
+  });
+
+  test('Base Test', function() {
+    assertTrue(!!pairedBluetoothListItem);
+  });
+});
\ No newline at end of file
diff --git a/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_tests.js b/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_tests.js
new file mode 100644
index 0000000..21023043
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_tests.js
@@ -0,0 +1,29 @@
+// Copyright 2021 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.
+
+// clang-format off
+// #import 'chrome://os-settings/chromeos/os_settings.js';
+
+// #import 'chrome://os-settings/strings.m.js';
+
+// #import {flush, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+// #import {assertTrue} from '../../../chai_assert.js';
+// clang-format on
+
+suite('OsPairedBluetoothListTest', function() {
+  /** @type {!SettingsPairedBluetoothListElement|undefined} */
+  let pairedBluetoothList;
+
+  setup(function() {
+    pairedBluetoothList =
+        document.createElement('os-settings-paired-bluetooth-list');
+    document.body.appendChild(pairedBluetoothList);
+    Polymer.dom.flush();
+  });
+
+  test('Base Test', function() {
+    const list = pairedBluetoothList.shadowRoot.querySelector('iron-list');
+    assertTrue(!!list);
+  });
+});
\ No newline at end of file
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
index 649b081..43b56d22 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
@@ -372,10 +372,13 @@
  ['NetworkProxySection', 'network_proxy_section_test.m.js'],
  ['NetworkSummary', 'network_summary_test.m.js'],
  ['NetworkSummaryItem', 'network_summary_item_test.m.js'],
+ ['OsBluetoothDevicesSubpage', 'os_bluetooth_devices_subpage_tests.m.js'],
  ['OsBluetoothPage', 'os_bluetooth_page_tests.m.js'],
  ['OsBluetoothSummary', 'os_bluetooth_summary_tests.m.js'],
  ['OsEditDictionaryPage', 'os_edit_dictionary_page_test.m.js'],
  ['OsLanguagesPageV2', 'os_languages_page_v2_tests.m.js'],
+ ['OsPairedBluetoothList', 'os_paired_bluetooth_list_tests.m.js'],
+ ['OsPairedBluetoothListItem', 'os_paired_bluetooth_list_item_tests.m.js'],
  ['OsSettingsUi', 'os_settings_ui_test.m.js'],
  ['OsSettingsUi2', 'os_settings_ui_test_2.m.js'],
  ['OsSettingsMain', 'os_settings_main_test.m.js'],
diff --git a/chrome/test/data/webui/settings/extension_controlled_indicator_tests.js b/chrome/test/data/webui/settings/extension_controlled_indicator_tests.js
index 695da6e..da4af4eb 100644
--- a/chrome/test/data/webui/settings/extension_controlled_indicator_tests.js
+++ b/chrome/test/data/webui/settings/extension_controlled_indicator_tests.js
@@ -19,7 +19,7 @@
   setup(function() {
     PolymerTest.clearBody();
     browserProxy = new TestExtensionControlBrowserProxy();
-    ExtensionControlBrowserProxyImpl.instance_ = browserProxy;
+    ExtensionControlBrowserProxyImpl.setInstance(browserProxy);
     indicator = document.createElement('extension-controlled-indicator');
     indicator.extensionId = 'peiafolljookckjknpgofpbjobgbmpge';
     indicator.extensionCanBeDisabled = true;
diff --git a/chrome/test/data/webui/settings/search_engines_page_test.js b/chrome/test/data/webui/settings/search_engines_page_test.js
index 66924310..1ca2c52 100644
--- a/chrome/test/data/webui/settings/search_engines_page_test.js
+++ b/chrome/test/data/webui/settings/search_engines_page_test.js
@@ -70,7 +70,7 @@
 
   setup(function() {
     browserProxy = new TestSearchEnginesBrowserProxy();
-    SearchEnginesBrowserProxyImpl.instance_ = browserProxy;
+    SearchEnginesBrowserProxyImpl.setInstance(browserProxy);
     PolymerTest.clearBody();
     dialog = document.createElement('settings-search-engine-dialog');
     document.body.appendChild(dialog);
@@ -177,7 +177,7 @@
 
   setup(function() {
     browserProxy = new TestSearchEnginesBrowserProxy();
-    SearchEnginesBrowserProxyImpl.instance_ = browserProxy;
+    SearchEnginesBrowserProxyImpl.setInstance(browserProxy);
     PolymerTest.clearBody();
     entry = document.createElement('settings-search-engine-entry');
     entry.set('engine', searchEngine);
@@ -311,7 +311,7 @@
       others: searchEnginesInfo.others.slice(),
       extensions: searchEnginesInfo.extensions.slice(),
     });
-    SearchEnginesBrowserProxyImpl.instance_ = browserProxy;
+    SearchEnginesBrowserProxyImpl.setInstance(browserProxy);
     PolymerTest.clearBody();
     page = document.createElement('settings-search-engines-page');
     document.body.appendChild(page);
@@ -485,7 +485,7 @@
 
   setup(function() {
     browserProxy = new TestExtensionControlBrowserProxy();
-    ExtensionControlBrowserProxyImpl.instance_ = browserProxy;
+    ExtensionControlBrowserProxyImpl.setInstance(browserProxy);
     PolymerTest.clearBody();
     entry = document.createElement('settings-omnibox-extension-entry');
     entry.set('engine', createSampleOmniboxExtension());
diff --git a/chrome/test/data/webui/settings/search_page_test.js b/chrome/test/data/webui/settings/search_page_test.js
index 17f3559..a2b0f9e 100644
--- a/chrome/test/data/webui/settings/search_page_test.js
+++ b/chrome/test/data/webui/settings/search_page_test.js
@@ -40,7 +40,7 @@
   setup(function() {
     browserProxy = new TestSearchEnginesBrowserProxy();
     browserProxy.setSearchEnginesInfo(generateSearchEngineInfo());
-    SearchEnginesBrowserProxyImpl.instance_ = browserProxy;
+    SearchEnginesBrowserProxyImpl.setInstance(browserProxy);
     document.body.innerHTML = '';
     page = /** @type {!SettingsSearchPageElement} */ (
         document.createElement('settings-search-page'));
diff --git a/chrome/test/data/webui/settings/test_search_engines_browser_proxy.js b/chrome/test/data/webui/settings/test_search_engines_browser_proxy.js
index 4a004d0..c8407ee 100644
--- a/chrome/test/data/webui/settings/test_search_engines_browser_proxy.js
+++ b/chrome/test/data/webui/settings/test_search_engines_browser_proxy.js
@@ -3,8 +3,6 @@
 // found in the LICENSE file.
 
 // clang-format off
-import { SearchEngine,SearchEnginesBrowserProxy, SearchEnginesInfo} from 'chrome://settings/settings.js';
-
 import {TestBrowserProxy} from '../test_browser_proxy.m.js';
 // clang-format on
 
diff --git a/chrome/updater/test/integration_test_commands_system.cc b/chrome/updater/test/integration_test_commands_system.cc
index 9e5c6cc..b8399fc 100644
--- a/chrome/updater/test/integration_test_commands_system.cc
+++ b/chrome/updater/test/integration_test_commands_system.cc
@@ -165,6 +165,9 @@
     EXPECT_TRUE(base::PathExists(path));
     path = MakeAbsoluteFilePath(path);
     path = path.Append(FILE_PATH_LITERAL("updater_integration_tests_helper"));
+#if defined(OS_WIN)
+    path = path.AddExtension(L"exe");
+#endif
     EXPECT_TRUE(base::PathExists(path));
 
     base::CommandLine helper_command(path);
diff --git a/chrome/updater/test/integration_tests_helper.cc b/chrome/updater/test/integration_tests_helper.cc
index ae52f63..e63de6a0 100644
--- a/chrome/updater/test/integration_tests_helper.cc
+++ b/chrome/updater/test/integration_tests_helper.cc
@@ -25,6 +25,10 @@
 #include "chrome/updater/updater_scope.h"
 #include "url/gurl.h"
 
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#endif
+
 namespace updater {
 namespace test {
 namespace {
@@ -220,6 +224,11 @@
                        /*enable_timestamp=*/true,
                        /*enable_tickcount=*/false);
 
+#if defined(OS_WIN)
+  auto scoped_com_initializer =
+      std::make_unique<base::win::ScopedCOMInitializer>(
+          base::win::ScopedCOMInitializer::kMTA);
+#endif
   return MakeAppTestHelper()->Run();
 }
 
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index 19b879a..78b4ea206 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -57,10 +57,14 @@
   return test_executable.DirName().AppendASCII(TEST_APP_FULLNAME_STRING ".exe");
 }
 
-absl::optional<base::FilePath> GetProductPath() {
+absl::optional<base::FilePath> GetProductPath(UpdaterScope scope) {
   base::FilePath app_data_dir;
-  if (!base::PathService::Get(base::DIR_LOCAL_APP_DATA, &app_data_dir))
+  if (!base::PathService::Get(scope == UpdaterScope::kSystem
+                                  ? base::DIR_PROGRAM_FILES
+                                  : base::DIR_LOCAL_APP_DATA,
+                              &app_data_dir)) {
     return absl::nullopt;
+  }
   return app_data_dir.AppendASCII(COMPANY_SHORTNAME_STRING)
       .AppendASCII(PRODUCT_FULLNAME_STRING)
       .AppendASCII(kUpdaterVersion);
@@ -84,7 +88,7 @@
 }  // namespace
 
 absl::optional<base::FilePath> GetInstalledExecutablePath(UpdaterScope scope) {
-  absl::optional<base::FilePath> path = GetProductPath();
+  absl::optional<base::FilePath> path = GetProductPath(scope);
   if (!path)
     return absl::nullopt;
   return path->AppendASCII("updater.exe");
@@ -93,7 +97,7 @@
 absl::optional<base::FilePath> GetFakeUpdaterInstallFolderPath(
     UpdaterScope scope,
     const base::Version& version) {
-  absl::optional<base::FilePath> path = GetProductPath();
+  absl::optional<base::FilePath> path = GetProductPath(scope);
   if (!path)
     return absl::nullopt;
   return path->AppendASCII(version.GetString());
@@ -162,7 +166,7 @@
   }
 
   // TODO(crbug.com/1062288): Delete the Wake task.
-  absl::optional<base::FilePath> path = GetProductPath();
+  absl::optional<base::FilePath> path = GetProductPath(scope);
   EXPECT_TRUE(path);
   if (path)
     EXPECT_TRUE(base::DeletePathRecursively(*path));
@@ -230,7 +234,7 @@
   // TODO(crbug.com/1062288): Assert there are no Wake tasks.
 
   // Files must not exist on the file system.
-  absl::optional<base::FilePath> path = GetProductPath();
+  absl::optional<base::FilePath> path = GetProductPath(scope);
   EXPECT_TRUE(path);
   if (path)
     EXPECT_FALSE(base::PathExists(*path));
@@ -257,7 +261,7 @@
   // TODO(crbug.com/1062288): Assert there are Wake tasks.
 
   // Files must exist on the file system.
-  absl::optional<base::FilePath> path = GetProductPath();
+  absl::optional<base::FilePath> path = GetProductPath(scope);
   EXPECT_TRUE(path);
   if (path)
     EXPECT_TRUE(base::PathExists(*path));
@@ -268,7 +272,7 @@
   // TODO(crbug.com/1062288): Assert there are no Wake tasks.
 
   // Files must not exist on the file system.
-  absl::optional<base::FilePath> path = GetProductPath();
+  absl::optional<base::FilePath> path = GetProductPath(scope);
   EXPECT_TRUE(path);
   if (path)
     EXPECT_FALSE(base::PathExists(*path));
@@ -278,7 +282,7 @@
   // TODO(crbug.com/1062288): Assert that COM interfaces point to this version.
 
   // Files must exist on the file system.
-  absl::optional<base::FilePath> path = GetProductPath();
+  absl::optional<base::FilePath> path = GetProductPath(scope);
   EXPECT_TRUE(path);
   if (path)
     EXPECT_TRUE(base::PathExists(*path));
@@ -323,8 +327,8 @@
   SleepFor(5);
 }
 
-void SetActive(UpdaterScope scope, const std::string& id) {
-  // TODO(crbug/1159498): Standardize registry access.
+void SetActive(UpdaterScope /*scope*/, const std::string& id) {
+  // TODO(crbug.com/1159498): Standardize registry access.
   base::win::RegKey key;
   ASSERT_EQ(key.Create(HKEY_CURRENT_USER, GetAppClientStateKey(id).c_str(),
                        KEY_WRITE | KEY_WOW64_32KEY),
@@ -332,8 +336,8 @@
   EXPECT_EQ(key.WriteValue(kDidRun, L"1"), ERROR_SUCCESS);
 }
 
-void ExpectActive(UpdaterScope scope, const std::string& id) {
-  // TODO(crbug/1159498): Standardize registry access.
+void ExpectActive(UpdaterScope /*scope*/, const std::string& id) {
+  // TODO(crbug.com/1159498): Standardize registry access.
   base::win::RegKey key;
   ASSERT_EQ(key.Open(HKEY_CURRENT_USER, GetAppClientStateKey(id).c_str(),
                      KEY_READ | KEY_WOW64_32KEY),
@@ -343,8 +347,8 @@
   EXPECT_EQ(value, L"1");
 }
 
-void ExpectNotActive(UpdaterScope scope, const std::string& id) {
-  // TODO(crbug/1159498): Standardize registry access.
+void ExpectNotActive(UpdaterScope /*scope*/, const std::string& id) {
+  // TODO(crbug.com/1159498): Standardize registry access.
   base::win::RegKey key;
   if (key.Open(HKEY_CURRENT_USER, GetAppClientStateKey(id).c_str(),
                KEY_READ | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 649a8c2..1ea330c 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -1656,6 +1656,14 @@
       <message name="IDS_TRAFFIC_COUNTERS_TRAFFIC_COUNTERS" desc="Label for showing traffic counters">
         Traffic Counters
       </message>
+
+      <!-- Bluetooth -->
+      <message name="IDS_BLUETOOTH_DEVICE_LIST_CURRENTLY_CONNECTED" desc="Title for list displaying paired Bluetooth devices that are currently connected.">
+        Currently connected
+      </message>
+      <message name="IDS_BLUETOOTH_DEVICE_LIST_PREVIOUSLY_CONNECTED" desc="Title for list displaying paired Bluetooth devices that have previously been connected.">
+        Previously connected
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/chromeos/chromeos_strings_grd/IDS_BLUETOOTH_DEVICE_LIST_CURRENTLY_CONNECTED.png.sha1 b/chromeos/chromeos_strings_grd/IDS_BLUETOOTH_DEVICE_LIST_CURRENTLY_CONNECTED.png.sha1
new file mode 100644
index 0000000..bc80186d
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_BLUETOOTH_DEVICE_LIST_CURRENTLY_CONNECTED.png.sha1
@@ -0,0 +1 @@
+863d6717575560355cb8fdf02928e0beb45132a2
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_BLUETOOTH_DEVICE_LIST_PREVIOUSLY_CONNECTED.png.sha1 b/chromeos/chromeos_strings_grd/IDS_BLUETOOTH_DEVICE_LIST_PREVIOUSLY_CONNECTED.png.sha1
new file mode 100644
index 0000000..bc80186d
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_BLUETOOTH_DEVICE_LIST_PREVIOUSLY_CONNECTED.png.sha1
@@ -0,0 +1 @@
+863d6717575560355cb8fdf02928e0beb45132a2
\ No newline at end of file
diff --git a/chromeos/components/camera_app_ui/resources.h b/chromeos/components/camera_app_ui/resources.h
index 4261ffb..c370eece 100644
--- a/chromeos/components/camera_app_ui/resources.h
+++ b/chromeos/components/camera_app_ui/resources.h
@@ -92,6 +92,7 @@
     {"new_control_navigation", IDS_NEW_CONTROL_NAVIGATION},
     {"new_control_toast", IDS_NEW_CONTROL_TOAST},
     {"new_document_scan_toast", IDS_NEW_DOCUMENT_SCAN_TOAST},
+    {"no_document_guide_msg", IDS_NO_DOCUMENT_GUIDE_MSG},
     {"open_ptz_panel_button", IDS_OPEN_PTZ_PANEL_BUTTON},
     {"pan_left_button", IDS_PAN_LEFT_BUTTON},
     {"pan_right_button", IDS_PAN_RIGHT_BUTTON},
diff --git a/chromeos/components/camera_app_ui/resources/.eslintrc.js b/chromeos/components/camera_app_ui/resources/.eslintrc.js
index 39e91f2..98f4117 100644
--- a/chromeos/components/camera_app_ui/resources/.eslintrc.js
+++ b/chromeos/components/camera_app_ui/resources/.eslintrc.js
@@ -381,6 +381,7 @@
     'BarcodeDetector': 'readable',
     'FileSystemFileHandle': 'readable',
     'FileSystemDirectoryHandle': 'readable',
+    'IdleDetector': 'readable',
 
     // TODO(b/172879638): Remove this once we have
     // https://github.com/sindresorhus/globals/pull/171 merged in ESLint and
diff --git a/chromeos/components/camera_app_ui/resources/css/scanner.css b/chromeos/components/camera_app_ui/resources/css/scanner.css
index 1b4d90c..932a796 100644
--- a/chromeos/components/camera_app_ui/resources/css/scanner.css
+++ b/chromeos/components/camera_app_ui/resources/css/scanner.css
@@ -296,7 +296,7 @@
   top: 0;
 }
 
-#preview-document-corner-overlay.clear div {
+#preview-document-corner-overlay:not(.show-corner-indicator) :is(.line, .corner) {
   display: none;
 }
 
@@ -342,3 +342,19 @@
   height: 1px;
   width: 1px;
 }
+
+#preview-document-corner-overlay .no-document-toast {
+  background: var(--grey-900);
+  border-radius: 6px;
+  color: var(--grey-500);
+  display: inline-block;
+  font: normal 400 14px/20px Roboto;
+  left: 50%;
+  padding: 10px 8px;
+  position: absolute;
+  text-align: center;
+  top: 22px;
+  transform: translateX(-50%);
+  transition: visibility var(--fast1-duration) ease-in;
+  white-space: nowrap;
+}
diff --git a/chromeos/components/camera_app_ui/resources/js/externs/chrome.js b/chromeos/components/camera_app_ui/resources/js/externs/chrome.js
index ffe3313..60a2051c 100644
--- a/chromeos/components/camera_app_ui/resources/js/externs/chrome.js
+++ b/chromeos/components/camera_app_ui/resources/js/externs/chrome.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+/* eslint-disable no-unused-vars, no-var */
+
 /**
  * It is available since we enabled and filled data in it while creating
  * content::WebUIDataSource for CCA.
@@ -20,7 +22,7 @@
  *   start: function(): !Promise<void>,
  * }}
  */
-window.IdleDetector;
+let IdleDetector;
 
 /**
  * @typedef {{
diff --git a/chromeos/components/camera_app_ui/resources/js/externs/types.d.ts b/chromeos/components/camera_app_ui/resources/js/externs/types.d.ts
index 61ee7f8..4637960 100644
--- a/chromeos/components/camera_app_ui/resources/js/externs/types.d.ts
+++ b/chromeos/components/camera_app_ui/resources/js/externs/types.d.ts
@@ -5,6 +5,9 @@
 // TODO(pihsun): Remove this once we fully specify all the types.
 /* eslint-disable @typescript-eslint/no-explicit-any */
 
+// ESLint doesn't like "declare class" without jsdoc.
+/* eslint-disable require-jsdoc */
+
 // TODO(b/172340451): Use the types generated by Mojo TypeScript binding
 // generator once https://crbug.com/1002798 is finished.
 type MojomNamespace = {
@@ -170,3 +173,11 @@
   };
   export const reportError: (info: ErrorInfo, callback: () => void) => void;
 }
+
+// Idle Detection API: This is currently a Chrome only API gated behind Origin
+// Trial, and the spec is still in working draft stage.
+// https://wicg.github.io/idle-detection/
+declare class IdleDetector extends EventTarget {
+  screenState: 'locked'|'unlocked';
+  start: () => Promise<void>;
+}
diff --git a/chromeos/components/camera_app_ui/resources/js/externs/w3c_api.js b/chromeos/components/camera_app_ui/resources/js/externs/w3c_api.js
index cc3c4d5..b35cf6c 100644
--- a/chromeos/components/camera_app_ui/resources/js/externs/w3c_api.js
+++ b/chromeos/components/camera_app_ui/resources/js/externs/w3c_api.js
@@ -146,6 +146,12 @@
 StylePropertyMap.prototype.get = function(property) {};
 
 /**
+ * @param {string} property
+ * @return {boolean}
+ */
+StylePropertyMap.prototype.has = function(property) {};
+
+/**
  * @constructor
  * @extends {CSSStyleValue}
  * @param {!CSSStyleValue} x
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera.js b/chromeos/components/camera_app_ui/resources/js/views/camera.js
index fac45ccf..1a5e08e 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera.js
@@ -351,7 +351,7 @@
     this.initOpenPTZPanel_();
 
     // Monitor the states to stop camera when locked/minimized.
-    const idleDetector = new window.IdleDetector();
+    const idleDetector = new IdleDetector();
     idleDetector.addEventListener('change', () => {
       this.locked_ = idleDetector.screenState === 'locked';
       if (this.locked_) {
@@ -779,6 +779,13 @@
   /**
    * @override
    */
+  waitPreviewReady() {
+    return this.preview_.waitReadyForTakePhoto();
+  }
+
+  /**
+   * @override
+   */
   async handleResultVideo({resolution, duration, videoSaver, everPaused}) {
     metrics.sendCaptureEvent({
       facing: this.facingMode_,
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/document_corner_overlay.js b/chromeos/components/camera_app_ui/resources/js/views/camera/document_corner_overlay.js
index e7ce0d2e..d5ba910 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera/document_corner_overlay.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera/document_corner_overlay.js
@@ -131,6 +131,11 @@
 }
 
 /**
+ * Timeout to show toast message when no document is detected within the time.
+ */
+const SHOW_NO_DOCUMENT_TOAST_TIMEOUT_MS = 4000;
+
+/**
  * An overlay to show document corner rectangles over preview.
  */
 export class DocumentCornerOverlay {
@@ -145,6 +150,13 @@
     this.overlay_ = dom.get('#preview-document-corner-overlay', HTMLDivElement);
 
     /**
+     * @const {!HTMLDivElement}
+     * @private
+     */
+    this.noDocumentToast_ =
+        dom.getFrom(this.overlay_, '.no-document-toast', HTMLDivElement);
+
+    /**
      * @type {?string}
      * @private
      */
@@ -183,7 +195,13 @@
       return corners;
     })();
 
-    this.clear_();
+    /**
+     * @type {?number}
+     * @private
+     */
+    this.noDocumentTimerId_ = null;
+
+    this.hide_();
   }
 
   /**
@@ -226,14 +244,17 @@
     this.observerId_ = await deviceOperator.registerDocumentCornersObserver(
         assertString(this.deviceId_), (corners) => {
           if (corners.length === 0) {
-            this.clear_();
+            this.onNoCornerDetected_();
             return;
           }
           const rect = this.overlay_.getBoundingClientRect();
           const toOverlaySpace = (pt) =>
               new Point(rect.width * pt.x, rect.height * pt.y);
-          this.setCorners_(corners.map(toOverlaySpace));
+          this.onCornerDetected_(corners.map(toOverlaySpace));
         });
+    this.hide_();
+    this.clearNoDocumentTimer_();
+    this.setNoDocumentTimer_();
   }
 
   /**
@@ -246,11 +267,12 @@
     const nonNullObserverId = this.observerId_;
     const deviceOperator =
         assertInstanceof(await DeviceOperator.getInstance(), DeviceOperator);
-    const isSuccess = deviceOperator.unregisterDocumentCornersObserver(
+    const isSuccess = await deviceOperator.unregisterDocumentCornersObserver(
         assertString(this.deviceId_), nonNullObserverId);
     assert(isSuccess);
     this.observerId_ = null;
-    this.clear_();
+    this.hide_();
+    this.clearNoDocumentTimer_();
   }
 
   /**
@@ -261,13 +283,29 @@
   }
 
   /**
+   * @private
+   */
+  onNoCornerDetected_() {
+    this.hideIndicators_();
+    if (this.isNoDocumentToastShown_()) {
+      return;
+    }
+    if (this.noDocumentTimerId_ === null) {
+      this.setNoDocumentTimer_();
+    }
+  }
+
+  /**
    * @param {!Array<!Point>} corners
    */
-  setCorners_(corners) {
-    if (this.overlay_.classList.contains('clear')) {
-      this.settleCorners_(corners);
-    } else {
+  onCornerDetected_(corners) {
+    this.hideNoDocumentToast_();
+    this.clearNoDocumentTimer_();
+    if (this.isIndicatorsShown_()) {
       this.updateCorners_(corners);
+    } else {
+      this.showIndicators_();
+      this.settleCorners_(corners);
     }
   }
 
@@ -277,8 +315,6 @@
    * @private
    */
   settleCorners_(corners) {
-    this.overlay_.classList.remove('clear');
-
     /**
      * Start point(corner coordinates + outer shift) of settle animation.
      * @param {!Point} corn
@@ -366,10 +402,78 @@
   }
 
   /**
-   * Clears all indicator on overlay.
+   * Hides overlay related UIs.
    * @private
    */
-  clear_() {
-    this.overlay_.classList.add('clear');
+  hide_() {
+    this.hideIndicators_();
+    this.hideNoDocumentToast_();
+  }
+
+  /**
+   * @private
+   * @return {boolean}
+   */
+  isIndicatorsShown_() {
+    return this.overlay_.classList.contains('show-corner-indicator');
+  }
+
+  /**
+   * @private
+   */
+  showIndicators_() {
+    this.overlay_.classList.add('show-corner-indicator');
+  }
+
+  /**
+   * @private
+   */
+  hideIndicators_() {
+    this.overlay_.classList.remove('show-corner-indicator');
+  }
+
+  /**
+   * @private
+   */
+  showNoDocumentToast_() {
+    this.noDocumentToast_.attributeStyleMap.delete('visibility');
+  }
+
+  /**
+   * @private
+   */
+  hideNoDocumentToast_() {
+    this.noDocumentToast_.attributeStyleMap.set('visibility', 'hidden');
+  }
+
+  /**
+   * @private
+   * @return {boolean}
+   */
+  isNoDocumentToastShown_() {
+    return !this.noDocumentToast_.attributeStyleMap.has('visibility');
+  }
+
+  /**
+   * @private
+   */
+  setNoDocumentTimer_() {
+    if (this.noDocumentTimerId_ !== null) {
+      clearTimeout(this.noDocumentTimerId_);
+    }
+    this.noDocumentTimerId_ = setTimeout(() => {
+      this.showNoDocumentToast_();
+      this.clearNoDocumentTimer_();
+    }, SHOW_NO_DOCUMENT_TOAST_TIMEOUT_MS);
+  }
+
+  /**
+   * @private
+   */
+  clearNoDocumentTimer_() {
+    if (this.noDocumentTimerId_ !== null) {
+      clearTimeout(this.noDocumentTimerId_);
+      this.noDocumentTimerId_ = null;
+    }
   }
 }
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/mode/photo.js b/chromeos/components/camera_app_ui/resources/js/views/camera/mode/photo.js
index 6593687..4f2419e 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera/mode/photo.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera/mode/photo.js
@@ -53,6 +53,12 @@
    * Plays UI effect when taking photo.
    */
   playShutterEffect() {}
+
+  /**
+   * @return {!Promise}
+   * @abstract
+   */
+  waitPreviewReady() {}
 }
 
 /**
@@ -167,6 +173,7 @@
         imageHeight: caps.imageHeight.max,
       });
     }
+    await this.handler_.waitPreviewReady();
     const results = await this.crosImageCapture_.takePhoto(photoSettings);
     return results[0];
   }
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/mode/scanner.js b/chromeos/components/camera_app_ui/resources/js/views/camera/mode/scanner.js
index 1a5339d..01a8c868 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera/mode/scanner.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera/mode/scanner.js
@@ -61,6 +61,12 @@
    * @abstract
    */
   handleResultDocument(result, name) {}
+
+  /**
+   * @return {!Promise}
+   * @abstract
+   */
+  waitPreviewReady() {}
 }
 
 /**
@@ -107,6 +113,13 @@
   playShutterEffect() {
     this.handler_.playBlockingShutterEffect();
   }
+
+  /**
+   * @override
+   */
+  waitPreviewReady() {
+    return this.handler_.waitPreviewReady();
+  }
 }
 
 /**
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/mode/square.js b/chromeos/components/camera_app_ui/resources/js/views/camera/mode/square.js
index 9e09f7b..ab5c76e 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera/mode/square.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera/mode/square.js
@@ -56,9 +56,9 @@
   /**
    * @override
    */
-  async handleResultPhoto(result, ...args) {
+  async handleResultPhoto(result, name) {
     result.blob = await cropSquare(result.blob);
-    await this.handler_.handleResultPhoto(result, ...args);
+    await this.handler_.handleResultPhoto(result, name);
   }
 
   /**
@@ -67,6 +67,13 @@
   playShutterEffect() {
     this.handler_.playShutterEffect();
   }
+
+  /**
+   * @override
+   */
+  waitPreviewReady() {
+    return this.handler_.waitPreviewReady();
+  }
 }
 
 /**
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/options.js b/chromeos/components/camera_app_ui/resources/js/views/camera/options.js
index 36f80ec..b191028 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera/options.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera/options.js
@@ -82,12 +82,10 @@
      */
     this.audioTrack_ = null;
 
-    [['#switch-device', () => this.switchDevice(undefined)],
-     ['#open-settings', () => nav.open(ViewName.SETTINGS)],
-    ]
-        .forEach(
-            ([selector, fn]) => dom.get(selector, HTMLButtonElement)
-                                    .addEventListener('click', fn));
+    dom.get('#switch-device', HTMLButtonElement)
+        .addEventListener('click', () => this.switchDevice(undefined));
+    dom.get('#open-settings', HTMLButtonElement)
+        .addEventListener('click', () => nav.open(ViewName.SETTINGS));
 
     this.toggleMic_.addEventListener('click', () => this.updateAudioByMic_());
     this.toggleMirror_.addEventListener('click', () => this.saveMirroring_());
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/preview.js b/chromeos/components/camera_app_ui/resources/js/views/camera/preview.js
index f4c792d..151b0f5f 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera/preview.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera/preview.js
@@ -14,12 +14,14 @@
 import * as nav from '../../nav.js';
 import * as state from '../../state.js';
 import {
+  CanceledError,
   ErrorLevel,
   ErrorType,
   Facing,
   Resolution,
 } from '../../type.js';
 import * as util from '../../util.js';
+import {WaitableEvent} from '../../waitable_event.js';
 import {windowController} from '../../window_controller.js';
 
 /**
@@ -97,6 +99,12 @@
      */
     this.constraints_ = null;
 
+    /**
+     * @type {?function()}
+     * @private
+     */
+    this.cancelWaitReadyForTakePhoto_ = null;
+
     window.addEventListener('resize', () => this.onWindowStatusChanged_());
 
     windowController.addListener(() => this.onWindowStatusChanged_());
@@ -300,12 +308,50 @@
       if (deviceOperator !== null) {
         deviceOperator.dropConnection(deviceId);
       }
+      if (this.cancelWaitReadyForTakePhoto_ !== null) {
+        this.cancelWaitReadyForTakePhoto_();
+      }
       this.stream_ = null;
     }
     state.set(state.State.STREAMING, false);
   }
 
   /**
+   * Waits for preview stream ready for taking photo.
+   * @return {!Promise}
+   */
+  async waitReadyForTakePhoto() {
+    if (this.stream_ === null) {
+      throw new CanceledError('Preview is closed');
+    }
+
+    // Chrome use muted state on video track representing no frame input
+    // returned from preview video for a while and call |takePhoto()| with
+    // video track in muted state will fail with |kInvalidStateError| exception.
+    // To mitigate chance of hitting this error, here we ensure frame inputs
+    // from the preview and checked video muted state before taking photo.
+    const track = this.getVideoTrack_();
+    const waitFrame = async () => {
+      const onReady = new WaitableEvent();
+      const callbackId = this.video_.requestVideoFrameCallback((now) => {
+        onReady.signal(true);
+      });
+      this.cancelWaitReadyForTakePhoto_ = () => {
+        this.video_.cancelVideoFrameCallback(callbackId);
+        onReady.signal(false);
+      };
+      const ready = await onReady.wait();
+      this.cancelWaitReadyForTakePhoto_ = null;
+      return ready;
+    };
+    do {
+      if (!await waitFrame()) {
+        throw new CanceledError('Preview is closed');
+      }
+    } while (track.muted);
+  }
+
+  /**
    * Checks preview whether to show preview metadata or not.
    * @private
    */
@@ -393,6 +439,7 @@
         'ANDROID_CONTROL_AE_ANTIBANDING_MODE_');
 
     const tag = cros.mojom.CameraMetadataTag;
+    /** @type {!Object<string, function(!Array<number>): void>} */
     const metadataEntryHandlers = {
       [tag.ANDROID_LENS_FOCUS_DISTANCE]: ([value]) => {
         if (value === 0) {
diff --git a/chromeos/components/camera_app_ui/resources/strings/camera_strings.grd b/chromeos/components/camera_app_ui/resources/strings/camera_strings.grd
index 323bae8..b212693 100644
--- a/chromeos/components/camera_app_ui/resources/strings/camera_strings.grd
+++ b/chromeos/components/camera_app_ui/resources/strings/camera_strings.grd
@@ -505,6 +505,9 @@
       <message desc="Text on ack button in dialog introducing document mode." name="IDS_DOCUMENT_MODE_DIALOG_GOT_IT">
         Got it
       </message>
+      <message desc="Toast message for guiding user to reposition document when no document in the camera FOV." name="IDS_NO_DOCUMENT_GUIDE_MSG">
+        Place all edges of the document within the frame
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/chromeos/components/camera_app_ui/resources/strings/camera_strings_grd/IDS_NO_DOCUMENT_GUIDE_MSG.png.sha1 b/chromeos/components/camera_app_ui/resources/strings/camera_strings_grd/IDS_NO_DOCUMENT_GUIDE_MSG.png.sha1
new file mode 100644
index 0000000..076d9594
--- /dev/null
+++ b/chromeos/components/camera_app_ui/resources/strings/camera_strings_grd/IDS_NO_DOCUMENT_GUIDE_MSG.png.sha1
@@ -0,0 +1 @@
+e9baf1de896a15acff7047007d9c8b4687064912
\ No newline at end of file
diff --git a/chromeos/components/camera_app_ui/resources/views/main.html b/chromeos/components/camera_app_ui/resources/views/main.html
index 6c69453ef..11d1153 100644
--- a/chromeos/components/camera_app_ui/resources/views/main.html
+++ b/chromeos/components/camera_app_ui/resources/views/main.html
@@ -98,6 +98,8 @@
                     tabindex="-1" hidden></object>
           </div>
           <div id="preview-document-corner-overlay">
+            <div class="no-document-toast" tabindex="0" aria-live="polite"
+                 i18n-text="no_document_guide_msg"></div>
           </div>
           <div class="barcode-scan-box centered-overlay"></div>
           <div class="snackbar" aria-live="polite"></div>
diff --git a/chromeos/components/drivefs/OWNERS b/chromeos/components/drivefs/OWNERS
index b9fd788..f30cc14 100644
--- a/chromeos/components/drivefs/OWNERS
+++ b/chromeos/components/drivefs/OWNERS
@@ -1,6 +1,6 @@
-dats@chromium.org
-sammc@chromium.org
-slangley@chromium.org
+austinct@chromium.org
+petermarshall@chromium.org
+simmonsjosh@google.com
 
 per-file *_mojom_traits*.*=set noparent
 per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/chromeos/services/ime/BUILD.gn b/chromeos/services/ime/BUILD.gn
index 71a81dc..f3cf421 100644
--- a/chromeos/services/ime/BUILD.gn
+++ b/chromeos/services/ime/BUILD.gn
@@ -26,7 +26,6 @@
   deps = [
     ":constants",
     "//base",
-    "//chromeos/services/ime/public/cpp:buildflags",
     "//chromeos/services/ime/public/cpp/shared_lib:interfaces",
   ]
 }
@@ -49,7 +48,6 @@
     ":decoder",
     "//ash/constants",
     "//base",
-    "//chromeos/services/ime/public/cpp:buildflags",
     "//chromeos/services/ime/public/cpp:rulebased",
     "//chromeos/services/ime/public/cpp/shared_lib:interfaces",
     "//chromeos/services/ime/public/mojom",
@@ -67,7 +65,6 @@
     ":constants",
     ":decoder",
     "//base",
-    "//chromeos/services/ime/public/cpp:buildflags",
     "//sandbox/linux:sandbox_services",
     "//sandbox/policy",
   ]
diff --git a/chromeos/services/ime/constants.cc b/chromeos/services/ime/constants.cc
index e8c3a58..1573d5b 100644
--- a/chromeos/services/ime/constants.cc
+++ b/chromeos/services/ime/constants.cc
@@ -23,8 +23,6 @@
     FILE_PATH_LITERAL("/usr/share/chromeos-assets/input_methods/input_tools");
 const base::FilePath::CharType kUserInputMethodsDirPath[] =
     FILE_PATH_LITERAL("/home/chronos/user/" IME_DIR_STRING);
-const base::FilePath::CharType kSharedInputMethodsDirPath[] =
-    FILE_PATH_LITERAL("/home/chronos/" IME_DIR_STRING);
 const base::FilePath::CharType kLanguageDataDirName[] =
     FILE_PATH_LITERAL("google");
 #else
@@ -34,8 +32,6 @@
     FILE_PATH_LITERAL("/tmp/" IME_DIR_STRING);
 const base::FilePath::CharType kUserInputMethodsDirPath[] =
     FILE_PATH_LITERAL("/tmp/" IME_DIR_STRING);
-const base::FilePath::CharType kSharedInputMethodsDirPath[] =
-    FILE_PATH_LITERAL("/tmp/" IME_DIR_STRING);
 const base::FilePath::CharType kLanguageDataDirName[] =
     FILE_PATH_LITERAL("data");
 #endif
diff --git a/chromeos/services/ime/constants.h b/chromeos/services/ime/constants.h
index 2d70d88b5..cf03f64 100644
--- a/chromeos/services/ime/constants.h
+++ b/chromeos/services/ime/constants.h
@@ -21,17 +21,6 @@
 COMPONENT_EXPORT(CHROMEOS_IME_CONSTANTS)
 extern const base::FilePath::CharType kUserInputMethodsDirPath[];
 
-// The path of downloaded IME language dictionaries which shared by all users.
-// It aims to reduce storage and improve responsiveness by reusing static
-// dictionary for all users. This feature is disabled by default. When
-// `CrosImeSharedDataEnabled` is on and the IME attempt to load some language
-// dictionary which is missing on the device, the IME service will try to
-// download it to the shared path. Because the language dictionary will be
-// accessible by all users, it will prevent duplicate downloads of the same
-// language dictionary.
-COMPONENT_EXPORT(CHROMEOS_IME_CONSTANTS)
-extern const base::FilePath::CharType kSharedInputMethodsDirPath[];
-
 // The name of the directory inside the profile where IME data are stored in.
 COMPONENT_EXPORT(CHROMEOS_IME_CONSTANTS)
 extern const base::FilePath::CharType kInputMethodsDirName[];
diff --git a/chromeos/services/ime/decoder/decoder_engine.cc b/chromeos/services/ime/decoder/decoder_engine.cc
index 6cddb2a..11f959f 100644
--- a/chromeos/services/ime/decoder/decoder_engine.cc
+++ b/chromeos/services/ime/decoder/decoder_engine.cc
@@ -8,7 +8,6 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "chromeos/services/ime/constants.h"
-#include "chromeos/services/ime/public/cpp/buildflags.h"
 
 namespace chromeos {
 namespace ime {
diff --git a/chromeos/services/ime/decoder/system_engine.cc b/chromeos/services/ime/decoder/system_engine.cc
index 36f25e23..7b5f577 100644
--- a/chromeos/services/ime/decoder/system_engine.cc
+++ b/chromeos/services/ime/decoder/system_engine.cc
@@ -9,7 +9,6 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "chromeos/services/ime/constants.h"
-#include "chromeos/services/ime/public/cpp/buildflags.h"
 
 namespace chromeos {
 namespace ime {
diff --git a/chromeos/services/ime/ime_sandbox_hook.cc b/chromeos/services/ime/ime_sandbox_hook.cc
index c930ad4..8b6f3ed 100644
--- a/chromeos/services/ime/ime_sandbox_hook.cc
+++ b/chromeos/services/ime/ime_sandbox_hook.cc
@@ -10,10 +10,8 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
-#include "build/buildflag.h"
 #include "chromeos/services/ime/constants.h"
 #include "chromeos/services/ime/ime_decoder.h"
-#include "chromeos/services/ime/public/cpp/buildflags.h"
 #include "sandbox/linux/syscall_broker/broker_command.h"
 #include "sandbox/linux/syscall_broker/broker_file_permission.h"
 
@@ -24,15 +22,6 @@
 namespace ime {
 
 namespace {
-// Whether IME instance shares a same language data path with each other.
-inline constexpr bool CrosImeSharedDataEnabled() {
-#if BUILDFLAG(ENABLE_CROS_IME_SHARED_DATA)
-  return true;
-#else
-  return false;
-#endif
-}
-
 void AddBundleFolder(std::vector<BrokerFilePermission>* permissions) {
   base::FilePath bundle_dir =
       base::FilePath(kBundledInputMethodsDirPath).AsEndingWithSeparator();
@@ -40,21 +29,6 @@
       BrokerFilePermission::ReadOnlyRecursive(bundle_dir.value()));
 }
 
-void AddSharedDataFolderIfEnabled(
-    std::vector<BrokerFilePermission>* permissions) {
-  if (!CrosImeSharedDataEnabled())
-    return;
-
-  // Without access to shared home folder, IME servcie will download all
-  // missing dictionaries to `kUserInputMethodsDirPath` of the current user.
-  base::FilePath shared_path =
-      base::FilePath(kSharedInputMethodsDirPath).AsEndingWithSeparator();
-  if (base::CreateDirectory(shared_path)) {
-    permissions->push_back(
-        BrokerFilePermission::ReadWriteCreateRecursive(shared_path.value()));
-  }
-}
-
 void AddUserDataFolder(std::vector<BrokerFilePermission>* permissions) {
   // When failed to access user profile folder, decoder still can work, but
   // user dictionary can not be saved.
@@ -79,7 +53,6 @@
 
   AddBundleFolder(&permissions);
   AddUserDataFolder(&permissions);
-  AddSharedDataFolderIfEnabled(&permissions);
   return permissions;
 }
 
diff --git a/chromeos/services/ime/ime_service.cc b/chromeos/services/ime/ime_service.cc
index 75e601bb0..7ee2a26 100644
--- a/chromeos/services/ime/ime_service.cc
+++ b/chromeos/services/ime/ime_service.cc
@@ -16,11 +16,9 @@
 #include "base/location.h"
 #include "base/notreached.h"
 #include "base/sequenced_task_runner.h"
-#include "build/buildflag.h"
 #include "chromeos/services/ime/constants.h"
 #include "chromeos/services/ime/decoder/decoder_engine.h"
 #include "chromeos/services/ime/decoder/system_engine.h"
-#include "chromeos/services/ime/public/cpp/buildflags.h"
 #include "chromeos/services/ime/rule_based_engine.h"
 #include "mojo/public/c/system/thunks.h"
 
@@ -132,8 +130,6 @@
 }
 
 const char* ImeService::GetImeGlobalDir() {
-  // Global IME data is supported yet.
-  NOTIMPLEMENTED();
   return "";
 }
 
diff --git a/chromeos/services/ime/ime_service.h b/chromeos/services/ime/ime_service.h
index 8bf759c..8035f5a 100644
--- a/chromeos/services/ime/ime_service.h
+++ b/chromeos/services/ime/ime_service.h
@@ -52,8 +52,10 @@
 
   // ImeCrosPlatform overrides:
   const char* GetImeBundleDir() override;
-  const char* GetImeGlobalDir() override;
   const char* GetImeUserHomeDir() override;
+  // To be deprecated soon. Do not make a call on it anymore.
+  const char* GetImeGlobalDir() override;
+
   int SimpleDownloadToFile(const char* url,
                            const char* file_path,
                            SimpleDownloadCallback callback) override;
diff --git a/chromeos/services/ime/public/cpp/BUILD.gn b/chromeos/services/ime/public/cpp/BUILD.gn
index a145924..749076d 100644
--- a/chromeos/services/ime/public/cpp/BUILD.gn
+++ b/chromeos/services/ime/public/cpp/BUILD.gn
@@ -3,16 +3,9 @@
 # found in the LICENSE file.
 
 import("//build/buildflag_header.gni")
-import("//chromeos/services/ime/public/features.gni")
 import("//testing/libfuzzer/fuzzer_test.gni")
 import("//third_party/libprotobuf-mutator/fuzzable_proto_library.gni")
 
-buildflag_header("buildflags") {
-  header = "buildflags.h"
-
-  flags = [ "ENABLE_CROS_IME_SHARED_DATA=$enable_cros_ime_shared_data" ]
-}
-
 source_set("structs") {
   sources = [ "suggestions.h" ]
 }
diff --git a/chromeos/services/ime/public/features.gni b/chromeos/services/ime/public/features.gni
deleted file mode 100644
index 500a2fc..0000000
--- a/chromeos/services/ime/public/features.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.
-
-declare_args() {
-  # Enable share language modules between IME services.
-  enable_cros_ime_shared_data = false
-}
diff --git a/components/arc/arc_features.cc b/components/arc/arc_features.cc
index f276dd5..4f6c09b 100644
--- a/components/arc/arc_features.cc
+++ b/components/arc/arc_features.cc
@@ -79,7 +79,7 @@
 // Controls ARCVM real time vcpu feature on a device with 2 logical cores
 // online.
 const base::Feature kRtVcpuDualCore{"ArcRtVcpuDualCore",
-                                    base::FEATURE_DISABLED_BY_DEFAULT};
+                                    base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Controls ARCVM real time vcpu feature on a device with 3+ logical cores
 // online.
diff --git a/components/arc/arc_util.cc b/components/arc/arc_util.cc
index 70481207..74adda1 100644
--- a/components/arc/arc_util.cc
+++ b/components/arc/arc_util.cc
@@ -4,19 +4,25 @@
 
 #include "components/arc/arc_util.h"
 
+#include <sys/prctl.h>
+
 #include <algorithm>
 #include <cstdio>
+#include <set>
 
 #include "ash/constants/app_types.h"
 #include "ash/constants/ash_switches.h"
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/feature_list.h"
+#include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/process/launch.h"
 #include "base/process/process_metrics.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/system/sys_info.h"
 #include "chromeos/dbus/concierge/concierge_client.h"
 #include "chromeos/dbus/debug_daemon/debug_daemon_client.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
@@ -29,6 +35,12 @@
 #include "ui/aura/window.h"
 #include "ui/display/types/display_constants.h"
 
+// Downstream core scheduling interface for 4.19, 5.4 kernels.
+// TODO(b/152605392): Remove once those kernel versions are obsolete.
+#ifndef PR_SET_CORE_SCHED
+#define PR_SET_CORE_SCHED 0x200
+#endif
+
 namespace arc {
 
 namespace {
@@ -482,4 +494,56 @@
   }
 }
 
+bool IsCoreSchedulingAvailable() {
+  static const bool has_vulns = []() {
+    // Check for support in the kernel by passing bad param `prctl(0x200, 2)`.
+    // If it is supported, we will get ERANGE rather than EINVAL.
+    // TODO(joelhockey): The public prctl(PR_SCHED_CORE, ...) API added in
+    // kernel 5.14 returns EINVAL rather than ERANGE for invalid params which
+    // doesn't give us any way to tell if core scheduling is supported. We
+    // should not remove PR_SET_CORE_SCHED 0x200 until all devices are at 5.14+.
+    if (prctl(PR_SET_CORE_SCHED, 2) == -1 && errno != ERANGE) {
+      VLOG(1) << "Core scheduling not supported in kernel";
+      return false;
+    }
+
+    base::FilePath sysfs_vulns("/sys/devices/system/cpu/vulnerabilities");
+    std::string buf;
+    for (const std::string& s : {"l1tf", "mds"}) {
+      base::FilePath vuln = sysfs_vulns.Append(s);
+      if (!base::ReadFileToString(vuln, &buf)) {
+        LOG(ERROR) << "Could not read " << vuln;
+        continue;
+      }
+      base::TrimWhitespaceASCII(buf, base::TRIM_ALL, &buf);
+      if (buf != "Not affected") {
+        VLOG(1) << "Core scheduling available: " << vuln << "=" << buf;
+        return true;
+      }
+    }
+    VLOG(1) << "Core scheduling not required";
+    return false;
+  }();
+  return has_vulns;
+}
+
+int NumberOfProcessorsForCoreScheduling() {
+  // cat /sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list |\
+  //   sort -u | wc -l
+  static const int num_physical_cores = []() {
+    std::set<std::string> lists;
+    std::string buf;
+    for (int i = 0; i < base::SysInfo::NumberOfProcessors(); ++i) {
+      base::FilePath list(base::StringPrintf(
+          "/sys/devices/system/cpu/cpu%d/topology/thread_siblings_list", i));
+      if (!base::ReadFileToString(list, &buf))
+        LOG(ERROR) << "Could not read " << list;
+      else
+        lists.insert(buf);
+    }
+    return lists.size() > 0 ? lists.size() : 1;
+  }();
+  return num_physical_cores;
+}
+
 }  // namespace arc
diff --git a/components/arc/arc_util.h b/components/arc/arc_util.h
index e8a0eeb..0130ed0c 100644
--- a/components/arc/arc_util.h
+++ b/components/arc/arc_util.h
@@ -206,6 +206,18 @@
 void ConfigureUpstartJobs(std::deque<JobDesc> jobs,
                           chromeos::VoidDBusMethodCallback callback);
 
+// Returns true if core scheduling is supported in the kernel, and CPU has MDS
+// or L1TF vulnerabilities. Core scheduling does not run on CPUs that are not
+// vulnerable.
+// TODO(yusukes): Once https://crrev.com/c/3063740 is submitted, switch to the
+// function in the CL and delete this.
+bool IsCoreSchedulingAvailable();
+
+// Returns number of physical cores.
+// TODO(yusukes): Once https://crrev.com/c/3063740 is submitted, switch to the
+// function in the CL and delete this.
+int NumberOfProcessorsForCoreScheduling();
+
 }  // namespace arc
 
 #endif  // COMPONENTS_ARC_ARC_UTIL_H_
diff --git a/components/arc/ime/arc_ime_bridge_impl.cc b/components/arc/ime/arc_ime_bridge_impl.cc
index aa3c796..4778168 100644
--- a/components/arc/ime/arc_ime_bridge_impl.cc
+++ b/components/arc/ime/arc_ime_bridge_impl.cc
@@ -165,12 +165,6 @@
   DVLOG(1) << "RequestHideIme is deprecated.";
 }
 
-void ArcImeBridgeImpl::ShouldEnableKeyEventForwarding(
-    ShouldEnableKeyEventForwardingCallback callback) {
-  // TODO(b/190487153): Clean up this once the caller is removed.
-  std::move(callback).Run(true);
-}
-
 void ArcImeBridgeImpl::SendKeyEvent(std::unique_ptr<ui::KeyEvent> key_event,
                                     SendKeyEventCallback callback) {
   delegate_->SendKeyEvent(std::move(key_event), std::move(callback));
diff --git a/components/arc/ime/arc_ime_bridge_impl.h b/components/arc/ime/arc_ime_bridge_impl.h
index b1553ef..55282e4 100644
--- a/components/arc/ime/arc_ime_bridge_impl.h
+++ b/components/arc/ime/arc_ime_bridge_impl.h
@@ -53,8 +53,6 @@
                                               const gfx::Range& selection_range,
                                               bool screen_coordinates) override;
   void RequestHideImeDeprecated() override;
-  void ShouldEnableKeyEventForwarding(
-      ShouldEnableKeyEventForwardingCallback callback) override;
   void SendKeyEvent(std::unique_ptr<ui::KeyEvent> key_event,
                     SendKeyEventCallback callback) override;
 
diff --git a/components/arc/mojom/ime.mojom b/components/arc/mojom/ime.mojom
index f0ca71b8..0e91a86 100644
--- a/components/arc/mojom/ime.mojom
+++ b/components/arc/mojom/ime.mojom
@@ -110,9 +110,6 @@
   // However, hiding can be canceled if a text field gets focus.
   [MinVersion=9] RequestHideImeDeprecated@5();
 
-  // Asks Chrome whether the proxy IME should send a key event to the host IME.
-  [MinVersion=13] ShouldEnableKeyEventForwarding@6() => (bool should_forward);
-
   // Sends a key event to Chrome to pass it to the Chrome OS IME.
   [MinVersion=14] SendKeyEvent@7(KeyEventData key_event_data)
       => (bool is_consumed);
diff --git a/components/arc/session/arc_vm_client_adapter.cc b/components/arc/session/arc_vm_client_adapter.cc
index 5053c69..035e3eb 100644
--- a/components/arc/session/arc_vm_client_adapter.cc
+++ b/components/arc/session/arc_vm_client_adapter.cc
@@ -822,13 +822,39 @@
 
   void OnDemoResourcesLoaded(chromeos::VoidDBusMethodCallback callback,
                              FileSystemStatus file_system_status) {
+    VLOG(2) << "Checking number of vcpus to spin up";
+    base::ThreadPool::PostTaskAndReplyWithResult(
+        FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
+        base::BindOnce(&ArcVmClientAdapter::GetNumVCPUsToStart,
+                       start_params_.num_cores_disabled),
+        base::BindOnce(&ArcVmClientAdapter::OnGetNumVCPUsToStart,
+                       weak_factory_.GetWeakPtr(), std::move(callback),
+                       std::move(file_system_status)));
+  }
+
+  static int32_t GetNumVCPUsToStart(uint32_t num_cores_disabled) {
+    // When the CPU has MDS or L1TF vulnerabilities, crosvm won't be allowed to
+    // run two vCPUs on the same physical core at the same time. This
+    // effectively disables SMT on crosvm. Because of this restriction, when the
+    // CPU has the vulnerabilities, set |cpus| to the number of physical cores.
+    // Otherwise, set the variable to the number of logical cores minus the ones
+    // disabled by chrome://flags/#scheduler-configuration.
+    // TODO(yusukes): Stop doing this on the other thread once we migrate to
+    // https://crrev.com/c/3063740.
+    const int32_t cpus =
+        IsCoreSchedulingAvailable()
+            ? NumberOfProcessorsForCoreScheduling()
+            : base::SysInfo::NumberOfProcessors() - num_cores_disabled;
+    DCHECK_LT(0, cpus);
+    return cpus;
+  }
+
+  void OnGetNumVCPUsToStart(chromeos::VoidDBusMethodCallback callback,
+                            FileSystemStatus file_system_status,
+                            int32_t cpus) {
     const base::FilePath demo_session_apps_path =
         demo_mode_delegate_->GetDemoAppsPath();
 
-    const int32_t cpus =
-        base::SysInfo::NumberOfProcessors() - start_params_.num_cores_disabled;
-    DCHECK_LT(0, cpus);
-
     std::vector<std::string> kernel_cmdline = GenerateKernelCmdline(
         start_params_, file_system_status, *is_dev_mode_, is_host_on_vm_,
         GetChromeOsChannelFromLsbRelease());
diff --git a/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java b/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java
index 9ea828f..bf01e34 100644
--- a/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java
+++ b/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java
@@ -189,11 +189,12 @@
             });
             ContactsPicker.showContactsPicker(mWindowAndroid, this, multiselect, includeNames,
                     includeEmails, includeTel, includeAddresses, includeIcons, "example.com");
-        });
 
-        mSelectionDelegate = mDialog.getCategoryViewForTesting().getSelectionDelegateForTesting();
-        if (!multiselect) mSelectionDelegate.setSingleSelectionMode();
-        mSelectionDelegate.addObserver(this);
+            mSelectionDelegate =
+                    mDialog.getCategoryViewForTesting().getSelectionDelegateForTesting();
+            if (!multiselect) mSelectionDelegate.setSingleSelectionMode();
+            mSelectionDelegate.addObserver(this);
+        });
     }
 
     /**
diff --git a/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/AppModalPresenterTest.java b/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/AppModalPresenterTest.java
index d11bcbf..d85ed8f8 100644
--- a/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/AppModalPresenterTest.java
+++ b/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/AppModalPresenterTest.java
@@ -85,7 +85,7 @@
 
     @After
     public void tearDown() {
-        sManager.destroy();
+        TestThreadUtils.runOnUiThreadBlocking(sManager::destroy);
     }
 
     @Test
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java
index 366cc33..9f8075e 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java
@@ -42,26 +42,28 @@
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
-        mModel = new PropertyModel.Builder(PromoCardProperties.ALL_KEYS).build();
+        mModel = TestThreadUtils.runOnUiThreadBlockingNoException(
+                () -> new PropertyModel.Builder(PromoCardProperties.ALL_KEYS).build());
     }
 
     @After
     public void tearDown() {
-        mPromoCardCoordinator.destroy();
+        TestThreadUtils.runOnUiThreadBlocking(mPromoCardCoordinator::destroy);
     }
 
     private void setupCoordinator(@LayoutStyle int layoutStyle) {
         // TODO(https://crbug.com/1217140): Remove this theme wrapper after dummy ui activity
         //  is based on material theme. For now we need the theme wrapper to inflate the layout;
         //  because we are not setting our theme overlay for the test apk
-        ContextThemeWrapper wrapperColor = new ContextThemeWrapper(
-                mContext, org.chromium.components.browser_ui.widget.R.style.ColorOverlay);
-        ContextThemeWrapper wrapperTheme = new ContextThemeWrapper(
-                wrapperColor, org.chromium.components.browser_ui.widget.R.style.Theme_BrowserUI);
-        mPromoCardCoordinator =
-                new PromoCardCoordinator(wrapperTheme, mModel, "test-feature", layoutStyle);
-        mView = (PromoCardView) mPromoCardCoordinator.getView();
-
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            ContextThemeWrapper wrapperColor = new ContextThemeWrapper(
+                    mContext, org.chromium.components.browser_ui.widget.R.style.ColorOverlay);
+            ContextThemeWrapper wrapperTheme = new ContextThemeWrapper(wrapperColor,
+                    org.chromium.components.browser_ui.widget.R.style.Theme_BrowserUI);
+            mPromoCardCoordinator =
+                    new PromoCardCoordinator(wrapperTheme, mModel, "test-feature", layoutStyle);
+            mView = (PromoCardView) mPromoCardCoordinator.getView();
+        });
         Assert.assertNotNull("PromoCardView is null", mView);
     }
 
diff --git a/components/feed/core/v2/metrics_reporter.cc b/components/feed/core/v2/metrics_reporter.cc
index 291b229..7d62370b 100644
--- a/components/feed/core/v2/metrics_reporter.cc
+++ b/components/feed/core/v2/metrics_reporter.cc
@@ -446,6 +446,7 @@
     case FeedUserActionType::kTappedUnfollowTryAgainOnSnackbar:
     case FeedUserActionType::kTappedGoToFeedPostFollowActiveHelp:
     case FeedUserActionType::kTappedDismissPostFollowActiveHelp:
+    case FeedUserActionType::kTappedDiscoverFeedPreview:
       // Nothing additional for these actions. Note that some of these are iOS
       // only.
 
diff --git a/components/feed/core/v2/public/common_enums.h b/components/feed/core/v2/public/common_enums.h
index 8ebfd5b..fbb9b6c 100644
--- a/components/feed/core/v2/public/common_enums.h
+++ b/components/feed/core/v2/public/common_enums.h
@@ -110,8 +110,11 @@
   // After following an active web feed, the user tapped to dismiss the
   // post-follow help dialog.
   kTappedDismissPostFollowActiveHelp = 38,
+  // After long-pressing on the feed and seeing the preview, the user tapped
+  // on the preview.
+  kTappedDiscoverFeedPreview = 39,
 
-  kMaxValue = kTappedDismissPostFollowActiveHelp,
+  kMaxValue = kTappedDiscoverFeedPreview,
 };
 
 // The requested order of the Feed content.
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerViewTest.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerViewTest.java
index 360a4ce..4f88c326 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerViewTest.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerViewTest.java
@@ -90,15 +90,16 @@
     @Test
     @MediumTest
     public void testSecondaryActionDirectCallback() {
-        PropertyModel propertyModel =
-                new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS)
-                        .with(MessageBannerProperties.MESSAGE_IDENTIFIER,
-                                MessageIdentifier.TEST_MESSAGE)
-                        .with(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID,
-                                android.R.drawable.ic_menu_add)
-                        .with(MessageBannerProperties.ON_SECONDARY_ACTION, mSecondaryActionCallback)
-                        .build();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
+            PropertyModel propertyModel =
+                    new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS)
+                            .with(MessageBannerProperties.MESSAGE_IDENTIFIER,
+                                    MessageIdentifier.TEST_MESSAGE)
+                            .with(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID,
+                                    android.R.drawable.ic_menu_add)
+                            .with(MessageBannerProperties.ON_SECONDARY_ACTION,
+                                    mSecondaryActionCallback)
+                            .build();
             PropertyModelChangeProcessor.create(
                     propertyModel, mMessageBannerView, MessageBannerViewBinder::bind);
         });
@@ -114,17 +115,18 @@
     @Test
     @MediumTest
     public void testSecondaryActionMenu() {
-        PropertyModel propertyModel =
-                new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS)
-                        .with(MessageBannerProperties.MESSAGE_IDENTIFIER,
-                                MessageIdentifier.TEST_MESSAGE)
-                        .with(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID,
-                                android.R.drawable.ic_menu_add)
-                        .with(MessageBannerProperties.SECONDARY_BUTTON_MENU_TEXT,
-                                SECONDARY_BUTTON_MENU_TEXT)
-                        .with(MessageBannerProperties.ON_SECONDARY_ACTION, mSecondaryActionCallback)
-                        .build();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
+            PropertyModel propertyModel =
+                    new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS)
+                            .with(MessageBannerProperties.MESSAGE_IDENTIFIER,
+                                    MessageIdentifier.TEST_MESSAGE)
+                            .with(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID,
+                                    android.R.drawable.ic_menu_add)
+                            .with(MessageBannerProperties.SECONDARY_BUTTON_MENU_TEXT,
+                                    SECONDARY_BUTTON_MENU_TEXT)
+                            .with(MessageBannerProperties.ON_SECONDARY_ACTION,
+                                    mSecondaryActionCallback)
+                            .build();
             PropertyModelChangeProcessor.create(
                     propertyModel, mMessageBannerView, MessageBannerViewBinder::bind);
         });
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessagesMetrics.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessagesMetrics.java
index e27485e3..8fa70243 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessagesMetrics.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessagesMetrics.java
@@ -91,6 +91,8 @@
                 return "PermissionUpdate";
             case MessageIdentifier.ADS_BLOCKED:
                 return "AdsBlocked";
+            case MessageIdentifier.DOWNLOAD_PROGRESS:
+                return "DownloadProgress";
             default:
                 return "Unknown";
         }
diff --git a/components/messages/android/message_enums.h b/components/messages/android/message_enums.h
index a340c6f..cfeb23b 100644
--- a/components/messages/android/message_enums.h
+++ b/components/messages/android/message_enums.h
@@ -86,6 +86,7 @@
   GROUPED_PERMISSION = 13,
   PERMISSION_UPDATE = 14,
   ADS_BLOCKED = 15,
+  DOWNLOAD_PROGRESS = 16,
 
   // Insert new values before this line.
   COUNT
diff --git a/components/metrics/log_store.h b/components/metrics/log_store.h
index cb0f633..da81179 100644
--- a/components/metrics/log_store.h
+++ b/components/metrics/log_store.h
@@ -16,6 +16,8 @@
 // at a time by staging and discarding logs, and persist/load the whole set.
 class LogStore {
  public:
+  virtual ~LogStore() = default;
+
   // Returns true if there are any logs waiting to be uploaded.
   virtual bool has_unsent_logs() const = 0;
 
diff --git a/components/metrics/metrics_log_store.cc b/components/metrics/metrics_log_store.cc
index 99b9f6b..aa07d38 100644
--- a/components/metrics/metrics_log_store.cc
+++ b/components/metrics/metrics_log_store.cc
@@ -57,49 +57,89 @@
       break;
     case MetricsLog::ONGOING_LOG:
     case MetricsLog::INDEPENDENT_LOG:
-      ongoing_log_queue_.StoreLog(log_data, log_metadata);
+      has_alternate_ongoing_log_store()
+          ? alternate_ongoing_log_queue_->StoreLog(log_data, log_metadata)
+          : ongoing_log_queue_.StoreLog(log_data, log_metadata);
       break;
   }
 }
 
+void MetricsLogStore::SetAlternateOngoingLogStore(
+    std::unique_ptr<UnsentLogStore> log_store) {
+  DCHECK(!has_alternate_ongoing_log_store());
+  DCHECK(unsent_logs_loaded_);
+  alternate_ongoing_log_queue_ = std::move(log_store);
+  alternate_ongoing_log_queue_->LoadPersistedUnsentLogs();
+}
+
+void MetricsLogStore::UnsetAlternateOngoingLogStore() {
+  DCHECK(has_alternate_ongoing_log_store());
+  alternate_ongoing_log_queue_->TrimAndPersistUnsentLogs();
+  alternate_ongoing_log_queue_.reset();
+}
+
 bool MetricsLogStore::has_unsent_logs() const {
   return initial_log_queue_.has_unsent_logs() ||
-         ongoing_log_queue_.has_unsent_logs();
+         ongoing_log_queue_.has_unsent_logs() ||
+         alternate_ongoing_log_store_has_unsent_logs();
 }
 
 bool MetricsLogStore::has_staged_log() const {
   return initial_log_queue_.has_staged_log() ||
-         ongoing_log_queue_.has_staged_log();
+         ongoing_log_queue_.has_staged_log() ||
+         alternate_ongoing_log_store_has_staged_log();
 }
 
 const std::string& MetricsLogStore::staged_log() const {
-  return initial_log_queue_.has_staged_log() ? initial_log_queue_.staged_log()
-                                             : ongoing_log_queue_.staged_log();
+  return initial_log_queue_.has_staged_log()
+             ? initial_log_queue_.staged_log()
+             : ongoing_log_store()->staged_log();
 }
 
 const std::string& MetricsLogStore::staged_log_hash() const {
   return initial_log_queue_.has_staged_log()
              ? initial_log_queue_.staged_log_hash()
-             : ongoing_log_queue_.staged_log_hash();
+             : ongoing_log_store()->staged_log_hash();
 }
 
 const std::string& MetricsLogStore::staged_log_signature() const {
   return initial_log_queue_.has_staged_log()
              ? initial_log_queue_.staged_log_signature()
-             : ongoing_log_queue_.staged_log_signature();
+             : ongoing_log_store()->staged_log_signature();
 }
 
 absl::optional<uint64_t> MetricsLogStore::staged_log_user_id() const {
-  // MetricsLogStore base class should never have any logs associated with a
-  // user ID.
-  return absl::nullopt;
+  return initial_log_queue_.has_staged_log()
+             ? initial_log_queue_.staged_log_user_id()
+             : ongoing_log_store()->staged_log_user_id();
+}
+
+bool MetricsLogStore::has_alternate_ongoing_log_store() const {
+  return alternate_ongoing_log_queue_ != nullptr;
+}
+
+bool MetricsLogStore::alternate_ongoing_log_store_has_unsent_logs() const {
+  return has_alternate_ongoing_log_store() &&
+         alternate_ongoing_log_queue_->has_unsent_logs();
+}
+
+bool MetricsLogStore::alternate_ongoing_log_store_has_staged_log() const {
+  return has_alternate_ongoing_log_store() &&
+         alternate_ongoing_log_queue_->has_staged_log();
+}
+
+const UnsentLogStore* MetricsLogStore::ongoing_log_store() const {
+  return has_alternate_ongoing_log_store() ? alternate_ongoing_log_queue_.get()
+                                           : &ongoing_log_queue_;
 }
 
 void MetricsLogStore::StageNextLog() {
   DCHECK(!has_staged_log());
   if (initial_log_queue_.has_unsent_logs())
     initial_log_queue_.StageNextLog();
-  else
+  else if (alternate_ongoing_log_store_has_unsent_logs())
+    alternate_ongoing_log_queue_->StageNextLog();
+  else if (ongoing_log_queue_.has_unsent_logs())
     ongoing_log_queue_.StageNextLog();
 }
 
@@ -107,8 +147,11 @@
   DCHECK(has_staged_log());
   if (initial_log_queue_.has_staged_log())
     initial_log_queue_.DiscardStagedLog();
-  else
+  else if (alternate_ongoing_log_store_has_staged_log())
+    alternate_ongoing_log_queue_->DiscardStagedLog();
+  else if (ongoing_log_queue_.has_staged_log())
     ongoing_log_queue_.DiscardStagedLog();
+
   DCHECK(!has_staged_log());
 }
 
@@ -116,7 +159,9 @@
   DCHECK(has_staged_log());
   if (initial_log_queue_.has_staged_log())
     initial_log_queue_.MarkStagedLogAsSent();
-  else
+  else if (alternate_ongoing_log_store_has_staged_log())
+    alternate_ongoing_log_queue_->MarkStagedLogAsSent();
+  else if (ongoing_log_queue_.has_staged_log())
     ongoing_log_queue_.MarkStagedLogAsSent();
 }
 
@@ -127,6 +172,8 @@
 
   initial_log_queue_.TrimAndPersistUnsentLogs();
   ongoing_log_queue_.TrimAndPersistUnsentLogs();
+  if (has_alternate_ongoing_log_store())
+    alternate_ongoing_log_queue_->TrimAndPersistUnsentLogs();
 }
 
 }  // namespace metrics
diff --git a/components/metrics/metrics_log_store.h b/components/metrics/metrics_log_store.h
index f17590f..6c07f28 100644
--- a/components/metrics/metrics_log_store.h
+++ b/components/metrics/metrics_log_store.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_METRICS_METRICS_LOG_STORE_H_
 #define COMPONENTS_METRICS_METRICS_LOG_STORE_H_
 
+#include <memory>
 #include <string>
 
 #include "base/macros.h"
@@ -25,6 +26,14 @@
 // This implementation keeps track of two types of logs, initial and ongoing,
 // each stored in UnsentLogStore. It prioritizes staging initial logs over
 // ongoing logs.
+//
+// An alternate log store can be set to persist ongoing logs. For example, this
+// can be used to separate user logs from device logs on Chrome OS. If set, all
+// ongoing logs will be written to this alternate log store. Ongoing logs from
+// the alternate log store will be prioritized over ongoing logs from the native
+// ongoing log store when logs are staged. If an alternate log store is bound,
+// then logs will be prioritized in the following order: initial, alternate
+// ongoing, native ongoing.
 class MetricsLogStore : public LogStore {
  public:
   // Configurable limits for ensuring and restricting local log storage.
@@ -56,7 +65,7 @@
   MetricsLogStore(PrefService* local_state,
                   StorageLimits storage_limits,
                   const std::string& signing_key);
-  ~MetricsLogStore();
+  ~MetricsLogStore() override;
 
   // Registers local state prefs used by this class.
   static void RegisterPrefs(PrefRegistrySimple* registry);
@@ -66,6 +75,24 @@
                 MetricsLog::LogType log_type,
                 const LogMetadata& log_metadata);
 
+  // Binds an alternate log store to be managed by |this|. All ongoing logs
+  // after this call will be written to |log_store| until it is unset. Only one
+  // alternate log store can be bound at a time. Returns true if log store is
+  // bound successfully.
+  //
+  // If an alternate log store is already bound, this function will not bind
+  // |log_store| and return false.
+  //
+  // This should be called after |LoadPersistedUnsentLogs()| and after
+  // initialization.
+  void SetAlternateOngoingLogStore(std::unique_ptr<UnsentLogStore> log_store);
+
+  // Unsets the alternate log store by flushing all existing logs to persistent
+  // storage before destructing the alternate log store.
+  //
+  // If no alternate log store is bound, then this function no-ops.
+  void UnsetAlternateOngoingLogStore();
+
   // LogStore:
   bool has_unsent_logs() const override;
   bool has_staged_log() const override;
@@ -84,6 +111,18 @@
   size_t initial_log_count() const { return initial_log_queue_.size(); }
 
  private:
+  // Returns true if alternate log store is set and it has unsent logs.
+  bool alternate_ongoing_log_store_has_unsent_logs() const;
+
+  // Returns true if alternate log store is set and it has a staged log.
+  bool alternate_ongoing_log_store_has_staged_log() const;
+
+  // Returns true if alternate log store is set.
+  bool has_alternate_ongoing_log_store() const;
+
+  // Returns the current ongoing log store being used.
+  const UnsentLogStore* ongoing_log_store() const;
+
   // Tracks whether unsent logs (if any) have been loaded from the serializer.
   bool unsent_logs_loaded_;
 
@@ -92,6 +131,10 @@
   UnsentLogStore initial_log_queue_;
   // Logs stored with the ONGOING_LOG type that haven't been sent yet.
   UnsentLogStore ongoing_log_queue_;
+  // Alternate place to store logs stored with ONGOING_LOG type that haven't
+  // been sent yet. If initialized, all logs of type ONGOING_LOG will be stored
+  // here instead of |ongoing_log_queue_|.
+  std::unique_ptr<UnsentLogStore> alternate_ongoing_log_queue_;
 
   DISALLOW_COPY_AND_ASSIGN(MetricsLogStore);
 };
diff --git a/components/metrics/metrics_log_store_unittest.cc b/components/metrics/metrics_log_store_unittest.cc
index 71647cf..5a3b16e 100644
--- a/components/metrics/metrics_log_store_unittest.cc
+++ b/components/metrics/metrics_log_store_unittest.cc
@@ -6,6 +6,8 @@
 
 #include "components/metrics/metrics_pref_names.h"
 #include "components/metrics/test/test_metrics_service_client.h"
+#include "components/metrics/unsent_log_store_metrics_impl.h"
+#include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -13,10 +15,34 @@
 
 namespace {
 
+const char kTestPrefName[] = "TestPref";
+
+class TestUnsentLogStore : public UnsentLogStore {
+ public:
+  explicit TestUnsentLogStore(PrefService* service)
+      : UnsentLogStore(std::make_unique<UnsentLogStoreMetricsImpl>(),
+                       service,
+                       kTestPrefName,
+                       nullptr,
+                       /* min_log_count= */ 3,
+                       /* min_log_bytes= */ 1,
+                       /* max_log_size= */ 0,
+                       std::string()) {}
+  ~TestUnsentLogStore() override = default;
+
+  TestUnsentLogStore(const TestUnsentLogStore&) = delete;
+  TestUnsentLogStore& operator=(const TestUnsentLogStore&) = delete;
+
+  static void RegisterPrefs(PrefRegistrySimple* registry) {
+    registry->RegisterListPref(kTestPrefName);
+  }
+};
+
 class MetricsLogStoreTest : public testing::Test {
  public:
   MetricsLogStoreTest() {
     MetricsLogStore::RegisterPrefs(pref_service_.registry());
+    TestUnsentLogStore::RegisterPrefs(pref_service_.registry());
   }
   ~MetricsLogStoreTest() override {}
 
@@ -201,4 +227,101 @@
   EXPECT_EQ(0U, log_store.initial_log_count());
 }
 
+TEST_F(MetricsLogStoreTest, WritesToAlternateOngoingLogStore) {
+  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
+                            std::string());
+  std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store =
+      std::make_unique<TestUnsentLogStore>(&pref_service_);
+  TestUnsentLogStore* alternate_ongoing_log_store_ptr =
+      alternate_ongoing_log_store.get();
+
+  // Needs to be called before writing logs to alternate ongoing store since
+  // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that
+  // the native initial and ongoing unsent logs have already been loaded.
+  log_store.LoadPersistedUnsentLogs();
+
+  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata());
+  log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store));
+  log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata());
+  log_store.StoreLog("c", MetricsLog::ONGOING_LOG, LogMetadata());
+
+  EXPECT_EQ(1U, log_store.ongoing_log_count());
+  EXPECT_EQ(2U, alternate_ongoing_log_store_ptr->size());
+}
+
+TEST_F(MetricsLogStoreTest, StagesInitialOverBothOngoing) {
+  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
+                            std::string());
+  std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store =
+      std::make_unique<TestUnsentLogStore>(&pref_service_);
+  TestUnsentLogStore* alternate_ongoing_log_store_ptr =
+      alternate_ongoing_log_store.get();
+
+  // Needs to be called before writing logs to alternate ongoing store since
+  // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that
+  // the native initial and ongoing unsent logs have already been loaded.
+  log_store.LoadPersistedUnsentLogs();
+
+  log_store.StoreLog("a", MetricsLog::INITIAL_STABILITY_LOG, LogMetadata());
+  log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata());
+  log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store));
+  log_store.StoreLog("c", MetricsLog::ONGOING_LOG, LogMetadata());
+  log_store.StageNextLog();
+  log_store.DiscardStagedLog();
+
+  // Discarded log should be from initial_log_store.
+  EXPECT_EQ(0U, log_store.initial_log_count());
+  EXPECT_EQ(1U, log_store.ongoing_log_count());
+  EXPECT_EQ(1U, alternate_ongoing_log_store_ptr->size());
+}
+
+TEST_F(MetricsLogStoreTest, StagesAlternateOverOngoing) {
+  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
+                            std::string());
+  std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store =
+      std::make_unique<TestUnsentLogStore>(&pref_service_);
+  TestUnsentLogStore* alternate_ongoing_log_store_ptr =
+      alternate_ongoing_log_store.get();
+
+  // Needs to be called before writing logs to alternate ongoing store since
+  // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that
+  // the native initial and ongoing unsent logs have already been loaded.
+  log_store.LoadPersistedUnsentLogs();
+
+  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata());
+  log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store));
+  log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata());
+  log_store.StageNextLog();
+  log_store.DiscardStagedLog();
+
+  // Discarded log should be from alternate_ongoing_log_store.
+  EXPECT_EQ(1U, log_store.ongoing_log_count());
+  EXPECT_EQ(0U, alternate_ongoing_log_store_ptr->size());
+}
+
+TEST_F(MetricsLogStoreTest,
+       UnboundAlternateOngoingLogStoreWritesToNativeOngoing) {
+  MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
+                            std::string());
+  std::unique_ptr<TestUnsentLogStore> alternate_ongoing_log_store =
+      std::make_unique<TestUnsentLogStore>(&pref_service_);
+
+  // Needs to be called before writing logs to alternate ongoing store since
+  // SetAlternateOngoingLogStore loads persisted unsent logs and assumes that
+  // the native initial and ongoing unsent logs have already been loaded.
+  log_store.LoadPersistedUnsentLogs();
+
+  log_store.SetAlternateOngoingLogStore(std::move(alternate_ongoing_log_store));
+  // Should be written to alternate ongoing log store.
+  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata());
+
+  log_store.UnsetAlternateOngoingLogStore();
+
+  // Should be in native ongoing log store.
+  log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata());
+  log_store.StoreLog("c", MetricsLog::ONGOING_LOG, LogMetadata());
+
+  EXPECT_EQ(2U, log_store.ongoing_log_count());
+}
+
 }  // namespace metrics
diff --git a/components/metrics/metrics_service.cc b/components/metrics/metrics_service.cc
index bd6f3da..97170e0f 100644
--- a/components/metrics/metrics_service.cc
+++ b/components/metrics/metrics_service.cc
@@ -434,6 +434,28 @@
   delegating_provider_.ClearSavedStabilityMetrics();
 }
 
+#if defined(OS_CHROMEOS)
+void MetricsService::SetUserLogStore(
+    std::unique_ptr<UnsentLogStore> user_log_store) {
+  // This should only be set after the service has finished initializing.
+  DCHECK_EQ(SENDING_LOGS, state_);
+
+  // Closes the current log so that a new log can be opened in the user log
+  // store.
+  PushPendingLogsToPersistentStorage();
+  log_store()->SetAlternateOngoingLogStore(std::move(user_log_store));
+  OpenNewLog();
+}
+
+void MetricsService::UnsetUserLogStore() {
+  DCHECK_EQ(SENDING_LOGS, state_);
+  // Pushes all the existing logs to user log store before unbound.
+  PushPendingLogsToPersistentStorage();
+  log_store()->UnsetAlternateOngoingLogStore();
+  OpenNewLog();
+}
+#endif
+
 bool MetricsService::StageCurrentLogForTest() {
   CloseCurrentLog();
 
@@ -450,7 +472,6 @@
 // private methods
 //------------------------------------------------------------------------------
 
-
 //------------------------------------------------------------------------------
 // Initialization methods
 
@@ -654,8 +675,7 @@
   // Even if reporting is disabled, the scheduler is needed to trigger the
   // creation of the initial log, which must be done in order for any logs to be
   // persisted on shutdown or backgrounding.
-  if (recording_active() &&
-      (reporting_active() || state_ < SENDING_LOGS)) {
+  if (recording_active() && (reporting_active() || state_ < SENDING_LOGS)) {
     rotation_scheduler_->Start();
     reporting_service_.Start();
   }
@@ -673,8 +693,7 @@
   // that has to happen in order for logs to be cut and stored when persisting.
   // TODO(stuartmorgan): Call Stop() on the scheduler when reporting and/or
   // recording are turned off instead of letting it fire and then aborting.
-  if (idle_since_last_transmission_ ||
-      !recording_active() ||
+  if (idle_since_last_transmission_ || !recording_active() ||
       (!reporting_active() && state_ >= SENDING_LOGS)) {
     rotation_scheduler_->Stop();
     rotation_scheduler_->RotationFinished();
diff --git a/components/metrics/metrics_service.h b/components/metrics/metrics_service.h
index fd126fa..f5bc248b 100644
--- a/components/metrics/metrics_service.h
+++ b/components/metrics/metrics_service.h
@@ -42,7 +42,7 @@
 namespace base {
 class HistogramSamples;
 class PrefService;
-}
+}  // namespace base
 
 namespace metrics {
 
@@ -158,6 +158,17 @@
   // Clears the stability metrics that are saved in local state.
   void ClearSavedStabilityMetrics();
 
+#if defined(OS_CHROMEOS)
+  // Binds a user log store to store unsent logs. This log store will be
+  // fully managed by MetricsLogStore. This will no-op if another log store has
+  // already been set.
+  void SetUserLogStore(std::unique_ptr<UnsentLogStore> user_log_store);
+
+  // Unbinds the user log store. If there was no user log store, then this does
+  // nothing.
+  void UnsetUserLogStore();
+#endif
+
   variations::SyntheticTrialRegistry* synthetic_trial_registry() {
     return &synthetic_trial_registry_;
   }
@@ -209,7 +220,7 @@
   enum RecordingState {
     INACTIVE,
     ACTIVE,
-    UNSET
+    UNSET,
   };
 
   // Gets the LogStore for UMA logs.
diff --git a/components/metrics/metrics_service_client.h b/components/metrics/metrics_service_client.h
index 5607a30..7015abd0 100644
--- a/components/metrics/metrics_service_client.h
+++ b/components/metrics/metrics_service_client.h
@@ -116,6 +116,9 @@
   // data.
   virtual bool ShouldStartUpFastForTesting() const;
 
+  // Called when loading state changed, e.g. start/stop loading.
+  virtual void LoadingStateChanged(bool is_loading) {}
+
   // Called on plugin loading errors.
   virtual void OnPluginLoadingError(const base::FilePath& plugin_path) {}
 
diff --git a/components/metrics/reporting_service_unittest.cc b/components/metrics/reporting_service_unittest.cc
index 47f2ec6..adac9f7 100644
--- a/components/metrics/reporting_service_unittest.cc
+++ b/components/metrics/reporting_service_unittest.cc
@@ -43,8 +43,8 @@
 
 class TestLogStore : public LogStore {
  public:
-  TestLogStore() {}
-  ~TestLogStore() {}
+  TestLogStore() = default;
+  ~TestLogStore() override = default;
 
   void AddLog(const TestLog& log) { logs_.push_back(log); }
 
diff --git a/components/metrics/structured/structured_metrics_provider.h b/components/metrics/structured/structured_metrics_provider.h
index b26d97c..5364e3d8 100644
--- a/components/metrics/structured/structured_metrics_provider.h
+++ b/components/metrics/structured/structured_metrics_provider.h
@@ -6,7 +6,6 @@
 #define COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_PROVIDER_H_
 
 #include <memory>
-#include <vector>
 
 #include "base/files/file_path.h"
 #include "base/memory/scoped_refptr.h"
diff --git a/components/metrics/unsent_log_store.h b/components/metrics/unsent_log_store.h
index 3107650..b3d7a2e 100644
--- a/components/metrics/unsent_log_store.h
+++ b/components/metrics/unsent_log_store.h
@@ -55,7 +55,7 @@
                  size_t min_log_bytes,
                  size_t max_log_size,
                  const std::string& signing_key);
-  ~UnsentLogStore();
+  ~UnsentLogStore() override;
 
   // LogStore:
   bool has_unsent_logs() const override;
diff --git a/components/metrics_services_manager/metrics_services_manager.cc b/components/metrics_services_manager/metrics_services_manager.cc
index d404a43..db25924f 100644
--- a/components/metrics_services_manager/metrics_services_manager.cc
+++ b/components/metrics_services_manager/metrics_services_manager.cc
@@ -54,6 +54,11 @@
   return variations_service_.get();
 }
 
+void MetricsServicesManager::LoadingStateChanged(bool is_loading) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  GetMetricsServiceClient()->LoadingStateChanged(is_loading);
+}
+
 void MetricsServicesManager::OnPluginLoadingError(
     const base::FilePath& plugin_path) {
   GetMetricsServiceClient()->OnPluginLoadingError(plugin_path);
diff --git a/components/metrics_services_manager/metrics_services_manager.h b/components/metrics_services_manager/metrics_services_manager.h
index d289602..501da0d 100644
--- a/components/metrics_services_manager/metrics_services_manager.h
+++ b/components/metrics_services_manager/metrics_services_manager.h
@@ -62,6 +62,9 @@
   // Returns the VariationsService, creating it if it hasn't been created yet.
   variations::VariationsService* GetVariationsService();
 
+  // Called when loading state changed.
+  void LoadingStateChanged(bool is_loading);
+
   // Should be called when a plugin loading error occurs.
   void OnPluginLoadingError(const base::FilePath& plugin_path);
 
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index a6e5077..f9394578 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -256,7 +256,7 @@
 
 // Feature used to enable the second batch of Pedals (Safety Check, etc.).
 const base::Feature kOmniboxPedalsBatch2{"OmniboxPedalsBatch2",
-                                         base::FEATURE_DISABLED_BY_DEFAULT};
+                                         base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Feature used to enable the second batch of Pedals (Safety Check, etc.)
 // for non-English locales (English locales are 'en' and 'en-GB').
@@ -270,7 +270,7 @@
 
 // Feature that enables use of the colored version of the default Pedal icon.
 const base::Feature kOmniboxPedalsDefaultIconColored{
-    "OmniboxPedalsDefaultIconColored", base::FEATURE_DISABLED_BY_DEFAULT};
+    "OmniboxPedalsDefaultIconColored", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Feature that enables loading synonyms from the translation console.
 const base::Feature kOmniboxPedalsTranslationConsole{
diff --git a/components/omnibox/resources/omnibox_pedal_synonyms.grd b/components/omnibox/resources/omnibox_pedal_synonyms.grd
index 838d391..70cbc558 100644
--- a/components/omnibox/resources/omnibox_pedal_synonyms.grd
+++ b/components/omnibox/resources/omnibox_pedal_synonyms.grd
@@ -113,11 +113,15 @@
     <output filename="omnibox_pedal_synonyms_en-XA.pak" type="data_package" lang="en-XA" />
   </outputs>
 
+  <!-- Note, the list of languages here must match the omnibox_pedal_synonyms_grd languages
+       list in tools/gritsettings/translation_expectations.pyl or TC pipeline breaks. -->
   <translations>
     <file path="translations/omnibox_pedal_synonyms_ar.xtb" lang="ar" />
+    <file path="translations/omnibox_pedal_synonyms_de.xtb" lang="de" />
     <file path="translations/omnibox_pedal_synonyms_es-419.xtb" lang="es-419" />
     <file path="translations/omnibox_pedal_synonyms_fr.xtb" lang="fr" />
     <file path="translations/omnibox_pedal_synonyms_ja.xtb" lang="ja" />
+    <file path="translations/omnibox_pedal_synonyms_zh-CN.xtb" lang="zh-CN" />
     <file path="translations/omnibox_pedal_synonyms_zh-TW.xtb" lang="zh-TW" />
   </translations>
 
diff --git a/components/omnibox/resources/translations/omnibox_pedal_synonyms_de.xtb b/components/omnibox/resources/translations/omnibox_pedal_synonyms_de.xtb
new file mode 100644
index 0000000..91de7f51
--- /dev/null
+++ b/components/omnibox/resources/translations/omnibox_pedal_synonyms_de.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="de">
+</translationbundle>
diff --git a/components/omnibox/resources/translations/omnibox_pedal_synonyms_zh-CN.xtb b/components/omnibox/resources/translations/omnibox_pedal_synonyms_zh-CN.xtb
new file mode 100644
index 0000000..26e8b40
--- /dev/null
+++ b/components/omnibox/resources/translations/omnibox_pedal_synonyms_zh-CN.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-CN">
+</translationbundle>
diff --git a/components/optimization_guide/core/optimization_guide_navigation_data.cc b/components/optimization_guide/core/optimization_guide_navigation_data.cc
index 65a8e1e..2c807bb 100644
--- a/components/optimization_guide/core/optimization_guide_navigation_data.cc
+++ b/components/optimization_guide/core/optimization_guide_navigation_data.cc
@@ -14,8 +14,9 @@
 #include "services/metrics/public/cpp/ukm_source_id.h"
 
 OptimizationGuideNavigationData::OptimizationGuideNavigationData(
-    int64_t navigation_id)
-    : navigation_id_(navigation_id) {}
+    int64_t navigation_id,
+    base::TimeTicks navigation_start)
+    : navigation_id_(navigation_id), navigation_start_(navigation_start) {}
 
 OptimizationGuideNavigationData::~OptimizationGuideNavigationData() {
   RecordMetrics();
diff --git a/components/optimization_guide/core/optimization_guide_navigation_data.h b/components/optimization_guide/core/optimization_guide_navigation_data.h
index bfb87f55..2a3ed6f 100644
--- a/components/optimization_guide/core/optimization_guide_navigation_data.h
+++ b/components/optimization_guide/core/optimization_guide_navigation_data.h
@@ -17,12 +17,14 @@
 #include "components/optimization_guide/proto/hints.pb.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
 
 // A representation of optimization guide information related to a navigation.
 // Metrics will be recorded upon this object's destruction.
 class OptimizationGuideNavigationData {
  public:
-  explicit OptimizationGuideNavigationData(int64_t navigation_id);
+  OptimizationGuideNavigationData(int64_t navigation_id,
+                                  base::TimeTicks navigation_start);
   ~OptimizationGuideNavigationData();
 
   OptimizationGuideNavigationData(
@@ -34,10 +36,20 @@
     return weak_ptr_factory_.GetWeakPtr();
   }
 
-  // The navigation ID of the navigation handle that this data is associated
-  // with.
+  // The navigation ID of the navigation handle that this data is
+  // associated with.
   int64_t navigation_id() const { return navigation_id_; }
 
+  // The time of navigation start.
+  base::TimeTicks navigation_start() const { return navigation_start_; }
+
+  // The navigation URL of the navigation handle that this data is
+  // associated with.
+  GURL navigation_url() const { return navigation_url_; }
+  void set_navigation_url(const GURL& navigation_url) {
+    navigation_url_ = navigation_url;
+  }
+
   // The optimization types that were registered at the start of the navigation.
   base::flat_set<optimization_guide::proto::OptimizationType>
   registered_optimization_types() const {
@@ -96,6 +108,13 @@
   // with.
   const int64_t navigation_id_;
 
+  // The time of navigation start.
+  const base::TimeTicks navigation_start_;
+
+  // The navigation URL of the navigation handle this data is associated with.
+  // Updated on navigation start and navigation redirects.
+  GURL navigation_url_;
+
   // The optimization types that were registered at the start of the navigation.
   base::flat_set<optimization_guide::proto::OptimizationType>
       registered_optimization_types_;
diff --git a/components/optimization_guide/core/optimization_guide_navigation_data_unittest.cc b/components/optimization_guide/core/optimization_guide_navigation_data_unittest.cc
index aad2a59..887d921 100644
--- a/components/optimization_guide/core/optimization_guide_navigation_data_unittest.cc
+++ b/components/optimization_guide/core/optimization_guide_navigation_data_unittest.cc
@@ -25,7 +25,8 @@
   ukm::TestAutoSetUkmRecorder ukm_recorder;
 
   std::unique_ptr<OptimizationGuideNavigationData> data =
-      std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3);
+      std::make_unique<OptimizationGuideNavigationData>(
+          /*navigation_id=*/3, /*navigation_start=*/base::TimeTicks::Now());
   data.reset();
 
   // Make sure no UKM recorded.
@@ -41,7 +42,8 @@
   ukm::TestAutoSetUkmRecorder ukm_recorder;
 
   std::unique_ptr<OptimizationGuideNavigationData> data =
-      std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3);
+      std::make_unique<OptimizationGuideNavigationData>(
+          /*navigation_id=*/3, /*navigation_start=*/base::TimeTicks::Now());
   data->set_registered_optimization_types(
       {optimization_guide::proto::NOSCRIPT,
        optimization_guide::proto::RESOURCE_LOADING});
@@ -67,7 +69,8 @@
   ukm::TestAutoSetUkmRecorder ukm_recorder;
 
   std::unique_ptr<OptimizationGuideNavigationData> data =
-      std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3);
+      std::make_unique<OptimizationGuideNavigationData>(
+          /*navigation_id=*/3, /*navigation_start=*/base::TimeTicks::Now());
   data->set_registered_optimization_targets(
       {optimization_guide::proto::OPTIMIZATION_TARGET_UNKNOWN,
        optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD});
@@ -93,7 +96,8 @@
   ukm::TestAutoSetUkmRecorder ukm_recorder;
 
   std::unique_ptr<OptimizationGuideNavigationData> data =
-      std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3);
+      std::make_unique<OptimizationGuideNavigationData>(
+          /*navigation_id=*/3, /*navigation_start=*/base::TimeTicks::Now());
   data->set_hints_fetch_attempt_status(
       optimization_guide::RaceNavigationFetchAttemptStatus::
           kRaceNavigationFetchHost);
@@ -119,9 +123,10 @@
 
   ukm::TestAutoSetUkmRecorder ukm_recorder;
 
-  std::unique_ptr<OptimizationGuideNavigationData> data =
-      std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3);
   base::TimeTicks now = base::TimeTicks::Now();
+  std::unique_ptr<OptimizationGuideNavigationData> data =
+      std::make_unique<OptimizationGuideNavigationData>(
+          /*navigation_id=*/3, /*navigation_start=*/now);
   data->set_hints_fetch_start(now);
   data->set_hints_fetch_end(now + base::TimeDelta::FromMilliseconds(123));
   data.reset();
@@ -145,9 +150,10 @@
 
   ukm::TestAutoSetUkmRecorder ukm_recorder;
 
-  std::unique_ptr<OptimizationGuideNavigationData> data =
-      std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3);
   base::TimeTicks now = base::TimeTicks::Now();
+  std::unique_ptr<OptimizationGuideNavigationData> data =
+      std::make_unique<OptimizationGuideNavigationData>(
+          /*navigation_id=*/3, /*navigation_start=*/now);
   data->set_hints_fetch_end(now);
   data.reset();
 
@@ -162,9 +168,10 @@
 
   ukm::TestAutoSetUkmRecorder ukm_recorder;
 
-  std::unique_ptr<OptimizationGuideNavigationData> data =
-      std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3);
   base::TimeTicks now = base::TimeTicks::Now();
+  std::unique_ptr<OptimizationGuideNavigationData> data =
+      std::make_unique<OptimizationGuideNavigationData>(
+          /*navigation_id=*/3, /*navigation_start=*/now);
   data->set_hints_fetch_start(now);
   data.reset();
 
@@ -187,9 +194,10 @@
 
   ukm::TestAutoSetUkmRecorder ukm_recorder;
 
-  std::unique_ptr<OptimizationGuideNavigationData> data =
-      std::make_unique<OptimizationGuideNavigationData>(/*navigation_id=*/3);
   base::TimeTicks now = base::TimeTicks::Now();
+  std::unique_ptr<OptimizationGuideNavigationData> data =
+      std::make_unique<OptimizationGuideNavigationData>(
+          /*navigation_id=*/3, /*navigation_start=*/now);
   data->set_hints_fetch_start(now);
   data->set_hints_fetch_end(now - base::TimeDelta::FromMilliseconds(123));
   data.reset();
diff --git a/components/security_interstitials/content/ssl_error_navigation_throttle.cc b/components/security_interstitials/content/ssl_error_navigation_throttle.cc
index 3f55f62..d9011de0 100644
--- a/components/security_interstitials/content/ssl_error_navigation_throttle.cc
+++ b/components/security_interstitials/content/ssl_error_navigation_throttle.cc
@@ -93,6 +93,12 @@
   // through the interstitial will continue the navigation in a regular browser
   // window.
   if (std::move(is_in_hosted_app_callback_).Run(handle->GetWebContents())) {
+    // Non-primary pages (e.g. prerendering, fenced-frame, portal) should not
+    // be handled as a hosted app since they are associated with the non-app
+    // WebContents. For prerendering specifically, we should already have
+    // canceled the prerender from OnSSLCertificateError before the throttle
+    // runs WillProcessResponse.
+    DCHECK(navigation_handle()->IsInPrimaryMainFrame());
     QueueShowInterstitial(
         std::move(handle_ssl_error_callback_), handle->GetWebContents(),
         // The navigation handle's net error code will be
@@ -138,6 +144,11 @@
   std::string error_page_content = blocking_page->GetHTMLContents();
 
   content::NavigationHandle* handle = navigation_handle();
+
+  // For non-primary pages (e.g. prerendering) we should already have canceled
+  // the prerender from OnSSLCertificateError before the throttle failure.
+  DCHECK(handle->IsInPrimaryMainFrame());
+
   security_interstitials::SecurityInterstitialTabHelper::AssociateBlockingPage(
       handle->GetWebContents(), handle->GetNavigationId(),
       std::move(blocking_page));
diff --git a/components/services/storage/public/cpp/buckets/bucket_info.cc b/components/services/storage/public/cpp/buckets/bucket_info.cc
index 9ae1f838..161a883 100644
--- a/components/services/storage/public/cpp/buckets/bucket_info.cc
+++ b/components/services/storage/public/cpp/buckets/bucket_info.cc
@@ -35,4 +35,8 @@
   return !(lhs == rhs);
 }
 
+bool operator<(const BucketInfo& lhs, const BucketInfo& rhs) {
+  return lhs.id < rhs.id;
+}
+
 }  // namespace storage
diff --git a/components/services/storage/public/cpp/buckets/bucket_info.h b/components/services/storage/public/cpp/buckets/bucket_info.h
index 6b17d2d..b7154b2 100644
--- a/components/services/storage/public/cpp/buckets/bucket_info.h
+++ b/components/services/storage/public/cpp/buckets/bucket_info.h
@@ -36,6 +36,9 @@
   COMPONENT_EXPORT(STORAGE_SERVICE_BUCKETS_SUPPORT)
   friend bool operator!=(const BucketInfo& lhs, const BucketInfo& rhs);
 
+  COMPONENT_EXPORT(STORAGE_SERVICE_BUCKETS_SUPPORT)
+  friend bool operator<(const BucketInfo& lhs, const BucketInfo& rhs);
+
   bool is_default() const { return name == kDefaultBucketName; }
 
   BucketId id;
diff --git a/components/translate/core/browser/mock_translate_metrics_logger.h b/components/translate/core/browser/mock_translate_metrics_logger.h
index 2a0d864..936833a 100644
--- a/components/translate/core/browser/mock_translate_metrics_logger.h
+++ b/components/translate/core/browser/mock_translate_metrics_logger.h
@@ -52,7 +52,7 @@
                void(const std::string&,
                     TranslateBrowserMetrics::TargetLanguageOrigin));
   MOCK_METHOD1(LogUIInteraction, void(UIInteraction));
-  MOCK_METHOD0(GetNextManualTranslationType, TranslationType());
+  MOCK_METHOD1(GetNextManualTranslationType, TranslationType(bool));
   MOCK_METHOD1(SetHasHrefTranslateTarget, void(bool));
   MOCK_METHOD1(LogWasContentEmpty, void(bool));
 
diff --git a/components/translate/core/browser/translate_infobar_delegate.cc b/components/translate/core/browser/translate_infobar_delegate.cc
index 0ac5215..46b7f60 100644
--- a/components/translate/core/browser/translate_infobar_delegate.cc
+++ b/components/translate/core/browser/translate_infobar_delegate.cc
@@ -267,7 +267,8 @@
   translate_manager_->TranslatePage(
       source_language_code(), target_language_code(), false,
       translate_manager_->GetActiveTranslateMetricsLogger()
-          ->GetNextManualTranslationType());
+          ->GetNextManualTranslationType(
+              /*is_context_menu_initiated_translation=*/false));
 }
 
 bool TranslateInfoBarDelegate::ShouldShowMessageInfoBarButton() {
diff --git a/components/translate/core/browser/translate_manager.cc b/components/translate/core/browser/translate_manager.cc
index 6cfbe41e..f5f8eaa 100644
--- a/components/translate/core/browser/translate_manager.cc
+++ b/components/translate/core/browser/translate_manager.cc
@@ -362,7 +362,8 @@
   if (auto_translate && !language_state_.IsPageTranslated()) {
     TranslatePage(
         source_code, target_lang, triggered_from_menu,
-        GetActiveTranslateMetricsLogger()->GetNextManualTranslationType());
+        GetActiveTranslateMetricsLogger()->GetNextManualTranslationType(
+            triggered_from_menu));
     return;
   }
 
diff --git a/components/translate/core/browser/translate_metrics_logger.h b/components/translate/core/browser/translate_metrics_logger.h
index 61827e1..34df1fe 100644
--- a/components/translate/core/browser/translate_metrics_logger.h
+++ b/components/translate/core/browser/translate_metrics_logger.h
@@ -41,29 +41,51 @@
 // numeric values should never be reused.
 enum class TranslationStatus {
   kUninitialized = 0,
-  kSuccessFromManualTranslation = 1,
+  // kSuccessFromManualTranslation = 1,  // no longer used, split into
+  // kSuccessFromManualUiTranslation and
+  // kSuccessFromManualContextMenuTranslation enum values.
   kSuccessFromAutomaticTranslationByPref = 2,
   kSuccessFromAutomaticTranslationByLink = 3,
-  kRevertedManualTranslation = 4,
+  // kManualTranslation = 4,  // no longer used, split into
+  // kRevertedManualUiTranslation and
+  // kRevertedManualContextMenuTranslation enum values.
   kRevertedAutomaticTranslation = 5,
   kNewTranslation = 6,
   kTranslationAbandoned = 7,
-  kFailedWithNoErrorManualTranslation = 8,
+  // kFailedWithNoErrorManualTranslation = 8,  // no longer used, split into
+  // kFailedWithNoErrorManualUiTranslation and
+  // kFailedWithNoErrorManualContextMenuTranslation enum values.
   kFailedWithNoErrorAutomaticTranslation = 9,
-  kFailedWithErrorManualTranslation = 10,
+  // kFailedWithErrorManualTranslation = 10,  // no longer used, split into
+  // kFailedWithErrorManualUiTranslation and
+  // kFailedWithErrorManualContextMenuTranslation enum values.
   kFailedWithErrorAutomaticTranslation = 11,
-  kMaxValue = kFailedWithErrorAutomaticTranslation,
+  kSuccessFromManualUiTranslation = 12,
+  kRevertedManualUiTranslation = 13,
+  kFailedWithNoErrorManualUiTranslation = 14,
+  kFailedWithErrorManualUiTranslation = 15,
+  kSuccessFromManualContextMenuTranslation = 16,
+  kRevertedManualContextMenuTranslation = 17,
+  kFailedWithNoErrorManualContextMenuTranslation = 18,
+  kFailedWithErrorManualContextMenuTranslation = 19,
+  kMaxValue = kFailedWithErrorManualContextMenuTranslation,
 };
 
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
 enum class TranslationType {
   kUninitialized = 0,
-  kManualInitialTranslation = 1,
-  kManualReTranslation = 2,
+  // kManualInitialTranslation = 1,  // no longer used, split into
+  // kManualUiInitialTranslation and kManualContextMenuInitialranslation
+  // kManualReTranslation = 2,  // no longer used, split into
+  // kManualUiReTranslation and kManualContextMenuReTranslation
   kAutomaticTranslationByPref = 3,
   kAutomaticTranslationByLink = 4,
-  kMaxValue = kAutomaticTranslationByLink,
+  kManualUiInitialTranslation = 5,
+  kManualUiReTranslation = 6,
+  kManualContextMenuInitialTranslation = 7,
+  kManualContextMenuReTranslation = 8,
+  kMaxValue = kManualContextMenuReTranslation,
 };
 
 // These values are persisted to logs. Entries should not be renumbered and
@@ -174,7 +196,8 @@
   virtual void LogUIInteraction(UIInteraction ui_interaction) = 0;
 
   // Returns the translation type of the next manual translation.
-  virtual TranslationType GetNextManualTranslationType() = 0;
+  virtual TranslationType GetNextManualTranslationType(
+      bool is_context_menu_initiated_translation) = 0;
 
   virtual void SetHasHrefTranslateTarget(bool has_href_translate_target) = 0;
 
diff --git a/components/translate/core/browser/translate_metrics_logger_impl.cc b/components/translate/core/browser/translate_metrics_logger_impl.cc
index b36b189..7f606d2f 100644
--- a/components/translate/core/browser/translate_metrics_logger_impl.cc
+++ b/components/translate/core/browser/translate_metrics_logger_impl.cc
@@ -75,7 +75,8 @@
 const char kTranslateApplicationStartNeverTranslateSiteCount[] =
     "Translate.ApplicationStart.NeverTranslateSite.Count";
 
-TranslationType NullTranslateMetricsLogger::GetNextManualTranslationType() {
+TranslationType NullTranslateMetricsLogger::GetNextManualTranslationType(
+    bool is_context_menu_initiated_translation) {
   return TranslationType::kUninitialized;
 }
 
@@ -473,10 +474,19 @@
   base::UmaHistogramEnumeration(kTranslateUiInteractionEvent, ui_interaction);
 }
 
-TranslationType TranslateMetricsLoggerImpl::GetNextManualTranslationType() {
+TranslationType TranslateMetricsLoggerImpl::GetNextManualTranslationType(
+    bool is_context_menu_initiated_translation) {
+  if (is_context_menu_initiated_translation) {
+    return has_any_translation_started_
+               ? TranslationType::kManualContextMenuReTranslation
+               : TranslationType::kManualContextMenuInitialTranslation;
+  }
+
+  // If the translation was not initiated from the context menu, then it must
+  // have been triggered from the translate UI.
   return has_any_translation_started_
-             ? TranslationType::kManualReTranslation
-             : TranslationType::kManualInitialTranslation;
+             ? TranslationType::kManualUiReTranslation
+             : TranslationType::kManualUiInitialTranslation;
 }
 
 void TranslateMetricsLoggerImpl::SetHasHrefTranslateTarget(
@@ -530,9 +540,13 @@
 TranslationStatus
 TranslateMetricsLoggerImpl::ConvertTranslationTypeToRevertedTranslationStatus(
     TranslationType translation_type) {
-  if (translation_type == TranslationType::kManualInitialTranslation ||
-      translation_type == TranslationType::kManualReTranslation)
-    return TranslationStatus::kRevertedManualTranslation;
+  if (translation_type == TranslationType::kManualUiInitialTranslation ||
+      translation_type == TranslationType::kManualUiReTranslation)
+    return TranslationStatus::kRevertedManualUiTranslation;
+  if (translation_type ==
+          TranslationType::kManualContextMenuInitialTranslation ||
+      translation_type == TranslationType::kManualContextMenuReTranslation)
+    return TranslationStatus::kRevertedManualContextMenuTranslation;
   if (translation_type == TranslationType::kAutomaticTranslationByPref ||
       translation_type == TranslationType::kAutomaticTranslationByLink)
     return TranslationStatus::kRevertedAutomaticTranslation;
@@ -543,12 +557,20 @@
 TranslateMetricsLoggerImpl::ConvertTranslationTypeToFailedTranslationStatus(
     TranslationType translation_type,
     bool was_translation_error) {
-  if (translation_type == TranslationType::kManualInitialTranslation ||
-      translation_type == TranslationType::kManualReTranslation) {
+  if (translation_type == TranslationType::kManualUiInitialTranslation ||
+      translation_type == TranslationType::kManualUiReTranslation) {
     if (was_translation_error)
-      return TranslationStatus::kFailedWithErrorManualTranslation;
+      return TranslationStatus::kFailedWithErrorManualUiTranslation;
     else
-      return TranslationStatus::kFailedWithNoErrorManualTranslation;
+      return TranslationStatus::kFailedWithNoErrorManualUiTranslation;
+  }
+  if (translation_type ==
+          TranslationType::kManualContextMenuInitialTranslation ||
+      translation_type == TranslationType::kManualContextMenuReTranslation) {
+    if (was_translation_error)
+      return TranslationStatus::kFailedWithErrorManualContextMenuTranslation;
+    else
+      return TranslationStatus::kFailedWithNoErrorManualContextMenuTranslation;
   }
   if (translation_type == TranslationType::kAutomaticTranslationByPref ||
       translation_type == TranslationType::kAutomaticTranslationByLink) {
@@ -566,9 +588,13 @@
     TranslationType translation_type) {
   if (is_translation_in_progress)
     return TranslationStatus::kTranslationAbandoned;
-  if (translation_type == TranslationType::kManualInitialTranslation ||
-      translation_type == TranslationType::kManualReTranslation)
-    return TranslationStatus::kSuccessFromManualTranslation;
+  if (translation_type == TranslationType::kManualUiInitialTranslation ||
+      translation_type == TranslationType::kManualUiReTranslation)
+    return TranslationStatus::kSuccessFromManualUiTranslation;
+  if (translation_type ==
+          TranslationType::kManualContextMenuInitialTranslation ||
+      translation_type == TranslationType::kManualContextMenuReTranslation)
+    return TranslationStatus::kSuccessFromManualContextMenuTranslation;
   if (translation_type == TranslationType::kAutomaticTranslationByPref)
     return TranslationStatus::kSuccessFromAutomaticTranslationByPref;
   if (translation_type == TranslationType::kAutomaticTranslationByLink)
diff --git a/components/translate/core/browser/translate_metrics_logger_impl.h b/components/translate/core/browser/translate_metrics_logger_impl.h
index ceb1080..ba761d6 100644
--- a/components/translate/core/browser/translate_metrics_logger_impl.h
+++ b/components/translate/core/browser/translate_metrics_logger_impl.h
@@ -92,7 +92,8 @@
   void LogDetectionReliabilityScore(
       const float& model_detection_reliability_score) override {}
   void LogUIInteraction(UIInteraction ui_interaction) override {}
-  TranslationType GetNextManualTranslationType() override;
+  TranslationType GetNextManualTranslationType(
+      bool is_context_menu_initiated_translation) override;
   void SetHasHrefTranslateTarget(bool has_href_translate_target) override {}
   void LogWasContentEmpty(bool was_content_empty) override {}
 };
@@ -153,7 +154,8 @@
   void LogDetectionReliabilityScore(
       const float& model_detection_reliability_score) override;
   void LogUIInteraction(UIInteraction ui_interaction) override;
-  TranslationType GetNextManualTranslationType() override;
+  TranslationType GetNextManualTranslationType(
+      bool is_context_menu_initiated_translation) override;
   void SetHasHrefTranslateTarget(bool has_href_translate_target) override;
   void LogWasContentEmpty(bool was_content_empty) override;
 
diff --git a/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc b/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc
index d27f861..6f615c7 100644
--- a/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc
+++ b/components/translate/core/browser/translate_metrics_logger_impl_unittest.cc
@@ -75,6 +75,113 @@
     return test_ukm_recorder_.get();
   }
 
+  void SimulateAndCheckManualTranslation(
+      bool is_context_menu_initiated_translation,
+      bool was_translation_successful,
+      TranslateErrors::Type translate_error_type,
+      TranslationType expected_translation_type,
+      TranslationStatus expected_translation_status) {
+    if (was_translation_successful)
+      EXPECT_EQ(translate_error_type, TranslateErrors::NONE);
+
+    translate_metrics_logger()->LogInitialState();
+
+    translate_metrics_logger()->LogTranslationStarted(
+        translate_metrics_logger()->GetNextManualTranslationType(
+            is_context_menu_initiated_translation));
+    translate_metrics_logger()->LogTranslationFinished(
+        was_translation_successful, translate_error_type);
+
+    translate_metrics_logger()->RecordMetrics(true);
+
+    histogram_tester()->ExpectUniqueSample(kTranslateTranslationType,
+                                           expected_translation_type, 1);
+    histogram_tester()->ExpectUniqueSample(kTranslateTranslationStatus,
+                                           expected_translation_status, 1);
+
+    CheckTranslateStateHistograms(
+        TranslateState::kNotTranslatedNoUI,
+        (was_translation_successful ? TranslateState::kTranslatedNoUI
+                                    : TranslateState::kNotTranslatedNoUI),
+        (was_translation_successful ? 1 : 0), 0);
+    CheckTranslateErrors(
+        translate_error_type,
+        (translate_error_type != TranslateErrors::NONE ? 1 : 0));
+  }
+
+  void SimulateAndCheckRepeatedManualTranslationsAndReversions(
+      int num_translations_and_reversions,
+      bool is_context_menu_initiated_translation,
+      TranslationType expected_initial_translation_type,
+      TranslationType expected_re_translation_type,
+      TranslationStatus expected_translation_status) {
+    translate_metrics_logger()->LogInitialState();
+    for (int i = 0; i < num_translations_and_reversions; i++) {
+      translate_metrics_logger()->LogTranslationStarted(
+          translate_metrics_logger()->GetNextManualTranslationType(
+              is_context_menu_initiated_translation));
+      translate_metrics_logger()->LogTranslationFinished(true,
+                                                         TranslateErrors::NONE);
+      translate_metrics_logger()->LogReversion();
+    }
+
+    translate_metrics_logger()->RecordMetrics(true);
+
+    histogram_tester()->ExpectTotalCount(kTranslateTranslationType,
+                                         num_translations_and_reversions);
+    histogram_tester()->ExpectBucketCount(kTranslateTranslationType,
+                                          expected_initial_translation_type, 1);
+    histogram_tester()->ExpectBucketCount(kTranslateTranslationType,
+                                          expected_re_translation_type,
+                                          num_translations_and_reversions - 1);
+    histogram_tester()->ExpectUniqueSample(kTranslateTranslationStatus,
+                                           expected_translation_status,
+                                           num_translations_and_reversions);
+
+    CheckTranslateStateHistograms(
+        TranslateState::kNotTranslatedNoUI, TranslateState::kNotTranslatedNoUI,
+        num_translations_and_reversions, num_translations_and_reversions);
+    CheckTranslateErrors(TranslateErrors::NONE, 0);
+  }
+
+  void SimulateAndCheckAutomaticThenManualTranslation(
+      bool is_context_menu_initiated_translation,
+      TranslationType expected_manual_translation_type,
+      TranslationStatus expected_manual_translation_status) {
+    translate_metrics_logger()->LogTranslationStarted(
+        TranslationType::kAutomaticTranslationByPref);
+    translate_metrics_logger()->LogInitialState();
+    translate_metrics_logger()->LogTranslationFinished(true,
+                                                       TranslateErrors::NONE);
+
+    translate_metrics_logger()->LogReversion();
+
+    translate_metrics_logger()->LogTranslationStarted(
+        translate_metrics_logger()->GetNextManualTranslationType(
+            is_context_menu_initiated_translation));
+    translate_metrics_logger()->LogTranslationFinished(true,
+                                                       TranslateErrors::NONE);
+
+    translate_metrics_logger()->RecordMetrics(true);
+
+    histogram_tester()->ExpectTotalCount(kTranslateTranslationType, 2);
+    histogram_tester()->ExpectBucketCount(
+        kTranslateTranslationType, TranslationType::kAutomaticTranslationByPref,
+        1);
+    histogram_tester()->ExpectBucketCount(kTranslateTranslationType,
+                                          expected_manual_translation_type, 1);
+    histogram_tester()->ExpectTotalCount(kTranslateTranslationStatus, 2);
+    histogram_tester()->ExpectBucketCount(
+        kTranslateTranslationStatus,
+        TranslationStatus::kRevertedAutomaticTranslation, 1);
+    histogram_tester()->ExpectBucketCount(
+        kTranslateTranslationStatus, expected_manual_translation_status, 1);
+
+    CheckTranslateStateHistograms(TranslateState::kTranslatedNoUI,
+                                  TranslateState::kTranslatedNoUI, 2, 1);
+    CheckTranslateErrors(TranslateErrors::NONE, 0);
+  }
+
   void CheckTranslateStateHistograms(TranslateState expected_initial_state,
                                      TranslateState expected_final_state,
                                      int expected_num_translations,
@@ -413,7 +520,8 @@
   translate_metrics_logger()->LogUIInteraction(UIInteraction::kTranslate);
 
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   test_clock.Advance(translation_delay1);
   translate_metrics_logger()->LogTranslationFinished(true,
                                                      TranslateErrors::NONE);
@@ -441,7 +549,8 @@
   translate_metrics_logger()->LogUIInteraction(UIInteraction::kTranslate);
 
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   test_clock.Advance(translation_delay2);
   translate_metrics_logger()->LogTranslationFinished(false,
                                                      TranslateErrors::NETWORK);
@@ -451,7 +560,8 @@
   translate_metrics_logger()->LogUIInteraction(UIInteraction::kTranslate);
 
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   test_clock.Advance(translation_delay3);
   translate_metrics_logger()->LogTranslationFinished(true,
                                                      TranslateErrors::NONE);
@@ -524,7 +634,8 @@
   translate_metrics_logger()->LogInitialState();
   translate_metrics_logger()->LogUIChange(true);
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   translate_metrics_logger()->LogTranslationFinished(true,
                                                      TranslateErrors::NONE);
   translate_metrics_logger()->LogReversion();
@@ -692,79 +803,62 @@
 }
 
 TEST_F(TranslateMetricsLoggerImplTest, LogTranslationAndReversion) {
-  // Simulate a page load where the user translates a page and it is successful.
-  translate_metrics_logger()->LogInitialState();
+  // Simulate a page load where the user translates a page via the Translate UI
+  // and it is successful.
+  SimulateAndCheckManualTranslation(
+      false, true, TranslateErrors::NONE,
+      TranslationType::kManualUiInitialTranslation,
+      TranslationStatus::kSuccessFromManualUiTranslation);
 
-  translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
-  translate_metrics_logger()->LogTranslationFinished(true,
-                                                     TranslateErrors::NONE);
-
-  translate_metrics_logger()->RecordMetrics(true);
-
-  histogram_tester()->ExpectUniqueSample(
-      kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1);
-  histogram_tester()->ExpectUniqueSample(
-      kTranslateTranslationStatus,
-      TranslationStatus::kSuccessFromManualTranslation, 1);
-
-  CheckTranslateStateHistograms(TranslateState::kNotTranslatedNoUI,
-                                TranslateState::kTranslatedNoUI, 1, 0);
-  CheckTranslateErrors(TranslateErrors::NONE, 0);
-
-  // Simulate a failed translation with an error.
+  // Simulate a page load where the user translates a page via the Context Menu
+  // and it is successful.
   ResetTest();
-  translate_metrics_logger()->LogInitialState();
+  SimulateAndCheckManualTranslation(
+      true, true, TranslateErrors::NONE,
+      TranslationType::kManualContextMenuInitialTranslation,
+      TranslationStatus::kSuccessFromManualContextMenuTranslation);
 
-  translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
-  translate_metrics_logger()->LogTranslationFinished(false,
-                                                     TranslateErrors::NETWORK);
-
-  translate_metrics_logger()->RecordMetrics(true);
-
-  histogram_tester()->ExpectUniqueSample(
-      kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1);
-  histogram_tester()->ExpectUniqueSample(
-      kTranslateTranslationStatus,
-      TranslationStatus::kFailedWithErrorManualTranslation, 1);
-
-  CheckTranslateStateHistograms(TranslateState::kNotTranslatedNoUI,
-                                TranslateState::kNotTranslatedNoUI, 0, 0);
-  CheckTranslateErrors(TranslateErrors::NETWORK, 1);
-
-  // Simulate a failed translation without an error.
+  // Simulate a failed manual translation via the Translate UI with an error.
   ResetTest();
-  translate_metrics_logger()->LogInitialState();
+  SimulateAndCheckManualTranslation(
+      false, false, TranslateErrors::NETWORK,
+      TranslationType::kManualUiInitialTranslation,
+      TranslationStatus::kFailedWithErrorManualUiTranslation);
 
-  translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
-  translate_metrics_logger()->LogTranslationFinished(false,
-                                                     TranslateErrors::NONE);
+  // Simulate a failed manual translation via the Context Menu with an error.
+  ResetTest();
+  SimulateAndCheckManualTranslation(
+      true, false, TranslateErrors::NETWORK,
+      TranslationType::kManualContextMenuInitialTranslation,
+      TranslationStatus::kFailedWithErrorManualContextMenuTranslation);
 
-  translate_metrics_logger()->RecordMetrics(true);
+  // Simulate a failed manualtranslation via the Translate UI without an error.
+  ResetTest();
+  SimulateAndCheckManualTranslation(
+      false, false, TranslateErrors::NONE,
+      TranslationType::kManualUiInitialTranslation,
+      TranslationStatus::kFailedWithNoErrorManualUiTranslation);
 
-  histogram_tester()->ExpectUniqueSample(
-      kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1);
-  histogram_tester()->ExpectUniqueSample(
-      kTranslateTranslationStatus,
-      TranslationStatus::kFailedWithNoErrorManualTranslation, 1);
-
-  CheckTranslateStateHistograms(TranslateState::kNotTranslatedNoUI,
-                                TranslateState::kNotTranslatedNoUI, 0, 0);
-  CheckTranslateErrors(TranslateErrors::NONE, 0);
+  // Simulate a failed manualtranslation via the Context Menu without an error.
+  ResetTest();
+  SimulateAndCheckManualTranslation(
+      true, false, TranslateErrors::NONE,
+      TranslationType::kManualContextMenuInitialTranslation,
+      TranslationStatus::kFailedWithNoErrorManualContextMenuTranslation);
 
   // Simulate a translation that does not finish.
   ResetTest();
   translate_metrics_logger()->LogInitialState();
 
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
 
   translate_metrics_logger()->RecordMetrics(true);
 
   histogram_tester()->ExpectUniqueSample(
-      kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1);
+      kTranslateTranslationType, TranslationType::kManualUiInitialTranslation,
+      1);
   histogram_tester()->ExpectUniqueSample(
       kTranslateTranslationStatus, TranslationStatus::kTranslationAbandoned, 1);
 
@@ -778,11 +872,13 @@
   translate_metrics_logger()->LogInitialState();
 
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   translate_metrics_logger()->LogTranslationFinished(true,
                                                      TranslateErrors::NONE);
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   translate_metrics_logger()->LogTranslationFinished(false,
                                                      TranslateErrors::NETWORK);
 
@@ -790,15 +886,16 @@
 
   histogram_tester()->ExpectTotalCount(kTranslateTranslationType, 2);
   histogram_tester()->ExpectBucketCount(
-      kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1);
+      kTranslateTranslationType, TranslationType::kManualUiInitialTranslation,
+      1);
   histogram_tester()->ExpectBucketCount(
-      kTranslateTranslationType, TranslationType::kManualReTranslation, 1);
+      kTranslateTranslationType, TranslationType::kManualUiReTranslation, 1);
   histogram_tester()->ExpectTotalCount(kTranslateTranslationStatus, 2);
   histogram_tester()->ExpectBucketCount(kTranslateTranslationStatus,
                                         TranslationStatus::kNewTranslation, 1);
   histogram_tester()->ExpectBucketCount(
       kTranslateTranslationStatus,
-      TranslationStatus::kFailedWithErrorManualTranslation, 1);
+      TranslationStatus::kFailedWithErrorManualUiTranslation, 1);
 
   CheckTranslateStateHistograms(TranslateState::kNotTranslatedNoUI,
                                 TranslateState::kTranslatedNoUI, 1, 0);
@@ -810,9 +907,11 @@
   translate_metrics_logger()->LogInitialState();
 
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   translate_metrics_logger()->LogTranslationFinished(true,
                                                      TranslateErrors::NONE);
 
@@ -820,13 +919,14 @@
 
   histogram_tester()->ExpectTotalCount(kTranslateTranslationType, 2);
   histogram_tester()->ExpectBucketCount(
-      kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1);
+      kTranslateTranslationType, TranslationType::kManualUiInitialTranslation,
+      1);
   histogram_tester()->ExpectBucketCount(
-      kTranslateTranslationType, TranslationType::kManualReTranslation, 1);
+      kTranslateTranslationType, TranslationType::kManualUiReTranslation, 1);
   histogram_tester()->ExpectTotalCount(kTranslateTranslationStatus, 2);
   histogram_tester()->ExpectBucketCount(
       kTranslateTranslationStatus,
-      TranslationStatus::kSuccessFromManualTranslation, 1);
+      TranslationStatus::kSuccessFromManualUiTranslation, 1);
   histogram_tester()->ExpectBucketCount(kTranslateTranslationStatus,
                                         TranslationStatus::kNewTranslation, 1);
 
@@ -940,39 +1040,21 @@
                                 TranslateState::kNotTranslatedNoUI, 0, 0);
   CheckTranslateErrors(TranslateErrors::NONE, 0);
 
-  // Simulate a page that is repeatedly translated and then reverted.
+  // Simulate a page that is repeatedly manually translated via the Translate UI
+  // and then reverted.
   ResetTest();
+  SimulateAndCheckRepeatedManualTranslationsAndReversions(
+      100, false, TranslationType::kManualUiInitialTranslation,
+      TranslationType::kManualUiReTranslation,
+      TranslationStatus::kRevertedManualUiTranslation);
 
-  int num_translations_and_reversions = 100;
-
-  translate_metrics_logger()->LogInitialState();
-
-  for (int i = 0; i < num_translations_and_reversions; i++) {
-    translate_metrics_logger()->LogTranslationStarted(
-        translate_metrics_logger()->GetNextManualTranslationType());
-    translate_metrics_logger()->LogTranslationFinished(true,
-                                                       TranslateErrors::NONE);
-    translate_metrics_logger()->LogReversion();
-  }
-
-  translate_metrics_logger()->RecordMetrics(true);
-
-  histogram_tester()->ExpectTotalCount(kTranslateTranslationType,
-                                       num_translations_and_reversions);
-  histogram_tester()->ExpectBucketCount(
-      kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1);
-  histogram_tester()->ExpectBucketCount(kTranslateTranslationType,
-                                        TranslationType::kManualReTranslation,
-                                        num_translations_and_reversions - 1);
-  histogram_tester()->ExpectUniqueSample(
-      kTranslateTranslationStatus,
-      TranslationStatus::kRevertedManualTranslation,
-      num_translations_and_reversions);
-
-  CheckTranslateStateHistograms(
-      TranslateState::kNotTranslatedNoUI, TranslateState::kNotTranslatedNoUI,
-      num_translations_and_reversions, num_translations_and_reversions);
-  CheckTranslateErrors(TranslateErrors::NONE, 0);
+  // Simulate a page that is repeatedly manually translated via the Context Menu
+  // and then reverted.
+  ResetTest();
+  SimulateAndCheckRepeatedManualTranslationsAndReversions(
+      100, true, TranslationType::kManualContextMenuInitialTranslation,
+      TranslationType::kManualContextMenuReTranslation,
+      TranslationStatus::kRevertedManualContextMenuTranslation);
 
   // Simulates a page that is automatically translated by pref then reverted.
   ResetTest();
@@ -998,40 +1080,18 @@
   CheckTranslateErrors(TranslateErrors::NONE, 0);
 
   // Simulates a page that is automatically translated, then reverted, and
-  // finally manually translated.
+  // finally manually translated via either the Translate UI.
   ResetTest();
-  translate_metrics_logger()->LogTranslationStarted(
-      TranslationType::kAutomaticTranslationByPref);
-  translate_metrics_logger()->LogInitialState();
-  translate_metrics_logger()->LogTranslationFinished(true,
-                                                     TranslateErrors::NONE);
+  SimulateAndCheckAutomaticThenManualTranslation(
+      false, TranslationType::kManualUiReTranslation,
+      TranslationStatus::kSuccessFromManualUiTranslation);
 
-  translate_metrics_logger()->LogReversion();
-
-  translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
-  translate_metrics_logger()->LogTranslationFinished(true,
-                                                     TranslateErrors::NONE);
-
-  translate_metrics_logger()->RecordMetrics(true);
-
-  histogram_tester()->ExpectTotalCount(kTranslateTranslationType, 2);
-  histogram_tester()->ExpectBucketCount(
-      kTranslateTranslationType, TranslationType::kAutomaticTranslationByPref,
-      1);
-  histogram_tester()->ExpectBucketCount(
-      kTranslateTranslationType, TranslationType::kManualReTranslation, 1);
-  histogram_tester()->ExpectTotalCount(kTranslateTranslationStatus, 2);
-  histogram_tester()->ExpectBucketCount(
-      kTranslateTranslationStatus,
-      TranslationStatus::kRevertedAutomaticTranslation, 1);
-  histogram_tester()->ExpectBucketCount(
-      kTranslateTranslationStatus,
-      TranslationStatus::kSuccessFromManualTranslation, 1);
-
-  CheckTranslateStateHistograms(TranslateState::kTranslatedNoUI,
-                                TranslateState::kTranslatedNoUI, 2, 1);
-  CheckTranslateErrors(TranslateErrors::NONE, 0);
+  // Simulates a page that is automatically translated, then reverted, and
+  // finally manually translated via either the Context Menu
+  ResetTest();
+  SimulateAndCheckAutomaticThenManualTranslation(
+      true, TranslationType::kManualContextMenuReTranslation,
+      TranslationStatus::kSuccessFromManualContextMenuTranslation);
 }
 
 TEST_F(TranslateMetricsLoggerImplTest, LogTranslationLanguages) {
@@ -1057,7 +1117,8 @@
 
     for (int i = 0; i < test.num_translations; ++i) {
       translate_metrics_logger()->LogTranslationStarted(
-          translate_metrics_logger()->GetNextManualTranslationType());
+          translate_metrics_logger()->GetNextManualTranslationType(
+              /*is_context_menu_initiated_translation=*/false));
       translate_metrics_logger()->LogTranslationFinished(true,
                                                          TranslateErrors::NONE);
     }
@@ -1119,7 +1180,8 @@
   // Simulates the translations with the predefined errors.
   for (const auto& test : kTests) {
     translate_metrics_logger()->LogTranslationStarted(
-        translate_metrics_logger()->GetNextManualTranslationType());
+        translate_metrics_logger()->GetNextManualTranslationType(
+            /*is_context_menu_initiated_translation=*/false));
     translate_metrics_logger()->LogTranslationFinished(
         test.was_translation_successful, test.error_type);
   }
@@ -1128,22 +1190,23 @@
 
   histogram_tester()->ExpectTotalCount(kTranslateTranslationType, 12);
   histogram_tester()->ExpectBucketCount(
-      kTranslateTranslationType, TranslationType::kManualInitialTranslation, 1);
+      kTranslateTranslationType, TranslationType::kManualUiInitialTranslation,
+      1);
   histogram_tester()->ExpectBucketCount(
-      kTranslateTranslationType, TranslationType::kManualReTranslation, 11);
+      kTranslateTranslationType, TranslationType::kManualUiReTranslation, 11);
 
   histogram_tester()->ExpectTotalCount(kTranslateTranslationStatus, 12);
   histogram_tester()->ExpectBucketCount(
       kTranslateTranslationStatus,
-      TranslationStatus::kSuccessFromManualTranslation, 1);
+      TranslationStatus::kSuccessFromManualUiTranslation, 1);
   histogram_tester()->ExpectBucketCount(kTranslateTranslationStatus,
                                         TranslationStatus::kNewTranslation, 3);
   histogram_tester()->ExpectBucketCount(
       kTranslateTranslationStatus,
-      TranslationStatus::kFailedWithNoErrorManualTranslation, 2);
+      TranslationStatus::kFailedWithNoErrorManualUiTranslation, 2);
   histogram_tester()->ExpectBucketCount(
       kTranslateTranslationStatus,
-      TranslationStatus::kFailedWithErrorManualTranslation, 6);
+      TranslationStatus::kFailedWithErrorManualUiTranslation, 6);
 
   // We expect to capture the first non-NONE value, and the total number of
   // non-NONE errors.
@@ -1163,7 +1226,8 @@
   translate_metrics_logger()->LogInitialState();
 
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   translate_metrics_logger()->LogTranslationFinished(true,
                                                      TranslateErrors::NONE);
   translate_metrics_logger()->LogUIChange(true);
@@ -1219,7 +1283,8 @@
 
   // Translate the page (while still in the background).
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   translate_metrics_logger()->LogTranslationFinished(true,
                                                      TranslateErrors::NONE);
 
@@ -1256,7 +1321,8 @@
   // Translation starts, but takes a while. We should count this time while the
   // translation is in progress as "not translated".
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
 
   test_clock.Advance(delay2);
 
@@ -1352,7 +1418,8 @@
   translate_metrics_logger()->SetInternalClockForTesting(&test_clock);
 
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   test_clock.Advance(default_delay);
   translate_metrics_logger()->LogTranslationFinished(true,
                                                      TranslateErrors::NONE);
@@ -1367,7 +1434,8 @@
   translate_metrics_logger()->SetInternalClockForTesting(&test_clock);
 
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   test_clock.Advance(default_delay);
   translate_metrics_logger()->LogTranslationFinished(false,
                                                      TranslateErrors::NETWORK);
@@ -1382,7 +1450,8 @@
   translate_metrics_logger()->SetInternalClockForTesting(&test_clock);
 
   translate_metrics_logger()->LogTranslationStarted(
-      translate_metrics_logger()->GetNextManualTranslationType());
+      translate_metrics_logger()->GetNextManualTranslationType(
+          /*is_context_menu_initiated_translation=*/false));
   test_clock.Advance(default_delay);
 
   translate_metrics_logger()->RecordMetrics(true);
@@ -1404,7 +1473,8 @@
 
   for (const auto& test : kTests) {
     translate_metrics_logger()->LogTranslationStarted(
-        translate_metrics_logger()->GetNextManualTranslationType());
+        translate_metrics_logger()->GetNextManualTranslationType(
+            /*is_context_menu_initiated_translation=*/false));
     test_clock.Advance(test.time_to_translate);
     translate_metrics_logger()->LogTranslationFinished(
         test.translate_error_type == TranslateErrors::NONE,
diff --git a/components/translate/core/browser/translate_ui_delegate.cc b/components/translate/core/browser/translate_ui_delegate.cc
index 4a11200..acef6acc 100644
--- a/components/translate/core/browser/translate_ui_delegate.cc
+++ b/components/translate/core/browser/translate_ui_delegate.cc
@@ -321,7 +321,8 @@
     translate_manager_->TranslatePage(
         GetSourceLanguageCode(), GetTargetLanguageCode(), false,
         translate_manager_->GetActiveTranslateMetricsLogger()
-            ->GetNextManualTranslationType());
+            ->GetNextManualTranslationType(
+                /*is_context_menu_initiated_translation=*/false));
     UMA_HISTOGRAM_BOOLEAN(kPerformTranslate, true);
     if (IsLikelyAmpCacheUrl(translate_driver_->GetLastCommittedURL()))
       UMA_HISTOGRAM_BOOLEAN(kPerformTranslateAmpCacheUrl, true);
diff --git a/components/zucchini/disassembler_elf.cc b/components/zucchini/disassembler_elf.cc
index 07726a86..9c9ebb8 100644
--- a/components/zucchini/disassembler_elf.cc
+++ b/components/zucchini/disassembler_elf.cc
@@ -43,8 +43,8 @@
 
 // Decides how a section affects ELF parsing, and returns a bit field composed
 // from SectionJudgement values.
-template <class Traits>
-int JudgeSection(size_t image_size, const typename Traits::Elf_Shdr* section) {
+template <class TRAITS>
+int JudgeSection(size_t image_size, const typename TRAITS::Elf_Shdr* section) {
   // BufferRegion uses |size_t| this can be 32-bit in some cases. For Elf64
   // |sh_addr|, |sh_offset| and |sh_size| are 64-bit this can result in
   // overflows in the subsequent validation steps.
@@ -96,21 +96,21 @@
 }
 
 // Determines whether |section| is a reloc section.
-template <class Traits>
-bool IsRelocSection(const typename Traits::Elf_Shdr& section) {
+template <class TRAITS>
+bool IsRelocSection(const typename TRAITS::Elf_Shdr& section) {
   DCHECK_GT(section.sh_size, 0U);
   if (section.sh_type == elf::SHT_REL) {
     // Also validate |section.sh_entsize|, which gets used later.
-    return section.sh_entsize == sizeof(typename Traits::Elf_Rel);
+    return section.sh_entsize == sizeof(typename TRAITS::Elf_Rel);
   }
   if (section.sh_type == elf::SHT_RELA)
-    return section.sh_entsize == sizeof(typename Traits::Elf_Rela);
+    return section.sh_entsize == sizeof(typename TRAITS::Elf_Rela);
   return false;
 }
 
 // Determines whether |section| is a section with executable code.
-template <class Traits>
-bool IsExecSection(const typename Traits::Elf_Shdr& section) {
+template <class TRAITS>
+bool IsExecSection(const typename TRAITS::Elf_Shdr& section) {
   DCHECK_GT(section.sh_size, 0U);
   return section.sh_type == elf::SHT_PROGBITS &&
          (section.sh_flags & elf::SHF_EXECINSTR) != 0;
@@ -149,8 +149,8 @@
 /******** DisassemblerElf ********/
 
 // static.
-template <class Traits>
-bool DisassemblerElf<Traits>::QuickDetect(ConstBufferView image) {
+template <class TRAITS>
+bool DisassemblerElf<TRAITS>::QuickDetect(ConstBufferView image) {
   BufferSource source(image);
 
   // Do not consume the bytes for the magic value, as they are part of the
@@ -183,25 +183,25 @@
   return true;
 }
 
-template <class Traits>
-DisassemblerElf<Traits>::~DisassemblerElf() = default;
+template <class TRAITS>
+DisassemblerElf<TRAITS>::~DisassemblerElf() = default;
 
-template <class Traits>
-ExecutableType DisassemblerElf<Traits>::GetExeType() const {
+template <class TRAITS>
+ExecutableType DisassemblerElf<TRAITS>::GetExeType() const {
   return Traits::kExeType;
 }
 
-template <class Traits>
-std::string DisassemblerElf<Traits>::GetExeTypeString() const {
+template <class TRAITS>
+std::string DisassemblerElf<TRAITS>::GetExeTypeString() const {
   return Traits::kExeTypeString;
 }
 
 // |num_equivalence_iterations_| = 2 for reloc -> abs32.
-template <class Traits>
-DisassemblerElf<Traits>::DisassemblerElf() : Disassembler(2) {}
+template <class TRAITS>
+DisassemblerElf<TRAITS>::DisassemblerElf() : Disassembler(2) {}
 
-template <class Traits>
-bool DisassemblerElf<Traits>::Parse(ConstBufferView image) {
+template <class TRAITS>
+bool DisassemblerElf<TRAITS>::Parse(ConstBufferView image) {
   image_ = image;
   if (!ParseHeader())
     return false;
@@ -209,8 +209,8 @@
   return true;
 }
 
-template <class Traits>
-std::unique_ptr<ReferenceReader> DisassemblerElf<Traits>::MakeReadRelocs(
+template <class TRAITS>
+std::unique_ptr<ReferenceReader> DisassemblerElf<TRAITS>::MakeReadRelocs(
     offset_t lo,
     offset_t hi) {
   DCHECK_LE(lo, hi);
@@ -224,14 +224,14 @@
       supported_relocation_type(), lo, hi, translator_);
 }
 
-template <class Traits>
-std::unique_ptr<ReferenceWriter> DisassemblerElf<Traits>::MakeWriteRelocs(
+template <class TRAITS>
+std::unique_ptr<ReferenceWriter> DisassemblerElf<TRAITS>::MakeWriteRelocs(
     MutableBufferView image) {
   return std::make_unique<RelocWriterElf>(image, Traits::kBitness, translator_);
 }
 
-template <class Traits>
-bool DisassemblerElf<Traits>::ParseHeader() {
+template <class TRAITS>
+bool DisassemblerElf<TRAITS>::ParseHeader() {
   BufferSource source(image_);
   // Ensure any offsets will fit within the |image_|'s bounds.
   if (!base::IsValueInRangeForNumericType<offset_t>(image_.size()))
@@ -329,8 +329,8 @@
   return true;
 }
 
-template <class Traits>
-void DisassemblerElf<Traits>::ExtractInterestingSectionHeaders() {
+template <class TRAITS>
+void DisassemblerElf<TRAITS>::ExtractInterestingSectionHeaders() {
   DCHECK(reloc_section_dims_.empty());
   DCHECK(exec_headers_.empty());
   for (elf::Elf32_Half i = 0; i < sections_count_; ++i) {
@@ -350,8 +350,8 @@
   std::sort(exec_headers_.begin(), exec_headers_.end(), comp);
 }
 
-template <class Traits>
-void DisassemblerElf<Traits>::GetAbs32FromRelocSections() {
+template <class TRAITS>
+void DisassemblerElf<TRAITS>::GetAbs32FromRelocSections() {
   constexpr int kAbs32Width = Traits::kVAWidth;
   DCHECK(abs32_locations_.empty());
 
@@ -380,15 +380,15 @@
   abs32_locations_.shrink_to_fit();
 }
 
-template <class Traits>
-void DisassemblerElf<Traits>::GetRel32FromCodeSections() {
+template <class TRAITS>
+void DisassemblerElf<TRAITS>::GetRel32FromCodeSections() {
   for (const typename Traits::Elf_Shdr* section : exec_headers_)
     ParseExecSection(*section);
   PostProcessRel32();
 }
 
-template <class Traits>
-void DisassemblerElf<Traits>::ParseSections() {
+template <class TRAITS>
+void DisassemblerElf<TRAITS>::ParseSections() {
   ExtractInterestingSectionHeaders();
   GetAbs32FromRelocSections();
   GetRel32FromCodeSections();
@@ -396,32 +396,34 @@
 
 /******** DisassemblerElfIntel ********/
 
-template <class Traits>
-DisassemblerElfIntel<Traits>::DisassemblerElfIntel() = default;
+template <class TRAITS>
+DisassemblerElfIntel<TRAITS>::DisassemblerElfIntel() = default;
 
-template <class Traits>
-DisassemblerElfIntel<Traits>::~DisassemblerElfIntel() = default;
+template <class TRAITS>
+DisassemblerElfIntel<TRAITS>::~DisassemblerElfIntel() = default;
 
-template <class Traits>
-std::vector<ReferenceGroup> DisassemblerElfIntel<Traits>::MakeReferenceGroups()
+template <class TRAITS>
+std::vector<ReferenceGroup> DisassemblerElfIntel<TRAITS>::MakeReferenceGroups()
     const {
   return {
-      {ReferenceTypeTraits{sizeof(Traits::Elf_Rel::r_offset), TypeTag(kReloc),
+      {ReferenceTypeTraits{sizeof(TRAITS::Elf_Rel::r_offset), TypeTag(kReloc),
                            PoolTag(kReloc)},
-       &DisassemblerElfIntel<Traits>::MakeReadRelocs,
-       &DisassemblerElfIntel<Traits>::MakeWriteRelocs},
+       &DisassemblerElfIntel<TRAITS>::MakeReadRelocs,
+       &DisassemblerElfIntel<TRAITS>::MakeWriteRelocs},
       {ReferenceTypeTraits{Traits::kVAWidth, TypeTag(kAbs32), PoolTag(kAbs32)},
-       &DisassemblerElfIntel<Traits>::MakeReadAbs32,
-       &DisassemblerElfIntel<Traits>::MakeWriteAbs32},
+       &DisassemblerElfIntel<TRAITS>::MakeReadAbs32,
+       &DisassemblerElfIntel<TRAITS>::MakeWriteAbs32},
       // N.B.: Rel32 |width| is 4 bytes, even for x64.
       {ReferenceTypeTraits{4, TypeTag(kRel32), PoolTag(kRel32)},
-       &DisassemblerElfIntel<Traits>::MakeReadRel32,
-       &DisassemblerElfIntel<Traits>::MakeWriteRel32}};
+       &DisassemblerElfIntel<TRAITS>::MakeReadRel32,
+       &DisassemblerElfIntel<TRAITS>::MakeWriteRel32}};
 }
 
-template <class Traits>
-void DisassemblerElfIntel<Traits>::ParseExecSection(
-    const typename Traits::Elf_Shdr& section) {
+template <class TRAITS>
+void DisassemblerElfIntel<TRAITS>::ParseExecSection(
+    const typename TRAITS::Elf_Shdr& section) {
+  constexpr int kAbs32Width = Traits::kVAWidth;
+
   // |this->| is needed to access protected members of templated base class. To
   // reduce noise, use local references for these.
   ConstBufferView& image_ = this->image_;
@@ -435,8 +437,8 @@
   AddressTranslator::RvaToOffsetCache target_rva_checker(translator_);
 
   ConstBufferView region(image_.begin() + section.sh_offset, section.sh_size);
-  Abs32GapFinder gap_finder(image_, region, abs32_locations_, 4);
-  typename Traits::Rel32FinderUse rel_finder(image_, translator_);
+  Abs32GapFinder gap_finder(image_, region, abs32_locations_, kAbs32Width);
+  typename TRAITS::Rel32FinderUse rel_finder(image_, translator_);
   // Iterate over gaps between abs32 references, to avoid collision.
   while (gap_finder.FindNext()) {
     rel_finder.SetRegion(gap_finder.GetGap());
@@ -452,43 +454,43 @@
   }
 }
 
-template <class Traits>
-void DisassemblerElfIntel<Traits>::PostProcessRel32() {
+template <class TRAITS>
+void DisassemblerElfIntel<TRAITS>::PostProcessRel32() {
   rel32_locations_.shrink_to_fit();
   std::sort(rel32_locations_.begin(), rel32_locations_.end());
 }
 
-template <class Traits>
-std::unique_ptr<ReferenceReader> DisassemblerElfIntel<Traits>::MakeReadAbs32(
+template <class TRAITS>
+std::unique_ptr<ReferenceReader> DisassemblerElfIntel<TRAITS>::MakeReadAbs32(
     offset_t lo,
     offset_t hi) {
   // TODO(huangs): Don't use Abs32RvaExtractorWin32 here; use new class that
   // caters to different ELF architectures.
   Abs32RvaExtractorWin32 abs_rva_extractor(
-      this->image_, AbsoluteAddress(Traits::kBitness, kElfImageBase),
+      this->image_, AbsoluteAddress(TRAITS::kBitness, kElfImageBase),
       this->abs32_locations_, lo, hi);
   return std::make_unique<Abs32ReaderWin32>(std::move(abs_rva_extractor),
                                             this->translator_);
 }
 
-template <class Traits>
-std::unique_ptr<ReferenceWriter> DisassemblerElfIntel<Traits>::MakeWriteAbs32(
+template <class TRAITS>
+std::unique_ptr<ReferenceWriter> DisassemblerElfIntel<TRAITS>::MakeWriteAbs32(
     MutableBufferView image) {
   return std::make_unique<Abs32WriterWin32>(
-      image, AbsoluteAddress(Traits::kBitness, kElfImageBase),
+      image, AbsoluteAddress(TRAITS::kBitness, kElfImageBase),
       this->translator_);
 }
 
-template <class Traits>
-std::unique_ptr<ReferenceReader> DisassemblerElfIntel<Traits>::MakeReadRel32(
+template <class TRAITS>
+std::unique_ptr<ReferenceReader> DisassemblerElfIntel<TRAITS>::MakeReadRel32(
     offset_t lo,
     offset_t hi) {
   return std::make_unique<Rel32ReaderX86>(this->image_, lo, hi,
                                           &rel32_locations_, this->translator_);
 }
 
-template <class Traits>
-std::unique_ptr<ReferenceWriter> DisassemblerElfIntel<Traits>::MakeWriteRel32(
+template <class TRAITS>
+std::unique_ptr<ReferenceWriter> DisassemblerElfIntel<TRAITS>::MakeWriteRel32(
     MutableBufferView image) {
   return std::make_unique<Rel32WriterX86>(image, this->translator_);
 }
diff --git a/components/zucchini/disassembler_elf.h b/components/zucchini/disassembler_elf.h
index 17e7523..ffd16907e 100644
--- a/components/zucchini/disassembler_elf.h
+++ b/components/zucchini/disassembler_elf.h
@@ -65,9 +65,10 @@
 };
 
 // Disassembler for ELF.
-template <class Traits>
+template <class TRAITS>
 class DisassemblerElf : public Disassembler {
  public:
+  using Traits = TRAITS;
   // Applies quick checks to determine whether |image| *may* point to the start
   // of an executable. Returns true iff the check passes.
   static bool QuickDetect(ConstBufferView image);
@@ -155,9 +156,10 @@
 };
 
 // Disassembler for ELF with Intel architectures.
-template <class Traits>
-class DisassemblerElfIntel : public DisassemblerElf<Traits> {
+template <class TRAITS>
+class DisassemblerElfIntel : public DisassemblerElf<TRAITS> {
  public:
+  using Traits = TRAITS;
   enum ReferenceType : uint8_t { kReloc, kAbs32, kRel32, kTypeCount };
 
   DisassemblerElfIntel();
diff --git a/components/zucchini/disassembler_win32.cc b/components/zucchini/disassembler_win32.cc
index a23201b..37e43e5 100644
--- a/components/zucchini/disassembler_win32.cc
+++ b/components/zucchini/disassembler_win32.cc
@@ -24,7 +24,6 @@
 // Decides whether |image| points to a Win32 PE file. If this is a possibility,
 // assigns |source| to enable further parsing, and returns true. Otherwise
 // leaves |source| at an undefined state and returns false.
-template <class Traits>
 bool ReadWin32Header(ConstBufferView image, BufferSource* source) {
   *source = BufferSource(image);
 
@@ -47,9 +46,9 @@
   return true;
 }
 
-template <class Traits>
+template <class TRAITS>
 const pe::ImageDataDirectory* ReadDataDirectory(
-    const typename Traits::ImageOptionalHeader* optional_header,
+    const typename TRAITS::ImageOptionalHeader* optional_header,
     size_t index) {
   if (index >= optional_header->number_of_rva_and_sizes)
     return nullptr;
@@ -57,7 +56,7 @@
 }
 
 // Decides whether |section| (assumed value) is a section that contains code.
-template <class Traits>
+template <class TRAITS>
 bool IsWin32CodeSection(const pe::ImageSectionHeader& section) {
   return (section.characteristics & kCodeCharacteristics) ==
          kCodeCharacteristics;
@@ -82,31 +81,31 @@
 /******** DisassemblerWin32 ********/
 
 // static.
-template <class Traits>
-bool DisassemblerWin32<Traits>::QuickDetect(ConstBufferView image) {
+template <class TRAITS>
+bool DisassemblerWin32<TRAITS>::QuickDetect(ConstBufferView image) {
   BufferSource source;
-  return ReadWin32Header<Traits>(image, &source);
+  return ReadWin32Header(image, &source);
 }
 
 // |num_equivalence_iterations_| = 2 for reloc -> abs32.
-template <class Traits>
-DisassemblerWin32<Traits>::DisassemblerWin32() : Disassembler(2) {}
+template <class TRAITS>
+DisassemblerWin32<TRAITS>::DisassemblerWin32() : Disassembler(2) {}
 
-template <class Traits>
-DisassemblerWin32<Traits>::~DisassemblerWin32() = default;
+template <class TRAITS>
+DisassemblerWin32<TRAITS>::~DisassemblerWin32() = default;
 
-template <class Traits>
-ExecutableType DisassemblerWin32<Traits>::GetExeType() const {
+template <class TRAITS>
+ExecutableType DisassemblerWin32<TRAITS>::GetExeType() const {
   return Traits::kExeType;
 }
 
-template <class Traits>
-std::string DisassemblerWin32<Traits>::GetExeTypeString() const {
+template <class TRAITS>
+std::string DisassemblerWin32<TRAITS>::GetExeTypeString() const {
   return Traits::kExeTypeString;
 }
 
-template <class Traits>
-std::vector<ReferenceGroup> DisassemblerWin32<Traits>::MakeReferenceGroups()
+template <class TRAITS>
+std::vector<ReferenceGroup> DisassemblerWin32<TRAITS>::MakeReferenceGroups()
     const {
   return {
       {ReferenceTypeTraits{2, TypeTag(kReloc), PoolTag(kReloc)},
@@ -118,8 +117,8 @@
   };
 }
 
-template <class Traits>
-std::unique_ptr<ReferenceReader> DisassemblerWin32<Traits>::MakeReadRelocs(
+template <class TRAITS>
+std::unique_ptr<ReferenceReader> DisassemblerWin32<TRAITS>::MakeReadRelocs(
     offset_t lo,
     offset_t hi) {
   if (!ParseAndStoreRelocBlocks())
@@ -135,8 +134,8 @@
                                             translator_);
 }
 
-template <class Traits>
-std::unique_ptr<ReferenceReader> DisassemblerWin32<Traits>::MakeReadAbs32(
+template <class TRAITS>
+std::unique_ptr<ReferenceReader> DisassemblerWin32<TRAITS>::MakeReadAbs32(
     offset_t lo,
     offset_t hi) {
   ParseAndStoreAbs32();
@@ -146,8 +145,8 @@
                                             translator_);
 }
 
-template <class Traits>
-std::unique_ptr<ReferenceReader> DisassemblerWin32<Traits>::MakeReadRel32(
+template <class TRAITS>
+std::unique_ptr<ReferenceReader> DisassemblerWin32<TRAITS>::MakeReadRel32(
     offset_t lo,
     offset_t hi) {
   ParseAndStoreRel32();
@@ -155,8 +154,8 @@
                                           translator_);
 }
 
-template <class Traits>
-std::unique_ptr<ReferenceWriter> DisassemblerWin32<Traits>::MakeWriteRelocs(
+template <class TRAITS>
+std::unique_ptr<ReferenceWriter> DisassemblerWin32<TRAITS>::MakeWriteRelocs(
     MutableBufferView image) {
   if (!ParseAndStoreRelocBlocks())
     return std::make_unique<EmptyReferenceWriter>();
@@ -166,30 +165,30 @@
                                             translator_);
 }
 
-template <class Traits>
-std::unique_ptr<ReferenceWriter> DisassemblerWin32<Traits>::MakeWriteAbs32(
+template <class TRAITS>
+std::unique_ptr<ReferenceWriter> DisassemblerWin32<TRAITS>::MakeWriteAbs32(
     MutableBufferView image) {
   return std::make_unique<Abs32WriterWin32>(
       image, AbsoluteAddress(Traits::kBitness, image_base_), translator_);
 }
 
-template <class Traits>
-std::unique_ptr<ReferenceWriter> DisassemblerWin32<Traits>::MakeWriteRel32(
+template <class TRAITS>
+std::unique_ptr<ReferenceWriter> DisassemblerWin32<TRAITS>::MakeWriteRel32(
     MutableBufferView image) {
   return std::make_unique<Rel32WriterX86>(image, translator_);
 }
 
-template <class Traits>
-bool DisassemblerWin32<Traits>::Parse(ConstBufferView image) {
+template <class TRAITS>
+bool DisassemblerWin32<TRAITS>::Parse(ConstBufferView image) {
   image_ = image;
   return ParseHeader();
 }
 
-template <class Traits>
-bool DisassemblerWin32<Traits>::ParseHeader() {
+template <class TRAITS>
+bool DisassemblerWin32<TRAITS>::ParseHeader() {
   BufferSource source;
 
-  if (!ReadWin32Header<Traits>(image_, &source))
+  if (!ReadWin32Header(image_, &source))
     return false;
 
   constexpr size_t kDataDirBase =
@@ -297,8 +296,8 @@
   return true;
 }
 
-template <class Traits>
-bool DisassemblerWin32<Traits>::ParseAndStoreRelocBlocks() {
+template <class TRAITS>
+bool DisassemblerWin32<TRAITS>::ParseAndStoreRelocBlocks() {
   if (has_parsed_relocs_)
     return reloc_region_.lo() != kInvalidOffset;
 
@@ -324,8 +323,8 @@
   return true;
 }
 
-template <class Traits>
-bool DisassemblerWin32<Traits>::ParseAndStoreAbs32() {
+template <class TRAITS>
+bool DisassemblerWin32<TRAITS>::ParseAndStoreAbs32() {
   if (has_parsed_abs32_)
     return true;
   has_parsed_abs32_ = true;
@@ -355,8 +354,8 @@
   return true;
 }
 
-template <class Traits>
-bool DisassemblerWin32<Traits>::ParseAndStoreRel32() {
+template <class TRAITS>
+bool DisassemblerWin32<TRAITS>::ParseAndStoreRel32() {
   if (has_parsed_rel32_)
     return true;
   has_parsed_rel32_ = true;
diff --git a/components/zucchini/disassembler_win32.h b/components/zucchini/disassembler_win32.h
index 8f9fd58..d952014 100644
--- a/components/zucchini/disassembler_win32.h
+++ b/components/zucchini/disassembler_win32.h
@@ -52,9 +52,10 @@
   using Address = uint64_t;
 };
 
-template <class Traits>
+template <class TRAITS>
 class DisassemblerWin32 : public Disassembler {
  public:
+  using Traits = TRAITS;
   enum ReferenceType : uint8_t { kReloc, kAbs32, kRel32, kTypeCount };
 
   // Applies quick checks to determine whether |image| *may* point to the start
diff --git a/components/zucchini/reloc_elf_unittest.cc b/components/zucchini/reloc_elf_unittest.cc
index b03f403..8a1b932 100644
--- a/components/zucchini/reloc_elf_unittest.cc
+++ b/components/zucchini/reloc_elf_unittest.cc
@@ -48,9 +48,10 @@
 }
 
 // Helper to manipulate an image with one or more relocation tables.
-template <class ElfIntelTraits>
+template <class ELF_INTEL_TRAITS>
 class FakeImageWithReloc {
  public:
+  using ElfIntelTraits = ELF_INTEL_TRAITS;
   struct RelocSpec {
     offset_t start;
     std::vector<uint8_t> data;
diff --git a/content/browser/background_fetch/background_fetch_context.cc b/content/browser/background_fetch/background_fetch_context.cc
index 84c8485..0521002d 100644
--- a/content/browser/background_fetch/background_fetch_context.cc
+++ b/content/browser/background_fetch/background_fetch_context.cc
@@ -36,17 +36,13 @@
     const scoped_refptr<ServiceWorkerContextWrapper>& service_worker_context,
     scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
     scoped_refptr<DevToolsBackgroundServicesContextImpl> devtools_context)
-    : base::RefCountedDeleteOnSequence<BackgroundFetchContext>(
-          BrowserThread::GetTaskRunnerForThread(
-              ServiceWorkerContext::GetCoreThreadId())),
-      service_worker_context_(service_worker_context),
+    : service_worker_context_(service_worker_context),
       devtools_context_(std::move(devtools_context)),
       registration_notifier_(
           std::make_unique<BackgroundFetchRegistrationNotifier>()),
       delegate_proxy_(storage_partition) {
-  // Although this lives only on the service worker core thread, it is
-  // constructed on UI thread.
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(service_worker_context_);
 
   data_manager_ = std::make_unique<BackgroundFetchDataManager>(
@@ -58,18 +54,18 @@
 }
 
 BackgroundFetchContext::~BackgroundFetchContext() {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   service_worker_context_->RemoveObserver(scheduler_.get());
   data_manager_->RemoveObserver(scheduler_.get());
 }
 
-void BackgroundFetchContext::InitializeOnCoreThread() {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+void BackgroundFetchContext::Initialize() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   service_worker_context_->AddObserver(scheduler_.get());
 
   data_manager_->AddObserver(scheduler_.get());
-  data_manager_->InitializeOnCoreThread();
+  data_manager_->Initialize();
   data_manager_->GetInitializationData(
       base::BindOnce(&BackgroundFetchContext::DidGetInitializationData,
                      weak_factory_.GetWeakPtr()));
@@ -79,6 +75,8 @@
     blink::mojom::BackgroundFetchError error,
     std::vector<background_fetch::BackgroundFetchInitializationData>
         initialization_data) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   if (error != blink::mojom::BackgroundFetchError::NONE)
     return;
 
@@ -99,7 +97,7 @@
     const blink::StorageKey& storage_key,
     const std::string& developer_id,
     blink::mojom::BackgroundFetchService::GetRegistrationCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   data_manager_->GetRegistration(
       service_worker_registration_id, storage_key, developer_id,
@@ -111,7 +109,7 @@
     int64_t service_worker_registration_id,
     const blink::StorageKey& storage_key,
     blink::mojom::BackgroundFetchService::GetDeveloperIdsCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   data_manager_->GetDeveloperIdsForServiceWorker(
       service_worker_registration_id, storage_key, std::move(callback));
@@ -122,7 +120,7 @@
     blink::mojom::BackgroundFetchError error,
     BackgroundFetchRegistrationId registration_id,
     blink::mojom::BackgroundFetchRegistrationDataPtr registration_data) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (error != blink::mojom::BackgroundFetchError::NONE) {
     std::move(callback).Run(
@@ -149,7 +147,7 @@
     int render_frame_tree_node_id,
     const WebContents::Getter& wc_getter,
     blink::mojom::BackgroundFetchService::FetchCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   // |registration_id| should be unique even if developer id has been
   // duplicated, because the caller of this function generates a new unique_id
@@ -174,14 +172,11 @@
     blink::mojom::BackgroundFetchUkmDataPtr ukm_data,
     int frame_tree_node_id,
     BackgroundFetchPermission permission) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  RunOrPostTaskOnThread(
-      FROM_HERE, BrowserThread::UI,
-      base::BindOnce(&background_fetch::RecordBackgroundFetchUkmEvent,
-                     registration_id.storage_key(), requests.size(),
-                     options.Clone(), icon, std::move(ukm_data),
-                     frame_tree_node_id, permission));
+  background_fetch::RecordBackgroundFetchUkmEvent(
+      registration_id.storage_key(), requests.size(), options.Clone(), icon,
+      std::move(ukm_data), frame_tree_node_id, permission);
 
   if (permission != BackgroundFetchPermission::BLOCKED) {
     data_manager_->CreateRegistration(
@@ -200,7 +195,7 @@
 
 void BackgroundFetchContext::GetIconDisplaySize(
     blink::mojom::BackgroundFetchService::GetIconDisplaySizeCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   delegate_proxy_.GetIconDisplaySize(std::move(callback));
 }
 
@@ -208,7 +203,7 @@
     const BackgroundFetchRegistrationId& registration_id,
     blink::mojom::BackgroundFetchError error,
     blink::mojom::BackgroundFetchRegistrationDataPtr registration_data) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   auto iter = fetch_callbacks_.find(registration_id);
 
@@ -234,6 +229,8 @@
     const std::string& unique_id,
     mojo::PendingRemote<blink::mojom::BackgroundFetchRegistrationObserver>
         observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   registration_notifier_->AddObserver(unique_id, std::move(observer));
 }
 
@@ -243,7 +240,7 @@
     const absl::optional<SkBitmap>& icon,
     blink::mojom::BackgroundFetchRegistrationService::UpdateUICallback
         callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   delegate_proxy_.UpdateUI(registration_id.unique_id(), title, icon,
                            std::move(callback));
@@ -257,7 +254,8 @@
 void BackgroundFetchContext::Abort(
     const BackgroundFetchRegistrationId& registration_id,
     blink::mojom::BackgroundFetchRegistrationService::AbortCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   scheduler_->Abort(registration_id, FailureReason::CANCELLED_BY_DEVELOPER,
                     std::move(callback));
 }
@@ -267,7 +265,7 @@
     std::unique_ptr<BackgroundFetchRequestMatchParams> match_params,
     blink::mojom::BackgroundFetchRegistrationService::MatchRequestsCallback
         callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   data_manager_->MatchRequests(
       registration_id, std::move(match_params),
@@ -282,7 +280,7 @@
         callback,
     blink::mojom::BackgroundFetchError error,
     std::vector<blink::mojom::BackgroundFetchSettledFetchPtr> settled_fetches) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (error != blink::mojom::BackgroundFetchError::NONE)
     DCHECK(settled_fetches.empty());
@@ -297,21 +295,13 @@
 }
 
 void BackgroundFetchContext::Shutdown() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  RunOrPostTaskOnThread(
-      FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-      base::BindOnce(&BackgroundFetchContext::ShutdownOnCoreThread, this));
-}
-
-void BackgroundFetchContext::ShutdownOnCoreThread() {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-
-  data_manager_->ShutdownOnCoreThread();
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  data_manager_->Shutdown();
 }
 
 void BackgroundFetchContext::SetDataManagerForTesting(
     std::unique_ptr<BackgroundFetchDataManager> data_manager) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(data_manager);
   data_manager_ = std::move(data_manager);
   scheduler_ = std::make_unique<BackgroundFetchScheduler>(
diff --git a/content/browser/background_fetch/background_fetch_context.h b/content/browser/background_fetch/background_fetch_context.h
index 0b7fe97..3b58bdbc 100644
--- a/content/browser/background_fetch/background_fetch_context.h
+++ b/content/browser/background_fetch/background_fetch_context.h
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted_delete_on_sequence.h"
 #include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
 #include "content/browser/background_fetch/background_fetch_delegate_proxy.h"
 #include "content/browser/background_fetch/background_fetch_event_dispatcher.h"
 #include "content/browser/background_fetch/storage/get_initialization_data_task.h"
@@ -42,11 +43,9 @@
 // Background Fetch requests function similarly to normal fetches except that
 // they are persistent across Chromium or service worker shutdown.
 //
-// Deleted on the service worker core thread.
-// TODO(crbug.com/824858): Make this single-threaded after the service worker
-// core thread moves to the UI thread.
+// Lives on the UI thread.
 class CONTENT_EXPORT BackgroundFetchContext
-    : public base::RefCountedDeleteOnSequence<BackgroundFetchContext> {
+    : public base::RefCounted<BackgroundFetchContext> {
  public:
   // The BackgroundFetchContext will watch the ServiceWorkerContextWrapper so
   // that it can respond to service worker events such as unregister.
@@ -56,7 +55,7 @@
       scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy,
       scoped_refptr<DevToolsBackgroundServicesContextImpl> devtools_context);
 
-  void InitializeOnCoreThread();
+  void Initialize();
 
   // Called by the StoragePartitionImpl destructor.
   void Shutdown();
@@ -142,13 +141,10 @@
                            JobsInitializedOnBrowserRestart);
   friend class BackgroundFetchServiceTest;
   friend class BackgroundFetchJobControllerTest;
-  friend class base::DeleteHelper<BackgroundFetchContext>;
-  friend class base::RefCountedDeleteOnSequence<BackgroundFetchContext>;
+  friend class base::RefCounted<BackgroundFetchContext>;
 
   ~BackgroundFetchContext();
 
-  void ShutdownOnCoreThread();
-
   // Called when an existing registration has been retrieved from the data
   // manager. If the registration does not exist then |registration| is nullptr.
   void DidGetRegistration(
@@ -209,6 +205,8 @@
            blink::mojom::BackgroundFetchService::FetchCallback>
       fetch_callbacks_;
 
+  SEQUENCE_CHECKER(sequence_checker_);
+
   base::WeakPtrFactory<BackgroundFetchContext> weak_factory_{
       this};  // Must be last.
 
diff --git a/content/browser/background_fetch/background_fetch_data_manager.cc b/content/browser/background_fetch/background_fetch_data_manager.cc
index d47e90c1..bf5e3fc 100644
--- a/content/browser/background_fetch/background_fetch_data_manager.cc
+++ b/content/browser/background_fetch/background_fetch_data_manager.cc
@@ -44,7 +44,7 @@
     : service_worker_context_(std::move(service_worker_context)),
       storage_partition_(std::move(storage_partition)),
       quota_manager_proxy_(std::move(quota_manager_proxy)) {
-  // Constructed on the UI thread, then used on the service worker core thread.
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(storage_partition_);
 
@@ -54,8 +54,8 @@
   DCHECK(blob_storage_context_);
 }
 
-void BackgroundFetchDataManager::InitializeOnCoreThread() {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+void BackgroundFetchDataManager::Initialize() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   // Delete inactive registrations still in the DB.
   Cleanup();
@@ -63,17 +63,18 @@
 
 void BackgroundFetchDataManager::AddObserver(
     BackgroundFetchDataManagerObserver* observer) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   observers_.AddObserver(observer);
 }
 
 void BackgroundFetchDataManager::RemoveObserver(
     BackgroundFetchDataManagerObserver* observer) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   observers_.RemoveObserver(observer);
 }
 
 void BackgroundFetchDataManager::Cleanup() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   AddDatabaseTask(std::make_unique<background_fetch::CleanupTask>(this));
 }
 
@@ -81,6 +82,8 @@
 BackgroundFetchDataManager::GetOrOpenCacheStorage(
     const blink::StorageKey& storage_key,
     const std::string& unique_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   auto it = cache_storage_remote_map_.find(unique_id);
   if (it != cache_storage_remote_map_.end()) {
     // TODO(enne): should we store `storage_key` so we can DCHECK it matches
@@ -110,6 +113,8 @@
     const std::string& unique_id,
     int64_t trace_id,
     blink::mojom::CacheStorage::OpenCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   auto& cache_storage = GetOrOpenCacheStorage(storage_key, unique_id);
   if (!cache_storage)
     return;
@@ -123,6 +128,8 @@
     const std::string& unique_id,
     int64_t trace_id,
     blink::mojom::CacheStorage::DeleteCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   auto& cache_storage = GetOrOpenCacheStorage(storage_key, unique_id);
   if (!cache_storage)
     return;
@@ -137,6 +144,8 @@
     const std::string& unique_id,
     blink::mojom::CacheStorage::DeleteCallback callback,
     blink::mojom::CacheStorageError result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   // Preserve the lifetime of the cache storage remote until here so that this
   // DidDeleteCache callback will not be dropped.
   cache_storage_remote_map_.erase(unique_id);
@@ -148,6 +157,8 @@
     const std::string& unique_id,
     int64_t trace_id,
     blink::mojom::CacheStorage::HasCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   auto& cache_storage = GetOrOpenCacheStorage(storage_key, unique_id);
   if (!cache_storage)
     return;
@@ -156,12 +167,12 @@
 }
 
 BackgroundFetchDataManager::~BackgroundFetchDataManager() {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
 void BackgroundFetchDataManager::GetInitializationData(
     GetInitializationDataCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   AddDatabaseTask(std::make_unique<background_fetch::GetInitializationDataTask>(
       this, std::move(callback)));
@@ -174,7 +185,7 @@
     const SkBitmap& icon,
     bool start_paused,
     CreateRegistrationCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   AddDatabaseTask(std::make_unique<background_fetch::CreateMetadataTask>(
       this, registration_id, std::move(requests), std::move(options), icon,
@@ -186,7 +197,7 @@
     const blink::StorageKey& storage_key,
     const std::string& developer_id,
     GetRegistrationCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   AddDatabaseTask(std::make_unique<background_fetch::GetRegistrationTask>(
       this, service_worker_registration_id, storage_key, developer_id,
@@ -196,7 +207,7 @@
 void BackgroundFetchDataManager::PopNextRequest(
     const BackgroundFetchRegistrationId& registration_id,
     NextRequestCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   AddDatabaseTask(
       std::make_unique<background_fetch::StartNextPendingRequestTask>(
@@ -207,7 +218,7 @@
     const BackgroundFetchRegistrationId& registration_id,
     const scoped_refptr<BackgroundFetchRequestInfo>& request_info,
     GetRequestBlobCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   AddDatabaseTask(std::make_unique<background_fetch::GetRequestBlobTask>(
       this, registration_id, request_info, std::move(callback)));
@@ -217,7 +228,7 @@
     const BackgroundFetchRegistrationId& registration_id,
     scoped_refptr<BackgroundFetchRequestInfo> request_info,
     MarkRequestCompleteCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   AddDatabaseTask(std::make_unique<background_fetch::MarkRequestCompleteTask>(
       this, registration_id, std::move(request_info), std::move(callback)));
@@ -227,7 +238,7 @@
     const BackgroundFetchRegistrationId& registration_id,
     std::unique_ptr<BackgroundFetchRequestMatchParams> match_params,
     SettledFetchesCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   AddDatabaseTask(std::make_unique<background_fetch::MatchRequestsTask>(
       this, registration_id, std::move(match_params), std::move(callback)));
@@ -237,7 +248,7 @@
     const BackgroundFetchRegistrationId& registration_id,
     bool check_for_failure,
     MarkRegistrationForDeletionCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   AddDatabaseTask(
       std::make_unique<background_fetch::MarkRegistrationForDeletionTask>(
@@ -247,7 +258,7 @@
 void BackgroundFetchDataManager::DeleteRegistration(
     const BackgroundFetchRegistrationId& registration_id,
     HandleBackgroundFetchErrorCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   AddDatabaseTask(std::make_unique<background_fetch::DeleteRegistrationTask>(
       this, registration_id.service_worker_registration_id(),
@@ -259,20 +270,22 @@
     int64_t service_worker_registration_id,
     const blink::StorageKey& storage_key,
     blink::mojom::BackgroundFetchService::GetDeveloperIdsCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   AddDatabaseTask(std::make_unique<background_fetch::GetDeveloperIdsTask>(
       this, service_worker_registration_id, storage_key, std::move(callback)));
 }
 
-void BackgroundFetchDataManager::ShutdownOnCoreThread() {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+void BackgroundFetchDataManager::Shutdown() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   shutting_down_ = true;
 }
 
 void BackgroundFetchDataManager::AddDatabaseTask(
     std::unique_ptr<background_fetch::DatabaseTask> task) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   // If Shutdown was called don't add any new tasks.
   if (shutting_down_)
     return;
@@ -284,7 +297,7 @@
 
 void BackgroundFetchDataManager::OnTaskFinished(
     background_fetch::DatabaseTask* task) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   DCHECK(!database_tasks_.empty());
   DCHECK_EQ(database_tasks_.front().get(), task);
@@ -295,11 +308,13 @@
 }
 
 BackgroundFetchDataManager* BackgroundFetchDataManager::data_manager() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return this;
 }
 
 base::WeakPtr<background_fetch::DatabaseTaskHost>
 BackgroundFetchDataManager::GetWeakPtr() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return weak_ptr_factory_.GetWeakPtr();
 }
 
diff --git a/content/browser/background_fetch/background_fetch_data_manager.h b/content/browser/background_fetch/background_fetch_data_manager.h
index 46d3a18..b2d3ea0 100644
--- a/content/browser/background_fetch/background_fetch_data_manager.h
+++ b/content/browser/background_fetch/background_fetch_data_manager.h
@@ -19,6 +19,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "base/sequence_checker.h"
 #include "content/browser/background_fetch/background_fetch.pb.h"
 #include "content/browser/background_fetch/background_fetch_registration_id.h"
 #include "content/browser/background_fetch/background_fetch_scheduler.h"
@@ -47,11 +48,10 @@
 // for Background Fetch.
 //
 // There must only be a single instance of this class per StoragePartition, and
-// it must only be used on the service worker core thread, since it relies on
-// there being no other code concurrently reading/writing the Background Fetch
-// keys of the same Service Worker database (except for deletions, e.g. it's
-// safe for the Service Worker code to remove a ServiceWorkerRegistration and
-// all its keys).
+// it must only be used on the UI thread, since it relies on there being no
+// other code concurrently reading/writing the Background Fetch keys of the same
+// Service Worker database (except for deletions, e.g. it's safe for the Service
+// Worker code to remove a ServiceWorkerRegistration and all its keys).
 //
 // Storage schema is documented in storage/README.md
 class CONTENT_EXPORT BackgroundFetchDataManager
@@ -90,7 +90,7 @@
   ~BackgroundFetchDataManager() override;
 
   // Grabs a reference to CacheStorageManager.
-  virtual void InitializeOnCoreThread();
+  virtual void Initialize();
 
   // Adds or removes the given |observer| to this data manager instance.
   void AddObserver(BackgroundFetchDataManagerObserver* observer);
@@ -182,7 +182,7 @@
     return observers_;
   }
 
-  void ShutdownOnCoreThread();
+  void Shutdown();
 
  private:
   FRIEND_TEST_ALL_PREFIXES(BackgroundFetchDataManagerTest, Cleanup);
@@ -270,6 +270,8 @@
       cache_storage_remote_map_;
   mojo::Remote<blink::mojom::CacheStorage> null_remote_;
 
+  SEQUENCE_CHECKER(sequence_checker_);
+
   base::WeakPtrFactory<BackgroundFetchDataManager> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(BackgroundFetchDataManager);
diff --git a/content/browser/background_fetch/background_fetch_data_manager_unittest.cc b/content/browser/background_fetch/background_fetch_data_manager_unittest.cc
index c6027a2a..d092f19 100644
--- a/content/browser/background_fetch/background_fetch_data_manager_unittest.cc
+++ b/content/browser/background_fetch/background_fetch_data_manager_unittest.cc
@@ -214,7 +214,7 @@
             embedded_worker_test_helper()->context_wrapper());
 
     background_fetch_data_manager_->AddObserver(this);
-    background_fetch_data_manager_->InitializeOnCoreThread();
+    background_fetch_data_manager_->Initialize();
   }
 
   // Synchronous version of BackgroundFetchDataManager::GetInitializationData().
diff --git a/content/browser/background_fetch/background_fetch_delegate_proxy.cc b/content/browser/background_fetch/background_fetch_delegate_proxy.cc
index 31188f67..1f02f6f 100644
--- a/content/browser/background_fetch/background_fetch_delegate_proxy.cc
+++ b/content/browser/background_fetch/background_fetch_delegate_proxy.cc
@@ -25,120 +25,105 @@
 
 namespace content {
 
-// Internal functionality of the BackgroundFetchDelegateProxy that lives on the
-// UI thread, where all interaction with the download manager must happen.
-class BackgroundFetchDelegateProxy::Core
-    : public BackgroundFetchDelegate::Client {
- public:
-  Core(base::WeakPtr<BackgroundFetchDelegateProxy> parent,
-       base::WeakPtr<StoragePartitionImpl> storage_partition)
-      : parent_(std::move(parent)),
-        storage_partition_(std::move(storage_partition)) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-    DCHECK(storage_partition_);
+BackgroundFetchDelegateProxy::BackgroundFetchDelegateProxy(
+    base::WeakPtr<StoragePartitionImpl> storage_partition)
+    : storage_partition_(std::move(storage_partition)) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+BackgroundFetchDelegateProxy::~BackgroundFetchDelegateProxy() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void BackgroundFetchDelegateProxy::SetClickEventDispatcher(
+    DispatchClickEventCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  click_event_dispatcher_callback_ = std::move(callback);
+}
+
+void BackgroundFetchDelegateProxy::GetIconDisplaySize(
+    BackgroundFetchDelegate::GetIconDisplaySizeCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (auto* delegate = GetDelegate()) {
+    delegate->GetIconDisplaySize(std::move(callback));
+  } else {
+    std::move(callback).Run(gfx::Size(0, 0));
   }
+}
 
-  ~Core() override { DCHECK_CURRENTLY_ON(BrowserThread::UI); }
+void BackgroundFetchDelegateProxy::GetPermissionForOrigin(
+    const url::Origin& origin,
+    const WebContents::Getter& wc_getter,
+    GetPermissionForOriginCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  base::WeakPtr<Core> GetWeakPtrOnUI() {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-    return weak_ptr_factory_.GetWeakPtr();
-  }
+  BackgroundFetchPermission result = BackgroundFetchPermission::BLOCKED;
 
-  void ForwardGetPermissionForOriginCallbackToParentThread(
-      GetPermissionForOriginCallback callback,
-      BackgroundFetchPermission permission) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-    RunOrPostTaskOnThread(FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-                          base::BindOnce(std::move(callback), permission));
-  }
-
-  void GetPermissionForOrigin(const url::Origin& origin,
-                              const WebContents::Getter& wc_getter,
-                              GetPermissionForOriginCallback callback) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-    BackgroundFetchPermission result = BackgroundFetchPermission::BLOCKED;
-
-    if (auto* controller = GetPermissionController()) {
-      content::WebContents* web_contents =
-          wc_getter ? wc_getter.Run() : nullptr;
-      content::RenderFrameHost* rfh =
-          web_contents ? web_contents->GetMainFrame() : nullptr;
-      blink::mojom::PermissionStatus permission_status =
-          rfh ? controller->GetPermissionStatusForFrame(
-                    PermissionType::BACKGROUND_FETCH, rfh,
-                    /*requesting_origin=*/origin.GetURL())
-              : controller->GetPermissionStatus(
-                    PermissionType::BACKGROUND_FETCH,
-                    /*requesting_origin=*/origin.GetURL(),
-                    /*embedding_origin=*/origin.GetURL());
-      switch (permission_status) {
-        case blink::mojom::PermissionStatus::GRANTED:
-          result = BackgroundFetchPermission::ALLOWED;
-          break;
-        case blink::mojom::PermissionStatus::DENIED:
-          result = BackgroundFetchPermission::BLOCKED;
-          break;
-        case blink::mojom::PermissionStatus::ASK:
-          result = BackgroundFetchPermission::ASK;
-          break;
-      }
-    }
-
-    RunOrPostTaskOnThread(FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-                          base::BindOnce(std::move(callback), result));
-  }
-
-  void ForwardGetIconDisplaySizeCallbackToParentThread(
-      BackgroundFetchDelegate::GetIconDisplaySizeCallback callback,
-      const gfx::Size& display_size) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-    RunOrPostTaskOnThread(FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-                          base::BindOnce(std::move(callback), display_size));
-  }
-
-  void GetIconDisplaySize(
-      BackgroundFetchDelegate::GetIconDisplaySizeCallback callback) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-    if (auto* delegate = GetDelegate()) {
-      delegate->GetIconDisplaySize(
-          base::BindOnce(&Core::ForwardGetIconDisplaySizeCallbackToParentThread,
-                         weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-    } else {
-      RunOrPostTaskOnThread(
-          FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-          base::BindOnce(std::move(callback), gfx::Size(0, 0)));
+  if (auto* controller = GetPermissionController()) {
+    content::WebContents* web_contents = wc_getter ? wc_getter.Run() : nullptr;
+    content::RenderFrameHost* rfh =
+        web_contents ? web_contents->GetMainFrame() : nullptr;
+    blink::mojom::PermissionStatus permission_status =
+        rfh ? controller->GetPermissionStatusForFrame(
+                  PermissionType::BACKGROUND_FETCH, rfh,
+                  /*requesting_origin=*/origin.GetURL())
+            : controller->GetPermissionStatus(
+                  PermissionType::BACKGROUND_FETCH,
+                  /*requesting_origin=*/origin.GetURL(),
+                  /*embedding_origin=*/origin.GetURL());
+    switch (permission_status) {
+      case blink::mojom::PermissionStatus::GRANTED:
+        result = BackgroundFetchPermission::ALLOWED;
+        break;
+      case blink::mojom::PermissionStatus::DENIED:
+        result = BackgroundFetchPermission::BLOCKED;
+        break;
+      case blink::mojom::PermissionStatus::ASK:
+        result = BackgroundFetchPermission::ASK;
+        break;
     }
   }
 
-  void CreateDownloadJob(
-      std::unique_ptr<BackgroundFetchDescription> fetch_description) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  std::move(callback).Run(result);
+}
 
-    auto* delegate = GetDelegate();
-    if (delegate)
-      delegate->CreateDownloadJob(GetWeakPtrOnUI(),
-                                  std::move(fetch_description));
+void BackgroundFetchDelegateProxy::CreateDownloadJob(
+    base::WeakPtr<Controller> controller,
+    std::unique_ptr<BackgroundFetchDescription> fetch_description) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DCHECK(!controller_map_.count(fetch_description->job_unique_id));
+  controller_map_[fetch_description->job_unique_id] = std::move(controller);
+
+  auto* delegate = GetDelegate();
+  if (delegate) {
+    delegate->CreateDownloadJob(weak_ptr_factory_.GetWeakPtr(),
+                                std::move(fetch_description));
   }
+}
 
-  void StartRequest(const std::string& job_unique_id,
-                    const url::Origin& origin,
-                    const scoped_refptr<BackgroundFetchRequestInfo>& request) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-    DCHECK(request);
+void BackgroundFetchDelegateProxy::StartRequest(
+    const std::string& job_unique_id,
+    const url::Origin& origin,
+    const scoped_refptr<BackgroundFetchRequestInfo>& request) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-    auto* delegate = GetDelegate();
-    if (!delegate)
-      return;
+  DCHECK(controller_map_.count(job_unique_id));
+  DCHECK(request);
+  DCHECK(!request->download_guid().empty());
 
-    const blink::mojom::FetchAPIRequestPtr& fetch_request =
-        request->fetch_request();
+  auto* delegate = GetDelegate();
+  if (!delegate)
+    return;
 
-    const net::NetworkTrafficAnnotationTag traffic_annotation(
-        net::DefineNetworkTrafficAnnotation("background_fetch_context",
-                                            R"(
+  const blink::mojom::FetchAPIRequestPtr& fetch_request =
+      request->fetch_request();
+
+  const net::NetworkTrafficAnnotationTag traffic_annotation(
+      net::DefineNetworkTrafficAnnotation("background_fetch_context",
+                                          R"(
             semantics {
               sender: "Background Fetch API"
               description:
@@ -165,264 +150,26 @@
               policy_exception_justification: "Not implemented."
             })"));
 
-    // TODO(peter): The |headers| should be populated with all the properties
-    // set in the |fetch_request| structure.
-    net::HttpRequestHeaders headers;
-    for (const auto& pair : fetch_request->headers)
-      headers.SetHeader(pair.first, pair.second);
+  // TODO(peter): The |headers| should be populated with all the properties
+  // set in the |fetch_request| structure.
+  net::HttpRequestHeaders headers;
+  for (const auto& pair : fetch_request->headers)
+    headers.SetHeader(pair.first, pair.second);
 
-    // Append the Origin header for requests whose CORS flag is set, or whose
-    // request method is not GET or HEAD. See section 3.1 of the standard:
-    // https://fetch.spec.whatwg.org/#origin-header
-    if (fetch_request->mode == network::mojom::RequestMode::kCors ||
-        fetch_request->mode ==
-            network::mojom::RequestMode::kCorsWithForcedPreflight ||
-        (fetch_request->method != "GET" && fetch_request->method != "HEAD")) {
-      headers.SetHeader("Origin", origin.Serialize());
-    }
-
-    delegate->DownloadUrl(
-        job_unique_id, request->download_guid(), fetch_request->method,
-        fetch_request->url, traffic_annotation, headers,
-        /* has_request_body= */ request->request_body_size() > 0u);
+  // Append the Origin header for requests whose CORS flag is set, or whose
+  // request method is not GET or HEAD. See section 3.1 of the standard:
+  // https://fetch.spec.whatwg.org/#origin-header
+  if (fetch_request->mode == network::mojom::RequestMode::kCors ||
+      fetch_request->mode ==
+          network::mojom::RequestMode::kCorsWithForcedPreflight ||
+      (fetch_request->method != "GET" && fetch_request->method != "HEAD")) {
+    headers.SetHeader("Origin", origin.Serialize());
   }
 
-  void Abort(const std::string& job_unique_id) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-    if (auto* delegate = GetDelegate())
-      delegate->Abort(job_unique_id);
-  }
-
-  void MarkJobComplete(const std::string& job_unique_id) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-    if (auto* delegate = GetDelegate())
-      delegate->MarkJobComplete(job_unique_id);
-  }
-
-  void UpdateUI(const std::string& job_unique_id,
-                const absl::optional<std::string>& title,
-                const absl::optional<SkBitmap>& icon) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-    if (auto* delegate = GetDelegate())
-      delegate->UpdateUI(job_unique_id, title, icon);
-  }
-
-  // BackgroundFetchDelegate::Client implementation:
-  void OnJobCancelled(
-      const std::string& job_unique_id,
-      blink::mojom::BackgroundFetchFailureReason reason_to_abort) override;
-  void OnDownloadUpdated(const std::string& job_unique_id,
-                         const std::string& guid,
-                         uint64_t bytes_uploaded,
-                         uint64_t bytes_downloaded) override;
-  void OnDownloadComplete(
-      const std::string& job_unique_id,
-      const std::string& guid,
-      std::unique_ptr<BackgroundFetchResult> result) override;
-  void OnDownloadStarted(
-      const std::string& job_unique_id,
-      const std::string& guid,
-      std::unique_ptr<content::BackgroundFetchResponse> response) override;
-  void OnUIActivated(const std::string& unique_id) override;
-  void OnUIUpdated(const std::string& unique_id) override;
-  void GetUploadData(
-      const std::string& job_unique_id,
-      const std::string& download_guid,
-      BackgroundFetchDelegate::GetUploadDataCallback callback) override;
-
- private:
-  BrowserContext* GetBrowserContext() {
-    if (!storage_partition_)
-      return nullptr;
-    return storage_partition_->browser_context();
-  }
-
-  BackgroundFetchDelegate* GetDelegate() {
-    auto* browser_context = GetBrowserContext();
-    if (!browser_context)
-      return nullptr;
-    return browser_context->GetBackgroundFetchDelegate();
-  }
-
-  PermissionControllerImpl* GetPermissionController() {
-    auto* browser_context = GetBrowserContext();
-    if (!browser_context)
-      return nullptr;
-    return PermissionControllerImpl::FromBrowserContext(browser_context);
-  }
-
-  // Weak reference to the service worker core thread outer class that owns us.
-  base::WeakPtr<BackgroundFetchDelegateProxy> parent_;
-
-  base::WeakPtr<StoragePartitionImpl> storage_partition_;
-
-  base::WeakPtrFactory<Core> weak_ptr_factory_{this};
-
-  DISALLOW_COPY_AND_ASSIGN(Core);
-};
-
-void BackgroundFetchDelegateProxy::Core::OnJobCancelled(
-    const std::string& job_unique_id,
-    blink::mojom::BackgroundFetchFailureReason reason_to_abort) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  RunOrPostTaskOnThread(
-      FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-      base::BindOnce(&BackgroundFetchDelegateProxy::OnJobCancelled, parent_,
-                     job_unique_id, reason_to_abort));
-}
-
-void BackgroundFetchDelegateProxy::Core::OnDownloadUpdated(
-    const std::string& job_unique_id,
-    const std::string& guid,
-    uint64_t bytes_uploaded,
-    uint64_t bytes_downloaded) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  RunOrPostTaskOnThread(
-      FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-      base::BindOnce(&BackgroundFetchDelegateProxy::OnDownloadUpdated, parent_,
-                     job_unique_id, guid, bytes_uploaded, bytes_downloaded));
-}
-
-void BackgroundFetchDelegateProxy::Core::OnDownloadComplete(
-    const std::string& job_unique_id,
-    const std::string& guid,
-    std::unique_ptr<BackgroundFetchResult> result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  RunOrPostTaskOnThread(
-      FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-      base::BindOnce(&BackgroundFetchDelegateProxy::OnDownloadComplete, parent_,
-                     job_unique_id, guid, std::move(result)));
-}
-
-void BackgroundFetchDelegateProxy::Core::OnDownloadStarted(
-    const std::string& job_unique_id,
-    const std::string& guid,
-    std::unique_ptr<content::BackgroundFetchResponse> response) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  RunOrPostTaskOnThread(
-      FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-      base::BindOnce(&BackgroundFetchDelegateProxy::DidStartRequest, parent_,
-                     job_unique_id, guid, std::move(response)));
-}
-
-void BackgroundFetchDelegateProxy::Core::OnUIActivated(
-    const std::string& job_unique_id) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  RunOrPostTaskOnThread(
-      FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-      base::BindOnce(&BackgroundFetchDelegateProxy::DidActivateUI, parent_,
-                     job_unique_id));
-}
-
-void BackgroundFetchDelegateProxy::Core::OnUIUpdated(
-    const std::string& job_unique_id) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  RunOrPostTaskOnThread(
-      FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-      base::BindOnce(&BackgroundFetchDelegateProxy::DidUpdateUI, parent_,
-                     job_unique_id));
-}
-
-void BackgroundFetchDelegateProxy::Core::GetUploadData(
-    const std::string& job_unique_id,
-    const std::string& download_guid,
-    BackgroundFetchDelegate::GetUploadDataCallback callback) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  // Pass this to the core thread for processing, but wrap |callback|
-  // to be posted back to the UI thread when executed.
-  BackgroundFetchDelegate::GetUploadDataCallback wrapped_callback =
-      base::BindOnce(
-          [](BackgroundFetchDelegate::GetUploadDataCallback callback,
-             blink::mojom::SerializedBlobPtr blob) {
-            GetUIThreadTaskRunner({})->PostTask(
-                FROM_HERE,
-                base::BindOnce(std::move(callback), std::move(blob)));
-          },
-          std::move(callback));
-
-  RunOrPostTaskOnThread(
-      FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-      base::BindOnce(&BackgroundFetchDelegateProxy::GetUploadData, parent_,
-                     job_unique_id, download_guid,
-                     std::move(wrapped_callback)));
-}
-
-BackgroundFetchDelegateProxy::BackgroundFetchDelegateProxy(
-    base::WeakPtr<StoragePartitionImpl> storage_partition) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-  // Normally it would be unsafe to obtain a weak pointer on the UI thread from
-  // a factory that lives on the service worker core thread, but it's ok in the
-  // constructor as |this| can't be destroyed before the constructor finishes.
-  ui_core_.reset(
-      new Core(weak_ptr_factory_.GetWeakPtr(), std::move(storage_partition)));
-
-  // Since this constructor runs on the UI thread, a WeakPtr can be safely
-  // obtained from the Core.
-  ui_core_ptr_ = ui_core_->GetWeakPtrOnUI();
-}
-
-BackgroundFetchDelegateProxy::~BackgroundFetchDelegateProxy() {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-}
-
-void BackgroundFetchDelegateProxy::SetClickEventDispatcher(
-    DispatchClickEventCallback callback) {
-  click_event_dispatcher_callback_ = std::move(callback);
-}
-
-void BackgroundFetchDelegateProxy::GetIconDisplaySize(
-    BackgroundFetchDelegate::GetIconDisplaySizeCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-  RunOrPostTaskOnThread(FROM_HERE, BrowserThread::UI,
-                        base::BindOnce(&Core::GetIconDisplaySize, ui_core_ptr_,
-                                       std::move(callback)));
-}
-
-void BackgroundFetchDelegateProxy::GetPermissionForOrigin(
-    const url::Origin& origin,
-    const WebContents::Getter& wc_getter,
-    GetPermissionForOriginCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-  RunOrPostTaskOnThread(
-      FROM_HERE, BrowserThread::UI,
-      base::BindOnce(&Core::GetPermissionForOrigin, ui_core_ptr_, origin,
-                     wc_getter, std::move(callback)));
-}
-
-void BackgroundFetchDelegateProxy::CreateDownloadJob(
-    base::WeakPtr<Controller> controller,
-    std::unique_ptr<BackgroundFetchDescription> fetch_description) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-
-  DCHECK(!controller_map_.count(fetch_description->job_unique_id));
-  controller_map_[fetch_description->job_unique_id] = std::move(controller);
-
-  RunOrPostTaskOnThread(FROM_HERE, BrowserThread::UI,
-                        base::BindOnce(&Core::CreateDownloadJob, ui_core_ptr_,
-                                       std::move(fetch_description)));
-}
-
-void BackgroundFetchDelegateProxy::StartRequest(
-    const std::string& job_unique_id,
-    const url::Origin& origin,
-    const scoped_refptr<BackgroundFetchRequestInfo>& request) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-
-  DCHECK(controller_map_.count(job_unique_id));
-  DCHECK(request);
-  DCHECK(!request->download_guid().empty());
-
-  RunOrPostTaskOnThread(FROM_HERE, BrowserThread::UI,
-                        base::BindOnce(&Core::StartRequest, ui_core_ptr_,
-                                       job_unique_id, origin, request));
+  delegate->DownloadUrl(
+      job_unique_id, request->download_guid(), fetch_request->method,
+      fetch_request->url, traffic_annotation, headers,
+      /* has_request_body= */ request->request_body_size() > 0u);
 }
 
 void BackgroundFetchDelegateProxy::UpdateUI(
@@ -431,36 +178,35 @@
     const absl::optional<SkBitmap>& icon,
     blink::mojom::BackgroundFetchRegistrationService::UpdateUICallback
         update_ui_callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   DCHECK(!update_ui_callback_map_.count(job_unique_id));
   update_ui_callback_map_.emplace(job_unique_id, std::move(update_ui_callback));
 
-  RunOrPostTaskOnThread(FROM_HERE, BrowserThread::UI,
-                        base::BindOnce(&Core::UpdateUI, ui_core_ptr_,
-                                       job_unique_id, title, icon));
+  if (auto* delegate = GetDelegate())
+    delegate->UpdateUI(job_unique_id, title, icon);
 }
 
 void BackgroundFetchDelegateProxy::Abort(const std::string& job_unique_id) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-
-  RunOrPostTaskOnThread(
-      FROM_HERE, BrowserThread::UI,
-      base::BindOnce(&Core::Abort, ui_core_ptr_, job_unique_id));
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (auto* delegate = GetDelegate())
+    delegate->Abort(job_unique_id);
 }
 
 void BackgroundFetchDelegateProxy::MarkJobComplete(
     const std::string& job_unique_id) {
-  RunOrPostTaskOnThread(
-      FROM_HERE, BrowserThread::UI,
-      base::BindOnce(&Core::MarkJobComplete, ui_core_ptr_, job_unique_id));
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (auto* delegate = GetDelegate())
+    delegate->MarkJobComplete(job_unique_id);
+
   controller_map_.erase(job_unique_id);
 }
 
 void BackgroundFetchDelegateProxy::OnJobCancelled(
     const std::string& job_unique_id,
     blink::mojom::BackgroundFetchFailureReason reason_to_abort) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(
       reason_to_abort ==
           blink::mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI ||
@@ -475,11 +221,11 @@
     controller->AbortFromDelegate(reason_to_abort);
 }
 
-void BackgroundFetchDelegateProxy::DidStartRequest(
+void BackgroundFetchDelegateProxy::OnDownloadStarted(
     const std::string& job_unique_id,
     const std::string& guid,
     std::unique_ptr<BackgroundFetchResponse> response) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   auto it = controller_map_.find(job_unique_id);
   if (it == controller_map_.end())
@@ -489,14 +235,16 @@
     controller->DidStartRequest(guid, std::move(response));
 }
 
-void BackgroundFetchDelegateProxy::DidActivateUI(
+void BackgroundFetchDelegateProxy::OnUIActivated(
     const std::string& job_unique_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(click_event_dispatcher_callback_);
   click_event_dispatcher_callback_.Run(job_unique_id);
 }
 
-void BackgroundFetchDelegateProxy::DidUpdateUI(
+void BackgroundFetchDelegateProxy::OnUIUpdated(
     const std::string& job_unique_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto it = update_ui_callback_map_.find(job_unique_id);
   if (it == update_ui_callback_map_.end())
     return;
@@ -511,7 +259,7 @@
     const std::string& guid,
     uint64_t bytes_uploaded,
     uint64_t bytes_downloaded) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   auto it = controller_map_.find(job_unique_id);
   if (it == controller_map_.end())
@@ -525,7 +273,7 @@
     const std::string& job_unique_id,
     const std::string& guid,
     std::unique_ptr<BackgroundFetchResult> result) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   auto it = controller_map_.find(job_unique_id);
   if (it == controller_map_.end())
@@ -539,6 +287,8 @@
     const std::string& job_unique_id,
     const std::string& download_guid,
     BackgroundFetchDelegate::GetUploadDataCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   auto it = controller_map_.find(job_unique_id);
   if (it == controller_map_.end()) {
     std::move(callback).Run(nullptr);
@@ -551,4 +301,25 @@
     std::move(callback).Run(nullptr);
 }
 
+BrowserContext* BackgroundFetchDelegateProxy::GetBrowserContext() {
+  if (!storage_partition_)
+    return nullptr;
+  return storage_partition_->browser_context();
+}
+
+BackgroundFetchDelegate* BackgroundFetchDelegateProxy::GetDelegate() {
+  auto* browser_context = GetBrowserContext();
+  if (!browser_context)
+    return nullptr;
+  return browser_context->GetBackgroundFetchDelegate();
+}
+
+PermissionControllerImpl*
+BackgroundFetchDelegateProxy::GetPermissionController() {
+  auto* browser_context = GetBrowserContext();
+  if (!browser_context)
+    return nullptr;
+  return PermissionControllerImpl::FromBrowserContext(browser_context);
+}
+
 }  // namespace content
diff --git a/content/browser/background_fetch/background_fetch_delegate_proxy.h b/content/browser/background_fetch/background_fetch_delegate_proxy.h
index 4b2ca68..d76bf7d 100644
--- a/content/browser/background_fetch/background_fetch_delegate_proxy.h
+++ b/content/browser/background_fetch/background_fetch_delegate_proxy.h
@@ -14,6 +14,7 @@
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
 #include "content/browser/background_fetch/background_fetch_request_info.h"
 #include "content/public/browser/background_fetch_delegate.h"
 #include "content/public/browser/background_fetch_description.h"
@@ -26,17 +27,18 @@
 
 namespace content {
 
+class PermissionControllerImpl;
 class StoragePartitionImpl;
 
-// Proxy class for passing messages between BackgroundFetchJobControllers on the
-// service worker core thread and BackgroundFetchDelegate on the UI thread.
+// This class was previously responsible for passing messages between
+// BackgroundFetchJobControllers on the service worker core thread and
+// BackgroundFetchDelegate on the UI thread. It may no longer be needed
+// now that these are the same thread.
 //
-// TODO(crbug.com/824858): This proxying should no longer be needed after
-// the service worker core thread becomes the UI thread.
-class CONTENT_EXPORT BackgroundFetchDelegateProxy {
+// Lives on the UI thread.
+class CONTENT_EXPORT BackgroundFetchDelegateProxy
+    : public BackgroundFetchDelegate::Client {
  public:
-  // Subclasses must only be destroyed on the service worker core thread, since
-  // these methods will be called on the service worker core thread.
   using DispatchClickEventCallback =
       base::RepeatingCallback<void(const std::string& unique_id)>;
   using GetPermissionForOriginCallback =
@@ -77,7 +79,7 @@
   explicit BackgroundFetchDelegateProxy(
       base::WeakPtr<StoragePartitionImpl> storage_partition);
 
-  ~BackgroundFetchDelegateProxy();
+  ~BackgroundFetchDelegateProxy() override;
 
   // Set BackgroundFetchClick event dispatcher callback, which is a method on
   // the background fetch context.
@@ -99,22 +101,20 @@
   // browser session, then |fetch_description->current_guids| should contain the
   // GUIDs of in progress downloads, while completed downloads are recorded in
   // |fetch_description->completed_requests|.
-  // Should only be called from the Controller (on the service worker core
-  // thread).
+  // Should only be called from the Controller.
   void CreateDownloadJob(
       base::WeakPtr<Controller> controller,
       std::unique_ptr<BackgroundFetchDescription> fetch_description);
 
   // Requests that the download manager start fetching |request|.
-  // Should only be called from the Controller (on the service worker core
-  // thread).
+  // Should only be called from the Controller.
   void StartRequest(const std::string& job_unique_id,
                     const url::Origin& origin,
                     const scoped_refptr<BackgroundFetchRequestInfo>& request);
 
   // Updates the representation of this registration in the user interface to
   // match the given |title| or |icon|.
-  // Called from the Controller (on the service worker core thread).
+  // Called from the Controller.
   void UpdateUI(
       const std::string& job_unique_id,
       const absl::optional<std::string>& title,
@@ -123,8 +123,8 @@
           update_ui_callback);
 
   // Aborts in progress downloads for the given registration. Called from the
-  // Controller (on the service worker core thread) after it is aborted. May
-  // occur even if all requests already called OnDownloadComplete.
+  // Controller after it is aborted. May occur even if all requests already
+  // called OnDownloadComplete.
   void Abort(const std::string& job_unique_id);
 
   // Called when the fetch associated |job_unique_id| is completed.
@@ -134,50 +134,34 @@
   void Shutdown();
 
  private:
-  class Core;
-
-  // Called when the job identified by |job_unique|id| was cancelled by the
-  // delegate. Should only be called on the service worker core thread.
+  // BackgroundFetchDelegate::Client implementation:
   void OnJobCancelled(
       const std::string& job_unique_id,
-      blink::mojom::BackgroundFetchFailureReason reason_to_abort);
-
-  // Called when the download identified by |guid| has succeeded/failed/aborted.
-  // Should only be called on the service worker core thread.
-  void OnDownloadComplete(const std::string& job_unique_id,
-                          const std::string& guid,
-                          std::unique_ptr<BackgroundFetchResult> result);
-
-  // Called when progress has been made for the download identified by |guid|.
-  // Progress is either the request body being uploaded or response body being
-  // downloaded. Should only be called on the service worker core thread.
+      blink::mojom::BackgroundFetchFailureReason reason_to_abort) override;
+  void OnDownloadComplete(
+      const std::string& job_unique_id,
+      const std::string& guid,
+      std::unique_ptr<BackgroundFetchResult> result) override;
   void OnDownloadUpdated(const std::string& job_unique_id,
                          const std::string& guid,
                          uint64_t bytes_uploaded,
-                         uint64_t bytes_downloaded);
+                         uint64_t bytes_downloaded) override;
+  void OnDownloadStarted(
+      const std::string& job_unique_id,
+      const std::string& guid,
+      std::unique_ptr<BackgroundFetchResponse> response) override;
+  void OnUIActivated(const std::string& job_unique_id) override;
+  void OnUIUpdated(const std::string& job_unique_id) override;
+  void GetUploadData(
+      const std::string& job_unique_id,
+      const std::string& download_guid,
+      BackgroundFetchDelegate::GetUploadDataCallback callback) override;
 
-  // Should only be called from the BackgroundFetchDelegate (on the service
-  // worker core thread).
-  void DidStartRequest(const std::string& job_unique_id,
-                       const std::string& guid,
-                       std::unique_ptr<BackgroundFetchResponse> response);
+  BrowserContext* GetBrowserContext();
 
-  // Should only be called from the BackgroundFetchDelegate (on the service
-  // worker core thread).
-  void DidActivateUI(const std::string& job_unique_id);
+  BackgroundFetchDelegate* GetDelegate();
 
-  // Should only be called from the BackgroundFetchDelegate (on the service
-  // worker core thread).
-  void DidUpdateUI(const std::string& job_unique_id);
-
-  // Should only be called from the BackgroundFetchDelegate (on the service
-  // worker core thread).
-  void GetUploadData(const std::string& job_unique_id,
-                     const std::string& download_guid,
-                     BackgroundFetchDelegate::GetUploadDataCallback callback);
-
-  std::unique_ptr<Core, BrowserThread::DeleteOnUIThread> ui_core_;
-  base::WeakPtr<Core> ui_core_ptr_;
+  PermissionControllerImpl* GetPermissionController();
 
   // Map from unique job ids to the controller.
   std::map<std::string, base::WeakPtr<Controller>> controller_map_;
@@ -188,6 +172,11 @@
       update_ui_callback_map_;
 
   DispatchClickEventCallback click_event_dispatcher_callback_;
+
+  base::WeakPtr<StoragePartitionImpl> storage_partition_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
   base::WeakPtrFactory<BackgroundFetchDelegateProxy> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(BackgroundFetchDelegateProxy);
diff --git a/content/browser/background_fetch/background_fetch_scheduler_unittest.cc b/content/browser/background_fetch/background_fetch_scheduler_unittest.cc
index 32b88d2d..1650520 100644
--- a/content/browser/background_fetch/background_fetch_scheduler_unittest.cc
+++ b/content/browser/background_fetch/background_fetch_scheduler_unittest.cc
@@ -78,7 +78,7 @@
     data_manager_ = std::make_unique<BackgroundFetchTestDataManager>(
         browser_context(), storage_partition(),
         embedded_worker_test_helper()->context_wrapper());
-    data_manager_->InitializeOnCoreThread();
+    data_manager_->Initialize();
 
     delegate_proxy_ =
         std::make_unique<BackgroundFetchDelegateProxy>(storage_partition());
diff --git a/content/browser/background_fetch/background_fetch_service_impl.cc b/content/browser/background_fetch/background_fetch_service_impl.cc
index 3951032..f61a180 100644
--- a/content/browser/background_fetch/background_fetch_service_impl.cc
+++ b/content/browser/background_fetch/background_fetch_service_impl.cc
@@ -46,16 +46,15 @@
   if (!render_process_host)
     return;
 
-  RunOrPostTaskOnThread(
-      FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-      base::BindOnce(
-          BackgroundFetchServiceImpl::CreateOnCoreThread,
-          WrapRefCounted(static_cast<StoragePartitionImpl*>(
-                             render_process_host->GetStoragePartition())
-                             ->GetBackgroundFetchContext()),
-          info.storage_key,
-          /* render_frame_tree_node_id= */ 0,
-          /* wc_getter= */ base::NullCallback(), std::move(receiver)));
+  scoped_refptr<BackgroundFetchContext> context =
+      WrapRefCounted(static_cast<StoragePartitionImpl*>(
+                         render_process_host->GetStoragePartition())
+                         ->GetBackgroundFetchContext());
+  mojo::MakeSelfOwnedReceiver(std::make_unique<BackgroundFetchServiceImpl>(
+                                  std::move(context), info.storage_key,
+                                  /*render_frame_tree_node_id=*/0,
+                                  /*wc_getter=*/base::NullCallback()),
+                              std::move(receiver));
 }
 
 // static
@@ -64,43 +63,27 @@
     mojo::PendingReceiver<blink::mojom::BackgroundFetchService> receiver) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(render_frame_host);
-  RenderProcessHost* render_process_host = render_frame_host->GetProcess();
+  auto* rfhi = static_cast<RenderFrameHostImpl*>(render_frame_host);
+  RenderProcessHost* render_process_host = rfhi->GetProcess();
   DCHECK(render_process_host);
 
   WebContents::Getter wc_getter = base::NullCallback();
 
   // Permissions need to go through the DownloadRequestLimiter if the fetch
   // is started from a top-level frame.
-  if (!render_frame_host->GetParent()) {
+  if (!rfhi->GetParent()) {
     wc_getter = base::BindRepeating(&WebContents::FromFrameTreeNodeId,
-                                    render_frame_host->GetFrameTreeNodeId());
+                                    rfhi->GetFrameTreeNodeId());
   }
 
-  RunOrPostTaskOnThread(
-      FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
-      base::BindOnce(
-          BackgroundFetchServiceImpl::CreateOnCoreThread,
-          WrapRefCounted(static_cast<StoragePartitionImpl*>(
-                             render_process_host->GetStoragePartition())
-                             ->GetBackgroundFetchContext()),
-          static_cast<RenderFrameHostImpl*>(render_frame_host)->storage_key(),
-          render_frame_host->GetFrameTreeNodeId(), std::move(wc_getter),
-          std::move(receiver)));
-}
-
-// static
-void BackgroundFetchServiceImpl::CreateOnCoreThread(
-    scoped_refptr<BackgroundFetchContext> background_fetch_context,
-    blink::StorageKey storage_key,
-    int render_frame_tree_node_id,
-    WebContents::Getter wc_getter,
-    mojo::PendingReceiver<blink::mojom::BackgroundFetchService> receiver) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
-
+  scoped_refptr<BackgroundFetchContext> context =
+      WrapRefCounted(static_cast<StoragePartitionImpl*>(
+                         render_process_host->GetStoragePartition())
+                         ->GetBackgroundFetchContext());
   mojo::MakeSelfOwnedReceiver(
       std::make_unique<BackgroundFetchServiceImpl>(
-          std::move(background_fetch_context), std::move(storage_key),
-          render_frame_tree_node_id, std::move(wc_getter)),
+          std::move(context), rfhi->storage_key(), rfhi->GetFrameTreeNodeId(),
+          std::move(wc_getter)),
       std::move(receiver));
 }
 
@@ -113,11 +96,13 @@
       storage_key_(std::move(storage_key)),
       render_frame_tree_node_id_(render_frame_tree_node_id),
       wc_getter_(std::move(wc_getter)) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(background_fetch_context_);
 }
 
 BackgroundFetchServiceImpl::~BackgroundFetchServiceImpl() {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
 void BackgroundFetchServiceImpl::Fetch(
@@ -128,7 +113,7 @@
     const SkBitmap& icon,
     blink::mojom::BackgroundFetchUkmDataPtr ukm_data,
     FetchCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!ValidateDeveloperId(developer_id) || !ValidateRequests(requests)) {
     std::move(callback).Run(
@@ -151,7 +136,7 @@
 
 void BackgroundFetchServiceImpl::GetIconDisplaySize(
     blink::mojom::BackgroundFetchService::GetIconDisplaySizeCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   background_fetch_context_->GetIconDisplaySize(std::move(callback));
 }
 
@@ -159,7 +144,7 @@
     int64_t service_worker_registration_id,
     const std::string& developer_id,
     GetRegistrationCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!ValidateDeveloperId(developer_id)) {
     std::move(callback).Run(
         blink::mojom::BackgroundFetchError::INVALID_ARGUMENT,
@@ -175,13 +160,15 @@
 void BackgroundFetchServiceImpl::GetDeveloperIds(
     int64_t service_worker_registration_id,
     GetDeveloperIdsCallback callback) {
-  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   background_fetch_context_->GetDeveloperIdsForServiceWorker(
       service_worker_registration_id, storage_key_, std::move(callback));
 }
 
 bool BackgroundFetchServiceImpl::ValidateDeveloperId(
     const std::string& developer_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   if (developer_id.empty() || developer_id.size() > kMaxDeveloperIdLength) {
     mojo::ReportBadMessage("Invalid developer_id");
     return false;
@@ -192,6 +179,8 @@
 
 bool BackgroundFetchServiceImpl::ValidateUniqueId(
     const std::string& unique_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   if (!base::IsValidGUIDOutputString(unique_id)) {
     mojo::ReportBadMessage("Invalid unique_id");
     return false;
@@ -202,6 +191,8 @@
 
 bool BackgroundFetchServiceImpl::ValidateRequests(
     const std::vector<blink::mojom::FetchAPIRequestPtr>& requests) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
   if (requests.empty()) {
     mojo::ReportBadMessage("Invalid requests");
     return false;
diff --git a/content/browser/background_fetch/background_fetch_service_impl.h b/content/browser/background_fetch/background_fetch_service_impl.h
index a3c48f5..2fcc822 100644
--- a/content/browser/background_fetch/background_fetch_service_impl.h
+++ b/content/browser/background_fetch/background_fetch_service_impl.h
@@ -12,6 +12,7 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/sequence_checker.h"
 #include "content/browser/background_fetch/background_fetch_context.h"
 #include "content/common/content_export.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
@@ -55,13 +56,6 @@
                        GetDeveloperIdsCallback callback) override;
 
  private:
-  static void CreateOnCoreThread(
-      scoped_refptr<BackgroundFetchContext> background_fetch_context,
-      blink::StorageKey storage_key,
-      int render_frame_tree_node_id,
-      WebContents::Getter wc_getter,
-      mojo::PendingReceiver<blink::mojom::BackgroundFetchService> receiver);
-
   // Validates and returns whether the |developer_id|, |unique_id|, |requests|
   // and |title| respectively have valid values. The renderer will be flagged
   // for having sent a bad message if the values are invalid.
@@ -77,6 +71,7 @@
 
   int render_frame_tree_node_id_;
   WebContents::Getter wc_getter_;
+  SEQUENCE_CHECKER(sequence_checker_);
 
   DISALLOW_COPY_AND_ASSIGN(BackgroundFetchServiceImpl);
 };
diff --git a/content/browser/background_fetch/background_fetch_service_unittest.cc b/content/browser/background_fetch/background_fetch_service_unittest.cc
index cbf7eea..7c5f78c 100644
--- a/content/browser/background_fetch/background_fetch_service_unittest.cc
+++ b/content/browser/background_fetch/background_fetch_service_unittest.cc
@@ -304,7 +304,7 @@
     browser_context()->SetPermissionControllerDelegate(
         std::move(mock_permission_manager));
 
-    context_->InitializeOnCoreThread();
+    context_->Initialize();
     service_ = std::make_unique<BackgroundFetchServiceImpl>(
         context_, storage_key(),
         /* render_frame_tree_node_id= */ 0,
diff --git a/content/browser/background_fetch/background_fetch_test_data_manager.cc b/content/browser/background_fetch/background_fetch_test_data_manager.cc
index 837f75ad4..65b9c87 100644
--- a/content/browser/background_fetch/background_fetch_test_data_manager.cc
+++ b/content/browser/background_fetch/background_fetch_test_data_manager.cc
@@ -70,7 +70,7 @@
       browser_context_(browser_context),
       storage_partition_(storage_partition) {}
 
-void BackgroundFetchTestDataManager::InitializeOnCoreThread() {
+void BackgroundFetchTestDataManager::Initialize() {
   // CacheStorage uses the default QuotaManager and not the mock one in this
   // class.  Set QuotaSettings appropriately so that all platforms have quota.
   // The mock one is still used for testing quota exceeded scenarios in
diff --git a/content/browser/background_fetch/background_fetch_test_data_manager.h b/content/browser/background_fetch/background_fetch_test_data_manager.h
index a1e91d1..9c20479 100644
--- a/content/browser/background_fetch/background_fetch_test_data_manager.h
+++ b/content/browser/background_fetch/background_fetch_test_data_manager.h
@@ -37,7 +37,7 @@
 
   ~BackgroundFetchTestDataManager() override;
 
-  void InitializeOnCoreThread() override;
+  void Initialize() override;
 
  private:
   friend class BackgroundFetchDataManagerTest;
diff --git a/content/browser/browsing_instance.cc b/content/browser/browsing_instance.cc
index a56ff6b..63ec7e7 100644
--- a/content/browser/browsing_instance.cc
+++ b/content/browser/browsing_instance.cc
@@ -108,6 +108,19 @@
   DCHECK(site_instance->browsing_instance_.get() == this);
   DCHECK(site_instance->HasSite());
 
+  // Verify that the SiteInstance's StoragePartitionConfig matches this
+  // BrowsingInstance's StoragePartitionConfig if it already has one.
+  const StoragePartitionConfig& storage_partition_config =
+      site_instance->GetSiteInfo().storage_partition_config();
+  if (storage_partition_config_.has_value()) {
+    // We should only use a single StoragePartition within a BrowsingInstance.
+    // If we're attempting to use multiple, something has gone wrong with the
+    // logic at upper layers.
+    CHECK_EQ(storage_partition_config_.value(), storage_partition_config);
+  } else {
+    storage_partition_config_ = storage_partition_config;
+  }
+
   // Explicitly prevent the default SiteInstance from being added since
   // the map is only supposed to contain instances that map to a single site.
   if (site_instance->IsDefaultSiteInstance()) {
@@ -170,7 +183,21 @@
 
 SiteInfo BrowsingInstance::ComputeSiteInfoForURL(
     const UrlInfo& url_info) const {
-  return SiteInfo::Create(isolation_context_, url_info,
+  // If a StoragePartitionConfig is specified in both |url_info| and this
+  // BrowsingInstance, make sure they match.
+  if (url_info.storage_partition_config.has_value() &&
+      storage_partition_config_.has_value()) {
+    CHECK_EQ(storage_partition_config_.value(),
+             url_info.storage_partition_config.value());
+  }
+  // If no StoragePartitionConfig was set in |url_info|, create a new UrlInfo
+  // that inherit's this BrowsingInstance's StoragePartitionConfig.
+  UrlInfo url_info_with_partition =
+      url_info.storage_partition_config.has_value()
+          ? url_info
+          : url_info.CreateCopyWithStoragePartitionConfig(
+                storage_partition_config_);
+  return SiteInfo::Create(isolation_context_, url_info_with_partition,
                           web_exposed_isolation_info_);
 }
 
diff --git a/content/browser/browsing_instance.h b/content/browser/browsing_instance.h
index 119cce27..2d66f5a 100644
--- a/content/browser/browsing_instance.h
+++ b/content/browser/browsing_instance.h
@@ -18,6 +18,8 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/render_process_host_observer.h"
+#include "content/public/browser/storage_partition_config.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/origin.h"
 
 class GURL;
@@ -230,6 +232,15 @@
   // and if so, from which top level origin.
   const WebExposedIsolationInfo web_exposed_isolation_info_;
 
+  // The StoragePartitionConfig that must be used by all SiteInstances in this
+  // BrowsingInstance. This will be set to the StoragePartitionConfig of the
+  // first SiteInstance that has its SiteInfo assigned in this
+  // BrowsingInstance, and cannot be changed afterwards.
+  //
+  // See crbug.com/1212266 for more context on why we track the
+  // StoragePartitionConfig here.
+  absl::optional<StoragePartitionConfig> storage_partition_config_;
+
   DISALLOW_COPY_AND_ASSIGN(BrowsingInstance);
 };
 
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc
index 9bbca7c..4dd0448 100644
--- a/content/browser/isolated_origin_browsertest.cc
+++ b/content/browser/isolated_origin_browsertest.cc
@@ -2918,9 +2918,13 @@
 
   // A SiteInstance created for an isolated origin ServiceWorker should
   // not reuse the unsuitable first process.
+  BrowserContext* browser_context = web_contents()->GetBrowserContext();
   scoped_refptr<SiteInstanceImpl> sw_site_instance =
       SiteInstanceImpl::CreateForServiceWorker(
-          web_contents()->GetBrowserContext(), hung_isolated_url,
+          browser_context,
+          UrlInfo::CreateForTesting(
+              hung_isolated_url,
+              StoragePartitionConfig::CreateDefault(browser_context)),
           WebExposedIsolationInfo::CreateNonIsolated(),
           /* can_reuse_process= */ true);
   RenderProcessHost* sw_host = sw_site_instance->GetProcess();
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
index 70b9878..4937a7e 100644
--- a/content/browser/renderer_host/render_frame_host_manager.cc
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -47,6 +47,7 @@
 #include "content/browser/renderer_host/render_widget_host_view_base.h"
 #include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
 #include "content/browser/site_instance_impl.h"
+#include "content/browser/storage_partition_impl.h"
 #include "content/browser/webui/web_ui_controller_factory_registry.h"
 #include "content/common/content_navigation_policy.h"
 #include "content/common/navigation_params_utils.h"
@@ -135,6 +136,20 @@
   return future_site_info.RequiresDedicatedProcess(future_isolation_context);
 }
 
+// Helper function to determine whether |dest_url_info| should be loaded in the
+// same StoragePartition that |current_instance| is currently using.
+bool DoesNavigationChangeStoragePartition(SiteInstanceImpl* current_instance,
+                                          const UrlInfo& dest_url_info) {
+  // Derive a new SiteInfo from |current_instance|, but don't treat the
+  // navigation as related to avoid StoragePartition propagation logic.
+  StoragePartitionConfig dest_partition_config =
+      current_instance->DeriveSiteInfo(dest_url_info, /*is_related=*/false)
+          .storage_partition_config();
+  StoragePartitionConfig current_partition_config =
+      current_instance->GetSiteInfo().storage_partition_config();
+  return current_partition_config != dest_partition_config;
+}
+
 bool IsSiteInstanceCompatibleWithErrorIsolation(SiteInstance* site_instance,
                                                 bool is_main_frame,
                                                 bool is_failure) {
@@ -1515,7 +1530,8 @@
     bool is_speculative) {
   const GURL& destination_url = destination_url_info.url;
   // A subframe must stay in the same BrowsingInstance as its parent.
-  if (!frame_tree_node_->IsMainFrame())
+  bool is_main_frame = frame_tree_node_->IsMainFrame();
+  if (!is_main_frame)
     return ShouldSwapBrowsingInstance::kNo_NotMainFrame;
 
   if (is_same_document)
@@ -1530,8 +1546,7 @@
   // when we are changing RenderFrames. Remove this when autoreload logic is
   // updated to handle different RenderFrames correctly.
   if (is_failure && is_reload &&
-      SiteIsolationPolicy::IsErrorPageIsolationEnabled(
-          frame_tree_node_->IsMainFrame())) {
+      SiteIsolationPolicy::IsErrorPageIsolationEnabled(is_main_frame)) {
     return ShouldSwapBrowsingInstance::kNo_ReloadingErrorPage;
   }
 
@@ -1650,8 +1665,9 @@
   // relationship for it (for isolated error pages).
   // See https://crbug.com/803367.
   bool is_for_isolated_error_page =
-      is_failure && SiteIsolationPolicy::IsErrorPageIsolationEnabled(
-                        frame_tree_node_->IsMainFrame());
+      is_failure &&
+      SiteIsolationPolicy::IsErrorPageIsolationEnabled(is_main_frame);
+
   if (current_instance->HasSite() &&
       !render_frame_host_->IsNavigationSameSite(destination_url_info,
                                                 web_exposed_isolation_info) &&
@@ -1665,6 +1681,22 @@
     return ShouldSwapBrowsingInstance::kYes_ForceSwap;
   }
 
+  // If the navigation should end up in a different StoragePartition, create a
+  // new BrowsingInstance, as we can only have one StoragePartition per
+  // BrowsingInstance.
+  //
+  // Skip this check if effective URLs are involved. This ensures same-site
+  // navigations to non-app sites from an isolated hosted app stay in the same
+  // StoragePartition and process. This behavior is covered in
+  // IsolatedAppTest.IsolatedAppProcessModel.
+  if (DoesNavigationChangeStoragePartition(current_instance,
+                                           destination_url_info) &&
+      !current_instance
+           ->IsNavigationAllowedToStayInSameProcessDueToEffectiveURLs(
+               browser_context, is_main_frame, destination_url_info.url)) {
+    return ShouldSwapBrowsingInstance::kYes_ForceSwap;
+  }
+
   // Experimental mode to swap BrowsingInstances on most navigations when there
   // are no other windows in the BrowsingInstance.
   return ShouldProactivelySwapBrowsingInstance(
@@ -2086,7 +2118,7 @@
   // If the entry has an instance already we should usually use it, unless it is
   // no longer suitable.
   if (dest_instance) {
-    // Note: The later call to IsSuitableForURL does not have context about
+    // Note: The later call to IsSuitableForUrlInfo does not have context about
     // error page navigations, so we cannot rely on it to return correct value
     // when error pages are involved.
     if (IsSiteInstanceCompatibleWithErrorIsolation(
@@ -2095,10 +2127,10 @@
               dest_instance, frame_tree_node_->IsMainFrame(), dest_url_info.url,
               web_exposed_isolation_info, is_speculative)) {
         // TODO(nasko,creis): The check whether data: or about: URLs are allowed
-        // to commit in the current process should be in IsSuitableForURL.
+        // to commit in the current process should be in IsSuitableForUrlInfo.
         // However, making this change has further implications and needs more
         // investigation of what behavior changes. For now, use a conservative
-        // approach and explicitly check before calling IsSuitableForURL.
+        // approach and explicitly check before calling IsSuitableForUrlInfo.
         SiteInstanceImpl* dest_instance_impl =
             static_cast<SiteInstanceImpl*>(dest_instance);
         if (IsDataOrAbout(dest_url_info.url) ||
diff --git a/content/browser/renderer_host/render_frame_host_manager_browsertest.cc b/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
index d5ae8ef..1d0791b 100644
--- a/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
@@ -74,6 +74,7 @@
 #include "content/shell/browser/shell.h"
 #include "content/test/content_browser_test_utils_internal.h"
 #include "content/test/render_document_feature.h"
+#include "content/test/storage_partition_test_helpers.h"
 #include "content/test/test_content_browser_client.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/controllable_http_response.h"
@@ -4368,6 +4369,96 @@
   }
 }
 
+// Verifies that a renderer-initiated navigation from a site in the default
+// StoragePartition to one that ContentBrowserClient places in a non-default
+// StoragePartition will swap to a new BrowsingInstance.
+IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
+                       NavigationToDifferentPartitionSwapsBrowsingInstance) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  // Set up a ContentBrowserClient that maps b.com to a non-default partition.
+  CustomStoragePartitionForSomeSites modified_client(GURL("http://b.com/"));
+  ContentBrowserClient* old_client =
+      SetBrowserClientForTesting(&modified_client);
+
+  // Load a page on a.com and verify that it uses the default partition.
+  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), a_url));
+  RenderFrameHostImpl* rfh =
+      static_cast<WebContentsImpl*>(shell()->web_contents())->GetMainFrame();
+  SiteInstanceImpl* a_site_instance = rfh->GetSiteInstance();
+  EXPECT_TRUE(
+      a_site_instance->GetSiteInfo().storage_partition_config().is_default());
+
+  // Make sure proactive BrowserInstance swapping doesn't interfere.
+  rfh->DisableProactiveBrowsingInstanceSwapForTesting();
+
+  // Do a renderer-initiated navigation to a page on b.com and verify that we
+  // swapped BrowsingInstances.
+  GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURLFromRenderer(shell(), b_url));
+  rfh = static_cast<WebContentsImpl*>(shell()->web_contents())->GetMainFrame();
+  SiteInstanceImpl* b_site_instance = rfh->GetSiteInstance();
+  EXPECT_FALSE(
+      b_site_instance->GetSiteInfo().storage_partition_config().is_default());
+  EXPECT_FALSE(a_site_instance->IsRelatedSiteInstance(b_site_instance));
+
+  // Restore the original ContentBrowserClient.
+  SetBrowserClientForTesting(old_client);
+}
+
+// Verifies that iframes inherit their StoragePartition, even if
+// ContentBrowserClient would normally place the iframe's URL in a dedicated
+// StoragePartition.
+IN_PROC_BROWSER_TEST_P(RenderFrameHostManagerTest,
+                       SubframeInheritsStoragePartition) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  // Set up a ContentBrowserClient that maps b.com to a non-default partition.
+  CustomStoragePartitionForSomeSites modified_client(GURL("http://b.com/"));
+  ContentBrowserClient* old_client =
+      SetBrowserClientForTesting(&modified_client);
+
+  // Load a page on b.com to verify that it uses the correct partition.
+  GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), b_url));
+  RenderFrameHostImpl* rfh =
+      static_cast<WebContentsImpl*>(shell()->web_contents())->GetMainFrame();
+  EXPECT_FALSE(rfh->GetSiteInstance()
+                   ->GetSiteInfo()
+                   .storage_partition_config()
+                   .is_default());
+
+  // Load a page on a.com that iframes a b.com page.
+  GURL a_url(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(b)"));
+  EXPECT_TRUE(NavigateToURL(shell(), a_url));
+  rfh = static_cast<WebContentsImpl*>(shell()->web_contents())->GetMainFrame();
+  SiteInstanceImpl* a_site_instance = rfh->GetSiteInstance();
+  if (AreDefaultSiteInstancesEnabled()) {
+    EXPECT_TRUE(a_site_instance->IsDefaultSiteInstance());
+  } else {
+    EXPECT_EQ("http://a.com/", a_site_instance->GetSiteURL());
+  }
+  EXPECT_TRUE(
+      a_site_instance->GetSiteInfo().storage_partition_config().is_default());
+
+  // Verify that the iframe uses the default StoragePartition.
+  EXPECT_EQ(2UL, rfh->GetFramesInSubtree().size());
+  SiteInstanceImpl* b_site_instance = static_cast<SiteInstanceImpl*>(
+      rfh->GetFramesInSubtree()[1]->GetSiteInstance());
+  if (AreDefaultSiteInstancesEnabled()) {
+    EXPECT_TRUE(b_site_instance->IsDefaultSiteInstance());
+  } else {
+    EXPECT_EQ("http://b.com/", b_site_instance->GetSiteURL());
+  }
+  EXPECT_TRUE(
+      b_site_instance->GetSiteInfo().storage_partition_config().is_default());
+
+  // Restore the original ContentBrowserClient.
+  SetBrowserClientForTesting(old_client);
+}
+
 // Ensure that these two browser-initiated navigations:
 //   foo.com -> about:blank -> foo.com
 // stay in the same SiteInstance.  This isn't technically required for
diff --git a/content/browser/renderer_host/render_process_host_browsertest.cc b/content/browser/renderer_host/render_process_host_browsertest.cc
index d6438e1f..18afbe89 100644
--- a/content/browser/renderer_host/render_process_host_browsertest.cc
+++ b/content/browser/renderer_host/render_process_host_browsertest.cc
@@ -37,6 +37,7 @@
 #include "content/shell/browser/shell_browser_context.h"
 #include "content/shell/browser/shell_browser_main_parts.h"
 #include "content/shell/browser/shell_content_browser_client.h"
+#include "content/test/storage_partition_test_helpers.h"
 #include "content/test/test_content_browser_client.h"
 #include "media/base/bind_to_current_loop.h"
 #include "media/base/media_switches.h"
@@ -486,44 +487,6 @@
   // tear down.
 }
 
-// Class that simulates the fact that some //content embedders (e.g. by
-// overriding ChromeContentBrowserClient::GetStoragePartitionConfigForSite) can
-// cause `browser_context->GetDefaultStoragePartition()` to differ from
-// `browser_context->GetStoragePartition(site_instance)` even if `site_instance`
-// is not for guests.
-class CustomStoragePartitionForSomeSites : public TestContentBrowserClient {
- public:
-  explicit CustomStoragePartitionForSomeSites(const GURL& site_to_isolate)
-      : site_to_isolate_(site_to_isolate) {}
-
-  StoragePartitionConfig GetStoragePartitionConfigForSite(
-      BrowserContext* browser_context,
-      const GURL& site) override {
-    // Override for |site_to_isolate_|.
-    if (site == site_to_isolate_) {
-      return StoragePartitionConfig::Create(
-          browser_context, "blah_isolated_storage", "blah_isolated_storage",
-          false /* in_memory */);
-    }
-
-    return StoragePartitionConfig::CreateDefault(browser_context);
-  }
-
-  StoragePartitionId GetStoragePartitionIdForSite(
-      BrowserContext* browser_context,
-      const GURL& site) override {
-    if (site == site_to_isolate_)
-      return StoragePartitionId(
-          site.spec(), GetStoragePartitionConfigForSite(browser_context, site));
-    return StoragePartitionId(browser_context);
-  }
-
- private:
-  GURL site_to_isolate_;
-
-  DISALLOW_COPY_AND_ASSIGN(CustomStoragePartitionForSomeSites);
-};
-
 // This test verifies that SpareRenderProcessHostManager correctly accounts
 // for StoragePartition differences when handing out the spare process.
 IN_PROC_BROWSER_TEST_F(RenderProcessHostTest,
@@ -539,7 +502,7 @@
   BrowserContext* browser_context =
       ShellContentBrowserClient::Get()->browser_context();
   scoped_refptr<SiteInstance> test_site_instance =
-      SiteInstance::Create(browser_context)->GetRelatedSiteInstance(test_url);
+      SiteInstance::CreateForURL(browser_context, test_url);
   StoragePartition* default_storage =
       browser_context->GetDefaultStoragePartition();
   StoragePartition* custom_storage =
diff --git a/content/browser/renderer_host/render_process_host_unittest.cc b/content/browser/renderer_host/render_process_host_unittest.cc
index 03c627e7..89e8e6d 100644
--- a/content/browser/renderer_host/render_process_host_unittest.cc
+++ b/content/browser/renderer_host/render_process_host_unittest.cc
@@ -24,6 +24,7 @@
 #include "content/public/test/navigation_simulator.h"
 #include "content/public/test/test_browser_context.h"
 #include "content/public/test/test_utils.h"
+#include "content/test/storage_partition_test_helpers.h"
 #include "content/test/test_render_frame_host.h"
 #include "content/test/test_render_view_host.h"
 #include "content/test/test_web_contents.h"
@@ -40,6 +41,16 @@
   scoped_refptr<SiteInstanceImpl> CreateForUrl(const GURL& url) {
     return SiteInstanceImpl::CreateForTesting(browser_context(), url);
   }
+
+  scoped_refptr<SiteInstanceImpl> CreateForServiceWorker(
+      const GURL& url,
+      bool can_reuse_process = false) {
+    return SiteInstanceImpl::CreateForServiceWorker(
+        browser_context(),
+        UrlInfo::CreateForTesting(url,
+                                  CreateStoragePartitionConfigForTesting()),
+        WebExposedIsolationInfo::CreateNonIsolated(), can_reuse_process);
+  }
 };
 
 // Tests that guest RenderProcessHosts are not considered suitable hosts when
@@ -205,9 +216,7 @@
 
   // Gets a RenderProcessHost for an unmatched service worker.
   scoped_refptr<SiteInstanceImpl> sw_site_instance1 =
-      SiteInstanceImpl::CreateForServiceWorker(
-          browser_context(), kUrl,
-          WebExposedIsolationInfo::CreateNonIsolated());
+      CreateForServiceWorker(kUrl);
   RenderProcessHost* sw_host1 = sw_site_instance1->GetProcess();
   EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS,
             sw_site_instance1->GetLastProcessAssignmentOutcome());
@@ -216,9 +225,7 @@
   // should not reuse the existing service worker's process. We create this
   // second service worker to test the "find the newest process" logic later.
   scoped_refptr<SiteInstanceImpl> sw_site_instance2 =
-      SiteInstanceImpl::CreateForServiceWorker(
-          browser_context(), kUrl,
-          WebExposedIsolationInfo::CreateNonIsolated());
+      CreateForServiceWorker(kUrl);
   RenderProcessHost* sw_host2 = sw_site_instance2->GetProcess();
   EXPECT_NE(sw_host1, sw_host2);
   EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS,
@@ -269,9 +276,7 @@
 
   // Gets a RenderProcessHost for an unmatched service worker.
   scoped_refptr<SiteInstanceImpl> sw_site_instance =
-      SiteInstanceImpl::CreateForServiceWorker(
-          browser_context(), kUrl,
-          WebExposedIsolationInfo::CreateNonIsolated());
+      CreateForServiceWorker(kUrl);
   RenderProcessHost* sw_host = sw_site_instance->GetProcess();
   EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS,
             sw_site_instance->GetLastProcessAssignmentOutcome());
@@ -305,9 +310,8 @@
 
   // Gets a RenderProcessHost for a service worker.
   scoped_refptr<SiteInstanceImpl> sw_site_instance1 =
-      SiteInstanceImpl::CreateForServiceWorker(
-          browser_context(), kUrl, WebExposedIsolationInfo::CreateNonIsolated(),
-          /* can_reuse_process */ true);
+      CreateForServiceWorker(kUrl,
+                             /*can_reuse_process=*/true);
   RenderProcessHost* sw_host1 = sw_site_instance1->GetProcess();
   EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS,
             sw_site_instance1->GetLastProcessAssignmentOutcome());
@@ -318,9 +322,7 @@
   // start the service worker and want to use a new process. We create this
   // second service worker to test the "find the newest process" logic later.
   scoped_refptr<SiteInstanceImpl> sw_site_instance2 =
-      SiteInstanceImpl::CreateForServiceWorker(
-          browser_context(), kUrl,
-          WebExposedIsolationInfo::CreateNonIsolated());
+      CreateForServiceWorker(kUrl);
   RenderProcessHost* sw_host2 = sw_site_instance2->GetProcess();
   EXPECT_NE(sw_host1, sw_host2);
   EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS,
@@ -330,9 +332,8 @@
   // REUSE_PENDING_OR_COMMITTED_SITE reuse policy should reuse the newest
   // unmatched service worker's process (i.e., sw_host2).
   scoped_refptr<SiteInstanceImpl> sw_site_instance3 =
-      SiteInstanceImpl::CreateForServiceWorker(
-          browser_context(), kUrl, WebExposedIsolationInfo::CreateNonIsolated(),
-          /* can_reuse_process */ true);
+      CreateForServiceWorker(kUrl,
+                             /*can_reuse_process=*/true);
   RenderProcessHost* sw_host3 = sw_site_instance3->GetProcess();
   EXPECT_EQ(sw_host2, sw_host3);
   EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS,
@@ -344,9 +345,8 @@
   // sw_host2 to be considered matched, so we can keep putting more service
   // workers in that process.
   scoped_refptr<SiteInstanceImpl> sw_site_instance4 =
-      SiteInstanceImpl::CreateForServiceWorker(
-          browser_context(), kUrl, WebExposedIsolationInfo::CreateNonIsolated(),
-          /* can_reuse_process */ true);
+      CreateForServiceWorker(kUrl,
+                             /*can_reuse_process=*/true);
   RenderProcessHost* sw_host4 = sw_site_instance4->GetProcess();
   EXPECT_EQ(sw_host2, sw_host4);
   EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS,
@@ -377,9 +377,7 @@
 
   // Gets a RenderProcessHost for a service worker with process-per-site flag.
   scoped_refptr<SiteInstanceImpl> sw_site_instance1 =
-      SiteInstanceImpl::CreateForServiceWorker(
-          browser_context(), kUrl,
-          WebExposedIsolationInfo::CreateNonIsolated());
+      CreateForServiceWorker(kUrl);
   RenderProcessHost* sw_host1 = sw_site_instance1->GetProcess();
   EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS,
             sw_site_instance1->GetLastProcessAssignmentOutcome());
@@ -387,9 +385,7 @@
   // Getting a RenderProcessHost for a service worker of the same site with
   // process-per-site flag should reuse the unmatched service worker's process.
   scoped_refptr<SiteInstanceImpl> sw_site_instance2 =
-      SiteInstanceImpl::CreateForServiceWorker(
-          browser_context(), kUrl,
-          WebExposedIsolationInfo::CreateNonIsolated());
+      CreateForServiceWorker(kUrl);
   RenderProcessHost* sw_host2 = sw_site_instance2->GetProcess();
   EXPECT_EQ(sw_host1, sw_host2);
   EXPECT_EQ(SiteInstanceProcessAssignment::REUSED_EXISTING_PROCESS,
@@ -418,9 +414,7 @@
 
   // Gets a RenderProcessHost for a service worker.
   scoped_refptr<SiteInstanceImpl> sw_site_instance1 =
-      SiteInstanceImpl::CreateForServiceWorker(
-          browser_context(), kUrl1,
-          WebExposedIsolationInfo::CreateNonIsolated());
+      CreateForServiceWorker(kUrl1);
   RenderProcessHost* sw_host1 = sw_site_instance1->GetProcess();
   EXPECT_EQ(SiteInstanceProcessAssignment::CREATED_NEW_PROCESS,
             sw_site_instance1->GetLastProcessAssignmentOutcome());
@@ -891,9 +885,7 @@
 
   // Create a RenderProcessHost for a service worker.
   scoped_refptr<SiteInstanceImpl> sw_site_instance =
-      SiteInstanceImpl::CreateForServiceWorker(
-          browser_context(), kUrl,
-          WebExposedIsolationInfo::CreateNonIsolated());
+      CreateForServiceWorker(kUrl);
   RenderProcessHost* sw_process = sw_site_instance->GetProcess();
 
   // Change foo.com SiteInstances to use a different StoragePartition.
diff --git a/content/browser/service_worker/service_worker_process_manager.cc b/content/browser/service_worker/service_worker_process_manager.cc
index fd4bf22..af46643 100644
--- a/content/browser/service_worker/service_worker_process_manager.cc
+++ b/content/browser/service_worker/service_worker_process_manager.cc
@@ -117,6 +117,7 @@
   // Create a SiteInstance to get the renderer process from. Use the site URL
   // from the StoragePartition in case this StoragePartition is for guests
   // (e.g., <webview>).
+  DCHECK(storage_partition_);
   const bool is_guest =
       storage_partition_ &&
       !storage_partition_->site_for_guest_service_worker_or_shared_worker()
@@ -129,9 +130,12 @@
       !is_guest && cross_origin_embedder_policy.has_value() &&
       network::CompatibleWithCrossOriginIsolated(
           cross_origin_embedder_policy->value);
+  const UrlInfo url_info(service_worker_url,
+                         UrlInfo::OriginIsolationRequest::kNone,
+                         storage_partition_->GetConfig());
   scoped_refptr<SiteInstanceImpl> site_instance =
       SiteInstanceImpl::CreateForServiceWorker(
-          browser_context_, service_worker_url,
+          browser_context_, url_info,
           is_coop_coep_cross_origin_isolated
               ? WebExposedIsolationInfo::CreateIsolated(
                     url::Origin::Create(service_worker_url))
diff --git a/content/browser/service_worker/service_worker_process_manager_unittest.cc b/content/browser/service_worker/service_worker_process_manager_unittest.cc
index 4bd09fb..df3808ed 100644
--- a/content/browser/service_worker/service_worker_process_manager_unittest.cc
+++ b/content/browser/service_worker/service_worker_process_manager_unittest.cc
@@ -66,6 +66,8 @@
     browser_context_ = std::make_unique<TestBrowserContext>();
     process_manager_ =
         std::make_unique<ServiceWorkerProcessManager>(browser_context_.get());
+    process_manager_->set_storage_partition(static_cast<StoragePartitionImpl*>(
+        browser_context_->GetDefaultStoragePartition()));
     script_url_ = GURL("http://www.example.com/sw.js");
     render_process_host_factory_ =
         std::make_unique<SiteInstanceRenderProcessHostFactory>();
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc
index 79974d5..b4b14f6b 100644
--- a/content/browser/site_instance_impl.cc
+++ b/content/browser/site_instance_impl.cc
@@ -92,6 +92,21 @@
   return GURL(kUnreachableWebDataURL);
 }
 
+// Asks the embedder whether effective URLs should be used when determining if
+// |dest_url| should end up in |site_instance|.
+// This is used to keep same-site scripting working for hosted apps.
+bool ShouldCompareEffectiveURLs(BrowserContext* browser_context,
+                                SiteInstanceImpl* site_instance,
+                                bool for_main_frame,
+                                const GURL& dest_url) {
+  return site_instance->IsDefaultSiteInstance() ||
+         GetContentClient()
+             ->browser()
+             ->ShouldCompareEffectiveURLsForSiteInstanceSelection(
+                 browser_context, site_instance, for_main_frame,
+                 site_instance->original_url(), dest_url);
+}
+
 SiteInstanceId::Generator g_site_instance_id_generator;
 
 }  // namespace
@@ -125,6 +140,13 @@
       storage_partition_config(storage_partition_config_in) {}
 UrlInfo::~UrlInfo() = default;
 
+UrlInfo UrlInfo::CreateCopyWithStoragePartitionConfig(
+    absl::optional<StoragePartitionConfig> storage_partition_config_in) const {
+  UrlInfo copy = *this;
+  copy.storage_partition_config = storage_partition_config_in;
+  return copy;
+}
+
 // static
 const GURL& SiteInstanceImpl::GetDefaultSiteURL() {
   struct DefaultSiteURL {
@@ -772,15 +794,16 @@
 // static
 scoped_refptr<SiteInstanceImpl> SiteInstanceImpl::CreateForServiceWorker(
     BrowserContext* browser_context,
-    const GURL& url,
+    const UrlInfo& url_info,
     const WebExposedIsolationInfo& web_exposed_isolation_info,
     bool can_reuse_process,
     bool is_guest) {
-  DCHECK(!url.SchemeIs(kChromeErrorScheme));
+  DCHECK(!url_info.url.SchemeIs(kChromeErrorScheme));
+  DCHECK(url_info.storage_partition_config.has_value());
   scoped_refptr<SiteInstanceImpl> site_instance;
 
   if (is_guest) {
-    site_instance = CreateForGuest(browser_context, url);
+    site_instance = CreateForGuest(browser_context, url_info.url);
   } else {
     // This will create a new SiteInstance and BrowsingInstance.
     scoped_refptr<BrowsingInstance> instance(
@@ -789,8 +812,7 @@
     // We do NOT want to allow the default site instance here because workers
     // need to be kept separate from other sites.
     site_instance = instance->GetSiteInstanceForURL(
-        UrlInfo(url, UrlInfo::OriginIsolationRequest::kNone),
-        /* allow_default_instance */ false);
+        url_info, /* allow_default_instance */ false);
   }
   DCHECK(!site_instance->GetSiteInfo().is_error_page());
   site_instance->is_for_service_worker_ = true;
@@ -1483,31 +1505,20 @@
   const GURL& dest_url = dest_url_info.url;
   BrowserContext* browser_context = GetBrowserContext();
 
-  // Ask embedder whether effective URLs should be used when determining if
-  // |dest_url| should end up in this SiteInstance.
-  // This is used to keep same-site scripting working for hosted apps.
-  bool should_compare_effective_urls =
-      IsDefaultSiteInstance() ||
-      GetContentClient()
-          ->browser()
-          ->ShouldCompareEffectiveURLsForSiteInstanceSelection(
-              browser_context, this, for_main_frame, original_url(), dest_url);
+  bool should_compare_effective_urls = ShouldCompareEffectiveURLs(
+      browser_context, this, for_main_frame, dest_url);
 
-  bool src_has_effective_url = !IsDefaultSiteInstance() &&
-                               HasEffectiveURL(browser_context, original_url());
-  bool dest_has_effective_url = HasEffectiveURL(browser_context, dest_url);
-
-  // If IsSuitableForURL finds a process type mismatch, return false
+  // If IsSuitableForUrlInfo finds a process type mismatch, return false
   // even if |dest_url| is same-site.  (The URL may have been installed as an
   // app since the last time we visited it.)
   //
-  // This check must be skipped to keep same-site subframe navigations from a
-  // hosted app to non-hosted app, and vice versa, in the same process.
-  // Otherwise, this would return false due to a process privilege level
-  // mismatch.
+  // This check must be skipped for certain same-site navigations from a hosted
+  // app to non-hosted app, and vice versa, to keep them in the same process
+  // due to scripting requirements. Otherwise, this would return false due to
+  // a process privilege level mismatch.
   bool should_check_for_wrong_process =
-      should_compare_effective_urls ||
-      (!src_has_effective_url && !dest_has_effective_url);
+      !IsNavigationAllowedToStayInSameProcessDueToEffectiveURLs(
+          browser_context, for_main_frame, dest_url);
   if (should_check_for_wrong_process && !IsSuitableForUrlInfo(dest_url_info))
     return false;
 
@@ -1560,6 +1571,22 @@
   return false;
 }
 
+bool SiteInstanceImpl::IsNavigationAllowedToStayInSameProcessDueToEffectiveURLs(
+    BrowserContext* browser_context,
+    bool for_main_frame,
+    const GURL& dest_url) {
+  if (ShouldCompareEffectiveURLs(browser_context, this, for_main_frame,
+                                 dest_url)) {
+    return false;
+  }
+
+  bool src_has_effective_url = !IsDefaultSiteInstance() &&
+                               HasEffectiveURL(browser_context, original_url());
+  if (src_has_effective_url)
+    return true;
+  return HasEffectiveURL(browser_context, dest_url);
+}
+
 // static
 bool SiteInstanceImpl::IsSameSite(const IsolationContext& isolation_context,
                                   const UrlInfo& real_src_url_info,
diff --git a/content/browser/site_instance_impl.h b/content/browser/site_instance_impl.h
index 578cdf8..2180214 100644
--- a/content/browser/site_instance_impl.h
+++ b/content/browser/site_instance_impl.h
@@ -106,6 +106,11 @@
     return (origin_isolation_request & OriginIsolationRequest::kCOOP);
   }
 
+  // Creates a copy of this UrlInfo that has its |storage_partition_config|
+  // field set to |storage_partition_config_in|.
+  UrlInfo CreateCopyWithStoragePartitionConfig(
+      absl::optional<StoragePartitionConfig> storage_partition_config_in) const;
+
   GURL url;
 
   // This field indicates whether the URL is requesting additional process
@@ -505,7 +510,7 @@
   // guest.
   static scoped_refptr<SiteInstanceImpl> CreateForServiceWorker(
       BrowserContext* browser_context,
-      const GURL& url,
+      const UrlInfo& url_info,
       const WebExposedIsolationInfo& web_exposed_isolation_info,
       bool can_reuse_process = false,
       bool is_guest = false);
@@ -633,6 +638,18 @@
                             bool for_main_frame,
                             const UrlInfo& dest_url_info);
 
+  // Returns true if a navigation to |dest_url| should be allowed to stay in
+  // the current process due to effective URLs being involved in the
+  // navigation, even if the navigation would normally result in a new process.
+  //
+  // This is needed to avoid BrowsingInstance swaps in cases where same-site
+  // navigations transition from a hosted app to a non-hosted app URL and must
+  // be kept in the same process due to scripting requirements.
+  bool IsNavigationAllowedToStayInSameProcessDueToEffectiveURLs(
+      BrowserContext* browser_context,
+      bool for_main_frame,
+      const GURL& dest_url);
+
   // SiteInfo related functions.
 
   // Returns the SiteInfo principal identifying all documents and workers within
diff --git a/content/browser/site_instance_impl_unittest.cc b/content/browser/site_instance_impl_unittest.cc
index b59986e..32e4d9ac 100644
--- a/content/browser/site_instance_impl_unittest.cc
+++ b/content/browser/site_instance_impl_unittest.cc
@@ -1964,4 +1964,35 @@
                 ->GetSiteInfo());
 }
 
+TEST_F(SiteInstanceTest, RelatedSitesInheritStoragePartitionConfig) {
+  const GURL test_url("https://example.com");
+  const auto isolation_info = WebExposedIsolationInfo::CreateNonIsolated();
+
+  // Create a UrlInfo for test_url loaded in a special StoragePartition.
+  const auto non_default_partition_config =
+      CreateStoragePartitionConfigForTesting(
+          /*in_memory=*/false, /*partition_domain=*/"test_partition");
+  const UrlInfo partitioned_url_info(test_url,
+                                     UrlInfo::OriginIsolationRequest::kNone,
+                                     non_default_partition_config);
+
+  // Create a SiteInstance for test_url in the special StoragePartition, and
+  // verify that the StoragePartition is correct.
+  const auto partitioned_instance = SiteInstanceImpl::CreateForUrlInfo(
+      context(), partitioned_url_info, isolation_info);
+  EXPECT_EQ(non_default_partition_config,
+            static_cast<SiteInstanceImpl*>(partitioned_instance.get())
+                ->GetSiteInfo()
+                .storage_partition_config());
+
+  // Create a related SiteInstance that doesn't specify a
+  // StoragePartitionConfig and make sure the StoragePartition gets propagated.
+  const auto related_instance =
+      partitioned_instance->GetRelatedSiteInstance(test_url);
+  EXPECT_EQ(non_default_partition_config,
+            static_cast<SiteInstanceImpl*>(related_instance.get())
+                ->GetSiteInfo()
+                .storage_partition_config());
+}
+
 }  // namespace content
diff --git a/content/browser/speech/tts_win.cc b/content/browser/speech/tts_win.cc
index 03056ea9..e5f60db 100644
--- a/content/browser/speech/tts_win.cc
+++ b/content/browser/speech/tts_win.cc
@@ -9,9 +9,8 @@
 #include <wrl/client.h>
 #include <wrl/implements.h>
 
-#include <algorithm>
-
 #include "base/bind.h"
+#include "base/cxx17_backports.h"
 #include "base/macros.h"
 #include "base/no_destructor.h"
 #include "base/sequenced_task_runner.h"
@@ -276,7 +275,7 @@
     // value to an int before calling NumberToWString. TODO(dtseng): cleanup if
     // we ever use any other properties that require xml.
     double adjusted_pitch =
-        std::max<double>(-10, std::min<double>(params.pitch * 10 - 10, 10));
+        base::clamp<double>(params.pitch * 10 - 10, -10, 10);
     std::wstring adjusted_pitch_string =
         base::NumberToWString(static_cast<int>(adjusted_pitch));
     prefix = L"<pitch absmiddle=\"" + adjusted_pitch_string + L"\">";
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 60b3ee2..bd09f16 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -32,6 +32,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
+#include "components/services/storage/public/cpp/buckets/bucket_info.h"
 #include "components/services/storage/public/cpp/constants.h"
 #include "components/services/storage/public/cpp/filesystem/filesystem_impl.h"
 #include "components/services/storage/public/mojom/filesystem/directory.mojom.h"
@@ -259,17 +260,17 @@
   }
 }
 
-void OnQuotaManagedStorageKeyDeleted(const blink::StorageKey& storage_key,
-                                     blink::mojom::StorageType type,
-                                     size_t* deletion_task_count,
-                                     base::OnceClosure callback,
-                                     blink::mojom::QuotaStatusCode status) {
+void OnQuotaManagedBucketDeleted(const storage::BucketInfo& bucket,
+                                 size_t* deletion_task_count,
+                                 base::OnceClosure callback,
+                                 blink::mojom::QuotaStatusCode status) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   DCHECK_GT(*deletion_task_count, 0u);
   if (status != blink::mojom::QuotaStatusCode::kOk) {
-    DLOG(ERROR) << "Couldn't remove data of type " << static_cast<int>(type)
-                << " for storage key " << storage_key.GetDebugString()
-                << ". Status: " << static_cast<int>(status);
+    DLOG(ERROR) << "Couldn't remove data type " << static_cast<int>(bucket.type)
+                << " for bucket " << bucket.name << " with storage key "
+                << bucket.storage_key.GetDebugString() << " and bucket id "
+                << bucket.id << ". Status: " << static_cast<int>(status);
   }
 
   (*deletion_task_count)--;
@@ -912,14 +913,14 @@
       StoragePartition::OriginMatcherFunction origin_matcher,
       bool perform_storage_cleanup);
 
-  void ClearStorageKeysOnIOThread(
+  void ClearBucketsOnIOThread(
       storage::QuotaManager* quota_manager,
       const scoped_refptr<storage::SpecialStoragePolicy>&
           special_storage_policy,
       StoragePartition::OriginMatcherFunction origin_matcher,
       bool perform_storage_cleanup,
       base::OnceClosure callback,
-      const std::set<blink::StorageKey>& storage_keys,
+      const std::set<storage::BucketInfo>& buckets,
       blink::mojom::StorageType quota_storage_type);
 
  private:
@@ -2205,60 +2206,57 @@
 
   if (quota_storage_remove_mask_ & QUOTA_MANAGED_STORAGE_MASK_PERSISTENT) {
     IncrementTaskCountOnIO();
-    // Ask the QuotaManager for all storage keys with persistent quota modified
+    // Ask the QuotaManager for all buckets with persistent quota modified
     // within the user-specified timeframe, and deal with the resulting set in
-    // ClearQuotaManagedOriginsOnIOThread().
-    quota_manager->GetStorageKeysModifiedBetween(
+    // ClearBucketsOnIOThread().
+    quota_manager->GetBucketsModifiedBetween(
         blink::mojom::StorageType::kPersistent, begin, end,
-        base::BindOnce(
-            &QuotaManagedDataDeletionHelper::ClearStorageKeysOnIOThread,
-            base::Unretained(this), base::RetainedRef(quota_manager),
-            special_storage_policy, origin_matcher, perform_storage_cleanup,
-            decrement_callback));
+        base::BindOnce(&QuotaManagedDataDeletionHelper::ClearBucketsOnIOThread,
+                       base::Unretained(this), base::RetainedRef(quota_manager),
+                       special_storage_policy, origin_matcher,
+                       perform_storage_cleanup, decrement_callback));
   }
 
   // Do the same for temporary quota.
   if (quota_storage_remove_mask_ & QUOTA_MANAGED_STORAGE_MASK_TEMPORARY) {
     IncrementTaskCountOnIO();
-    quota_manager->GetStorageKeysModifiedBetween(
+    quota_manager->GetBucketsModifiedBetween(
         blink::mojom::StorageType::kTemporary, begin, end,
-        base::BindOnce(
-            &QuotaManagedDataDeletionHelper::ClearStorageKeysOnIOThread,
-            base::Unretained(this), base::RetainedRef(quota_manager),
-            special_storage_policy, origin_matcher, perform_storage_cleanup,
-            decrement_callback));
+        base::BindOnce(&QuotaManagedDataDeletionHelper::ClearBucketsOnIOThread,
+                       base::Unretained(this), base::RetainedRef(quota_manager),
+                       special_storage_policy, origin_matcher,
+                       perform_storage_cleanup, decrement_callback));
   }
 
   // Do the same for syncable quota.
   if (quota_storage_remove_mask_ & QUOTA_MANAGED_STORAGE_MASK_SYNCABLE) {
     IncrementTaskCountOnIO();
-    quota_manager->GetStorageKeysModifiedBetween(
+    quota_manager->GetBucketsModifiedBetween(
         blink::mojom::StorageType::kSyncable, begin, end,
-        base::BindOnce(
-            &QuotaManagedDataDeletionHelper::ClearStorageKeysOnIOThread,
-            base::Unretained(this), base::RetainedRef(quota_manager),
-            special_storage_policy, std::move(origin_matcher),
-            perform_storage_cleanup, decrement_callback));
+        base::BindOnce(&QuotaManagedDataDeletionHelper::ClearBucketsOnIOThread,
+                       base::Unretained(this), base::RetainedRef(quota_manager),
+                       special_storage_policy, std::move(origin_matcher),
+                       perform_storage_cleanup, decrement_callback));
   }
 
   DecrementTaskCountOnIO();
 }
 
 void StoragePartitionImpl::QuotaManagedDataDeletionHelper::
-    ClearStorageKeysOnIOThread(
+    ClearBucketsOnIOThread(
         storage::QuotaManager* quota_manager,
         const scoped_refptr<storage::SpecialStoragePolicy>&
             special_storage_policy,
         StoragePartition::OriginMatcherFunction origin_matcher,
         bool perform_storage_cleanup,
         base::OnceClosure callback,
-        const std::set<blink::StorageKey>& storage_keys,
+        const std::set<storage::BucketInfo>& buckets,
         blink::mojom::StorageType quota_storage_type) {
   // The QuotaManager manages all storage other than cookies, LocalStorage,
   // and SessionStorage. This loop wipes out most HTML5 storage for the given
   // storage keys.
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (storage_keys.empty()) {
+  if (buckets.empty()) {
     std::move(callback).Run();
     return;
   }
@@ -2278,12 +2276,13 @@
 
   size_t* deletion_task_count = new size_t(0u);
   (*deletion_task_count)++;
-  for (const auto& storage_key : storage_keys) {
+  for (const auto& bucket : buckets) {
     // TODO(mkwst): Clean this up, it's slow. http://crbug.com/130746
-    if (storage_origin_.has_value() && storage_key.origin() != *storage_origin_)
+    if (storage_origin_.has_value() &&
+        bucket.storage_key.origin() != *storage_origin_)
       continue;
 
-    if (origin_matcher && !origin_matcher.Run(storage_key.origin(),
+    if (origin_matcher && !origin_matcher.Run(bucket.storage_key.origin(),
                                               special_storage_policy.get())) {
       continue;
     }
@@ -2292,11 +2291,10 @@
     done_callback = std::move(split_callback.first);
 
     (*deletion_task_count)++;
-    quota_manager->DeleteStorageKeyData(
-        storage_key, quota_storage_type, quota_client_types,
-        base::BindOnce(&OnQuotaManagedStorageKeyDeleted, storage_key,
-                       quota_storage_type, deletion_task_count,
-                       std::move(split_callback.second)));
+    quota_manager->DeleteBucketData(
+        bucket, quota_client_types,
+        base::BindOnce(&OnQuotaManagedBucketDeleted, bucket,
+                       deletion_task_count, std::move(split_callback.second)));
   }
   (*deletion_task_count)--;
 
diff --git a/content/browser/storage_partition_impl_map.cc b/content/browser/storage_partition_impl_map.cc
index 7ff330d..d1803e8 100644
--- a/content/browser/storage_partition_impl_map.cc
+++ b/content/browser/storage_partition_impl_map.cc
@@ -466,20 +466,18 @@
 
   // Check first to avoid memory leak in unittests.
   if (BrowserThread::IsThreadInitialized(BrowserThread::IO)) {
-    // Use PostTask() instead of RunOrPostTaskOnThread() because not posting a
-    // task causes it to run before the CacheStorageManager has been
-    // initialized, and then CacheStorageContextImpl::CacheManager() ends up
-    // returning null instead of using the CrossSequenceCacheStorageManager in
-    // unit tests that don't use a real IO thread, violating the DCHECK in
+    // Use PostTask() because not posting a task causes it to run before the
+    // CacheStorageManager has been initialized, and then
+    // CacheStorageContextImpl::CacheManager() ends up returning null instead of
+    // using the CrossSequenceCacheStorageManager in unit tests that don't use a
+    // real IO thread, violating the DCHECK in
     // BackgroundFetchDataManager::InitializeOnCoreThread().
     // TODO(crbug.com/960012): This workaround should be unnecessary after
     // CacheStorage moves off the IO thread to the thread pool.
-    BrowserThread::GetTaskRunnerForThread(
-        ServiceWorkerContext::GetCoreThreadId())
-        ->PostTask(
-            FROM_HERE,
-            base::BindOnce(&BackgroundFetchContext::InitializeOnCoreThread,
-                           partition->GetBackgroundFetchContext()));
+    BrowserThread::GetTaskRunnerForThread(BrowserThread::UI)
+        ->PostTask(FROM_HERE,
+                   base::BindOnce(&BackgroundFetchContext::Initialize,
+                                  partition->GetBackgroundFetchContext()));
 
     // We do not call InitializeURLRequestContext() for media contexts because,
     // other than the HTTP cache, the media contexts share the same backing
diff --git a/content/browser/storage_partition_impl_unittest.cc b/content/browser/storage_partition_impl_unittest.cc
index 5c34baa5..1122c23 100644
--- a/content/browser/storage_partition_impl_unittest.cc
+++ b/content/browser/storage_partition_impl_unittest.cc
@@ -987,57 +987,17 @@
                                     storage::QuotaClientType::kNativeIO));
 }
 
-void PopulateTestQuotaManagedPersistentData(
+storage::BucketInfo AddQuotaManagedBucket(
     storage::MockQuotaManager* manager,
-    const blink::StorageKey& storage_key_1,
-    const blink::StorageKey& storage_key_2) {
-  manager->AddStorageKey(storage_key_1, kPersistent, {kClientFile},
-                         base::Time());
-  manager->AddStorageKey(storage_key_2, kPersistent, {kClientFile},
-                         base::Time::Now() - base::TimeDelta::FromDays(1));
-
-  EXPECT_TRUE(
-      manager->StorageKeyHasData(storage_key_1, kPersistent, kClientFile));
-  EXPECT_TRUE(
-      manager->StorageKeyHasData(storage_key_2, kPersistent, kClientFile));
-}
-
-void PopulateTestQuotaManagedTemporaryData(
-    storage::MockQuotaManager* manager,
-    const blink::StorageKey& storage_key_1,
-    const blink::StorageKey& storage_key_2) {
-  manager->AddStorageKey(storage_key_1, kTemporary, {kClientFile},
-                         base::Time::Now());
-  manager->AddStorageKey(storage_key_2, kTemporary, {kClientFile},
-                         base::Time::Now() - base::TimeDelta::FromDays(1));
-
-  EXPECT_TRUE(
-      manager->StorageKeyHasData(storage_key_1, kTemporary, kClientFile));
-  EXPECT_TRUE(
-      manager->StorageKeyHasData(storage_key_2, kTemporary, kClientFile));
-}
-
-void PopulateTestQuotaManagedData(storage::MockQuotaManager* manager,
-                                  const blink::StorageKey& storage_key_1,
-                                  const blink::StorageKey& storage_key_2,
-                                  const blink::StorageKey& storage_key_3) {
-  // Set up storage_key_1 with a temporary quota, storage_key_2 with a
-  // persistent quota, and storage_key_3 with both. storage_key_1 is modified
-  // now, storage_key_2 is modified at the beginning of time, and storage_key_3
-  // is modified one day ago.
-  PopulateTestQuotaManagedTemporaryData(manager, storage_key_1, storage_key_3);
-  PopulateTestQuotaManagedPersistentData(manager, storage_key_2, storage_key_3);
-  EXPECT_FALSE(
-      manager->StorageKeyHasData(storage_key_1, kPersistent, kClientFile));
-  EXPECT_FALSE(
-      manager->StorageKeyHasData(storage_key_2, kTemporary, kClientFile));
-}
-
-void PopulateTestQuotaManagedNonBrowsingData(
     const blink::StorageKey& storage_key,
-    storage::MockQuotaManager* manager) {
-  manager->AddStorageKey(storage_key, kTemporary, {kClientFile}, base::Time());
-  manager->AddStorageKey(storage_key, kPersistent, {kClientFile}, base::Time());
+    const std::string& bucket_name,
+    blink::mojom::StorageType type,
+    base::Time modified = base::Time::Now()) {
+  storage::BucketInfo bucket =
+      manager->CreateBucket(storage_key, bucket_name, type);
+  manager->AddBucket(bucket, {kClientFile}, modified);
+  EXPECT_TRUE(manager->BucketHasData(bucket, kClientFile));
+  return bucket;
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverBoth) {
@@ -1048,8 +1008,15 @@
   const blink::StorageKey kStorageKey3 =
       blink::StorageKey::CreateFromStringForTesting("http://host3:1/");
 
-  PopulateTestQuotaManagedData(GetMockManager(), kStorageKey1, kStorageKey2,
-                               kStorageKey3);
+  AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
+                        storage::kDefaultBucketName, kTemporary);
+  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
+                        storage::kDefaultBucketName, kTemporary);
+  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
+                        storage::kDefaultBucketName, kPersistent);
+  AddQuotaManagedBucket(GetMockManager(), kStorageKey3,
+                        storage::kDefaultBucketName, kPersistent);
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);
 
   StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
       browser_context()->GetDefaultStoragePartition());
@@ -1060,18 +1027,7 @@
       FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent,
-                                                   kClientFile));
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverOnlyTemporary) {
@@ -1080,8 +1036,11 @@
   const blink::StorageKey kStorageKey2 =
       blink::StorageKey::CreateFromStringForTesting("http://host2:1/");
 
-  PopulateTestQuotaManagedTemporaryData(GetMockManager(), kStorageKey1,
-                                        kStorageKey2);
+  AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
+                        storage::kDefaultBucketName, kTemporary);
+  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
+                        storage::kDefaultBucketName, kTemporary);
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
 
   StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
       browser_context()->GetDefaultStoragePartition());
@@ -1092,14 +1051,7 @@
       FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent,
-                                                   kClientFile));
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverOnlyPersistent) {
@@ -1108,8 +1060,11 @@
   const blink::StorageKey kStorageKey2 =
       blink::StorageKey::CreateFromStringForTesting("http://host2:1/");
 
-  PopulateTestQuotaManagedPersistentData(GetMockManager(), kStorageKey1,
-                                         kStorageKey2);
+  AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
+                        storage::kDefaultBucketName, kPersistent);
+  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
+                        storage::kDefaultBucketName, kPersistent);
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
 
   StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
       browser_context()->GetDefaultStoragePartition());
@@ -1120,23 +1075,11 @@
       FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent,
-                                                   kClientFile));
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverNeither) {
-  const blink::StorageKey kStorageKey1 =
-      blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
-  const blink::StorageKey kStorageKey2 =
-      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");
-  const blink::StorageKey kStorageKey3 =
-      blink::StorageKey::CreateFromStringForTesting("http://host3:1/");
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
 
   StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
       browser_context()->GetDefaultStoragePartition());
@@ -1147,18 +1090,7 @@
       FROM_HERE, base::BindOnce(&ClearQuotaData, partition, &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent,
-                                                   kClientFile));
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForeverSpecificOrigin) {
@@ -1169,8 +1101,16 @@
   const blink::StorageKey kStorageKey3 =
       blink::StorageKey::CreateFromStringForTesting("http://host3:1/");
 
-  PopulateTestQuotaManagedData(GetMockManager(), kStorageKey1, kStorageKey2,
-                               kStorageKey3);
+  storage::BucketInfo host1_temp_bucket = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey1, storage::kDefaultBucketName, kTemporary);
+  storage::BucketInfo host2_temp_bucket = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kTemporary);
+  storage::BucketInfo host2_perm_bucket = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kPersistent);
+  storage::BucketInfo host3_perm_bucket = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey3, storage::kDefaultBucketName, kPersistent);
+
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);
 
   StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
       browser_context()->GetDefaultStoragePartition());
@@ -1183,18 +1123,11 @@
                      kStorageKey1.origin().GetURL(), base::Time(), &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary,
-                                                   kClientFile));
-  EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary,
-                                                  kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent,
-                                                   kClientFile));
-  EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent,
-                                                  kClientFile));
-  EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent,
-                                                  kClientFile));
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 3);
+  EXPECT_FALSE(GetMockManager()->BucketHasData(host1_temp_bucket, kClientFile));
+  EXPECT_TRUE(GetMockManager()->BucketHasData(host2_temp_bucket, kClientFile));
+  EXPECT_TRUE(GetMockManager()->BucketHasData(host2_perm_bucket, kClientFile));
+  EXPECT_TRUE(GetMockManager()->BucketHasData(host3_perm_bucket, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForLastHour) {
@@ -1205,8 +1138,33 @@
   const blink::StorageKey kStorageKey3 =
       blink::StorageKey::CreateFromStringForTesting("http://host3:1/");
 
-  PopulateTestQuotaManagedData(GetMockManager(), kStorageKey1, kStorageKey2,
-                               kStorageKey3);
+  // Buckets modified now.
+  base::Time now = base::Time::Now();
+  storage::BucketInfo host1_temp_bucket_now = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey1, "temp_bucket_now", kTemporary, now);
+  storage::BucketInfo host1_perm_bucket_now = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey1, "perm_bucket_now", kPersistent, now);
+  storage::BucketInfo host2_temp_bucket_now = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey2, "temp_bucket_now", kTemporary, now);
+  storage::BucketInfo host2_perm_bucket_now = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey2, "perm_bucket_now", kPersistent, now);
+
+  // Buckets modified a day ago.
+  base::Time yesterday = now - base::TimeDelta::FromDays(1);
+  storage::BucketInfo host1_temp_bucket_yesterday =
+      AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
+                            "temp_bucket_yesterday", kTemporary, yesterday);
+  storage::BucketInfo host1_perm_bucket_yesterday =
+      AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
+                            "perm_bucket_yesterday", kPersistent, yesterday);
+  storage::BucketInfo host2_temp_bucket_yesterday =
+      AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
+                            "temp_bucket_yesterday", kTemporary, yesterday);
+  storage::BucketInfo host2_perm_bucket_yesterday =
+      AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
+                            "perm_bucket_yesterday", kPersistent, yesterday);
+
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 8);
 
   StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
       browser_context()->GetDefaultStoragePartition());
@@ -1220,35 +1178,56 @@
                      &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary,
-                                                   kClientFile));
-  EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary,
-                                                  kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent,
-                                                   kClientFile));
-  EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent,
-                                                  kClientFile));
-  EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent,
-                                                  kClientFile));
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);
+  EXPECT_FALSE(
+      GetMockManager()->BucketHasData(host1_temp_bucket_now, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->BucketHasData(host1_perm_bucket_now, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->BucketHasData(host2_temp_bucket_now, kClientFile));
+  EXPECT_FALSE(
+      GetMockManager()->BucketHasData(host2_perm_bucket_now, kClientFile));
+  EXPECT_TRUE(GetMockManager()->BucketHasData(host1_temp_bucket_yesterday,
+                                              kClientFile));
+  EXPECT_TRUE(GetMockManager()->BucketHasData(host1_perm_bucket_yesterday,
+                                              kClientFile));
+  EXPECT_TRUE(GetMockManager()->BucketHasData(host2_temp_bucket_yesterday,
+                                              kClientFile));
+  EXPECT_TRUE(GetMockManager()->BucketHasData(host2_perm_bucket_yesterday,
+                                              kClientFile));
 }
 
-TEST_F(StoragePartitionImplTest, RemoveQuotaManagedDataForLastWeek) {
-  const blink::StorageKey kStorageKey1 =
+TEST_F(StoragePartitionImplTest,
+       RemoveQuotaManagedNonPersistentDataForLastWeek) {
+  const blink::StorageKey kStorageKey =
       blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
-  const blink::StorageKey kStorageKey2 =
-      blink::StorageKey::CreateFromStringForTesting("http://host2:1/");
-  const blink::StorageKey kStorageKey3 =
-      blink::StorageKey::CreateFromStringForTesting("http://host3:1/");
 
-  PopulateTestQuotaManagedData(GetMockManager(), kStorageKey1, kStorageKey2,
-                               kStorageKey3);
+  // Buckets modified yesterday.
+  base::Time now = base::Time::Now();
+  base::Time yesterday = now - base::TimeDelta::FromDays(1);
+  storage::BucketInfo temp_bucket_yesterday =
+      AddQuotaManagedBucket(GetMockManager(), kStorageKey,
+                            "temp_bucket_yesterday", kTemporary, yesterday);
+  storage::BucketInfo perm_bucket_yesterday =
+      AddQuotaManagedBucket(GetMockManager(), kStorageKey,
+                            "perm_bucket_yesterday", kPersistent, yesterday);
+
+  // Buckets modified 10 days ago.
+  base::Time ten_days_ago = now - base::TimeDelta::FromDays(10);
+  storage::BucketInfo temp_bucket_ten_days_ago = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey, "temp_bucket_ten_days_ago", kTemporary,
+      ten_days_ago);
+  storage::BucketInfo perm_bucket_ten_days_ago = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey, "perm_bucket_ten_days_ago", kPersistent,
+      ten_days_ago);
+
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);
 
   base::RunLoop run_loop;
   StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
       browser_context()->GetDefaultStoragePartition());
   partition->OverrideQuotaManagerForTesting(GetMockManager());
+
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(&ClearQuotaDataForNonPersistent, partition,
@@ -1256,18 +1235,15 @@
                      &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent,
-                                                   kClientFile));
-  EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent,
-                                                  kClientFile));
-  EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent,
-                                                  kClientFile));
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 3);
+  EXPECT_FALSE(
+      GetMockManager()->BucketHasData(temp_bucket_yesterday, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->BucketHasData(perm_bucket_yesterday, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->BucketHasData(temp_bucket_ten_days_ago, kClientFile));
+  EXPECT_TRUE(
+      GetMockManager()->BucketHasData(perm_bucket_ten_days_ago, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedUnprotectedOrigins) {
@@ -1275,16 +1251,22 @@
       blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
   const blink::StorageKey kStorageKey2 =
       blink::StorageKey::CreateFromStringForTesting("http://host2:1/");
-  const blink::StorageKey kStorageKey3 =
-      blink::StorageKey::CreateFromStringForTesting("http://host3:1/");
+
+  storage::BucketInfo host1_temp_bucket = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey1, storage::kDefaultBucketName, kTemporary);
+  storage::BucketInfo host1_perm_bucket = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey1, storage::kDefaultBucketName, kPersistent);
+  storage::BucketInfo host2_temp_bucket = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kTemporary);
+  storage::BucketInfo host2_perm_bucket = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey2, storage::kDefaultBucketName, kPersistent);
+
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);
 
   // Protect kStorageKey1.
   auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
   mock_policy->AddProtected(kStorageKey1.origin().GetURL());
 
-  PopulateTestQuotaManagedData(GetMockManager(), kStorageKey1, kStorageKey2,
-                               kStorageKey3);
-
   StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
       browser_context()->GetDefaultStoragePartition());
   partition->OverrideQuotaManagerForTesting(GetMockManager());
@@ -1298,18 +1280,11 @@
                      base::Time(), &run_loop));
   run_loop.Run();
 
-  EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary,
-                                                  kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent,
-                                                   kClientFile));
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
+  EXPECT_TRUE(GetMockManager()->BucketHasData(host1_temp_bucket, kClientFile));
+  EXPECT_TRUE(GetMockManager()->BucketHasData(host1_perm_bucket, kClientFile));
+  EXPECT_FALSE(GetMockManager()->BucketHasData(host2_temp_bucket, kClientFile));
+  EXPECT_FALSE(GetMockManager()->BucketHasData(host2_perm_bucket, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedProtectedOrigins) {
@@ -1317,22 +1292,28 @@
       blink::StorageKey::CreateFromStringForTesting("http://host1:1/");
   const blink::StorageKey kStorageKey2 =
       blink::StorageKey::CreateFromStringForTesting("http://host2:1/");
-  const blink::StorageKey kStorageKey3 =
-      blink::StorageKey::CreateFromStringForTesting("http://host3:1/");
+
+  AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
+                        storage::kDefaultBucketName, kTemporary);
+  AddQuotaManagedBucket(GetMockManager(), kStorageKey1,
+                        storage::kDefaultBucketName, kPersistent);
+  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
+                        storage::kDefaultBucketName, kTemporary);
+  AddQuotaManagedBucket(GetMockManager(), kStorageKey2,
+                        storage::kDefaultBucketName, kPersistent);
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 4);
 
   // Protect kStorageKey1.
   auto mock_policy = base::MakeRefCounted<storage::MockSpecialStoragePolicy>();
   mock_policy->AddProtected(kStorageKey1.origin().GetURL());
 
-  PopulateTestQuotaManagedData(GetMockManager(), kStorageKey1, kStorageKey2,
-                               kStorageKey3);
-
   // Try to remove kStorageKey1. Expect success.
   base::RunLoop run_loop;
   StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
       browser_context()->GetDefaultStoragePartition());
   partition->OverrideQuotaManagerForTesting(GetMockManager());
   partition->OverrideSpecialStoragePolicyForTesting(mock_policy.get());
+
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition,
@@ -1341,18 +1322,7 @@
                      base::Time(), &run_loop));
   run_loop.Run();
 
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kTemporary,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey1, kPersistent,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey2, kPersistent,
-                                                   kClientFile));
-  EXPECT_FALSE(GetMockManager()->StorageKeyHasData(kStorageKey3, kPersistent,
-                                                   kClientFile));
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 0);
 }
 
 TEST_F(StoragePartitionImplTest, RemoveQuotaManagedIgnoreDevTools) {
@@ -1360,12 +1330,19 @@
       blink::StorageKey::CreateFromStringForTesting(
           "devtools://abcdefghijklmnopqrstuvw/");
 
-  PopulateTestQuotaManagedNonBrowsingData(kStorageKey, GetMockManager());
+  storage::BucketInfo temp_bucket = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey, storage::kDefaultBucketName, kTemporary,
+      base::Time());
+  storage::BucketInfo perm_bucket = AddQuotaManagedBucket(
+      GetMockManager(), kStorageKey, storage::kDefaultBucketName, kPersistent,
+      base::Time());
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
 
   base::RunLoop run_loop;
   StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
       browser_context()->GetDefaultStoragePartition());
   partition->OverrideQuotaManagerForTesting(GetMockManager());
+
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(&ClearQuotaDataWithOriginMatcher, partition,
                                 base::BindRepeating(&DoesOriginMatchUnprotected,
@@ -1374,10 +1351,9 @@
   run_loop.Run();
 
   // Check that devtools data isn't removed.
-  EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey, kTemporary,
-                                                  kClientFile));
-  EXPECT_TRUE(GetMockManager()->StorageKeyHasData(kStorageKey, kPersistent,
-                                                  kClientFile));
+  EXPECT_EQ(GetMockManager()->BucketDataCount(kClientFile), 2);
+  EXPECT_TRUE(GetMockManager()->BucketHasData(temp_bucket, kClientFile));
+  EXPECT_TRUE(GetMockManager()->BucketHasData(perm_bucket, kClientFile));
 }
 
 TEST_F(StoragePartitionImplTest, RemoveCookieForever) {
diff --git a/content/browser/web_contents/web_contents_impl_unittest.cc b/content/browser/web_contents/web_contents_impl_unittest.cc
index b430d4e3..51c924f 100644
--- a/content/browser/web_contents/web_contents_impl_unittest.cc
+++ b/content/browser/web_contents/web_contents_impl_unittest.cc
@@ -1333,9 +1333,9 @@
   webui_rfh = contents()->GetSpeculativePrimaryMainFrame();
 
   // DidNavigate from the second back.
-  // Note that the process in instance1 is gone at this point, but we will still
-  // use instance1 and entry1 because IsSuitableForURL will return true when
-  // there is no process and the site URL matches.
+  // Note that the process in instance1 is gone at this point, but we will
+  // still use instance1 and entry1 because IsSuitableForUrlInfo will return
+  // true when there is no process and the site URL matches.
   back_navigation2->Commit();
 
   // That should have landed us on the first entry.
diff --git a/content/browser/worker_host/shared_worker_service_impl.cc b/content/browser/worker_host/shared_worker_service_impl.cc
index 8979c43..669f4ff 100644
--- a/content/browser/worker_host/shared_worker_service_impl.cc
+++ b/content/browser/worker_host/shared_worker_service_impl.cc
@@ -304,7 +304,8 @@
     } else {
       site_instance = SiteInstanceImpl::CreateForUrlInfo(
           partition->browser_context(),
-          UrlInfo(instance.url(), UrlInfo::OriginIsolationRequest::kNone),
+          UrlInfo(instance.url(), UrlInfo::OriginIsolationRequest::kNone,
+                  partition->GetConfig()),
           WebExposedIsolationInfo::CreateNonIsolated());
     }
   }
diff --git a/content/test/data/gpu/webgpu-iframe-removed.html b/content/test/data/gpu/webgpu-iframe-removed.html
index 2c38f5d..8823648 100644
--- a/content/test/data/gpu/webgpu-iframe-removed.html
+++ b/content/test/data/gpu/webgpu-iframe-removed.html
@@ -178,8 +178,8 @@
         parent.removeIframe();
       });
 
-      // Test creating a mappable buffer and then removing the iframe while
-      // the mapping is in flight.
+      // Test creating a mappable buffer and then in the next task, removing the
+      // iframe while the mapping is in flight.
       // Mappable buffers are tracked because mappings need to be detached
       // when the context is destroyed.
       await runTestCase(async () => {
@@ -203,6 +203,30 @@
         }, 0);
       });
 
+      // Test creating a mappable buffer and then in the same task, removing the
+      // iframe while the mapping is in flight.
+      // Mappable buffers are tracked because mappings need to be detached
+      // when the context is destroyed.
+      await runTestCase(async () => {
+        const adapter = navigator.gpu && await navigator.gpu.requestAdapter();
+        if (!adapter) {
+          console.warn('WebGPU not supported');
+          parent.removeIframe();
+          return;
+        }
+        const device = await adapter.requestDevice();
+
+        const buffer = device.createBuffer({
+          size: 4,
+          usage: GPUBufferUsage.MAP_READ,
+        });
+
+        buffer.mapAsync(GPUMapMode.READ);
+        /* Empty submit to force a flush */
+        device.queue.submit([]);
+        parent.removeIframe();
+      });
+
       // Test removing the iframe while the buffer is mapped.
       // Mappable buffers are tracked because mappings need to be detached
       // when the context is destroyed.
diff --git a/content/test/storage_partition_test_helpers.cc b/content/test/storage_partition_test_helpers.cc
index e2c3aad..ea61145c 100644
--- a/content/test/storage_partition_test_helpers.cc
+++ b/content/test/storage_partition_test_helpers.cc
@@ -36,4 +36,32 @@
   return StoragePartitionConfig(partition_domain, partition_name, in_memory);
 }
 
+CustomStoragePartitionForSomeSites::CustomStoragePartitionForSomeSites(
+    const GURL& site_to_isolate)
+    : site_to_isolate_(site_to_isolate) {}
+
+StoragePartitionConfig
+CustomStoragePartitionForSomeSites::GetStoragePartitionConfigForSite(
+    BrowserContext* browser_context,
+    const GURL& site) {
+  // Override for |site_to_isolate_|.
+  if (site == site_to_isolate_) {
+    return StoragePartitionConfig::Create(
+        browser_context, "blah_isolated_storage", "blah_isolated_storage",
+        false /* in_memory */);
+  }
+
+  return StoragePartitionConfig::CreateDefault(browser_context);
+}
+
+StoragePartitionId
+CustomStoragePartitionForSomeSites::GetStoragePartitionIdForSite(
+    BrowserContext* browser_context,
+    const GURL& site) {
+  if (site == site_to_isolate_)
+    return StoragePartitionId(
+        site.spec(), GetStoragePartitionConfigForSite(browser_context, site));
+  return StoragePartitionId(browser_context);
+}
+
 }  // namespace content
diff --git a/content/test/storage_partition_test_helpers.h b/content/test/storage_partition_test_helpers.h
index db566bb..79050ce 100644
--- a/content/test/storage_partition_test_helpers.h
+++ b/content/test/storage_partition_test_helpers.h
@@ -6,6 +6,7 @@
 #define CONTENT_TEST_STORAGE_PARTITION_TEST_HELPERS_H_
 
 #include "base/callback.h"
+#include "content/test/test_content_browser_client.h"
 
 namespace content {
 class StoragePartition;
@@ -26,6 +27,24 @@
     const std::string& partition_domain = "",
     const std::string& partition_name = "");
 
+// Class that requests that all pages belonging to the provided site get loaded
+// in a non-default StoragePartition.
+class CustomStoragePartitionForSomeSites : public TestContentBrowserClient {
+ public:
+  explicit CustomStoragePartitionForSomeSites(const GURL& site_to_isolate);
+
+  StoragePartitionConfig GetStoragePartitionConfigForSite(
+      BrowserContext* browser_context,
+      const GURL& site) override;
+
+  StoragePartitionId GetStoragePartitionIdForSite(
+      BrowserContext* browser_context,
+      const GURL& site) override;
+
+ private:
+  GURL site_to_isolate_;
+};
+
 }  // namespace content
 
 #endif  // CONTENT_TEST_STORAGE_PARTITION_TEST_HELPERS_H_
diff --git a/device/base/features.cc b/device/base/features.cc
index 59506ef..cd871b3 100644
--- a/device/base/features.cc
+++ b/device/base/features.cc
@@ -14,6 +14,11 @@
 #endif  // defined(OS_MAC)
 
 #if defined(OS_WIN)
+const base::Feature kNewUsbBackend{"NewUsbBackend",
+                                   base::FEATURE_ENABLED_BY_DEFAULT};
+#endif  // defined(OS_WIN)
+
+#if defined(OS_WIN)
 const base::Feature kNewBLEWinImplementation{"NewBLEWinImplementation",
                                              base::FEATURE_ENABLED_BY_DEFAULT};
 
diff --git a/device/base/features.h b/device/base/features.h
index 4105f3c..80bf44d 100644
--- a/device/base/features.h
+++ b/device/base/features.h
@@ -12,9 +12,9 @@
 
 namespace device {
 
-#if defined(OS_MAC)
+#if defined(OS_WIN) || defined(OS_MAC)
 DEVICE_BASE_EXPORT extern const base::Feature kNewUsbBackend;
-#endif  // defined(OS_MAC)
+#endif  // defined(OS_WIN) || defined(OS_MAC)
 
 #if defined(OS_WIN)
 DEVICE_BASE_EXPORT extern const base::Feature kNewBLEWinImplementation;
diff --git a/device/gamepad/xbox_controller_mac.mm b/device/gamepad/xbox_controller_mac.mm
index 823e2f7..bac7db8 100644
--- a/device/gamepad/xbox_controller_mac.mm
+++ b/device/gamepad/xbox_controller_mac.mm
@@ -18,6 +18,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/check_op.h"
+#include "base/cxx17_backports.h"
 #include "base/location.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_ioobject.h"
@@ -337,9 +338,8 @@
     return;
 
   // Clamp magnitudes to [0,1]
-  strong_magnitude =
-      std::max<double>(0.0, std::min<double>(strong_magnitude, 1.0));
-  weak_magnitude = std::max<double>(0.0, std::min<double>(weak_magnitude, 1.0));
+  strong_magnitude = base::clamp<double>(strong_magnitude, 0.0, 1.0);
+  weak_magnitude = base::clamp<double>(weak_magnitude, 0.0, 1.0);
 
   if (xinput_type_ == kXInputTypeXbox360) {
     WriteXbox360Rumble(static_cast<uint8_t>(strong_magnitude * 255.0),
diff --git a/gpu/command_buffer/client/dawn_client_memory_transfer_service.cc b/gpu/command_buffer/client/dawn_client_memory_transfer_service.cc
index 4695ce1..bdbacb8 100644
--- a/gpu/command_buffer/client/dawn_client_memory_transfer_service.cc
+++ b/gpu/command_buffer/client/dawn_client_memory_transfer_service.cc
@@ -125,7 +125,7 @@
 void* DawnClientMemoryTransferService::AllocateHandle(
     size_t size,
     MemoryTransferHandle* handle) {
-  if (size > std::numeric_limits<uint32_t>::max()) {
+  if (size > std::numeric_limits<uint32_t>::max() || disconnected_) {
     return nullptr;
   }
 
@@ -155,5 +155,9 @@
   }
 }
 
+void DawnClientMemoryTransferService::Disconnect() {
+  disconnected_ = true;
+}
+
 }  // namespace webgpu
 }  // namespace gpu
diff --git a/gpu/command_buffer/client/dawn_client_memory_transfer_service.h b/gpu/command_buffer/client/dawn_client_memory_transfer_service.h
index 61efd12..3c8780cd 100644
--- a/gpu/command_buffer/client/dawn_client_memory_transfer_service.h
+++ b/gpu/command_buffer/client/dawn_client_memory_transfer_service.h
@@ -17,7 +17,7 @@
 
 struct MemoryTransferHandle;
 
-class DawnClientMemoryTransferService final
+class DawnClientMemoryTransferService
     : public dawn_wire::client::MemoryTransferService {
  public:
   DawnClientMemoryTransferService(MappedMemoryManager* mapped_memory);
@@ -35,6 +35,8 @@
   // process.
   void FreeHandles(CommandBufferHelper* helper);
 
+  void Disconnect();
+
  private:
   class ReadHandleImpl;
   class WriteHandleImpl;
@@ -50,6 +52,9 @@
   // Pointers to memory allocated by the MappedMemoryManager to free after
   // the next Flush.
   std::vector<void*> free_blocks_;
+
+  // If disconnected, new handle creation always returns null.
+  bool disconnected_ = false;
 };
 
 }  // namespace webgpu
diff --git a/gpu/command_buffer/client/dawn_client_serializer.cc b/gpu/command_buffer/client/dawn_client_serializer.cc
index e5d1abe..31154e2 100644
--- a/gpu/command_buffer/client/dawn_client_serializer.cc
+++ b/gpu/command_buffer/client/dawn_client_serializer.cc
@@ -14,37 +14,16 @@
 namespace gpu {
 namespace webgpu {
 
-// static
-std::unique_ptr<DawnClientSerializer> DawnClientSerializer::Create(
-    WebGPUImplementation* client,
-    WebGPUCmdHelper* helper,
-    DawnClientMemoryTransferService* memory_transfer_service,
-    const SharedMemoryLimits& limits) {
-  std::unique_ptr<TransferBuffer> transfer_buffer =
-      std::make_unique<TransferBuffer>(helper);
-  if (!transfer_buffer->Initialize(limits.start_transfer_buffer_size,
-                                   /* start offset */ 0,
-                                   limits.min_transfer_buffer_size,
-                                   limits.max_transfer_buffer_size,
-                                   /* alignment */ 8)) {
-    return nullptr;
-  }
-  return std::make_unique<DawnClientSerializer>(
-      client, helper, memory_transfer_service, std::move(transfer_buffer),
-      limits.start_transfer_buffer_size);
-}
-
 DawnClientSerializer::DawnClientSerializer(
     WebGPUImplementation* client,
     WebGPUCmdHelper* helper,
     DawnClientMemoryTransferService* memory_transfer_service_,
-    std::unique_ptr<TransferBuffer> transfer_buffer,
-    uint32_t buffer_initial_size)
+    std::unique_ptr<TransferBuffer> transfer_buffer)
     : client_(client),
       helper_(helper),
       memory_transfer_service_(memory_transfer_service_),
       transfer_buffer_(std::move(transfer_buffer)),
-      buffer_initial_size_(buffer_initial_size),
+      buffer_initial_size_(transfer_buffer_->GetSize()),
       buffer_(helper_, transfer_buffer_.get()) {
   DCHECK_GT(buffer_initial_size_, 0u);
 }
@@ -131,7 +110,16 @@
 
 void DawnClientSerializer::Disconnect() {
   buffer_.Discard();
-  transfer_buffer_ = nullptr;
+  if (transfer_buffer_) {
+    auto transfer_buffer = std::move(transfer_buffer_);
+    // Wait for commands to finish before we free shared memory that
+    // the GPU process is using.
+    // TODO(crbug.com/1231599): This Finish may not be necessary if the
+    // shared memory is not immediately freed. Investigate this and
+    // consider optimization.
+    helper_->Finish();
+    transfer_buffer = nullptr;
+  }
 }
 
 bool DawnClientSerializer::Flush() {
diff --git a/gpu/command_buffer/client/dawn_client_serializer.h b/gpu/command_buffer/client/dawn_client_serializer.h
index 65ca6345..980440b 100644
--- a/gpu/command_buffer/client/dawn_client_serializer.h
+++ b/gpu/command_buffer/client/dawn_client_serializer.h
@@ -13,7 +13,6 @@
 
 namespace gpu {
 
-struct SharedMemoryLimits;
 class TransferBuffer;
 
 namespace webgpu {
@@ -22,19 +21,12 @@
 class WebGPUCmdHelper;
 class WebGPUImplementation;
 
-class DawnClientSerializer final : public dawn_wire::CommandSerializer {
+class DawnClientSerializer : public dawn_wire::CommandSerializer {
  public:
-  static std::unique_ptr<DawnClientSerializer> Create(
-      WebGPUImplementation* client,
-      WebGPUCmdHelper* helper,
-      DawnClientMemoryTransferService* memory_transfer_service,
-      const SharedMemoryLimits& limits);
-
   DawnClientSerializer(WebGPUImplementation* client,
                        WebGPUCmdHelper* helper,
                        DawnClientMemoryTransferService* memory_transfer_service,
-                       std::unique_ptr<TransferBuffer> transfer_buffer,
-                       uint32_t buffer_initial_size);
+                       std::unique_ptr<TransferBuffer> transfer_buffer);
   ~DawnClientSerializer() override;
 
   // dawn_wire::CommandSerializer implementation
diff --git a/gpu/command_buffer/client/webgpu_cmd_helper_autogen.h b/gpu/command_buffer/client/webgpu_cmd_helper_autogen.h
index 2758247..e17e061 100644
--- a/gpu/command_buffer/client/webgpu_cmd_helper_autogen.h
+++ b/gpu/command_buffer/client/webgpu_cmd_helper_autogen.h
@@ -67,11 +67,4 @@
   }
 }
 
-void DestroyServer() {
-  webgpu::cmds::DestroyServer* c = GetCmdSpace<webgpu::cmds::DestroyServer>();
-  if (c) {
-    c->Init();
-  }
-}
-
 #endif  // GPU_COMMAND_BUFFER_CLIENT_WEBGPU_CMD_HELPER_AUTOGEN_H_
diff --git a/gpu/command_buffer/client/webgpu_implementation.cc b/gpu/command_buffer/client/webgpu_implementation.cc
index a155f47f..f5ee3c1a 100644
--- a/gpu/command_buffer/client/webgpu_implementation.cc
+++ b/gpu/command_buffer/client/webgpu_implementation.cc
@@ -22,6 +22,58 @@
 namespace gpu {
 namespace webgpu {
 
+#if BUILDFLAG(USE_DAWN)
+class DawnWireServices : public APIChannel {
+ private:
+  friend class base::RefCounted<DawnWireServices>;
+  ~DawnWireServices() override = default;
+
+ public:
+  DawnWireServices(WebGPUImplementation* webgpu_implementation,
+                   WebGPUCmdHelper* helper,
+                   MappedMemoryManager* mapped_memory,
+                   std::unique_ptr<TransferBuffer> transfer_buffer)
+      : memory_transfer_service_(mapped_memory),
+        serializer_(webgpu_implementation,
+                    helper,
+                    &memory_transfer_service_,
+                    std::move(transfer_buffer)),
+        wire_client_(dawn_wire::WireClientDescriptor{
+            &serializer_,
+            &memory_transfer_service_,
+        }) {}
+
+  const DawnProcTable& GetProcs() const override {
+    return dawn_wire::client::GetProcs();
+  }
+
+  dawn_wire::WireClient* wire_client() { return &wire_client_; }
+  DawnClientSerializer* serializer() { return &serializer_; }
+  DawnClientMemoryTransferService* memory_transfer_service() {
+    return &memory_transfer_service_;
+  }
+
+  void Disconnect() override {
+    disconnected_ = true;
+    wire_client_.Disconnect();
+    serializer_.Disconnect();
+    memory_transfer_service_.Disconnect();
+  }
+
+  bool IsDisconnected() const { return disconnected_; }
+
+  void FreeMappedResources(WebGPUCmdHelper* helper) {
+    memory_transfer_service_.FreeHandles(helper);
+  }
+
+ private:
+  bool disconnected_ = false;
+  DawnClientMemoryTransferService memory_transfer_service_;
+  DawnClientSerializer serializer_;
+  dawn_wire::WireClient wire_client_;
+};
+#endif
+
 // Include the auto-generated part of this file. We split this because it means
 // we can easily edit the non-auto generated parts right here in this file
 // instead of having to edit some template or the code generator.
@@ -35,26 +87,42 @@
       helper_(helper) {}
 
 WebGPUImplementation::~WebGPUImplementation() {
+  LoseContext();
+
+  // Before destroying WebGPUImplementation, all mappable buffers
+  // must be destroyed first. This means that all shared memory mappings are
+  // detached. If they are not destroyed, MappedMemoryManager (member of
+  // base class ImplementationBase) will assert on destruction that some
+  // memory blocks are in use. Calling |FreeMappedResources| marks all
+  // blocks that are no longer in use as free.
 #if BUILDFLAG(USE_DAWN)
-  if (!wire_client_) {
-    // Initialization failed.
-    return;
+  dawn_wire_->FreeMappedResources(helper_);
+#endif
+
+  // Wait for commands to finish before we continue destruction.
+  // WebGPUImplementation no longer owns the WebGPU transfer buffer, but still
+  // owns the GPU command buffer. We should not free shared memory that the
+  // GPU process is using.
+  helper_->Finish();
+}
+
+void WebGPUImplementation::LoseContext() {
+  lost_ = true;
+#if BUILDFLAG(USE_DAWN)
+  dawn_wire_->Disconnect();
+
+  auto request_adapter_callback_map = std::move(request_adapter_callback_map_);
+  auto request_device_callback_map = std::move(request_device_callback_map_);
+  for (auto& it : request_adapter_callback_map) {
+    std::move(it.second).Run(-1, {}, "Context Lost");
+  }
+  for (auto& it : request_device_callback_map) {
+    std::move(it.second).Run(false);
   }
 
-  // Commit all commands and synchronously wait for them to finish.
-  // TODO(enga): Investigate if we can just Disconnect() instead.
-  wire_serializer_->Commit();
-  helper_->Finish();
-
-  // Now that commands are finished, free the wire and serializers.
-  wire_client_.reset();
-  wire_serializer_.reset();
-
-  // All client-side Dawn objects are now destroyed.
-  // Shared memory allocations for buffers that were still mapped at the time
-  // of destruction can now be safely freed.
-  memory_transfer_service_->FreeHandles(helper_);
-  helper_->Finish();
+  // After |lost_| is set to true, callbacks should not be enqueued anymore.
+  DCHECK(request_adapter_callback_map_.empty());
+  DCHECK(request_device_callback_map_.empty());
 #endif
 }
 
@@ -66,17 +134,19 @@
     return result;
   }
 
+  std::unique_ptr<TransferBuffer> transfer_buffer =
+      std::make_unique<TransferBuffer>(helper_);
+  if (!transfer_buffer->Initialize(limits.start_transfer_buffer_size,
+                                   /* start offset */ 0,
+                                   limits.min_transfer_buffer_size,
+                                   limits.max_transfer_buffer_size,
+                                   /* alignment */ 8)) {
+    return gpu::ContextResult::kFatalFailure;
+  }
+
 #if BUILDFLAG(USE_DAWN)
-  memory_transfer_service_ =
-      std::make_unique<DawnClientMemoryTransferService>(mapped_memory_.get());
-
-  wire_serializer_ = DawnClientSerializer::Create(
-      this, helper_, memory_transfer_service_.get(), limits);
-
-  dawn_wire::WireClientDescriptor descriptor = {};
-  descriptor.serializer = wire_serializer_.get();
-  descriptor.memoryTransferService = memory_transfer_service_.get();
-  wire_client_ = std::make_unique<dawn_wire::WireClient>(descriptor);
+  dawn_wire_ = base::MakeRefCounted<DawnWireServices>(
+      this, helper_, mapped_memory_.get(), std::move(transfer_buffer));
 
   // TODO(senorblanco): Do this only once per process. Doing it once per
   // WebGPUImplementation is non-optimal but valid, since the returned
@@ -193,7 +263,7 @@
   // Need to commit the commands to the GPU command buffer first for SyncToken
   // to work.
 #if BUILDFLAG(USE_DAWN)
-  wire_serializer_->Commit();
+  dawn_wire_->serializer()->Commit();
 #endif
   ImplementationBase::GenSyncToken(sync_token);
 }
@@ -201,7 +271,7 @@
   // Need to commit the commands to the GPU command buffer first for SyncToken
   // to work.
 #if BUILDFLAG(USE_DAWN)
-  wire_serializer_->Commit();
+  dawn_wire_->serializer()->Commit();
 #endif
   ImplementationBase::GenUnverifiedSyncToken(sync_token);
 }
@@ -213,7 +283,7 @@
   // Need to commit the commands to the GPU command buffer first for SyncToken
   // to work.
 #if BUILDFLAG(USE_DAWN)
-  wire_serializer_->Commit();
+  dawn_wire_->serializer()->Commit();
 #endif
   ImplementationBase::WaitSyncToken(sync_token);
 }
@@ -240,9 +310,8 @@
 }
 
 // GpuControlClient implementation.
-// TODO(jiawei.shao@intel.com): do other clean-ups when the context is lost.
 void WebGPUImplementation::OnGpuControlLostContext() {
-  OnGpuControlLostContextMaybeReentrant();
+  LoseContext();
 
   // This should never occur more than once.
   DCHECK(!lost_context_callback_run_);
@@ -252,11 +321,12 @@
   }
 }
 void WebGPUImplementation::OnGpuControlLostContextMaybeReentrant() {
+  // If this function is called, we are guaranteed to also get a call
+  // to |OnGpuControlLostContext| when the callstack unwinds. Thus, this
+  // function only handles immediately setting state so that other operations
+  // which occur while the callstack is unwinding are aware that the context
+  // is lost.
   lost_ = true;
-#if BUILDFLAG(USE_DAWN)
-  wire_client_->Disconnect();
-  wire_serializer_->Disconnect();
-#endif
 }
 void WebGPUImplementation::OnGpuControlErrorMessage(const char* message,
                                                     int32_t id) {
@@ -299,10 +369,13 @@
 
       const cmds::DawnReturnCommandsInfo* dawn_return_commands_info =
           reinterpret_cast<const cmds::DawnReturnCommandsInfo*>(data.data());
+      if (dawn_wire_->IsDisconnected()) {
+        break;
+      }
       // TODO(enga): Instead of a CHECK, this could generate a device lost
       // event on just that device. It doesn't seem worth doing right now
       // since a failure here is likely not recoverable.
-      CHECK(wire_client_->HandleCommands(
+      CHECK(dawn_wire_->wire_client()->HandleCommands(
           reinterpret_cast<const char*>(
               dawn_return_commands_info->deserialized_buffer),
           data.size() -
@@ -372,19 +445,9 @@
 #endif
 }
 
-const DawnProcTable& WebGPUImplementation::GetProcs() const {
-#if !BUILDFLAG(USE_DAWN)
-  NOTREACHED();
-  static DawnProcTable null_procs = {};
-  return null_procs;
-#else
-  return dawn_wire::client::GetProcs();
-#endif
-}
-
 void WebGPUImplementation::FlushCommands() {
 #if BUILDFLAG(USE_DAWN)
-  wire_serializer_->Commit();
+  dawn_wire_->serializer()->Commit();
   helper_->Flush();
 #endif
 }
@@ -394,15 +457,15 @@
   // If there is already a flush waiting, we don't need to flush.
   // We only want to set |needs_flush| on state transition from
   // false -> true.
-  if (wire_serializer_->AwaitingFlush()) {
+  if (dawn_wire_->serializer()->AwaitingFlush()) {
     *needs_flush = false;
     return;
   }
 
   // Set the state to waiting for flush, and then write |needs_flush|.
   // Could still be false if there's no data to flush.
-  wire_serializer_->SetAwaitingFlush(true);
-  *needs_flush = wire_serializer_->AwaitingFlush();
+  dawn_wire_->serializer()->SetAwaitingFlush(true);
+  *needs_flush = dawn_wire_->serializer()->AwaitingFlush();
 #else
   *needs_flush = false;
 #endif
@@ -410,21 +473,18 @@
 
 void WebGPUImplementation::FlushAwaitingCommands() {
 #if BUILDFLAG(USE_DAWN)
-  if (wire_serializer_->AwaitingFlush()) {
-    wire_serializer_->Commit();
+  if (dawn_wire_->serializer()->AwaitingFlush()) {
+    dawn_wire_->serializer()->Commit();
     helper_->Flush();
   }
 #endif
 }
 
-void WebGPUImplementation::DisconnectContextAndDestroyServer() {
-  // Treat this like a context lost since the context is no longer usable.
-  OnGpuControlLostContextMaybeReentrant();
-
+scoped_refptr<APIChannel> WebGPUImplementation::GetAPIChannel() const {
 #if BUILDFLAG(USE_DAWN)
-  // Send a message to eagerly free server-side resources.
-  helper_->DestroyServer();
-  helper_->Flush();
+  return dawn_wire_.get();
+#else
+  return nullptr;
 #endif
 }
 
@@ -432,9 +492,9 @@
 #if BUILDFLAG(USE_DAWN)
   // Commit because we need to make sure messages that free a previously used
   // texture are seen first. ReserveTexture may reuse an existing ID.
-  wire_serializer_->Commit();
+  dawn_wire_->serializer()->Commit();
 
-  auto reservation = wire_client_->ReserveTexture(device);
+  auto reservation = dawn_wire_->wire_client()->ReserveTexture(device);
   ReservedTexture result;
   result.texture = reservation.texture;
   result.id = reservation.id;
@@ -512,22 +572,23 @@
 
   // Commit because we need to make sure messages that free a previously used
   // device are seen first. ReserveDevice may reuse an existing ID.
-  wire_serializer_->Commit();
+  dawn_wire_->serializer()->Commit();
 
-  dawn_wire::ReservedDevice reservation = wire_client_->ReserveDevice();
+  dawn_wire::ReservedDevice reservation =
+      dawn_wire_->wire_client()->ReserveDevice();
 
   request_device_callback_map_[request_device_serial] = base::BindOnce(
-      [](dawn_wire::WireClient* wire_client,
+      [](scoped_refptr<DawnWireServices> dawn_wire,
          dawn_wire::ReservedDevice reservation,
          base::OnceCallback<void(WGPUDevice)> callback, bool success) {
         WGPUDevice device = reservation.device;
         if (!success) {
-          wire_client->ReclaimDeviceReservation(reservation);
+          dawn_wire->wire_client()->ReclaimDeviceReservation(reservation);
           device = nullptr;
         }
         std::move(callback).Run(device);
       },
-      wire_client_.get(), reservation, std::move(request_device_callback));
+      dawn_wire_, reservation, std::move(request_device_callback));
 
   dawn_wire::SerializeWGPUDeviceProperties(
       &requested_device_properties, reinterpret_cast<char*>(buffer.address()));
@@ -586,7 +647,7 @@
   // and need to be resolved prior to the AssociateMailbox command. Otherwise
   // the service side might not know, for example that the previous texture
   // using that ID has been released.
-  wire_serializer_->Commit();
+  dawn_wire_->serializer()->Commit();
   helper_->AssociateMailboxImmediate(device_id, device_generation, texture_id,
                                      texture_generation, usage, mailbox);
 #endif
@@ -597,7 +658,7 @@
 #if BUILDFLAG(USE_DAWN)
   // Commit previous Dawn commands that might be rendering to the texture, prior
   // to Dissociating the shared image from that texture.
-  wire_serializer_->Commit();
+  dawn_wire_->serializer()->Commit();
   helper_->DissociateMailbox(texture_id, texture_generation);
 #endif
 }
diff --git a/gpu/command_buffer/client/webgpu_implementation.h b/gpu/command_buffer/client/webgpu_implementation.h
index f79c3d7..0720033 100644
--- a/gpu/command_buffer/client/webgpu_implementation.h
+++ b/gpu/command_buffer/client/webgpu_implementation.h
@@ -28,8 +28,7 @@
 namespace gpu {
 namespace webgpu {
 
-class DawnClientMemoryTransferService;
-class DawnClientSerializer;
+class DawnWireServices;
 
 class WEBGPU_EXPORT WebGPUImplementation final : public WebGPUInterface,
                                                  public ImplementationBase {
@@ -115,11 +114,10 @@
   void OnGpuControlReturnData(base::span<const uint8_t> data) final;
 
   // WebGPUInterface implementation
-  const DawnProcTable& GetProcs() const override;
   void FlushCommands() override;
   void EnsureAwaitingFlush(bool* needs_flush) override;
   void FlushAwaitingCommands() override;
-  void DisconnectContextAndDestroyServer() override;
+  scoped_refptr<APIChannel> GetAPIChannel() const override;
   ReservedTexture ReserveTexture(WGPUDevice device) override;
   void RequestAdapterAsync(
       PowerPreference power_preference,
@@ -138,12 +136,11 @@
   void CheckGLError() {}
   DawnRequestAdapterSerial NextRequestAdapterSerial();
   DawnRequestDeviceSerial NextRequestDeviceSerial();
+  void LoseContext();
 
   WebGPUCmdHelper* helper_;
 #if BUILDFLAG(USE_DAWN)
-  std::unique_ptr<DawnClientMemoryTransferService> memory_transfer_service_;
-  std::unique_ptr<DawnClientSerializer> wire_serializer_;
-  std::unique_ptr<dawn_wire::WireClient> wire_client_;
+  scoped_refptr<DawnWireServices> dawn_wire_;
 #endif
   WGPUDevice deprecated_default_device_ = nullptr;
 
diff --git a/gpu/command_buffer/client/webgpu_interface.h b/gpu/command_buffer/client/webgpu_interface.h
index df6094f3..8aee665 100644
--- a/gpu/command_buffer/client/webgpu_interface.h
+++ b/gpu/command_buffer/client/webgpu_interface.h
@@ -24,12 +24,28 @@
   uint32_t deviceGeneration;
 };
 
+// APIChannel is a RefCounted class which holds the Dawn wire client.
+class APIChannel : public base::RefCounted<APIChannel> {
+ public:
+  // Get the proc table.
+  // As long as a reference to this APIChannel alive, it is valid to
+  // call these procs.
+  virtual const DawnProcTable& GetProcs() const = 0;
+
+  // Disconnect. All commands using the WebGPU API should become a
+  // no-op and server-side resources can be freed.
+  virtual void Disconnect() = 0;
+
+ protected:
+  friend class base::RefCounted<APIChannel>;
+  APIChannel() = default;
+  virtual ~APIChannel() = default;
+};
+
 class WebGPUInterface : public InterfaceBase {
  public:
-  WebGPUInterface() {}
-  virtual ~WebGPUInterface() {}
-
-  virtual const DawnProcTable& GetProcs() const = 0;
+  WebGPUInterface() = default;
+  virtual ~WebGPUInterface() = default;
 
   // Flush all commands.
   virtual void FlushCommands() = 0;
@@ -44,9 +60,8 @@
   // nothing.
   virtual void FlushAwaitingCommands() = 0;
 
-  // Disconnect. All commands should become a no-op and server-side resources
-  // can be freed.
-  virtual void DisconnectContextAndDestroyServer() = 0;
+  // Get a strong reference to the APIChannel backing the implementation.
+  virtual scoped_refptr<APIChannel> GetAPIChannel() const = 0;
 
   virtual ReservedTexture ReserveTexture(WGPUDevice device) = 0;
   virtual void RequestAdapterAsync(
diff --git a/gpu/command_buffer/client/webgpu_interface_stub.cc b/gpu/command_buffer/client/webgpu_interface_stub.cc
index b724897..9aca0fe0 100644
--- a/gpu/command_buffer/client/webgpu_interface_stub.cc
+++ b/gpu/command_buffer/client/webgpu_interface_stub.cc
@@ -7,10 +7,34 @@
 namespace gpu {
 namespace webgpu {
 
-WebGPUInterfaceStub::WebGPUInterfaceStub() = default;
+namespace {
+
+class APIChannelStub : public APIChannel {
+ public:
+  APIChannelStub() = default;
+
+  const DawnProcTable& GetProcs() const override { return procs_; }
+  void Disconnect() override {}
+
+  DawnProcTable* procs() { return &procs_; }
+
+ private:
+  ~APIChannelStub() override = default;
+
+  DawnProcTable procs_ = {};
+};
+
+}  // anonymous namespace
+
+WebGPUInterfaceStub::WebGPUInterfaceStub()
+    : api_channel_(base::MakeRefCounted<APIChannelStub>()) {}
 
 WebGPUInterfaceStub::~WebGPUInterfaceStub() = default;
 
+DawnProcTable* WebGPUInterfaceStub::procs() {
+  return static_cast<APIChannelStub*>(api_channel_.get())->procs();
+}
+
 // InterfaceBase implementation.
 void WebGPUInterfaceStub::GenSyncTokenCHROMIUM(GLbyte* sync_token) {}
 void WebGPUInterfaceStub::GenUnverifiedSyncTokenCHROMIUM(GLbyte* sync_token) {}
@@ -20,13 +44,12 @@
 void WebGPUInterfaceStub::ShallowFlushCHROMIUM() {}
 
 // WebGPUInterface implementation
-const DawnProcTable& WebGPUInterfaceStub::GetProcs() const {
-  return null_procs_;
+scoped_refptr<APIChannel> WebGPUInterfaceStub::GetAPIChannel() const {
+  return api_channel_;
 }
 void WebGPUInterfaceStub::FlushCommands() {}
 void WebGPUInterfaceStub::EnsureAwaitingFlush(bool* needs_flush) {}
 void WebGPUInterfaceStub::FlushAwaitingCommands() {}
-void WebGPUInterfaceStub::DisconnectContextAndDestroyServer() {}
 ReservedTexture WebGPUInterfaceStub::ReserveTexture(WGPUDevice) {
   return {nullptr, 0, 0, 0, 0};
 }
diff --git a/gpu/command_buffer/client/webgpu_interface_stub.h b/gpu/command_buffer/client/webgpu_interface_stub.h
index 2c7d9d5f..e8b5da5a 100644
--- a/gpu/command_buffer/client/webgpu_interface_stub.h
+++ b/gpu/command_buffer/client/webgpu_interface_stub.h
@@ -24,11 +24,10 @@
   void ShallowFlushCHROMIUM() override;
 
   // WebGPUInterface implementation
-  const DawnProcTable& GetProcs() const override;
+  scoped_refptr<APIChannel> GetAPIChannel() const override;
   void FlushCommands() override;
   void EnsureAwaitingFlush(bool* needs_flush) override;
   void FlushAwaitingCommands() override;
-  void DisconnectContextAndDestroyServer() override;
   ReservedTexture ReserveTexture(WGPUDevice device) override;
   void RequestAdapterAsync(
       PowerPreference power_preference,
@@ -47,8 +46,11 @@
 // this file instead of having to edit some template or the code generator.
 #include "gpu/command_buffer/client/webgpu_interface_stub_autogen.h"
 
+ protected:
+  DawnProcTable* procs();
+
  private:
-  DawnProcTable null_procs_;
+  scoped_refptr<APIChannel> api_channel_;
 };
 
 }  // namespace webgpu
diff --git a/gpu/command_buffer/common/webgpu_cmd_format_autogen.h b/gpu/command_buffer/common/webgpu_cmd_format_autogen.h
index 6f91209..a82850b 100644
--- a/gpu/command_buffer/common/webgpu_cmd_format_autogen.h
+++ b/gpu/command_buffer/common/webgpu_cmd_format_autogen.h
@@ -282,30 +282,4 @@
     offsetof(RequestDevice, request_device_properties_size) == 28,
     "offset of RequestDevice request_device_properties_size should be 28");
 
-struct DestroyServer {
-  typedef DestroyServer ValueType;
-  static const CommandId kCmdId = kDestroyServer;
-  static const cmd::ArgFlags kArgFlags = cmd::kFixed;
-  static const uint8_t cmd_flags = CMD_FLAG_SET_TRACE_LEVEL(3);
-
-  static uint32_t ComputeSize() {
-    return static_cast<uint32_t>(sizeof(ValueType));  // NOLINT
-  }
-
-  void SetHeader() { header.SetCmd<ValueType>(); }
-
-  void Init() { SetHeader(); }
-
-  void* Set(void* cmd) {
-    static_cast<ValueType*>(cmd)->Init();
-    return NextCmdAddress<ValueType>(cmd);
-  }
-
-  gpu::CommandHeader header;
-};
-
-static_assert(sizeof(DestroyServer) == 4, "size of DestroyServer should be 4");
-static_assert(offsetof(DestroyServer, header) == 0,
-              "offset of DestroyServer header should be 0");
-
 #endif  // GPU_COMMAND_BUFFER_COMMON_WEBGPU_CMD_FORMAT_AUTOGEN_H_
diff --git a/gpu/command_buffer/common/webgpu_cmd_format_test_autogen.h b/gpu/command_buffer/common/webgpu_cmd_format_test_autogen.h
index ae13151..3234f419 100644
--- a/gpu/command_buffer/common/webgpu_cmd_format_test_autogen.h
+++ b/gpu/command_buffer/common/webgpu_cmd_format_test_autogen.h
@@ -112,13 +112,4 @@
   CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd));
 }
 
-TEST_F(WebGPUFormatTest, DestroyServer) {
-  cmds::DestroyServer& cmd = *GetBufferAs<cmds::DestroyServer>();
-  void* next_cmd = cmd.Set(&cmd);
-  EXPECT_EQ(static_cast<uint32_t>(cmds::DestroyServer::kCmdId),
-            cmd.header.command);
-  EXPECT_EQ(sizeof(cmd), cmd.header.size * 4u);
-  CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd));
-}
-
 #endif  // GPU_COMMAND_BUFFER_COMMON_WEBGPU_CMD_FORMAT_TEST_AUTOGEN_H_
diff --git a/gpu/command_buffer/common/webgpu_cmd_ids_autogen.h b/gpu/command_buffer/common/webgpu_cmd_ids_autogen.h
index 4241d0f..3e70690 100644
--- a/gpu/command_buffer/common/webgpu_cmd_ids_autogen.h
+++ b/gpu/command_buffer/common/webgpu_cmd_ids_autogen.h
@@ -16,8 +16,7 @@
   OP(AssociateMailboxImmediate) /* 257 */ \
   OP(DissociateMailbox)         /* 258 */ \
   OP(RequestAdapter)            /* 259 */ \
-  OP(RequestDevice)             /* 260 */ \
-  OP(DestroyServer)             /* 261 */
+  OP(RequestDevice)             /* 260 */
 
 enum CommandId {
   kOneBeforeStartPoint =
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index 99fe542..448bfc67 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -917,14 +917,6 @@
       sizeof(return_request_device_info)));
 }
 
-error::Error WebGPUDecoderImpl::HandleDestroyServer(
-    uint32_t immediate_data_size,
-    const volatile void* cmd_data) {
-  Destroy(false);
-
-  return error::kNoError;
-}
-
 error::Error WebGPUDecoderImpl::HandleRequestAdapter(
     uint32_t immediate_data_size,
     const volatile void* cmd_data) {
diff --git a/gpu/command_buffer/tests/webgpu_test.cc b/gpu/command_buffer/tests/webgpu_test.cc
index dd59e917..eb6b2fac 100644
--- a/gpu/command_buffer/tests/webgpu_test.cc
+++ b/gpu/command_buffer/tests/webgpu_test.cc
@@ -123,7 +123,7 @@
     RunPendingTasks();
   }
 
-  DawnProcTable procs = webgpu()->GetProcs();
+  DawnProcTable procs = webgpu()->GetAPIChannel()->GetProcs();
   dawnProcSetProcs(&procs);
 }
 
@@ -291,52 +291,6 @@
   EXPECT_TRUE(called);
 }
 
-TEST_F(WebGPUTest, RequestAdapterAfterServerDestroyed) {
-  if (!WebGPUSupported()) {
-    LOG(ERROR) << "Test skipped because WebGPU isn't supported";
-    return;
-  }
-
-  Initialize(WebGPUTest::Options());
-
-  webgpu()->DisconnectContextAndDestroyServer();
-
-  bool called = false;
-  webgpu()->RequestAdapterAsync(
-      webgpu::PowerPreference::kDefault,
-      base::BindOnce(
-          [](bool* called, int32_t adapter_id, const WGPUDeviceProperties&,
-             const char*) {
-            EXPECT_EQ(adapter_id, -1);
-            *called = true;
-          },
-          &called));
-  RunPendingTasks();
-  EXPECT_TRUE(called);
-}
-
-TEST_F(WebGPUTest, RequestDeviceAfterServerDestroyed) {
-  if (!WebGPUSupported()) {
-    LOG(ERROR) << "Test skipped because WebGPU isn't supported";
-    return;
-  }
-
-  Initialize(WebGPUTest::Options());
-
-  webgpu()->DisconnectContextAndDestroyServer();
-
-  bool called = false;
-  webgpu()->RequestDeviceAsync(GetAdapterId(), GetDeviceProperties(),
-                               base::BindOnce(
-                                   [](bool* called, WGPUDevice device) {
-                                     EXPECT_EQ(device, nullptr);
-                                     *called = true;
-                                   },
-                                   &called));
-  RunPendingTasks();
-  EXPECT_TRUE(called);
-}
-
 TEST_F(WebGPUTest, RequestDeviceWitUnsupportedExtension) {
   if (!WebGPUSupported()) {
     LOG(ERROR) << "Test skipped because WebGPU isn't supported";
diff --git a/gpu/command_buffer/webgpu_cmd_buffer_functions.txt b/gpu/command_buffer/webgpu_cmd_buffer_functions.txt
index 654ec8ff..8ac5f0da 100644
--- a/gpu/command_buffer/webgpu_cmd_buffer_functions.txt
+++ b/gpu/command_buffer/webgpu_cmd_buffer_functions.txt
@@ -11,4 +11,3 @@
 GL_APICALL void GL_APIENTRY wgDissociateMailbox (GLuint texture_id, GLuint texture_generation);
 GL_APICALL void GL_APIENTRY wgRequestAdapter (GLuint64 request_adapter_serial, EnumClassPowerPreference power_preference = PowerPreference::kDefault);
 GL_APICALL void GL_APIENTRY wgRequestDevice (GLuint64 request_device_serial, GLuint adapter_service_id, GLuint device_id, GLuint device_generation, const char* dawn_request_device_properties, size_t request_device_properties_size);
-GL_APICALL void GL_APIENTRY wgDestroyServer (void);
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index 7833e21b..5b055b08 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -3760,6 +3760,22 @@
       "features": [
         "disable_canvas_oop_rasterization"
       ]
+    },
+    {
+      "id": 379,
+      "cr_bugs": [1217298],
+      "description": "Disable VP9 HW encode on Intel Gen 9.5 SoCs using the i965 VA driver backend",
+      "os": {
+        "type": "chromeos"
+      },
+      "vendor_id": "0x8086",
+      "intel_gpu_series": [
+        "kabylake",
+        "geminilake"
+      ],
+      "features": [
+        "disable_accelerated_vp9_encode"
+      ]
     }
   ]
 }
diff --git a/gpu/config/gpu_workaround_list.txt b/gpu/config/gpu_workaround_list.txt
index ff63e95..e3ffc937 100644
--- a/gpu/config/gpu_workaround_list.txt
+++ b/gpu/config/gpu_workaround_list.txt
@@ -18,6 +18,7 @@
 disable_accelerated_vp8_decode
 disable_accelerated_vp8_encode
 disable_accelerated_vp9_decode
+disable_accelerated_vp9_encode
 disable_accelerated_vp9_profile2_decode
 disable_async_readpixels
 disable_av_sample_buffer_display_layer
diff --git a/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.h b/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.h
index 9ff08cb6..c57ab57 100644
--- a/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.h
+++ b/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.h
@@ -22,6 +22,9 @@
 // visible.
 - (void)recordDeviceOrientationChanged:(UIDeviceOrientation)orientation;
 
+// Record metrics for when the user has tapped on the feed preview.
+- (void)recordDiscoverFeedPreviewTapped;
+
 // Record metrics for when the user selects the 'Learn More' item in the feed
 // header menu.
 - (void)recordHeaderMenuLearnMoreTapped;
diff --git a/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.mm b/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.mm
index faf418d..6695184 100644
--- a/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.mm
+++ b/ios/chrome/browser/ui/content_suggestions/discover_feed_metrics_recorder.mm
@@ -107,6 +107,8 @@
     "ContentSuggestions.Feed.CardAction.ReportContent";
 const char kDiscoverFeedUserActionReportContentClosed[] =
     "ContentSuggestions.Feed.CardAction.ClosedReportContent";
+const char kDiscoverFeedUserActionPreviewTapped[] =
+    "ContentSuggestions.Feed.CardAction.TapPreview";
 
 // User action names for feed header menu.
 const char kDiscoverFeedUserActionManageActivityTapped[] =
@@ -225,6 +227,13 @@
   }
 }
 
+- (void)recordDiscoverFeedPreviewTapped {
+  [self recordDiscoverFeedUserActionHistogram:FeedUserActionType::
+                                                  kTappedDiscoverFeedPreview];
+  base::RecordAction(
+      base::UserMetricsAction(kDiscoverFeedUserActionPreviewTapped));
+}
+
 - (void)recordHeaderMenuLearnMoreTapped {
   [self recordDiscoverFeedUserActionHistogram:FeedUserActionType::
                                                   kTappedLearnMore];
diff --git a/ipc/ipc_sync_message_unittest.h b/ipc/ipc_sync_message_unittest.h
index da1699a7..924285b8 100644
--- a/ipc/ipc_sync_message_unittest.h
+++ b/ipc/ipc_sync_message_unittest.h
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// no-include-guard-because-multiply-included
+// Multiply-included message file, hence no include guard here.
+
 #include <string>
 
 #include "ipc/ipc_message_macros.h"
diff --git a/media/base/video_decoder.cc b/media/base/video_decoder.cc
index a593bb3..2133c50e06 100644
--- a/media/base/video_decoder.cc
+++ b/media/base/video_decoder.cc
@@ -4,7 +4,10 @@
 
 #include "media/base/video_decoder.h"
 
+#include <algorithm>
+
 #include "base/command_line.h"
+#include "base/cxx17_backports.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/system/sys_info.h"
 #include "media/base/limits.h"
@@ -56,9 +59,9 @@
   // zero threads; I.e., decoding will execute on the calling thread. Therefore,
   // at least two threads are required to allow decoding to progress outside of
   // each Decode() call.
-  return std::min(std::max(desired_threads,
-                           static_cast<int>(limits::kMinVideoDecodeThreads)),
-                  static_cast<int>(limits::kMaxVideoDecodeThreads));
+  return base::clamp(desired_threads,
+                     static_cast<int>(limits::kMinVideoDecodeThreads),
+                     static_cast<int>(limits::kMaxVideoDecodeThreads));
 }
 
 }  // namespace media
diff --git a/media/capture/video/chromeos/camera_app_device_impl.cc b/media/capture/video/chromeos/camera_app_device_impl.cc
index 5d9f8c5..df0d299 100644
--- a/media/capture/video/chromeos/camera_app_device_impl.cc
+++ b/media/capture/video/chromeos/camera_app_device_impl.cc
@@ -4,6 +4,8 @@
 
 #include "media/capture/video/chromeos/camera_app_device_impl.h"
 
+#include <cmath>
+
 #include "base/bind_post_task.h"
 #include "gpu/ipc/common/gpu_memory_buffer_impl.h"
 #include "media/base/bind_to_current_loop.h"
@@ -201,7 +203,8 @@
 }
 
 void CameraAppDeviceImpl::DetectDocumentCorners(
-    std::unique_ptr<gpu::GpuMemoryBufferImpl> gmb) {
+    std::unique_ptr<gpu::GpuMemoryBufferImpl> gmb,
+    VideoRotation rotation) {
   DCHECK(gmb);
   if (!gmb->Map()) {
     LOG(ERROR) << "Failed to map frame buffer";
@@ -238,7 +241,7 @@
         base::BindPostTask(
             mojo_task_runner_,
             base::BindOnce(&CameraAppDeviceImpl::OnDetectedDocumentCorners,
-                           weak_ptr_factory_for_mojo_.GetWeakPtr())));
+                           weak_ptr_factory_for_mojo_.GetWeakPtr(), rotation)));
   }
 }
 
@@ -455,6 +458,7 @@
 }
 
 void CameraAppDeviceImpl::OnDetectedDocumentCorners(
+    VideoRotation rotation,
     bool success,
     const std::vector<gfx::PointF>& corners) {
   base::AutoLock lock(document_corners_observer_lock_);
@@ -464,8 +468,33 @@
     return;
   }
 
+  // Rotate a point in coordination space {x: [0.0, 1.0], y: [0.0, 1.0]} with
+  // anchor point {x: 0.5, y: 0.5}.
+  auto rotate_corner = [&](const gfx::PointF& corner) -> gfx::PointF {
+    float x = base::clamp(corner.x(), 0.0f, 1.0f);
+    float y = base::clamp(corner.y(), 0.0f, 1.0f);
+
+    switch (rotation) {
+      case VIDEO_ROTATION_0:
+        return {x, y};
+      case VIDEO_ROTATION_90:
+        return {y, 1.0f - x};
+      case VIDEO_ROTATION_180:
+        return {1.0f - x, 1.0f - y};
+      case VIDEO_ROTATION_270:
+        return {1.0f - y, x};
+      default:
+        NOTREACHED();
+    }
+  };
+
+  std::vector<gfx::PointF> rotated_corners;
+  for (auto& corner : corners) {
+    rotated_corners.push_back(rotate_corner(corner));
+  }
+
   for (auto& observer : document_corners_observers_) {
-    observer.second->OnDocumentCornersUpdated(corners);
+    observer.second->OnDocumentCornersUpdated(rotated_corners);
   }
 }
 
diff --git a/media/capture/video/chromeos/camera_app_device_impl.h b/media/capture/video/chromeos/camera_app_device_impl.h
index 9d800e3..ee3719a 100644
--- a/media/capture/video/chromeos/camera_app_device_impl.h
+++ b/media/capture/video/chromeos/camera_app_device_impl.h
@@ -17,6 +17,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/synchronization/lock.h"
 #include "chromeos/components/camera_app_ui/document_scanner_service_client.h"
+#include "media/base/video_transformation.h"
 #include "media/capture/capture_export.h"
 #include "media/capture/mojom/image_capture.mojom.h"
 #include "media/capture/video/chromeos/mojom/camera3.mojom.h"
@@ -123,7 +124,8 @@
   bool ShouldDetectDocumentCorners();
 
   // Detect document corners on the frame given by its gpu memory buffer.
-  void DetectDocumentCorners(std::unique_ptr<gpu::GpuMemoryBufferImpl> gmb);
+  void DetectDocumentCorners(std::unique_ptr<gpu::GpuMemoryBufferImpl> gmb,
+                             VideoRotation rotation);
 
   // cros::mojom::CameraAppDevice implementations.
   void GetCameraInfo(GetCameraInfoCallback callback) override;
@@ -163,7 +165,8 @@
  private:
   static void DisableEeNr(ReprocessTask* task);
 
-  void OnDetectedDocumentCorners(bool success,
+  void OnDetectedDocumentCorners(VideoRotation rotation,
+                                 bool success,
                                  const std::vector<gfx::PointF>& corners);
 
   void OnMojoConnectionError();
diff --git a/media/capture/video/chromeos/request_manager.cc b/media/capture/video/chromeos/request_manager.cc
index 1de8c8d..980a498 100644
--- a/media/capture/video/chromeos/request_manager.cc
+++ b/media/capture/video/chromeos/request_manager.cc
@@ -970,17 +970,6 @@
             stream_type, buffer_ipc_id, &format);
     CHECK(buffer);
 
-    auto camera_app_device =
-        CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
-            device_id_);
-    if (camera_app_device && stream_type == StreamType::kPreviewOutput &&
-        camera_app_device->ShouldDetectDocumentCorners()) {
-      camera_app_device->DetectDocumentCorners(
-          stream_buffer_manager_->CreateGpuMemoryBuffer(
-              buffer->handle_provider->GetGpuMemoryBufferHandle(), format,
-              gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE));
-    }
-
     // TODO: Figure out the right color space for the camera frame.  We may need
     // to populate the camera metadata with the color space reported by the V4L2
     // device.
@@ -1008,6 +997,19 @@
       // All frames are pre-rotated to the display orientation.
       metadata.transformation = VIDEO_ROTATION_0;
     }
+
+    auto camera_app_device =
+        CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
+            device_id_);
+    if (camera_app_device && stream_type == StreamType::kPreviewOutput &&
+        camera_app_device->ShouldDetectDocumentCorners()) {
+      camera_app_device->DetectDocumentCorners(
+          stream_buffer_manager_->CreateGpuMemoryBuffer(
+              buffer->handle_provider->GetGpuMemoryBufferHandle(), format,
+              gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE),
+          metadata.transformation->rotation);
+    }
+
     device_context_->SubmitCapturedVideoCaptureBuffer(
         client_type, std::move(*buffer), format, pending_result.reference_time,
         pending_result.timestamp, metadata);
diff --git a/media/gpu/gpu_video_encode_accelerator_factory.cc b/media/gpu/gpu_video_encode_accelerator_factory.cc
index 4be2b053..21b82df 100644
--- a/media/gpu/gpu_video_encode_accelerator_factory.cc
+++ b/media/gpu/gpu_video_encode_accelerator_factory.cc
@@ -191,6 +191,13 @@
     });
   }
 
+  if (gpu_workarounds.disable_accelerated_vp9_encode) {
+    base::EraseIf(profiles, [](const auto& vea_profile) {
+      return vea_profile.profile >= VP9PROFILE_PROFILE0 &&
+             vea_profile.profile <= VP9PROFILE_PROFILE3;
+    });
+  }
+
   if (gpu_workarounds.disable_accelerated_h264_encode) {
     base::EraseIf(profiles, [](const auto& vea_profile) {
       return vea_profile.profile >= H264PROFILE_MIN &&
diff --git a/media/gpu/vaapi/vaapi_video_decoder.cc b/media/gpu/vaapi/vaapi_video_decoder.cc
index 6245147d..47fa4cb2 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.cc
+++ b/media/gpu/vaapi/vaapi_video_decoder.cc
@@ -35,6 +35,7 @@
 #include "media/gpu/vaapi/vp8_vaapi_video_decoder_delegate.h"
 #include "media/gpu/vaapi/vp9_vaapi_video_decoder_delegate.h"
 #include "media/media_buildflags.h"
+#include "ui/gfx/buffer_format_util.h"
 
 #if BUILDFLAG(ENABLE_PLATFORM_HEVC_DECODING)
 #include "media/gpu/vaapi/h265_vaapi_video_decoder_delegate.h"
@@ -223,21 +224,20 @@
 
   if (config.is_encrypted()) {
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
-    SetState(State::kError);
+    SetErrorState("encrypted content is not supported");
     std::move(init_cb).Run(StatusCode::kEncryptedContentUnsupported);
     return;
 #else
     if (!cdm_context || !cdm_context->GetChromeOsCdmContext()) {
-      LOG(ERROR) << "Cannot support encrypted stream w/out ChromeOsCdmContext";
-      SetState(State::kError);
+      SetErrorState("cannot support encrypted stream w/out ChromeOsCdmContext");
       std::move(init_cb).Run(StatusCode::kDecoderMissingCdmForEncryptedContent);
       return;
     }
     if (config.codec() != kCodecH264 && config.codec() != kCodecVP9 &&
         config.codec() != kCodecHEVC) {
-      VLOGF(1)
-          << "Vaapi decoder does not support this codec for encrypted content";
-      SetState(State::kError);
+      SetErrorState(
+          base::StringPrintf("%s is not supported for encrypted content",
+                             GetCodecName(config.codec()).c_str()));
       std::move(init_cb).Run(StatusCode::kEncryptedContentUnsupported);
       return;
     }
@@ -254,8 +254,7 @@
   } else if (config.codec() == kCodecHEVC &&
              !base::CommandLine::ForCurrentProcess()->HasSwitch(
                  switches::kEnableClearHevcForTesting)) {
-    DVLOG(1) << "Clear HEVC content is not supported";
-    SetState(State::kError);
+    SetErrorState("clear HEVC content is not supported");
     std::move(init_cb).Run(StatusCode::kClearContentUnsupported);
     return;
 #endif
@@ -278,9 +277,9 @@
   UMA_HISTOGRAM_BOOLEAN("Media.VaapiVideoDecoder.VaapiWrapperCreationSuccess",
                         vaapi_wrapper_.get());
   if (!vaapi_wrapper_.get()) {
-    VLOGF(1) << "Failed initializing VAAPI for profile "
-             << GetProfileName(profile);
-    SetState(State::kError);
+    SetErrorState(
+        base::StringPrintf("failed initializing VaapiWrapper for profile %s, ",
+                           GetProfileName(profile).c_str()));
     std::move(init_cb).Run(StatusCode::kDecoderUnsupportedProfile);
     return;
   }
@@ -292,7 +291,7 @@
                                       : config.encryption_scheme();
   auto accel_status = CreateAcceleratedVideoDecoder();
   if (!accel_status.is_ok()) {
-    SetState(State::kError);
+    SetErrorState("failed to create decoder delegate");
     std::move(init_cb).Run(std::move(accel_status));
     return;
   }
@@ -421,13 +420,11 @@
       SetState(State::kWaitingForOutput);
       break;
     case AcceleratedVideoDecoder::kNeedContextUpdate:
-      LOG(ERROR) << "Context updates not supported";
-      SetState(State::kError);
+      SetErrorState("context updates not supported");
       break;
     case AcceleratedVideoDecoder::kDecodeError:
-      LOG(ERROR) << "Error decoding stream";
       UMA_HISTOGRAM_BOOLEAN("Media.VaapiVideoDecoder.DecodeError", true);
-      SetState(State::kError);
+      SetErrorState("error decoding stream");
       break;
     case AcceleratedVideoDecoder::kTryAgain:
       DVLOG(1) << "Decoder going into the waiting for protected state";
@@ -486,16 +483,14 @@
     scoped_refptr<gfx::NativePixmap> pixmap =
         CreateNativePixmapDmaBuf(frame.get());
     if (!pixmap) {
-      LOG(ERROR) << "Failed to create NativePixmap from VideoFrame";
-      SetState(State::kError);
+      SetErrorState("failed to create NativePixmap from VideoFrame");
       return nullptr;
     }
 
     va_surface = vaapi_wrapper_->CreateVASurfaceForPixmap(std::move(pixmap),
                                                           transcryption_);
     if (!va_surface || va_surface->id() == VA_INVALID_ID) {
-      LOG(ERROR) << "Failed to create VASurface from VideoFrame";
-      SetState(State::kError);
+      SetErrorState("failed to create VASurface from VideoFrame");
       return nullptr;
     }
 
@@ -674,7 +669,7 @@
   const absl::optional<VideoPixelFormat> format =
       GetPixelFormatForBitDepth(bit_depth);
   if (!format) {
-    SetState(State::kError);
+    SetErrorState(base::StringPrintf("unsupported bit depth: %d", bit_depth));
     return;
   }
 
@@ -696,8 +691,7 @@
   gfx::Rect output_visible_rect = decoder_->GetVisibleRect();
   gfx::Size output_pic_size = decoder_->GetPicSize();
   if (output_pic_size.IsEmpty()) {
-    DLOG(ERROR) << "Empty picture size in decoder";
-    SetState(State::kError);
+    SetErrorState("|decoder_| returned an empty picture size");
     return;
   }
   const auto format_fourcc = Fourcc::FromVideoPixelFormat(*format);
@@ -764,7 +758,9 @@
           VideoPixelFormatToGfxBufferFormat(*format);
       if (!buffer_format) {
         decode_to_output_scale_factor_.reset();
-        SetState(State::kError);
+        SetErrorState(
+            base::StringPrintf("unsupported pixel format: %s",
+                               VideoPixelFormatToString(*format).c_str()));
         return;
       }
       const uint32_t va_fourcc =
@@ -773,7 +769,9 @@
           VaapiWrapper::BufferFormatToVARTFormat(*buffer_format);
       if (!va_fourcc || !va_rt_format) {
         decode_to_output_scale_factor_.reset();
-        SetState(State::kError);
+        SetErrorState(
+            base::StringPrintf("VA-API does not support: %s",
+                               gfx::BufferFormatToString(*buffer_format)));
         return;
       }
       const gfx::Size decoder_pic_size = decoder_->GetPicSize();
@@ -783,7 +781,7 @@
           /*visible_size=*/absl::nullopt, va_fourcc);
       if (scoped_va_surfaces.empty()) {
         decode_to_output_scale_factor_.reset();
-        SetState(State::kError);
+        SetErrorState("failed creating VASurfaces");
         return;
       }
 
@@ -796,8 +794,7 @@
   if (!frame_pool_->Initialize(
           *format_fourcc, output_pic_size, output_visible_rect, natural_size,
           decoder_->GetRequiredNumOfPictures(), !!cdm_context_ref_)) {
-    DLOG(WARNING) << "Failed Initialize()ing the frame pool.";
-    SetState(State::kError);
+    SetErrorState("failed Initialize()ing the frame pool");
     return;
   }
 
@@ -815,8 +812,7 @@
         base::BindRepeating(&ReportVaapiErrorToUMA,
                             "Media.VaapiVideoDecoder.VAAPIError"));
     if (!new_vaapi_wrapper.get()) {
-      DLOG(WARNING) << "Failed creating VaapiWrapper";
-      SetState(State::kError);
+      SetErrorState("failed (re)creating VaapiWrapper");
       return;
     }
     decoder_delegate_->set_vaapi_wrapper(new_vaapi_wrapper.get());
@@ -824,8 +820,7 @@
   }
 
   if (!vaapi_wrapper_->CreateContext(decoder_->GetPicSize())) {
-    VLOGF(1) << "Failed creating context";
-    SetState(State::kError);
+    SetErrorState("failed creating VAContext");
     return;
   }
 
@@ -898,8 +893,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!success) {
-    LOG(ERROR) << "Terminating decoding after failed protected update";
-    SetState(State::kError);
+    SetErrorState("terminating decoding after failed protected update");
     return;
   }
 
@@ -925,8 +919,7 @@
   // Flush will block until SurfaceReady() has been called for every frame
   // currently decoding.
   if (!decoder_->Flush()) {
-    LOG(ERROR) << "Failed to flush the decoder";
-    SetState(State::kError);
+    SetErrorState("failed to flush the decoder delegate");
     return;
   }
 
@@ -961,7 +954,7 @@
     // internal data structures.
     decoder_delegate_->OnVAContextDestructionSoon();
     if (!CreateAcceleratedVideoDecoder().is_ok()) {
-      SetState(State::kError);
+      SetErrorState("failed to (re)create decoder/delegate");
       std::move(reset_cb).Run();
       return;
     }
@@ -1091,13 +1084,18 @@
     case State::kError:
       ClearDecodeTaskQueue(DecodeStatus::DECODE_ERROR);
       break;
-    default:
-      NOTREACHED() << "Invalid state change";
   }
 
   state_ = state;
 }
 
+void VaapiVideoDecoder::SetErrorState(std::string message) {
+  LOG(ERROR) << message;
+  if (media_log_)
+    MEDIA_LOG(ERROR, media_log_) << "VaapiVideoDecoder: " << message;
+  SetState(State::kError);
+}
+
 void VaapiVideoDecoder::ReturnDecodeSurfaceToPool(
     std::unique_ptr<ScopedVASurface> surface,
     VASurfaceID) {
diff --git a/media/gpu/vaapi/vaapi_video_decoder.h b/media/gpu/vaapi/vaapi_video_decoder.h
index 5407010..15d52ec 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.h
+++ b/media/gpu/vaapi/vaapi_video_decoder.h
@@ -153,6 +153,10 @@
   // Change the current |state_| to the specified |state|.
   void SetState(State state);
 
+  // Tell SetState() to change the |state_| to kError and send |message| to
+  // MediaLog and to LOG(ERROR).
+  void SetErrorState(std::string message);
+
   // Callback for the CDM to notify |this|.
   void OnCdmContextEvent(CdmContext::Event event);
 
diff --git a/media/gpu/video_encode_accelerator_perf_tests.cc b/media/gpu/video_encode_accelerator_perf_tests.cc
index 839c276..3e2dc4d 100644
--- a/media/gpu/video_encode_accelerator_perf_tests.cc
+++ b/media/gpu/video_encode_accelerator_perf_tests.cc
@@ -398,31 +398,69 @@
 }
 
 // Video encode test class. Performs setup and teardown for each single test.
+// It measures the performance in encoding NV12 GpuMemoryBuffer based
+// VideoFrame.
 class VideoEncoderTest : public ::testing::Test {
  public:
-  // Create a new video encoder instance.
+  // Creates VideoEncoder for encoding NV12 GpuMemoryBuffer based VideoFrames.
+  // The input VideoFrames are provided every 1 / |encoder_rate| seconds if it
+  // is specified. Or they are provided as soon as the previous input VideoFrame
+  // is consumed by VideoEncoder. |measure_quality| measures SSIM and PSNR
+  // values of encoded bitstream comparing the original input VideoFrames.
   std::unique_ptr<VideoEncoder> CreateVideoEncoder(
-      Video* video,
-      VideoCodecProfile profile,
-      const media::VideoBitrateAllocation& bitrate,
-      uint32_t encoder_rate = 0) {
-    auto performance_evaluator = std::make_unique<PerformanceEvaluator>();
-    performance_evaluator_ = performance_evaluator.get();
+      absl::optional<uint32_t> encode_rate,
+      bool measure_quality) {
+    Video* video = g_env->GenerateNV12Video();
+    VideoCodecProfile profile = g_env->Profile();
+    const media::VideoBitrateAllocation& bitrate = g_env->Bitrate();
+
     std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors;
-    bitstream_processors.push_back(std::move(performance_evaluator));
-    return CreateVideoEncoderWithProcessors(
-        video, profile, bitrate, encoder_rate, std::move(bitstream_processors));
+    if (measure_quality) {
+      bitstream_processors =
+          CreateBitstreamProcessorsForQualityPerformance(video, profile);
+    } else {
+      auto performance_evaluator = std::make_unique<PerformanceEvaluator>();
+      performance_evaluator_ = performance_evaluator.get();
+      bitstream_processors.push_back(std::move(performance_evaluator));
+    }
+    LOG_ASSERT(!bitstream_processors.empty())
+        << "Failed to create bitstream processors";
+
+    VideoEncoderClientConfig config(video, profile, g_env->SpatialLayers(),
+                                    bitrate);
+    config.input_storage_type =
+        VideoEncodeAccelerator::Config::StorageType::kGpuMemoryBuffer;
+    config.num_frames_to_encode = kNumFramesToEncodeForPerformance;
+    if (encode_rate) {
+      config.encode_interval =
+          base::TimeDelta::FromSeconds(1u) / encode_rate.value();
+    }
+
+    auto video_encoder =
+        VideoEncoder::Create(config, g_env->GetGpuMemoryBufferFactory(),
+                             std::move(bitstream_processors));
+    LOG_ASSERT(video_encoder);
+    LOG_ASSERT(video_encoder->Initialize(video));
+
+    return video_encoder;
   }
 
-  // Create a new video encoder instance for quality performance tests.
-  std::unique_ptr<VideoEncoder> CreateVideoEncoderForQualityPerformance(
-      Video* video,
-      VideoCodecProfile profile,
-      const media::VideoBitrateAllocation& bitrate) {
+ protected:
+  PerformanceEvaluator* performance_evaluator_;
+  SSIMVideoFrameValidator* ssim_validator_;
+  PSNRVideoFrameValidator* psnr_validator_;
+
+ private:
+  // Create bitstream processors for quality performance tests.
+  std::vector<std::unique_ptr<BitstreamProcessor>>
+  CreateBitstreamProcessorsForQualityPerformance(Video* video,
+                                                 VideoCodecProfile profile) {
+    std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors;
+
     raw_data_helper_ = RawDataHelper::Create(video);
     if (!raw_data_helper_) {
       LOG(ERROR) << "Failed to create raw data helper";
-      return nullptr;
+      return bitstream_processors;
     }
 
     std::vector<std::unique_ptr<VideoFrameProcessor>> video_frame_processors;
@@ -455,34 +493,9 @@
         decoder_config, kNumFramesToEncodeForPerformance - 1,
         std::move(video_frame_processors));
     LOG_ASSERT(bitstream_validator);
-    std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors;
     bitstream_processors.push_back(std::move(bitstream_validator));
-    return CreateVideoEncoderWithProcessors(video, profile, bitrate,
-                                            /*encoder_rate=*/0,
-                                            std::move(bitstream_processors));
-  }
 
-  std::unique_ptr<VideoEncoder> CreateVideoEncoderWithProcessors(
-      Video* video,
-      VideoCodecProfile profile,
-      const media::VideoBitrateAllocation& bitrate,
-      uint32_t encoder_rate,
-      std::vector<std::unique_ptr<BitstreamProcessor>> bitstream_processors) {
-    LOG_ASSERT(video);
-    VideoEncoderClientConfig config(video, profile, g_env->SpatialLayers(),
-                                    bitrate);
-    config.num_frames_to_encode = kNumFramesToEncodeForPerformance;
-    if (encoder_rate != 0)
-      config.encode_interval =
-          base::TimeDelta::FromSeconds(/*secs=*/1u) / encoder_rate;
-
-    auto video_encoder =
-        VideoEncoder::Create(config, g_env->GetGpuMemoryBufferFactory(),
-                             std::move(bitstream_processors));
-    LOG_ASSERT(video_encoder);
-    LOG_ASSERT(video_encoder->Initialize(video));
-
-    return video_encoder;
+    return bitstream_processors;
   }
 
   scoped_refptr<const VideoFrame> GetModelFrame(size_t frame_index) {
@@ -492,10 +505,6 @@
   }
 
   std::unique_ptr<RawDataHelper> raw_data_helper_;
-
-  PerformanceEvaluator* performance_evaluator_;
-  SSIMVideoFrameValidator* ssim_validator_;
-  PSNRVideoFrameValidator* psnr_validator_;
 };
 
 }  // namespace
@@ -504,8 +513,8 @@
 // performance. This test will encode a video as fast as possible, and gives an
 // idea about the maximum output of the encoder.
 TEST_F(VideoEncoderTest, MeasureUncappedPerformance) {
-  auto encoder =
-      CreateVideoEncoder(g_env->Video(), g_env->Profile(), g_env->Bitrate());
+  auto encoder = CreateVideoEncoder(/*encode_rate=*/absl::nullopt,
+                                    /*measure_quality=*/false);
   encoder->SetEventWaitTimeout(kPerfEventTimeout);
 
   performance_evaluator_->StartMeasuring();
@@ -526,8 +535,8 @@
 // This test can be used to measure the cpu metrics during encoding.
 TEST_F(VideoEncoderTest, MeasureCappedPerformance) {
   const uint32_t kEncodeRate = 30;
-  auto encoder = CreateVideoEncoder(g_env->Video(), g_env->Profile(),
-                                    g_env->Bitrate(), kEncodeRate);
+  auto encoder = CreateVideoEncoder(/*encode_rate=*/kEncodeRate,
+                                    /*measure_quality=*/false);
   encoder->SetEventWaitTimeout(kPerfEventTimeout);
 
   performance_evaluator_->StartMeasuring();
@@ -544,8 +553,8 @@
 }
 
 TEST_F(VideoEncoderTest, MeasureProducedBitstreamQuality) {
-  auto encoder = CreateVideoEncoderForQualityPerformance(
-      g_env->Video(), g_env->Profile(), g_env->Bitrate());
+  auto encoder = CreateVideoEncoder(/*encode_rate=*/absl::nullopt,
+                                    /*measure_quality=*/true);
   encoder->SetEventWaitTimeout(kPerfEventTimeout);
 
   encoder->Encode();
diff --git a/media/mojo/mojom/video_encode_accelerator.mojom b/media/mojo/mojom/video_encode_accelerator.mojom
index a274f81..b1f7a69 100644
--- a/media/mojo/mojom/video_encode_accelerator.mojom
+++ b/media/mojo/mojom/video_encode_accelerator.mojom
@@ -178,14 +178,28 @@
   Flush() => (bool result);
 };
 
+// H264Metadata, Vp8Metadata, Vp9Metadata define mojo transport formats for
+// media::H264Metadata, media::Vp8Metadata and media::Vp9Metadata, respectively.
+// See the structures defined video_encode_accelerator.h for the descriptions of
+// the variables.
+// Either of them is filled in GPU process only in the case of temporal/spatial
+// SVC encoding. That is, none of them is filled in the case of non
+// temporal/spatial SVC encoding. Thus CodecMetadata is union and CodecMetadata
+// exists as optional in BitstreamBufferMetadata.
+// BitstreamBufferMetadata is metadata about a bitstream buffer produced by a
+// hardware encoder. The structure is passed from GPU process to renderer
+// process in BitstreamBufferReady() call.
+struct H264Metadata {
+  uint8 temporal_idx;
+  bool layer_sync;
+};
+
 struct Vp8Metadata {
   bool non_reference;
   uint8 temporal_idx;
   bool layer_sync;
 };
 
-// This defines a mojo transport format for media::Vp9Metadata.
-// See media::Vp9Metadata for the descriptions of the variables.
 struct Vp9Metadata {
   bool has_reference;
   bool temporal_up_switch;
@@ -200,6 +214,7 @@
 
 // Codec specific metadata.
 union CodecMetadata {
+  H264Metadata h264;
   Vp8Metadata vp8;
   Vp9Metadata vp9;
 };
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc b/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
index 57dec1c..10c3fd1 100644
--- a/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
+++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
@@ -116,6 +116,9 @@
     Read(media::mojom::CodecMetadataDataView data,
          media::BitstreamBufferMetadata* out) {
   switch (data.tag()) {
+    case media::mojom::CodecMetadataDataView::Tag::H264: {
+      return data.ReadH264(&out->h264);
+    }
     case media::mojom::CodecMetadataDataView::Tag::VP8: {
       return data.ReadVp8(&out->vp8);
     }
@@ -142,6 +145,15 @@
 }
 
 // static
+bool StructTraits<media::mojom::H264MetadataDataView, media::H264Metadata>::
+    Read(media::mojom::H264MetadataDataView data,
+         media::H264Metadata* out_metadata) {
+  out_metadata->temporal_idx = data.temporal_idx();
+  out_metadata->layer_sync = data.layer_sync();
+  return true;
+}
+
+// static
 bool StructTraits<media::mojom::Vp8MetadataDataView, media::Vp8Metadata>::Read(
     media::mojom::Vp8MetadataDataView data,
     media::Vp8Metadata* out_metadata) {
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits.h b/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
index 6133db3..99bf03b1 100644
--- a/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
+++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
@@ -74,7 +74,9 @@
                    media::BitstreamBufferMetadata> {
   static media::mojom::CodecMetadataDataView::Tag GetTag(
       const media::BitstreamBufferMetadata& metadata) {
-    if (metadata.vp8) {
+    if (metadata.h264) {
+      return media::mojom::CodecMetadataDataView::Tag::H264;
+    } else if (metadata.vp8) {
       return media::mojom::CodecMetadataDataView::Tag::VP8;
     } else if (metadata.vp9) {
       return media::mojom::CodecMetadataDataView::Tag::VP9;
@@ -84,14 +86,20 @@
   }
 
   static bool IsNull(const media::BitstreamBufferMetadata& metadata) {
-    return !metadata.vp8 && !metadata.vp9;
+    return !metadata.h264 && !metadata.vp8 && !metadata.vp9;
   }
 
   static void SetToNull(media::BitstreamBufferMetadata* metadata) {
+    metadata->h264.reset();
     metadata->vp8.reset();
     metadata->vp9.reset();
   }
 
+  static const media::H264Metadata& h264(
+      const media::BitstreamBufferMetadata& metadata) {
+    return *metadata.h264;
+  }
+
   static const media::Vp8Metadata& vp8(
       const media::BitstreamBufferMetadata& metadata) {
     return *metadata.vp8;
@@ -129,6 +137,21 @@
 };
 
 template <>
+class StructTraits<media::mojom::H264MetadataDataView, media::H264Metadata> {
+ public:
+  static uint8_t temporal_idx(const media::H264Metadata& vp8) {
+    return vp8.temporal_idx;
+  }
+
+  static bool layer_sync(const media::H264Metadata& vp8) {
+    return vp8.layer_sync;
+  }
+
+  static bool Read(media::mojom::H264MetadataDataView data,
+                   media::H264Metadata* out_metadata);
+};
+
+template <>
 class StructTraits<media::mojom::Vp8MetadataDataView, media::Vp8Metadata> {
  public:
   static bool non_reference(const media::Vp8Metadata& vp8) {
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc b/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc
index 373ff6c..0dbc1ca 100644
--- a/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc
+++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc
@@ -111,6 +111,17 @@
           input_metadata, output_metadata));
   EXPECT_EQ(input_metadata, output_metadata);
 
+  H264Metadata h264;
+  h264.temporal_idx = 1;
+  h264.layer_sync = true;
+  input_metadata.h264 = h264;
+  output_metadata = ::media::BitstreamBufferMetadata();
+  ASSERT_TRUE(
+      mojo::test::SerializeAndDeserialize<mojom::BitstreamBufferMetadata>(
+          input_metadata, output_metadata));
+  EXPECT_EQ(input_metadata, output_metadata);
+  input_metadata.h264.reset();
+
   Vp8Metadata vp8;
   vp8.non_reference = true;
   vp8.temporal_idx = 1;
diff --git a/media/mojo/services/mojo_video_encode_accelerator_service.cc b/media/mojo/services/mojo_video_encode_accelerator_service.cc
index 07ec05b4..c9a6035 100644
--- a/media/mojo/services/mojo_video_encode_accelerator_service.cc
+++ b/media/mojo/services/mojo_video_encode_accelerator_service.cc
@@ -63,6 +63,14 @@
     return;
   }
 
+  if (gpu_workarounds_.disable_accelerated_vp9_encode &&
+      config.output_profile >= VP9PROFILE_PROFILE0 &&
+      config.output_profile <= VP9PROFILE_PROFILE3) {
+    LOG(ERROR) << __func__ << " VP9 encoding disabled by GPU policy";
+    std::move(success_callback).Run(false);
+    return;
+  }
+
   if (encoder_) {
     DLOG(ERROR) << __func__ << " VEA is already initialized";
     std::move(success_callback).Run(false);
diff --git a/media/video/video_encode_accelerator.cc b/media/video/video_encode_accelerator.cc
index e513e22..81f8036 100644
--- a/media/video/video_encode_accelerator.cc
+++ b/media/video/video_encode_accelerator.cc
@@ -13,6 +13,10 @@
 
 namespace media {
 
+H264Metadata::H264Metadata() = default;
+H264Metadata::~H264Metadata() = default;
+H264Metadata::H264Metadata(const H264Metadata&) = default;
+
 Vp8Metadata::Vp8Metadata()
     : non_reference(false), temporal_idx(0), layer_sync(false) {}
 
@@ -187,6 +191,10 @@
       Bitrate::ConstantBitrate(bitrate_allocation.GetSumBps()), framerate);
 }
 
+bool operator==(const H264Metadata& l, const H264Metadata& r) {
+  return l.temporal_idx == r.temporal_idx && l.layer_sync == r.layer_sync;
+}
+
 bool operator==(const Vp8Metadata& l, const Vp8Metadata& r) {
   return l.non_reference == r.non_reference &&
          l.temporal_idx == r.temporal_idx && l.layer_sync == r.layer_sync;
diff --git a/media/video/video_encode_accelerator.h b/media/video/video_encode_accelerator.h
index 8afb378..7414091 100644
--- a/media/video/video_encode_accelerator.h
+++ b/media/video/video_encode_accelerator.h
@@ -30,6 +30,20 @@
 class BitstreamBuffer;
 class VideoFrame;
 
+//  Metadata for a H264 bitstream buffer.
+//  |temporal_idx|  indicates the temporal index for this frame.
+//  |layer_sync|    is true iff this frame has |temporal_idx| > 0 and does NOT
+//                  reference any reference buffer containing a frame with
+//                  temporal_idx > 0.
+struct MEDIA_EXPORT H264Metadata final {
+  H264Metadata();
+  ~H264Metadata();
+  H264Metadata(const H264Metadata&);
+
+  uint8_t temporal_idx = 0;
+  bool layer_sync = false;
+};
+
 //  Metadata for a VP8 bitstream buffer.
 //  |non_reference| is true iff this frame does not update any reference buffer,
 //                  meaning dropping this frame still results in a decodable
@@ -97,8 +111,9 @@
   bool key_frame;
   base::TimeDelta timestamp;
 
-  // Either |vp8| or |vp9| may be set, but not both of them. Presumably, it's
-  // also possible for none of them to be set.
+  // |h264|, |vp8| or |vp9| may be set, but not multiple of them. Presumably,
+  // it's also possible for none of them to be set.
+  absl::optional<H264Metadata> h264;
   absl::optional<Vp8Metadata> vp8;
   absl::optional<Vp9Metadata> vp9;
 };
diff --git a/media/video/video_encode_accelerator_adapter.cc b/media/video/video_encode_accelerator_adapter.cc
index 91c721a..f647948 100644
--- a/media/video/video_encode_accelerator_adapter.cc
+++ b/media/video/video_encode_accelerator_adapter.cc
@@ -450,7 +450,9 @@
   result.key_frame = metadata.key_frame;
   result.timestamp = metadata.timestamp;
   result.size = metadata.payload_size_bytes;
-  if (metadata.vp9.has_value())
+  if (metadata.h264.has_value())
+    result.temporal_id = metadata.h264.value().temporal_idx;
+  else if (metadata.vp9.has_value())
     result.temporal_id = metadata.vp9.value().temporal_idx;
   else if (metadata.vp8.has_value())
     result.temporal_id = metadata.vp8.value().temporal_idx;
diff --git a/media/video/video_encode_accelerator_adapter_test.cc b/media/video/video_encode_accelerator_adapter_test.cc
index baa9c4f..81add5e 100644
--- a/media/video/video_encode_accelerator_adapter_test.cc
+++ b/media/video/video_encode_accelerator_adapter_test.cc
@@ -225,6 +225,8 @@
           absl::optional<VideoEncoder::CodecDescription>) {
         if (output.timestamp == base::TimeDelta::FromMilliseconds(1))
           EXPECT_EQ(output.temporal_id, 1);
+        else if (output.timestamp == base::TimeDelta::FromMilliseconds(2))
+          EXPECT_EQ(output.temporal_id, 1);
         else
           EXPECT_EQ(output.temporal_id, 2);
         outputs_count++;
@@ -234,6 +236,9 @@
       [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) {
         BitstreamBufferMetadata result(1, keyframe, frame->timestamp());
         if (frame->timestamp() == base::TimeDelta::FromMilliseconds(1)) {
+          result.h264 = H264Metadata();
+          result.h264->temporal_idx = 1;
+        } else if (frame->timestamp() == base::TimeDelta::FromMilliseconds(2)) {
           result.vp8 = Vp8Metadata();
           result.vp8->temporal_idx = 1;
         } else {
@@ -249,11 +254,15 @@
                                  base::TimeDelta::FromMilliseconds(1));
   auto frame2 = CreateGreenFrame(options.frame_size, pixel_format,
                                  base::TimeDelta::FromMilliseconds(2));
+  auto frame3 = CreateGreenFrame(options.frame_size, pixel_format,
+                                 base::TimeDelta::FromMilliseconds(3));
   adapter()->Encode(frame1, true, ValidatingStatusCB());
   RunUntilIdle();
   adapter()->Encode(frame2, true, ValidatingStatusCB());
   RunUntilIdle();
-  EXPECT_EQ(outputs_count, 2);
+  adapter()->Encode(frame3, true, ValidatingStatusCB());
+  RunUntilIdle();
+  EXPECT_EQ(outputs_count, 3);
 }
 
 TEST_F(VideoEncodeAcceleratorAdapterTest, FlushDuringInitialize) {
diff --git a/mojo/public/cpp/bindings/struct_traits.h b/mojo/public/cpp/bindings/struct_traits.h
index a0f226ad..ca63042 100644
--- a/mojo/public/cpp/bindings/struct_traits.h
+++ b/mojo/public/cpp/bindings/struct_traits.h
@@ -98,22 +98,6 @@
 //      that case, an incoming null value is considered invalid and causes the
 //      message pipe to be disconnected.
 //
-//   4. [Optional] As mentioned above, getters for string/struct/array/map/union
-//      fields are called multiple times (twice to be exact). If you need to do
-//      some expensive calculation/conversion, you probably want to cache the
-//      result across multiple calls. You can introduce an arbitrary context
-//      object by adding two optional methods:
-//        static void* SetUpContext(const T& input);
-//        static void TearDownContext(const T& input, void* context);
-//
-//      And then you append a second parameter, void* context, to getters:
-//        static <return type> <field name>(const T& input, void* context);
-//
-//      If a T instance is not null, the serialization code will call
-//      SetUpContext() at the beginning, and pass the resulting context pointer
-//      to getters. After serialization is done, it calls TearDownContext() so
-//      that you can do any necessary cleanup.
-//
 // In the description above, methods having an |input| parameter define it as
 // const reference of T. Actually, it can be a non-const reference of T too.
 // E.g., if T contains Mojo handles or interfaces whose ownership needs to be
diff --git a/mojo/public/cpp/bindings/union_traits.h b/mojo/public/cpp/bindings/union_traits.h
index 243addd..97aa14ba 100644
--- a/mojo/public/cpp/bindings/union_traits.h
+++ b/mojo/public/cpp/bindings/union_traits.h
@@ -21,11 +21,10 @@
 //   1. Getters for each field in the Mojom type.
 //   2. Read() method.
 //   3. [Optional] IsNull() and SetToNull().
-//   4. [Optional] SetUpContext() and TearDownContext().
 // Please see the documentation of StructTraits for details of these methods.
 //
 // Unlike StructTraits, there is one more method to implement:
-//   5. A static GetTag() method indicating which field is the current active
+//   4. A static GetTag() method indicating which field is the current active
 //      field for serialization:
 //
 //        static DataViewType::Tag GetTag(const T& input);
diff --git a/mojo/public/tools/bindings/README.md b/mojo/public/tools/bindings/README.md
index bbc5e76..da427e4 100644
--- a/mojo/public/tools/bindings/README.md
+++ b/mojo/public/tools/bindings/README.md
@@ -448,7 +448,15 @@
   matching `value` in the list of `enabled_features`, the definition will be
   disabled. This is useful for mojom definitions that only make sense on one
   platform. Note that the `EnableIf` attribute can only be set once per
-  definition.
+  definition and cannot be set at the same time as `EnableIfNot`.
+
+* **`[EnableIfNot=value]`**:
+  The `EnableIfNot` attribute is used to conditionally enable definitions when
+  the mojom is parsed. If the `mojom` target in the GN file includes the
+  matching `value` in the list of `enabled_features`, the definition will be
+  disabled. This is useful for mojom definitions that only make sense on all but
+  one platform. Note that the `EnableIfNot` attribute can only be set once per
+  definition and cannot be set at the same time as `EnableIf`.
 
 * **`[ServiceSandbox=value]`**:
   The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a
diff --git a/mojo/public/tools/mojom/mojom/parse/conditional_features.py b/mojo/public/tools/mojom/mojom/parse/conditional_features.py
index 3cb73c5..ea253c31 100644
--- a/mojo/public/tools/mojom/mojom/parse/conditional_features.py
+++ b/mojo/public/tools/mojom/mojom/parse/conditional_features.py
@@ -17,8 +17,10 @@
 def _IsEnabled(definition, enabled_features):
   """Returns true if a definition is enabled.
 
-  A definition is enabled if it has no EnableIf attribute, or if the value of
-  the EnableIf attribute is in enabled_features.
+  A definition is enabled if it has no EnableIf/EnableIfNot attribute.
+  It is retained if it has an EnableIf attribute and the attribute is in
+  enabled_features. It is retained if it has an EnableIfNot attribute and the
+  attribute is not in enabled features.
   """
   if not hasattr(definition, "attribute_list"):
     return True
@@ -27,17 +29,19 @@
 
   already_defined = False
   for a in definition.attribute_list:
-    if a.key == 'EnableIf':
+    if a.key == 'EnableIf' or a.key == 'EnableIfNot':
       if already_defined:
         raise EnableIfError(
             definition.filename,
-            "EnableIf attribute may only be defined once per field.",
+            "EnableIf/EnableIfNot attribute may only be set once per field.",
             definition.lineno)
       already_defined = True
 
   for attribute in definition.attribute_list:
     if attribute.key == 'EnableIf' and attribute.value not in enabled_features:
       return False
+    if attribute.key == 'EnableIfNot' and attribute.value in enabled_features:
+      return False
   return True
 
 
diff --git a/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
index aa609be7..de5d48f3 100644
--- a/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
+++ b/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
@@ -55,6 +55,48 @@
     """
     self.parseAndAssertEqual(const_source, expected_source)
 
+  def testFilterIfNotConst(self):
+    """Test that Consts are correctly filtered."""
+    const_source = """
+      [EnableIfNot=blue]
+      const int kMyConst1 = 1;
+      [EnableIfNot=orange]
+      const double kMyConst2 = 2;
+      [EnableIf=blue,orange]
+      const int kMyConst3 = 3;
+      [EnableIfNot=blue,orange]
+      const int kMyConst4 = 4;
+      [EnableIfNot=purple,orange]
+      const int kMyConst5 = 5;
+    """
+    expected_source = """
+      [EnableIfNot=orange]
+      const double kMyConst2 = 2;
+      [EnableIf=blue,orange]
+      const int kMyConst3 = 3;
+      [EnableIfNot=purple,orange]
+      const int kMyConst5 = 5;
+    """
+    self.parseAndAssertEqual(const_source, expected_source)
+
+  def testFilterIfNotMultipleConst(self):
+    """Test that Consts are correctly filtered."""
+    const_source = """
+      [EnableIfNot=blue]
+      const int kMyConst1 = 1;
+      [EnableIfNot=orange,blue]
+      const double kMyConst2 = 2;
+      [EnableIfNot=orange,purple]
+      const int kMyConst3 = 3;
+    """
+    expected_source = """
+      [EnableIfNot=orange,blue]
+      const double kMyConst2 = 2;
+      [EnableIfNot=orange,purple]
+      const int kMyConst3 = 3;
+    """
+    self.parseAndAssertEqual(const_source, expected_source)
+
   def testFilterEnum(self):
     """Test that EnumValues are correctly filtered from an Enum."""
     enum_source = """
@@ -91,6 +133,24 @@
     """
     self.parseAndAssertEqual(import_source, expected_source)
 
+  def testFilterIfNotImport(self):
+    """Test that imports are correctly filtered from a Mojom."""
+    import_source = """
+      [EnableIf=blue]
+      import "foo.mojom";
+      [EnableIfNot=purple]
+      import "bar.mojom";
+      [EnableIfNot=green]
+      import "baz.mojom";
+    """
+    expected_source = """
+      [EnableIf=blue]
+      import "foo.mojom";
+      [EnableIfNot=purple]
+      import "bar.mojom";
+    """
+    self.parseAndAssertEqual(import_source, expected_source)
+
   def testFilterInterface(self):
     """Test that definitions are correctly filtered from an Interface."""
     interface_source = """
@@ -175,6 +235,50 @@
     """
     self.parseAndAssertEqual(struct_source, expected_source)
 
+  def testFilterIfNotStruct(self):
+    """Test that definitions are correctly filtered from a Struct."""
+    struct_source = """
+      struct MyStruct {
+        [EnableIf=blue]
+        enum MyEnum {
+          VALUE1,
+          [EnableIfNot=red]
+          VALUE2,
+        };
+        [EnableIfNot=yellow]
+        const double kMyConst = 1.23;
+        [EnableIf=green]
+        int32 a;
+        double b;
+        [EnableIfNot=purple]
+        int32 c;
+        [EnableIf=blue]
+        double d;
+        int32 e;
+        [EnableIfNot=red]
+        double f;
+      };
+    """
+    expected_source = """
+      struct MyStruct {
+        [EnableIf=blue]
+        enum MyEnum {
+          VALUE1,
+        };
+        [EnableIfNot=yellow]
+        const double kMyConst = 1.23;
+        [EnableIf=green]
+        int32 a;
+        double b;
+        [EnableIfNot=purple]
+        int32 c;
+        [EnableIf=blue]
+        double d;
+        int32 e;
+      };
+    """
+    self.parseAndAssertEqual(struct_source, expected_source)
+
   def testFilterUnion(self):
     """Test that UnionFields are correctly filtered from a Union."""
     union_source = """
@@ -228,6 +332,30 @@
                       conditional_features.RemoveDisabledDefinitions,
                       definition, ENABLED_FEATURES)
 
+  def testMultipleEnableIfs(self):
+    source = """
+      enum Foo {
+        [EnableIf=red,EnableIfNot=yellow]
+        kBarValue = 5,
+      };
+    """
+    definition = parser.Parse(source, "my_file.mojom")
+    self.assertRaises(conditional_features.EnableIfError,
+                      conditional_features.RemoveDisabledDefinitions,
+                      definition, ENABLED_FEATURES)
+
+  def testMultipleEnableIfs(self):
+    source = """
+      enum Foo {
+        [EnableIfNot=red,EnableIfNot=yellow]
+        kBarValue = 5,
+      };
+    """
+    definition = parser.Parse(source, "my_file.mojom")
+    self.assertRaises(conditional_features.EnableIfError,
+                      conditional_features.RemoveDisabledDefinitions,
+                      definition, ENABLED_FEATURES)
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/mojo/public/tools/mojom/mojom_parser.py b/mojo/public/tools/mojom/mojom_parser.py
index 07a7ba5..74beb077 100755
--- a/mojo/public/tools/mojom/mojom_parser.py
+++ b/mojo/public/tools/mojom/mojom_parser.py
@@ -274,7 +274,7 @@
     module_root_paths: A list of absolute filesystem paths which contain
         already-generated modules for any non-transitive dependencies.
     enabled_features: A list of enabled feature names, controlling which AST
-        nodes are filtered by [EnableIf] attributes.
+        nodes are filtered by [EnableIf] or [EnableIfNot] attributes.
     module_metadata: A list of 2-tuples representing metadata key-value pairs to
         attach to each compiled module output.
 
@@ -433,9 +433,9 @@
       help='Enables a named feature when parsing the given mojoms. Features '
       'are identified by arbitrary string values. Specifying this flag with a '
       'given FEATURE name will cause the parser to process any syntax elements '
-      'tagged with an [EnableIf=FEATURE] attribute. If this flag is not '
-      'provided for a given FEATURE, such tagged elements are discarded by the '
-      'parser and will not be present in the compiled output.')
+      'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this '
+      'flag is not provided for a given FEATURE, such tagged elements are '
+      'discarded by the parser and will not be present in the compiled output.')
   arg_parser.add_argument(
       '--check-imports',
       dest='build_metadata_filename',
diff --git a/pdf/pdfium/pdfium_page.cc b/pdf/pdfium/pdfium_page.cc
index 27ceeed..2d66b6f 100644
--- a/pdf/pdfium/pdfium_page.cc
+++ b/pdf/pdfium/pdfium_page.cc
@@ -14,6 +14,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/check_op.h"
+#include "base/cxx17_backports.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/numerics/math_constants.h"
 #include "base/numerics/safe_math.h"
@@ -933,7 +934,7 @@
   // If `x` < 0, scroll to the left side of the page.
   // If `x` > page width, scroll to the right side of the page.
   return TransformPageToScreenX(
-      std::max(std::min(x, FPDF_GetPageWidthF(GetPage())), 0.0f));
+      base::clamp(x, 0.0f, FPDF_GetPageWidthF(GetPage())));
 }
 
 float PDFiumPage::PreProcessAndTransformInPageCoordY(float y) {
diff --git a/ppapi/shared_impl/BUILD.gn b/ppapi/shared_impl/BUILD.gn
index 4118baa..5d1c48b 100644
--- a/ppapi/shared_impl/BUILD.gn
+++ b/ppapi/shared_impl/BUILD.gn
@@ -4,36 +4,135 @@
 
 import("//build/config/nacl/config.gni")
 
-component("shared_impl") {
-  output_name = "ppapi_shared"
+# //ppapi/shared_impl and //ppapi/thunk go into the same library.
+config("export_shared_impl_and_thunk") {
+  visibility = [
+    ":*",
+    "//ppapi/thunk:*",
+  ]
+  defines = [
+    "PPAPI_SHARED_IMPLEMENTATION",
+    "PPAPI_THUNK_IMPLEMENTATION",
+  ]
+}
+
+source_set("headers") {
+  visibility = [
+    ":*",
+    "//ppapi/thunk:*",
+  ]
+
+  sources = [
+    "host_resource.h",
+    "ppapi_globals.h",
+    "proxy_lock.h",
+    "resource.h",
+  ]
+
+  configs += [ ":export_shared_impl_and_thunk" ]
+
+  deps = [
+    "//base",
+    "//ppapi/c/",
+  ]
+}
+
+# This contains the things that //ppapi/thunk needs.
+source_set("common") {
+  visibility = [
+    ":*",
+    "//ppapi/thunk:*",
+  ]
 
   sources = [
     "array_var.cc",
     "array_var.h",
-    "array_writer.cc",
-    "array_writer.h",
     "callback_tracker.cc",
     "callback_tracker.h",
     "dictionary_var.cc",
     "dictionary_var.h",
+    "file_ref_create_info.cc",
+    "file_ref_create_info.h",
+    "host_resource.cc",
+    "id_assignment.cc",
+    "id_assignment.h",
+    "ppapi_globals.cc",
+    "ppb_audio_config_shared.cc",
+    "ppb_audio_config_shared.h",
+    "ppb_device_ref_shared.cc",
+    "ppb_device_ref_shared.h",
+    "ppb_flash_font_file_shared.h",
+    "ppb_image_data_shared.cc",
+    "ppb_image_data_shared.h",
+    "ppb_message_loop_shared.cc",
+    "ppb_message_loop_shared.h",
+    "proxy_lock.cc",
+    "resource.cc",
+    "resource_tracker.cc",
+    "resource_tracker.h",
+    "resource_var.cc",
+    "resource_var.h",
+    "scoped_pp_var.cc",
+    "scoped_pp_var.h",
+    "tracked_callback.cc",
+    "tracked_callback.h",
+    "url_response_info_data.cc",
+    "url_response_info_data.h",
+    "var.cc",
+    "var.h",
+    "var_tracker.cc",
+    "var_tracker.h",
+  ]
+
+  if (!is_nacl) {
+    sources += [
+      "ppb_url_util_shared.cc",
+      "ppb_url_util_shared.h",
+      "private/ppb_char_set_shared.cc",
+      "private/ppb_char_set_shared.h",
+    ]
+  }
+
+  configs += [ ":export_shared_impl_and_thunk" ]
+
+  public_deps = [ ":headers" ]
+
+  deps = [
+    "//base",
+    "//base:i18n",
+    "//build:chromeos_buildflags",
+    "//ppapi/c:c",
+    "//ppapi/thunk:headers",
+    "//third_party/icu:icuuc",
+  ]
+
+  if (!is_nacl) {
+    deps += [ "//skia" ]
+  }
+
+  if (!is_nacl_nonsfi) {
+    deps += [ "//url" ]
+  }
+}
+
+component("shared_impl") {
+  output_name = "ppapi_shared"
+
+  sources = [
+    "array_writer.cc",
+    "array_writer.h",
     "file_growth.cc",
     "file_growth.h",
     "file_io_state_manager.cc",
     "file_io_state_manager.h",
     "file_path.cc",
     "file_path.h",
-    "file_ref_create_info.cc",
-    "file_ref_create_info.h",
     "file_ref_util.cc",
     "file_ref_util.h",
     "file_system_util.cc",
     "file_system_util.h",
     "file_type_conversion.cc",
     "file_type_conversion.h",
-    "host_resource.cc",
-    "host_resource.h",
-    "id_assignment.cc",
-    "id_assignment.h",
     "media_stream_audio_track_shared.cc",
     "media_stream_audio_track_shared.h",
     "media_stream_buffer.h",
@@ -46,8 +145,6 @@
     "platform_file.cc",
     "platform_file.h",
     "ppapi_constants.h",
-    "ppapi_globals.cc",
-    "ppapi_globals.h",
     "ppapi_nacl_plugin_args.cc",
     "ppapi_nacl_plugin_args.h",
     "ppapi_permissions.cc",
@@ -56,27 +153,18 @@
     "ppapi_preferences.h",
     "ppapi_switches.cc",
     "ppapi_switches.h",
-    "ppb_audio_config_shared.cc",
-    "ppb_audio_config_shared.h",
     "ppb_audio_shared.cc",
     "ppb_audio_shared.h",
     "ppb_crypto_shared.cc",
-    "ppb_device_ref_shared.cc",
-    "ppb_device_ref_shared.h",
-    "ppb_flash_font_file_shared.h",
     "ppb_gamepad_shared.cc",
     "ppb_gamepad_shared.h",
     "ppb_graphics_3d_shared.cc",
     "ppb_graphics_3d_shared.h",
-    "ppb_image_data_shared.cc",
-    "ppb_image_data_shared.h",
     "ppb_input_event_shared.cc",
     "ppb_input_event_shared.h",
     "ppb_instance_shared.cc",
     "ppb_instance_shared.h",
     "ppb_memory_shared.cc",
-    "ppb_message_loop_shared.cc",
-    "ppb_message_loop_shared.h",
     "ppb_opengles2_shared.cc",
     "ppb_opengles2_shared.h",
     "ppb_tcp_socket_shared.cc",
@@ -89,34 +177,16 @@
     "ppb_view_shared.h",
     "ppp_instance_combined.cc",
     "ppp_instance_combined.h",
-    "proxy_lock.cc",
-    "proxy_lock.h",
-    "resource.cc",
-    "resource.h",
-    "resource_tracker.cc",
-    "resource_tracker.h",
-    "resource_var.cc",
-    "resource_var.h",
     "scoped_pp_resource.cc",
     "scoped_pp_resource.h",
-    "scoped_pp_var.cc",
-    "scoped_pp_var.h",
     "socket_option_data.cc",
     "socket_option_data.h",
     "thread_aware_callback.cc",
     "thread_aware_callback.h",
     "time_conversion.cc",
     "time_conversion.h",
-    "tracked_callback.cc",
-    "tracked_callback.h",
     "url_request_info_data.cc",
     "url_request_info_data.h",
-    "url_response_info_data.cc",
-    "url_response_info_data.h",
-    "var.cc",
-    "var.h",
-    "var_tracker.cc",
-    "var_tracker.h",
     "vpn_provider_util.cc",
     "vpn_provider_util.h",
 
@@ -130,12 +200,8 @@
 
   if (!is_nacl) {
     sources += [
-      "ppb_url_util_shared.cc",
-      "ppb_url_util_shared.h",
       "ppb_video_decoder_shared.cc",
       "ppb_video_decoder_shared.h",
-      "private/ppb_char_set_shared.cc",
-      "private/ppb_char_set_shared.h",
       "private/ppb_x509_util_shared.cc",
       "private/ppb_x509_util_shared.h",
     ]
@@ -153,15 +219,13 @@
     ]
   }
 
-  configs += [ "//build/config:precompiled_headers" ]
-  defines = [
-    "PPAPI_SHARED_IMPLEMENTATION",
-
-    # This target goes in the same library as thunk (in GYP they are the same).
-    "PPAPI_THUNK_IMPLEMENTATION",
+  configs += [
+    ":export_shared_impl_and_thunk",
+    "//build/config:precompiled_headers",
   ]
 
   public_deps = [
+    ":common",
     "//base",
     "//ppapi/c",
     "//ppapi/thunk",
@@ -181,19 +245,13 @@
   if (!is_nacl) {
     deps += [
       "//net",
-      "//skia",
       "//ui/events:events_base",
       "//ui/surface",
     ]
   }
 
   if (!is_nacl_nonsfi) {
-    deps += [
-      "//base:i18n",
-      "//base/third_party/dynamic_annotations",
-      "//third_party/icu:icuuc",
-      "//url",
-    ]
+    deps += [ "//base/third_party/dynamic_annotations" ]
   }
 
   if (is_mac) {
diff --git a/ppapi/thunk/BUILD.gn b/ppapi/thunk/BUILD.gn
index b353f80..cd0d1da 100644
--- a/ppapi/thunk/BUILD.gn
+++ b/ppapi/thunk/BUILD.gn
@@ -2,17 +2,13 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-source_set("thunk") {
-  # In GYP this is the same target as shared_impl. In GN these are split apart
-  # for clarity but to get component builds correct, targets must only depend
-  # on these via the shared_impl component.
-  # TODO(brettw) separate these when GYP compat is no longer required.
-  visibility = [ "//ppapi/shared_impl" ]
-
+source_set("headers") {
+  visibility = [
+    ":thunk",
+    "//ppapi/shared_impl:common",
+  ]
   sources = [
-    "enter.cc",
     "enter.h",
-    "interfaces_legacy.h",
     "interfaces_postamble.h",
     "interfaces_ppb_private.h",
     "interfaces_ppb_private_flash.h",
@@ -23,10 +19,33 @@
     "interfaces_ppb_public_stable.h",
     "interfaces_preamble.h",
     "ppapi_thunk_export.h",
+    "ppb_audio_config_api.h",
+    "ppb_device_ref_api.h",
+    "ppb_instance_api.h",
+    "ppb_message_loop_api.h",
+    "resource_creation_api.h",
+    "thunk.h",
+  ]
+
+  configs += [ "//ppapi/shared_impl:export_shared_impl_and_thunk" ]
+
+  deps = [
+    "//base",
+    "//gpu/command_buffer/common",
+    "//ppapi/c",
+    "//ppapi/shared_impl:headers",
+  ]
+}
+
+source_set("thunk") {
+  visibility = [ "//ppapi/shared_impl" ]
+
+  sources = [
+    "enter.cc",
+    "interfaces_legacy.h",
     "ppb_audio_api.h",
     "ppb_audio_buffer_api.h",
     "ppb_audio_buffer_thunk.cc",
-    "ppb_audio_config_api.h",
     "ppb_audio_config_thunk.cc",
     "ppb_audio_input_api.h",
     "ppb_audio_output_api.h",
@@ -40,7 +59,6 @@
     "ppb_camera_device_private_thunk.cc",
     "ppb_console_thunk.cc",
     "ppb_cursor_control_thunk.cc",
-    "ppb_device_ref_api.h",
     "ppb_device_ref_dev_thunk.cc",
     "ppb_ext_crx_file_system_private_thunk.cc",
     "ppb_file_chooser_api.h",
@@ -71,7 +89,6 @@
     "ppb_image_data_thunk.cc",
     "ppb_input_event_api.h",
     "ppb_input_event_thunk.cc",
-    "ppb_instance_api.h",
     "ppb_instance_private_thunk.cc",
     "ppb_instance_thunk.cc",
     "ppb_isolated_file_system_private_api.h",
@@ -80,7 +97,6 @@
     "ppb_media_stream_audio_track_thunk.cc",
     "ppb_media_stream_video_track_api.h",
     "ppb_media_stream_video_track_thunk.cc",
-    "ppb_message_loop_api.h",
     "ppb_messaging_thunk.cc",
     "ppb_mouse_cursor_thunk.cc",
     "ppb_mouse_lock_thunk.cc",
@@ -132,8 +148,6 @@
     "ppb_vpn_provider_thunk.cc",
     "ppb_websocket_api.h",
     "ppb_websocket_thunk.cc",
-    "resource_creation_api.h",
-    "thunk.h",
   ]
 
   if (!is_nacl) {
@@ -169,14 +183,17 @@
     ]
   }
 
-  configs += [ "//build/config:precompiled_headers" ]
-  defines = [
-    # This target goes in the same library as shared_impl (in GYP they are the
-    # same).
-    "PPAPI_SHARED_IMPLEMENTATION",
-
-    "PPAPI_THUNK_IMPLEMENTATION",
+  configs += [
+    "//ppapi/shared_impl:export_shared_impl_and_thunk",
+    "//build/config:precompiled_headers",
   ]
 
-  public_deps = [ "//base" ]
+  deps = [
+    "//base",
+    "//gpu/command_buffer/common",
+    "//ppapi/c",
+    "//ppapi/shared_impl:common",
+    "//ppapi/shared_impl:headers",
+  ]
+  public_deps = [ ":headers" ]
 }
diff --git a/ppapi/thunk/enter.cc b/ppapi/thunk/enter.cc
index b03ca24..b774cdd 100644
--- a/ppapi/thunk/enter.cc
+++ b/ppapi/thunk/enter.cc
@@ -10,6 +10,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/lock.h"
 #include "ppapi/shared_impl/ppapi_globals.h"
+#include "ppapi/shared_impl/resource_tracker.h"
 #include "ppapi/shared_impl/tracked_callback.h"
 #include "ppapi/thunk/ppb_instance_api.h"
 #include "ppapi/thunk/resource_creation_api.h"
diff --git a/ppapi/thunk/enter.h b/ppapi/thunk/enter.h
index 25e7935..470ecd2 100644
--- a/ppapi/thunk/enter.h
+++ b/ppapi/thunk/enter.h
@@ -14,14 +14,15 @@
 #include "ppapi/shared_impl/ppapi_globals.h"
 #include "ppapi/shared_impl/proxy_lock.h"
 #include "ppapi/shared_impl/resource.h"
-#include "ppapi/shared_impl/resource_tracker.h"
 #include "ppapi/shared_impl/singleton_resource_id.h"
-#include "ppapi/shared_impl/tracked_callback.h"
 #include "ppapi/thunk/ppapi_thunk_export.h"
 #include "ppapi/thunk/ppb_instance_api.h"
 #include "ppapi/thunk/resource_creation_api.h"
 
 namespace ppapi {
+
+class TrackedCallback;
+
 namespace thunk {
 
 // Enter* helper objects: These objects wrap a call from the C PPAPI into
diff --git a/ppapi/thunk/ppb_instance_api.h b/ppapi/thunk/ppb_instance_api.h
index 7b8c6b2..157dc86 100644
--- a/ppapi/thunk/ppb_instance_api.h
+++ b/ppapi/thunk/ppb_instance_api.h
@@ -22,7 +22,6 @@
 #include "ppapi/c/ppb_text_input_controller.h"
 #include "ppapi/c/private/ppb_instance_private.h"
 #include "ppapi/shared_impl/api_id.h"
-#include "ppapi/shared_impl/resource.h"
 #include "ppapi/shared_impl/singleton_resource_id.h"
 
 // Windows headers interfere with this file.
diff --git a/ppapi/thunk/resource_creation_api.h b/ppapi/thunk/resource_creation_api.h
index eeef46e..30a2681 100644
--- a/ppapi/thunk/resource_creation_api.h
+++ b/ppapi/thunk/resource_creation_api.h
@@ -25,7 +25,6 @@
 #include "ppapi/c/ppb_websocket.h"
 #include "ppapi/c/private/pp_private_font_charset.h"
 #include "ppapi/shared_impl/api_id.h"
-#include "ppapi/shared_impl/ppb_image_data_shared.h"
 
 // Windows defines 'PostMessage', so we have to undef it.
 #ifdef PostMessage
diff --git a/remoting/host/installer/mac/uninstaller/remoting_uninstaller.h b/remoting/host/installer/mac/uninstaller/remoting_uninstaller.h
index 3a3b8ca..365a8a6e 100644
--- a/remoting/host/installer/mac/uninstaller/remoting_uninstaller.h
+++ b/remoting/host/installer/mac/uninstaller/remoting_uninstaller.h
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifndef REMOTING_HOST_INSTALLER_MAC_UNINSTALLER_REMOTING_UNINSTALLER_H_
+#define REMOTING_HOST_INSTALLER_MAC_UNINSTALLER_REMOTING_UNINSTALLER_H_
+
 #import <Cocoa/Cocoa.h>
 
 @interface RemotingUninstaller : NSObject {
@@ -10,3 +13,5 @@
 - (OSStatus)remotingUninstall;
 
 @end
+
+#endif  // REMOTING_HOST_INSTALLER_MAC_UNINSTALLER_REMOTING_UNINSTALLER_H_
\ No newline at end of file
diff --git a/remoting/host/installer/mac/uninstaller/remoting_uninstaller_app.h b/remoting/host/installer/mac/uninstaller/remoting_uninstaller_app.h
index 5346c6d..880a75a9 100644
--- a/remoting/host/installer/mac/uninstaller/remoting_uninstaller_app.h
+++ b/remoting/host/installer/mac/uninstaller/remoting_uninstaller_app.h
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifndef REMOTING_HOST_INSTALLER_MAC_UNINSTALLER_REMOTING_UNINSTALLER_APP_H_
+#define REMOTING_HOST_INSTALLER_MAC_UNINSTALLER_REMOTING_UNINSTALLER_APP_H_
+
 #import <Cocoa/Cocoa.h>
 
 @interface RemotingUninstallerAppDelegate : NSObject {
@@ -12,3 +15,5 @@
 
 - (IBAction)handleMenuClose:(NSMenuItem*)sender;
 @end
+
+#endif  // REMOTING_HOST_INSTALLER_MAC_UNINSTALLER_REMOTING_UNINSTALLER_APP_H_
\ No newline at end of file
diff --git a/remoting/tools/winext/manifest/rdp.h b/remoting/tools/winext/manifest/rdp.h
index 698af9a8..63ab638 100644
--- a/remoting/tools/winext/manifest/rdp.h
+++ b/remoting/tools/winext/manifest/rdp.h
@@ -13,6 +13,9 @@
 module MSTSCAX.DLL:
 category rdp:
 
+#ifndef REMOTING_TOOLS_WINEXT_MANIFEST_RDP_H_
+#define REMOTING_TOOLS_WINEXT_MANIFEST_RDP_H_
+
 //
 // GUIDs
 //
@@ -846,3 +849,5 @@
 };
 
 HRESULT DllGetClassObject(REFCLSID rclsid, [iid] REFIID riid, [out] COM_INTERFACE_PTR* ppv);
+
+#endif  // REMOTING_TOOLS_WINEXT_MANIFEST_RDP_H_
\ No newline at end of file
diff --git a/services/device/BUILD.gn b/services/device/BUILD.gn
index d1956023..3468be4 100644
--- a/services/device/BUILD.gn
+++ b/services/device/BUILD.gn
@@ -356,8 +356,7 @@
   }
 
   # UsbContext is a libusb-specific object.
-  # TODO(https://crbug.com/1096743) Remove these tests.
-  if (is_mac) {
+  if (is_mac || is_win) {
     sources += [ "usb/usb_context_unittest.cc" ]
     deps += [ "//third_party/libusb" ]
   }
diff --git a/services/device/usb/BUILD.gn b/services/device/usb/BUILD.gn
index bb40dc9..348e922 100644
--- a/services/device/usb/BUILD.gn
+++ b/services/device/usb/BUILD.gn
@@ -62,14 +62,10 @@
       "usb_service_android.cc",
       "usb_service_android.h",
     ]
-
-    deps += [ ":jni_headers" ]
   }
 
   if (is_win) {
     sources += [
-      "scoped_winusb_handle.cc",
-      "scoped_winusb_handle.h",
       "usb_device_handle_win.cc",
       "usb_device_handle_win.h",
       "usb_device_win.cc",
@@ -77,13 +73,6 @@
       "usb_service_win.cc",
       "usb_service_win.h",
     ]
-
-    libs = [
-      "setupapi.lib",
-      "winusb.lib",
-    ]
-
-    deps += [ "//third_party/re2" ]
   }
 
   if (is_mac) {
@@ -95,9 +84,30 @@
       "usb_service_mac.cc",
       "usb_service_mac.h",
     ]
+  }
 
-    # These sources and deps are required for libusb.
-    # TODO(https://crbug.com/1096743) Remove these sources.
+  if (is_linux || is_chromeos) {
+    sources += [
+      "usb_device_linux.cc",
+      "usb_device_linux.h",
+    ]
+  }
+
+  if (use_udev) {
+    if (is_linux || is_chromeos) {
+      sources += [
+        "usb_service_linux.cc",
+        "usb_service_linux.h",
+      ]
+    }
+    deps += [ "//device/udev_linux" ]
+  }
+
+  if (is_android) {
+    deps += [ ":jni_headers" ]
+  }
+
+  if (is_win || is_mac) {
     sources += [
       "scoped_libusb_device_handle.cc",
       "scoped_libusb_device_handle.h",
@@ -118,21 +128,18 @@
     deps += [ "//third_party/libusb" ]
   }
 
-  if (is_linux || is_chromeos) {
+  if (is_win) {
     sources += [
-      "usb_device_linux.cc",
-      "usb_device_linux.h",
+      "scoped_winusb_handle.cc",
+      "scoped_winusb_handle.h",
     ]
-  }
 
-  if (use_udev) {
-    if (is_linux || is_chromeos) {
-      sources += [
-        "usb_service_linux.cc",
-        "usb_service_linux.h",
-      ]
-    }
-    deps += [ "//device/udev_linux" ]
+    libs = [
+      "setupapi.lib",
+      "winusb.lib",
+    ]
+
+    deps += [ "//third_party/re2" ]
   }
 
   if (is_android || is_chromeos || is_linux) {
diff --git a/services/device/usb/usb_context_unittest.cc b/services/device/usb/usb_context_unittest.cc
index 0148827e..6f1faee 100644
--- a/services/device/usb/usb_context_unittest.cc
+++ b/services/device/usb/usb_context_unittest.cc
@@ -5,6 +5,7 @@
 #include "services/device/usb/usb_context.h"
 #include "base/macros.h"
 #include "base/threading/platform_thread.h"
+#include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/libusb/src/libusb/libusb.h"
 
@@ -27,7 +28,17 @@
 
 }  // namespace
 
-TEST_F(UsbContextTest, GracefulShutdown) {
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+// Linux trybot does not support usb.
+#define MAYBE_GracefulShutdown DISABLED_GracefulShutdown
+#elif defined(OS_ANDROID)
+// Android build does not include usb support.
+#define MAYBE_GracefulShutdown DISABLED_GracefulShutdown
+#else
+#define MAYBE_GracefulShutdown GracefulShutdown
+#endif
+
+TEST_F(UsbContextTest, MAYBE_GracefulShutdown) {
   base::TimeTicks start = base::TimeTicks::Now();
   {
     PlatformUsbContext platform_context;
diff --git a/services/device/usb/usb_service.cc b/services/device/usb/usb_service.cc
index 7c23cce..f3a8da62 100644
--- a/services/device/usb/usb_service.cc
+++ b/services/device/usb/usb_service.cc
@@ -23,12 +23,14 @@
 #include "services/device/usb/usb_service_android.h"
 #elif defined(USE_UDEV)
 #include "services/device/usb/usb_service_linux.h"
-#elif defined(OS_MAC)
-#include "services/device/usb/usb_service_impl.h"
+#else
+#if defined(OS_MAC)
 #include "services/device/usb/usb_service_mac.h"
 #elif defined(OS_WIN)
 #include "services/device/usb/usb_service_win.h"
 #endif
+#include "services/device/usb/usb_service_impl.h"
+#endif
 
 namespace device {
 
@@ -53,7 +55,10 @@
 #elif defined(USE_UDEV)
   return base::WrapUnique(new UsbServiceLinux());
 #elif defined(OS_WIN)
-  return base::WrapUnique(new UsbServiceWin());
+  if (base::FeatureList::IsEnabled(kNewUsbBackend))
+    return base::WrapUnique(new UsbServiceWin());
+  else
+    return base::WrapUnique(new UsbServiceImpl());
 #elif defined(OS_MAC)
   if (base::FeatureList::IsEnabled(kNewUsbBackend))
     return base::WrapUnique(new UsbServiceMac());
diff --git a/services/device/usb/usb_service_impl.cc b/services/device/usb/usb_service_impl.cc
index 74bfe41d..0b38f32 100644
--- a/services/device/usb/usb_service_impl.cc
+++ b/services/device/usb/usb_service_impl.cc
@@ -31,6 +31,16 @@
 #include "services/device/usb/webusb_descriptors.h"
 #include "third_party/libusb/src/libusb/libusb.h"
 
+#if defined(OS_WIN)
+#define INITGUID
+#include <devpkey.h>
+#include <setupapi.h>
+#include <usbiodef.h>
+
+#include "base/strings/string_util.h"
+#include "device/base/device_info_query_win.h"
+#endif  // OS_WIN
+
 namespace device {
 
 namespace {
@@ -38,6 +48,42 @@
 // Standard USB requests and descriptor types:
 const uint16_t kUsbVersion2_1 = 0x0210;
 
+#if defined(OS_WIN)
+
+bool IsWinUsbInterface(const std::wstring& device_path) {
+  DeviceInfoQueryWin device_info_query;
+  if (!device_info_query.device_info_list_valid()) {
+    USB_PLOG(ERROR) << "Failed to create a device information set";
+    return false;
+  }
+
+  // This will add the device so we can query driver info.
+  if (!device_info_query.AddDevice(device_path)) {
+    USB_PLOG(ERROR) << "Failed to get device interface data for "
+                    << device_path;
+    return false;
+  }
+
+  if (!device_info_query.GetDeviceInfo()) {
+    USB_PLOG(ERROR) << "Failed to get device info for " << device_path;
+    return false;
+  }
+
+  std::string buffer;
+  if (!device_info_query.GetDeviceStringProperty(DEVPKEY_Device_Service,
+                                                 &buffer)) {
+    USB_PLOG(ERROR) << "Failed to get device service property";
+    return false;
+  }
+
+  USB_LOG(DEBUG) << "Driver for " << device_path << " is " << buffer << ".";
+  if (base::StartsWith(buffer, "WinUSB", base::CompareCase::INSENSITIVE_ASCII))
+    return true;
+  return false;
+}
+
+#endif  // OS_WIN
+
 scoped_refptr<UsbContext> InitializeUsbContextBlocking() {
   PlatformUsbContext platform_context = nullptr;
   int rv = libusb_init(&platform_context);
@@ -51,10 +97,21 @@
 }
 
 absl::optional<std::vector<ScopedLibusbDeviceRef>> GetDeviceListBlocking(
+    const std::wstring& new_device_path,
     scoped_refptr<UsbContext> usb_context) {
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                 base::BlockingType::MAY_BLOCK);
 
+#if defined(OS_WIN)
+  if (!new_device_path.empty()) {
+    if (!IsWinUsbInterface(new_device_path)) {
+      // Wait to call libusb_get_device_list until libusb will be able to find
+      // a WinUSB interface for the device.
+      return absl::nullopt;
+    }
+  }
+#endif  // defined(OS_WIN)
+
   libusb_device** platform_devices = NULL;
   const ssize_t device_count =
       libusb_get_device_list(usb_context->context(), &platform_devices);
@@ -184,7 +241,7 @@
 
 UsbServiceImpl::~UsbServiceImpl() {
   NotifyWillDestroyUsbService();
-  if (context_)
+  if (hotplug_enabled_)
     libusb_hotplug_deregister_callback(context_->context(), hotplug_handle_);
 }
 
@@ -198,14 +255,40 @@
     return;
   }
 
-  if (enumeration_in_progress_) {
+  if (hotplug_enabled_ && !enumeration_in_progress_) {
+    // The device list is updated live when hotplug events are supported.
+    UsbService::GetDevices(std::move(callback));
+  } else {
     pending_enumeration_callbacks_.push_back(std::move(callback));
-    return;
+    RefreshDevices();
   }
-
-  UsbService::GetDevices(std::move(callback));
 }
 
+#if defined(OS_WIN)
+
+void UsbServiceImpl::OnDeviceAdded(const GUID& class_guid,
+                                   const std::wstring& device_path) {
+  // Only the root node of a composite USB device has the class GUID
+  // GUID_DEVINTERFACE_USB_DEVICE but we want to wait until WinUSB is loaded.
+  // This first pass filter will catch anything that's sitting on the USB bus
+  // (including devices on 3rd party USB controllers) to avoid the more
+  // expensive driver check that needs to be done on the FILE thread.
+  if (device_path.find(L"usb") != std::wstring::npos) {
+    pending_path_enumerations_.push(device_path);
+    RefreshDevices();
+  }
+}
+
+void UsbServiceImpl::OnDeviceRemoved(const GUID& class_guid,
+                                     const std::wstring& device_path) {
+  // The root USB device node is removed last.
+  if (class_guid == GUID_DEVINTERFACE_USB_DEVICE) {
+    RefreshDevices();
+  }
+}
+
+#endif  // OS_WIN
+
 void UsbServiceImpl::OnUsbContext(scoped_refptr<UsbContext> context) {
   if (!context) {
     usb_unavailable_ = true;
@@ -221,27 +304,37 @@
       static_cast<libusb_hotplug_flag>(0), LIBUSB_HOTPLUG_MATCH_ANY,
       LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
       &UsbServiceImpl::HotplugCallback, this, &hotplug_handle_);
-  if (rv != LIBUSB_SUCCESS) {
-    usb_unavailable_ = true;
-    context_.reset();
-    return;
-  }
+  if (rv == LIBUSB_SUCCESS)
+    hotplug_enabled_ = true;
 
   // This will call any enumeration callbacks queued while initializing.
   RefreshDevices();
+
+#if defined(OS_WIN)
+  DeviceMonitorWin* device_monitor = DeviceMonitorWin::GetForAllInterfaces();
+  if (device_monitor)
+    device_observation_.Observe(device_monitor);
+#endif  // OS_WIN
 }
 
 void UsbServiceImpl::RefreshDevices() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(context_);
-  DCHECK(!enumeration_in_progress_);
+
+  if (!context_ || enumeration_in_progress_)
+    return;
 
   enumeration_in_progress_ = true;
   DCHECK(devices_being_enumerated_.empty());
 
+  std::wstring device_path;
+  if (!pending_path_enumerations_.empty()) {
+    device_path = pending_path_enumerations_.front();
+    pending_path_enumerations_.pop();
+  }
+
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, kBlockingTaskTraits,
-      base::BindOnce(&GetDeviceListBlocking, context_),
+      base::BindOnce(&GetDeviceListBlocking, device_path, context_),
       base::BindOnce(&UsbServiceImpl::OnDeviceList,
                      weak_factory_.GetWeakPtr()));
 }
@@ -322,6 +415,10 @@
     for (GetDevicesCallback& callback : callbacks)
       std::move(callback).Run(result);
   }
+
+  if (!pending_path_enumerations_.empty()) {
+    RefreshDevices();
+  }
 }
 
 void UsbServiceImpl::EnumerateDevice(ScopedLibusbDeviceRef platform_device,
diff --git a/services/device/usb/usb_service_impl.h b/services/device/usb/usb_service_impl.h
index fcb24e4..21edd890 100644
--- a/services/device/usb/usb_service_impl.h
+++ b/services/device/usb/usb_service_impl.h
@@ -24,6 +24,11 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/libusb/src/libusb/libusb.h"
 
+#if defined(OS_WIN)
+#include "base/scoped_observation.h"
+#include "device/base/device_monitor_win.h"
+#endif  // OS_WIN
+
 struct libusb_context;
 
 namespace device {
@@ -32,7 +37,11 @@
 
 class UsbDeviceImpl;
 
-class UsbServiceImpl final : public UsbService {
+class UsbServiceImpl final :
+#if defined(OS_WIN)
+    public DeviceMonitorWin::Observer,
+#endif  // OS_WIN
+    public UsbService {
  public:
   UsbServiceImpl();
   ~UsbServiceImpl() override;
@@ -41,6 +50,14 @@
   // device::UsbService implementation
   void GetDevices(GetDevicesCallback callback) override;
 
+#if defined(OS_WIN)
+  // device::DeviceMonitorWin::Observer implementation
+  void OnDeviceAdded(const GUID& class_guid,
+                     const std::wstring& device_path) override;
+  void OnDeviceRemoved(const GUID& class_guid,
+                       const std::wstring& device_path) override;
+#endif  // OS_WIN
+
   void OnUsbContext(scoped_refptr<UsbContext> context);
 
   // Enumerate USB devices from OS and update devices_ map.
@@ -77,11 +94,14 @@
 
   // When available the device list will be updated when new devices are
   // connected instead of only when a full enumeration is requested.
+  // TODO(reillyg): Support this on all platforms. crbug.com/411715
+  bool hotplug_enabled_ = false;
   libusb_hotplug_callback_handle hotplug_handle_;
 
   // Enumeration callbacks are queued until an enumeration completes.
   bool enumeration_ready_ = false;
   bool enumeration_in_progress_ = false;
+  base::queue<std::wstring> pending_path_enumerations_;
   std::vector<GetDevicesCallback> pending_enumeration_callbacks_;
 
   // The map from libusb_device to UsbDeviceImpl. The key is a weak pointer to
@@ -99,6 +119,11 @@
   // UsbDeviceImpl.
   std::set<libusb_device*> devices_being_enumerated_;
 
+#if defined(OS_WIN)
+  base::ScopedObservation<DeviceMonitorWin, DeviceMonitorWin::Observer>
+      device_observation_{this};
+#endif  // OS_WIN
+
   // This WeakPtr is used to safely post hotplug events back to the thread this
   // object lives on.
   base::WeakPtr<UsbServiceImpl> weak_self_;
diff --git a/services/device/usb/usb_service_unittest.cc b/services/device/usb/usb_service_unittest.cc
index ada5b04..971ec7d4 100644
--- a/services/device/usb/usb_service_unittest.cc
+++ b/services/device/usb/usb_service_unittest.cc
@@ -54,7 +54,7 @@
   }
 }
 
-#if defined(OS_MAC)
+#if defined(OS_WIN)
 TEST_F(UsbServiceTest, GetDevicesNewBackend) {
   base::test::ScopedFeatureList features;
   features.InitAndEnableFeature(device::kNewUsbBackend);
@@ -66,7 +66,7 @@
     loop.Run();
   }
 }
-#endif  // defined(OS_MAC)
+#endif  // defined(OS_WIN)
 
 TEST_F(UsbServiceTest, ClaimGadget) {
   if (!UsbTestGadget::IsTestEnabled() || !usb_service_)
diff --git a/services/device/usb/usb_service_win.cc b/services/device/usb/usb_service_win.cc
index 554c351..3b68c55 100644
--- a/services/device/usb/usb_service_win.cc
+++ b/services/device/usb/usb_service_win.cc
@@ -4,16 +4,14 @@
 
 #include "services/device/usb/usb_service_win.h"
 
-// windows.h must be included first.
-#include <windows.h>
-
-#define INITGUID
-
-#include <devpkey.h>
 #include <objbase.h>
 #include <setupapi.h>
 #include <stdint.h>
 #include <usbiodef.h>
+#include "base/strings/string_piece_forward.h"
+
+#define INITGUID
+#include <devpkey.h>
 
 #include "base/bind.h"
 #include "base/containers/contains.h"
@@ -23,7 +21,6 @@
 #include "base/scoped_generic.h"
 #include "base/single_thread_task_runner.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/strings/string_piece_forward.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
diff --git a/storage/browser/blob/blob_url_utils.h b/storage/browser/blob/blob_url_utils.h
index 4c125ecf..de742e2 100644
--- a/storage/browser/blob/blob_url_utils.h
+++ b/storage/browser/blob/blob_url_utils.h
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifndef STORAGE_BROWSER_BLOB_BLOB_URL_UTILS_H_
+#define STORAGE_BROWSER_BLOB_BLOB_URL_UTILS_H_
+
 #include "url/gurl.h"
 
 namespace storage {
@@ -21,3 +24,5 @@
 
 }  // namespace BlobUrlUtils
 }  // namespace storage
+
+#endif  // STORAGE_BROWSER_BLOB_BLOB_URL_UTILS_H_
\ No newline at end of file
diff --git a/storage/browser/quota/quota_callbacks.h b/storage/browser/quota/quota_callbacks.h
index d7002097..36013d11 100644
--- a/storage/browser/quota/quota_callbacks.h
+++ b/storage/browser/quota/quota_callbacks.h
@@ -42,9 +42,11 @@
 using AvailableSpaceCallback =
     base::OnceCallback<void(blink::mojom::QuotaStatusCode, int64_t)>;
 using StatusCallback = base::OnceCallback<void(blink::mojom::QuotaStatusCode)>;
-using GetStorageKeysCallback =
-    base::OnceCallback<void(const std::set<blink::StorageKey>& storage_keys,
+using GetBucketsCallback =
+    base::OnceCallback<void(const std::set<BucketInfo>& buckets,
                             blink::mojom::StorageType type)>;
+using GetStorageKeysCallback =
+    base::OnceCallback<void(const std::set<blink::StorageKey>& storage_keys)>;
 using GetUsageInfoCallback = base::OnceCallback<void(UsageInfoEntries)>;
 using GetBucketCallback =
     base::OnceCallback<void(const absl::optional<BucketInfo>& bucket_info)>;
diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc
index 2d2baeac..ca3143e7 100644
--- a/storage/browser/quota/quota_database.cc
+++ b/storage/browser/quota/quota_database.cc
@@ -482,11 +482,10 @@
     return false;
 
   static constexpr char kSql[] =
-      "DELETE FROM buckets WHERE origin = ? AND type = ? AND name = ?";
+      "DELETE FROM buckets WHERE origin = ? AND type = ?";
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindString(0, storage_key.Serialize());
   statement.BindInt(1, static_cast<int>(type));
-  statement.BindString(2, kDefaultBucketName);
 
   if (!statement.Run())
     return false;
@@ -564,57 +563,44 @@
   return QuotaError::kNotFound;
 }
 
-bool QuotaDatabase::GetStorageKeysModifiedBetween(
-    StorageType type,
-    std::set<StorageKey>* storage_keys,
-    base::Time begin,
-    base::Time end) {
+QuotaErrorOr<std::set<StorageKey>> QuotaDatabase::GetStorageKeysForType(
+    StorageType type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(storage_keys);
-  if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone)
-    return false;
+  QuotaError open_error = LazyOpen(LazyOpenMode::kFailIfNotFound);
+  if (open_error != QuotaError::kNone)
+    return open_error;
 
-  DCHECK(!begin.is_max());
-  DCHECK(end != base::Time());
   static constexpr char kSql[] =
-      // clang-format off
-      "SELECT origin FROM buckets "
-        "WHERE type = ? AND name = ?"
-        "AND last_modified >= ? AND last_modified < ?";
-  // clang-format on
+      "SELECT DISTINCT origin FROM buckets WHERE type = ?";
 
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindInt(0, static_cast<int>(type));
-  statement.BindString(1, kDefaultBucketName);
-  statement.BindTime(2, begin);
-  statement.BindTime(3, end);
 
-  storage_keys->clear();
+  std::set<StorageKey> storage_keys;
   while (statement.Step()) {
     absl::optional<StorageKey> read_storage_key =
         StorageKey::Deserialize(statement.ColumnString(0));
     if (!read_storage_key.has_value())
       continue;
-    storage_keys->insert(std::move(read_storage_key).value());
+    storage_keys.insert(read_storage_key.value());
   }
-
-  return statement.Succeeded();
+  return storage_keys;
 }
 
-bool QuotaDatabase::GetBucketsModifiedBetween(StorageType type,
-                                              std::set<BucketId>* bucket_ids,
-                                              base::Time begin,
-                                              base::Time end) {
+QuotaErrorOr<std::set<BucketInfo>> QuotaDatabase::GetBucketsModifiedBetween(
+    StorageType type,
+    base::Time begin,
+    base::Time end) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(bucket_ids);
-  if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone)
-    return false;
+  QuotaError open_error = LazyOpen(LazyOpenMode::kFailIfNotFound);
+  if (open_error != QuotaError::kNone)
+    return open_error;
 
   DCHECK(!begin.is_max());
   DCHECK(end != base::Time());
+  // clang-format off
   static constexpr char kSql[] =
-      // clang-format off
-      "SELECT id FROM buckets "
+      "SELECT id, origin, name, expiration, quota FROM buckets "
         "WHERE type = ? AND last_modified >= ? AND last_modified < ?";
   // clang-format on
 
@@ -623,11 +609,18 @@
   statement.BindTime(1, begin);
   statement.BindTime(2, end);
 
-  bucket_ids->clear();
-  while (statement.Step())
-    bucket_ids->insert(BucketId(statement.ColumnInt64(0)));
-
-  return statement.Succeeded();
+  std::set<BucketInfo> buckets;
+  while (statement.Step()) {
+    absl::optional<StorageKey> read_storage_key =
+        StorageKey::Deserialize(statement.ColumnString(1));
+    if (!read_storage_key.has_value())
+      continue;
+    buckets.emplace(
+        BucketInfo(BucketId(statement.ColumnInt64(0)), read_storage_key.value(),
+                   type, statement.ColumnString(2), statement.ColumnTime(3),
+                   statement.ColumnInt(4)));
+  }
+  return buckets;
 }
 
 bool QuotaDatabase::IsStorageKeyDatabaseBootstrapped() {
diff --git a/storage/browser/quota/quota_database.h b/storage/browser/quota/quota_database.h
index 1bad41f5..9091dee 100644
--- a/storage/browser/quota/quota_database.h
+++ b/storage/browser/quota/quota_database.h
@@ -147,8 +147,7 @@
   // origin bucket can be found.
   bool GetBucketInfo(BucketId bucket_id, BucketTableEntry* entry);
 
-  // TODO(crbug.com/1202167): Remove once all usages have been updated to use
-  // DeleteBucketInfo. Deletes the default bucket for `storage_key`.
+  // Removes all buckets for `storage_key` with `type`.
   bool DeleteStorageKeyInfo(const blink::StorageKey& storage_key,
                             blink::mojom::StorageType type);
 
@@ -167,21 +166,16 @@
       const std::set<BucketId>& bucket_exceptions,
       SpecialStoragePolicy* special_storage_policy);
 
-  // TODO(crbug.com/1202167): Remove once all usages have been updated to use
-  // GetBucketsModifiedBetween. Populates `storage_keys` with the ones that have
-  // had their default bucket modified since the `begin` and until the `end`.
-  // Returns whether the operation succeeded.
-  bool GetStorageKeysModifiedBetween(blink::mojom::StorageType type,
-                                     std::set<blink::StorageKey>* storage_keys,
-                                     base::Time begin,
-                                     base::Time end);
+  // Returns all storage keys for `type` in the bucket database.
+  QuotaErrorOr<std::set<blink::StorageKey>> GetStorageKeysForType(
+      blink::mojom::StorageType type);
 
-  // Populates `bucket_ids` with the buckets that have been modified since the
-  // `begin` and until the `end`. Returns whether the operation succeeded.
-  bool GetBucketsModifiedBetween(blink::mojom::StorageType type,
-                                 std::set<BucketId>* bucket_ids,
-                                 base::Time begin,
-                                 base::Time end);
+  // Returns a set of buckets that have been modified since the `begin` and
+  // until the `end`. Returns a QuotaError if the operations has failed.
+  QuotaErrorOr<std::set<BucketInfo>> GetBucketsModifiedBetween(
+      blink::mojom::StorageType type,
+      base::Time begin,
+      base::Time end);
 
   // Returns false if SetStorageKeyDatabaseBootstrapped has never
   // been called before, which means existing storage keys may not have been
diff --git a/storage/browser/quota/quota_database_unittest.cc b/storage/browser/quota/quota_database_unittest.cc
index b2d685c..6dd0e80a 100644
--- a/storage/browser/quota/quota_database_unittest.cc
+++ b/storage/browser/quota/quota_database_unittest.cc
@@ -11,6 +11,7 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/containers/contains.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/test/task_environment.h"
@@ -298,6 +299,40 @@
 }
 #endif  // !defined(OS_FUCHSIA)
 
+TEST_P(QuotaDatabaseTest, DeleteStorageKeyInfo) {
+  QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
+
+  const StorageKey storage_key =
+      StorageKey::CreateFromStringForTesting("http://example-a/");
+  QuotaErrorOr<BucketInfo> temp_bucket1 =
+      db.CreateBucketForTesting(storage_key, "temp1", kTemp);
+  QuotaErrorOr<BucketInfo> temp_bucket2 =
+      db.CreateBucketForTesting(storage_key, "temp2", kTemp);
+  QuotaErrorOr<BucketInfo> perm_bucket =
+      db.CreateBucketForTesting(storage_key, "perm", kPerm);
+
+  db.DeleteStorageKeyInfo(storage_key, kTemp);
+
+  QuotaErrorOr<BucketInfo> result =
+      db.GetBucket(storage_key, temp_bucket1->name, kTemp);
+  ASSERT_FALSE(result.ok());
+  ASSERT_EQ(result.error(), QuotaError::kNotFound);
+
+  result = db.GetBucket(storage_key, temp_bucket2->name, kTemp);
+  ASSERT_FALSE(result.ok());
+  ASSERT_EQ(result.error(), QuotaError::kNotFound);
+
+  result = db.GetBucket(storage_key, perm_bucket->name, kPerm);
+  ASSERT_TRUE(result.ok());
+
+  db.DeleteStorageKeyInfo(storage_key, kPerm);
+
+  result = db.GetBucket(storage_key, perm_bucket->name, kPerm);
+  ASSERT_FALSE(result.ok());
+  ASSERT_EQ(result.error(), QuotaError::kNotFound);
+}
+
 TEST_P(QuotaDatabaseTest, BucketLastAccessTimeLRU) {
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
   EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
@@ -408,193 +443,144 @@
   EXPECT_EQ(result.error(), QuotaError::kNotFound);
 }
 
-TEST_P(QuotaDatabaseTest, StorageKeyLastModifiedBetween) {
+TEST_P(QuotaDatabaseTest, GetStorageKeysForType) {
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
   EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
 
-  std::set<StorageKey> storage_keys;
-  EXPECT_TRUE(db.GetStorageKeysModifiedBetween(
-      kTemp, &storage_keys, base::Time(), base::Time::Max()));
-  EXPECT_TRUE(storage_keys.empty());
+  const StorageKey storage_key1 =
+      StorageKey::CreateFromStringForTesting("http://example-a/");
+  const StorageKey storage_key2 =
+      StorageKey::CreateFromStringForTesting("http://example-b/");
+  const StorageKey storage_key3 =
+      StorageKey::CreateFromStringForTesting("http://example-c/");
 
-  const StorageKey kStorageKey1 =
-      StorageKey::CreateFromStringForTesting("http://a/");
-  const StorageKey kStorageKey2 =
-      StorageKey::CreateFromStringForTesting("http://b/");
-  const StorageKey kStorageKey3 =
-      StorageKey::CreateFromStringForTesting("http://c/");
+  QuotaErrorOr<BucketInfo> temp_bucket1 =
+      db.CreateBucketForTesting(storage_key1, "bucket_a", kTemp);
+  QuotaErrorOr<BucketInfo> temp_bucket2 =
+      db.CreateBucketForTesting(storage_key2, "bucket_b", kTemp);
+  QuotaErrorOr<BucketInfo> perm_bucket1 =
+      db.CreateBucketForTesting(storage_key2, "bucket_b", kPerm);
+  QuotaErrorOr<BucketInfo> perm_bucket2 =
+      db.CreateBucketForTesting(storage_key3, "bucket_b", kPerm);
 
-  // Report last mod time for the test storage_keys.
-  EXPECT_TRUE(db.SetStorageKeyLastModifiedTime(kStorageKey1, kTemp,
-                                               base::Time::FromJavaTime(0)));
-  EXPECT_TRUE(db.SetStorageKeyLastModifiedTime(kStorageKey2, kTemp,
-                                               base::Time::FromJavaTime(10)));
-  EXPECT_TRUE(db.SetStorageKeyLastModifiedTime(kStorageKey3, kTemp,
-                                               base::Time::FromJavaTime(20)));
+  QuotaErrorOr<std::set<StorageKey>> result = db.GetStorageKeysForType(kTemp);
+  ASSERT_TRUE(result.ok());
+  ASSERT_TRUE(base::Contains(result.value(), storage_key1));
+  ASSERT_TRUE(base::Contains(result.value(), storage_key2));
+  ASSERT_FALSE(base::Contains(result.value(), storage_key3));
 
-  EXPECT_TRUE(db.GetStorageKeysModifiedBetween(
-      kTemp, &storage_keys, base::Time(), base::Time::Max()));
-  EXPECT_EQ(3U, storage_keys.size());
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey1));
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey2));
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey3));
-
-  EXPECT_TRUE(db.GetStorageKeysModifiedBetween(
-      kTemp, &storage_keys, base::Time::FromJavaTime(5), base::Time::Max()));
-  EXPECT_EQ(2U, storage_keys.size());
-  EXPECT_EQ(0U, storage_keys.count(kStorageKey1));
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey2));
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey3));
-
-  EXPECT_TRUE(db.GetStorageKeysModifiedBetween(
-      kTemp, &storage_keys, base::Time::FromJavaTime(15), base::Time::Max()));
-  EXPECT_EQ(1U, storage_keys.size());
-  EXPECT_EQ(0U, storage_keys.count(kStorageKey1));
-  EXPECT_EQ(0U, storage_keys.count(kStorageKey2));
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey3));
-
-  EXPECT_TRUE(db.GetStorageKeysModifiedBetween(
-      kTemp, &storage_keys, base::Time::FromJavaTime(25), base::Time::Max()));
-  EXPECT_TRUE(storage_keys.empty());
-
-  EXPECT_TRUE(db.GetStorageKeysModifiedBetween(kTemp, &storage_keys,
-                                               base::Time::FromJavaTime(5),
-                                               base::Time::FromJavaTime(15)));
-  EXPECT_EQ(1U, storage_keys.size());
-  EXPECT_EQ(0U, storage_keys.count(kStorageKey1));
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey2));
-  EXPECT_EQ(0U, storage_keys.count(kStorageKey3));
-
-  EXPECT_TRUE(db.GetStorageKeysModifiedBetween(kTemp, &storage_keys,
-                                               base::Time::FromJavaTime(0),
-                                               base::Time::FromJavaTime(20)));
-  EXPECT_EQ(2U, storage_keys.size());
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey1));
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey2));
-  EXPECT_EQ(0U, storage_keys.count(kStorageKey3));
-
-  // Update storage key's mod time but for persistent storage.
-  EXPECT_TRUE(db.SetStorageKeyLastModifiedTime(kStorageKey1, kPerm,
-                                               base::Time::FromJavaTime(30)));
-
-  // Must have no effects on temporary storage_keys info.
-  EXPECT_TRUE(db.GetStorageKeysModifiedBetween(
-      kTemp, &storage_keys, base::Time::FromJavaTime(5), base::Time::Max()));
-  EXPECT_EQ(2U, storage_keys.size());
-  EXPECT_EQ(0U, storage_keys.count(kStorageKey1));
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey2));
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey3));
-
-  // One more update for persistent storage key.
-  EXPECT_TRUE(db.SetStorageKeyLastModifiedTime(kStorageKey2, kPerm,
-                                               base::Time::FromJavaTime(40)));
-
-  EXPECT_TRUE(db.GetStorageKeysModifiedBetween(
-      kPerm, &storage_keys, base::Time::FromJavaTime(25), base::Time::Max()));
-  EXPECT_EQ(2U, storage_keys.size());
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey1));
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey2));
-  EXPECT_EQ(0U, storage_keys.count(kStorageKey3));
-
-  EXPECT_TRUE(db.GetStorageKeysModifiedBetween(
-      kPerm, &storage_keys, base::Time::FromJavaTime(35), base::Time::Max()));
-  EXPECT_EQ(1U, storage_keys.size());
-  EXPECT_EQ(0U, storage_keys.count(kStorageKey1));
-  EXPECT_EQ(1U, storage_keys.count(kStorageKey2));
-  EXPECT_EQ(0U, storage_keys.count(kStorageKey3));
+  result = db.GetStorageKeysForType(kPerm);
+  ASSERT_TRUE(result.ok());
+  ASSERT_FALSE(base::Contains(result.value(), storage_key1));
+  ASSERT_TRUE(base::Contains(result.value(), storage_key2));
+  ASSERT_TRUE(base::Contains(result.value(), storage_key3));
 }
 
 TEST_P(QuotaDatabaseTest, BucketLastModifiedBetween) {
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
   EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
 
-  std::set<BucketId> bucket_ids;
-  EXPECT_TRUE(db.GetBucketsModifiedBetween(kTemp, &bucket_ids, base::Time(),
-                                           base::Time::Max()));
-  EXPECT_TRUE(bucket_ids.empty());
+  QuotaErrorOr<std::set<BucketInfo>> result =
+      db.GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max());
+  EXPECT_TRUE(result.ok());
+  std::set<BucketInfo> buckets = result.value();
+  EXPECT_TRUE(buckets.empty());
 
-  // Insert bucket entries into BucketTable.
-  base::Time now = base::Time::Now();
-  using Entry = QuotaDatabase::BucketTableEntry;
-  Entry bucket1 = Entry(
-      BucketId(1), StorageKey::CreateFromStringForTesting("http://example-a/"),
-      kTemp, "bucket_a", 0, now, now);
-  Entry bucket2 = Entry(
-      BucketId(2), StorageKey::CreateFromStringForTesting("http://example-b/"),
-      kTemp, "bucket_b", 0, now, now);
-  Entry bucket3 = Entry(
-      BucketId(3), StorageKey::CreateFromStringForTesting("http://example-c/"),
-      kTemp, "bucket_c", 0, now, now);
-  Entry bucket4 = Entry(
-      BucketId(4), StorageKey::CreateFromStringForTesting("http://example-d/"),
-      kPerm, "bucket_d", 0, now, now);
-  Entry kTableEntries[] = {bucket1, bucket2, bucket3, bucket4};
-  AssignBucketTable(&db, kTableEntries);
+  QuotaErrorOr<BucketInfo> result1 = db.CreateBucketForTesting(
+      StorageKey::CreateFromStringForTesting("http://example-a/"), "bucket_a",
+      kTemp);
+  EXPECT_TRUE(result1.ok());
+  BucketInfo bucket1 = result1.value();
+  QuotaErrorOr<BucketInfo> result2 = db.CreateBucketForTesting(
+      StorageKey::CreateFromStringForTesting("http://example-b/"), "bucket_b",
+      kTemp);
+  EXPECT_TRUE(result2.ok());
+  BucketInfo bucket2 = result2.value();
+  QuotaErrorOr<BucketInfo> result3 = db.CreateBucketForTesting(
+      StorageKey::CreateFromStringForTesting("http://example-c/"), "bucket_c",
+      kTemp);
+  EXPECT_TRUE(result3.ok());
+  BucketInfo bucket3 = result3.value();
+  QuotaErrorOr<BucketInfo> result4 = db.CreateBucketForTesting(
+      StorageKey::CreateFromStringForTesting("http://example-d/"), "bucket_d",
+      kPerm);
+  EXPECT_TRUE(result4.ok());
+  BucketInfo bucket4 = result4.value();
 
-  // Report last mod time for the buckets.
-  EXPECT_TRUE(db.SetBucketLastModifiedTime(bucket1.bucket_id,
-                                           base::Time::FromJavaTime(0)));
-  EXPECT_TRUE(db.SetBucketLastModifiedTime(bucket2.bucket_id,
-                                           base::Time::FromJavaTime(10)));
-  EXPECT_TRUE(db.SetBucketLastModifiedTime(bucket3.bucket_id,
-                                           base::Time::FromJavaTime(20)));
-  EXPECT_TRUE(db.SetBucketLastModifiedTime(bucket4.bucket_id,
-                                           base::Time::FromJavaTime(30)));
+  // Report last modified time for the buckets.
+  EXPECT_TRUE(
+      db.SetBucketLastModifiedTime(bucket1.id, base::Time::FromJavaTime(0)));
+  EXPECT_TRUE(
+      db.SetBucketLastModifiedTime(bucket2.id, base::Time::FromJavaTime(10)));
+  EXPECT_TRUE(
+      db.SetBucketLastModifiedTime(bucket3.id, base::Time::FromJavaTime(20)));
+  EXPECT_TRUE(
+      db.SetBucketLastModifiedTime(bucket4.id, base::Time::FromJavaTime(30)));
 
-  EXPECT_TRUE(db.GetBucketsModifiedBetween(kTemp, &bucket_ids, base::Time(),
-                                           base::Time::Max()));
-  EXPECT_EQ(3U, bucket_ids.size());
-  EXPECT_EQ(1U, bucket_ids.count(bucket1.bucket_id));
-  EXPECT_EQ(1U, bucket_ids.count(bucket2.bucket_id));
-  EXPECT_EQ(1U, bucket_ids.count(bucket3.bucket_id));
-  EXPECT_EQ(0U, bucket_ids.count(bucket4.bucket_id));
+  result = db.GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max());
+  EXPECT_TRUE(result.ok());
+  buckets = result.value();
+  EXPECT_EQ(3U, buckets.size());
+  EXPECT_EQ(1U, buckets.count(bucket1));
+  EXPECT_EQ(1U, buckets.count(bucket2));
+  EXPECT_EQ(1U, buckets.count(bucket3));
+  EXPECT_EQ(0U, buckets.count(bucket4));
 
-  EXPECT_TRUE(db.GetBucketsModifiedBetween(
-      kTemp, &bucket_ids, base::Time::FromJavaTime(5), base::Time::Max()));
-  EXPECT_EQ(2U, bucket_ids.size());
-  EXPECT_EQ(0U, bucket_ids.count(bucket1.bucket_id));
-  EXPECT_EQ(1U, bucket_ids.count(bucket2.bucket_id));
-  EXPECT_EQ(1U, bucket_ids.count(bucket3.bucket_id));
-  EXPECT_EQ(0U, bucket_ids.count(bucket4.bucket_id));
+  result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(5),
+                                        base::Time::Max());
+  EXPECT_TRUE(result.ok());
+  buckets = result.value();
+  EXPECT_EQ(2U, buckets.size());
+  EXPECT_EQ(0U, buckets.count(bucket1));
+  EXPECT_EQ(1U, buckets.count(bucket2));
+  EXPECT_EQ(1U, buckets.count(bucket3));
+  EXPECT_EQ(0U, buckets.count(bucket4));
 
-  EXPECT_TRUE(db.GetBucketsModifiedBetween(
-      kTemp, &bucket_ids, base::Time::FromJavaTime(15), base::Time::Max()));
-  EXPECT_EQ(1U, bucket_ids.size());
-  EXPECT_EQ(0U, bucket_ids.count(bucket1.bucket_id));
-  EXPECT_EQ(0U, bucket_ids.count(bucket2.bucket_id));
-  EXPECT_EQ(1U, bucket_ids.count(bucket3.bucket_id));
-  EXPECT_EQ(0U, bucket_ids.count(bucket4.bucket_id));
+  result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(15),
+                                        base::Time::Max());
+  EXPECT_TRUE(result.ok());
+  buckets = result.value();
+  EXPECT_EQ(1U, buckets.size());
+  EXPECT_EQ(0U, buckets.count(bucket1));
+  EXPECT_EQ(0U, buckets.count(bucket2));
+  EXPECT_EQ(1U, buckets.count(bucket3));
+  EXPECT_EQ(0U, buckets.count(bucket4));
 
-  EXPECT_TRUE(db.GetBucketsModifiedBetween(
-      kTemp, &bucket_ids, base::Time::FromJavaTime(25), base::Time::Max()));
-  EXPECT_TRUE(bucket_ids.empty());
+  result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(25),
+                                        base::Time::Max());
+  EXPECT_TRUE(result.ok());
+  buckets = result.value();
+  EXPECT_TRUE(buckets.empty());
 
-  EXPECT_TRUE(db.GetBucketsModifiedBetween(kTemp, &bucket_ids,
-                                           base::Time::FromJavaTime(5),
-                                           base::Time::FromJavaTime(15)));
-  EXPECT_EQ(1U, bucket_ids.size());
-  EXPECT_EQ(0U, bucket_ids.count(bucket1.bucket_id));
-  EXPECT_EQ(1U, bucket_ids.count(bucket2.bucket_id));
-  EXPECT_EQ(0U, bucket_ids.count(bucket3.bucket_id));
-  EXPECT_EQ(0U, bucket_ids.count(bucket4.bucket_id));
+  result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(5),
+                                        base::Time::FromJavaTime(15));
+  EXPECT_TRUE(result.ok());
+  buckets = result.value();
+  EXPECT_EQ(1U, buckets.size());
+  EXPECT_EQ(0U, buckets.count(bucket1));
+  EXPECT_EQ(1U, buckets.count(bucket2));
+  EXPECT_EQ(0U, buckets.count(bucket3));
+  EXPECT_EQ(0U, buckets.count(bucket4));
 
-  EXPECT_TRUE(db.GetBucketsModifiedBetween(kTemp, &bucket_ids,
-                                           base::Time::FromJavaTime(0),
-                                           base::Time::FromJavaTime(20)));
-  EXPECT_EQ(2U, bucket_ids.size());
-  EXPECT_EQ(1U, bucket_ids.count(bucket1.bucket_id));
-  EXPECT_EQ(1U, bucket_ids.count(bucket2.bucket_id));
-  EXPECT_EQ(0U, bucket_ids.count(bucket3.bucket_id));
-  EXPECT_EQ(0U, bucket_ids.count(bucket4.bucket_id));
+  result = db.GetBucketsModifiedBetween(kTemp, base::Time::FromJavaTime(0),
+                                        base::Time::FromJavaTime(20));
+  EXPECT_TRUE(result.ok());
+  buckets = result.value();
+  EXPECT_EQ(2U, buckets.size());
+  EXPECT_EQ(1U, buckets.count(bucket1));
+  EXPECT_EQ(1U, buckets.count(bucket2));
+  EXPECT_EQ(0U, buckets.count(bucket3));
+  EXPECT_EQ(0U, buckets.count(bucket4));
 
-  EXPECT_TRUE(db.GetBucketsModifiedBetween(kPerm, &bucket_ids,
-                                           base::Time::FromJavaTime(0),
-                                           base::Time::FromJavaTime(35)));
-  EXPECT_EQ(1U, bucket_ids.size());
-  EXPECT_EQ(0U, bucket_ids.count(bucket1.bucket_id));
-  EXPECT_EQ(0U, bucket_ids.count(bucket2.bucket_id));
-  EXPECT_EQ(0U, bucket_ids.count(bucket3.bucket_id));
-  EXPECT_EQ(1U, bucket_ids.count(bucket4.bucket_id));
+  result = db.GetBucketsModifiedBetween(kPerm, base::Time::FromJavaTime(0),
+                                        base::Time::FromJavaTime(35));
+  EXPECT_TRUE(result.ok());
+  buckets = result.value();
+  EXPECT_EQ(1U, buckets.size());
+  EXPECT_EQ(0U, buckets.count(bucket1));
+  EXPECT_EQ(0U, buckets.count(bucket2));
+  EXPECT_EQ(0U, buckets.count(bucket3));
+  EXPECT_EQ(1U, buckets.count(bucket4));
 }
 
 TEST_P(QuotaDatabaseTest, RegisterInitialStorageKeyInfo) {
diff --git a/storage/browser/quota/quota_manager_impl.cc b/storage/browser/quota/quota_manager_impl.cc
index 7285da4a..700e2d87 100644
--- a/storage/browser/quota/quota_manager_impl.cc
+++ b/storage/browser/quota/quota_manager_impl.cc
@@ -120,6 +120,22 @@
   return database->GetBucket(storage_key, bucket_name, type);
 }
 
+QuotaErrorOr<std::set<StorageKey>> GetStorageKeysForTypeOnDBThread(
+    StorageType type,
+    QuotaDatabase* database) {
+  DCHECK(database);
+  return database->GetStorageKeysForType(type);
+}
+
+QuotaErrorOr<std::set<BucketInfo>> GetModifiedBetweenOnDBThread(
+    StorageType type,
+    base::Time begin,
+    base::Time end,
+    QuotaDatabase* database) {
+  DCHECK(database);
+  return database->GetBucketsModifiedBetween(type, begin, end);
+}
+
 bool GetPersistentHostQuotaOnDBThread(const std::string& host,
                                       int64_t* quota,
                                       QuotaDatabase* database) {
@@ -917,42 +933,6 @@
   base::WeakPtrFactory<StorageCleanupHelper> weak_factory_{this};
 };
 
-// Fetch storage keys that have been modified since the specified time. This is
-// used to clear data for storage keys that have been modified within the user
-// specified time frame.
-//
-// This class is granted ownership of itself when it is passed to
-// DidGetModifiedBetween() via base::Owned(). When the closure for said
-// function goes out of scope, the object is deleted. This is a thread-safe
-// class.
-class QuotaManagerImpl::GetModifiedSinceHelper {
- public:
-  bool GetModifiedBetweenOnDBThread(StorageType type,
-                                    base::Time begin,
-                                    base::Time end,
-                                    QuotaDatabase* database) {
-    DCHECK(database);
-    return database->GetStorageKeysModifiedBetween(type, &storage_keys_, begin,
-                                                   end);
-  }
-
-  void DidGetModifiedBetween(const base::WeakPtr<QuotaManagerImpl>& manager,
-                             GetStorageKeysCallback callback,
-                             StorageType type,
-                             bool success) {
-    if (!manager) {
-      // The operation was aborted.
-      std::move(callback).Run(std::set<StorageKey>(), type);
-      return;
-    }
-    manager->DidDatabaseWork(success);
-    std::move(callback).Run(storage_keys_, type);
-  }
-
- private:
-  std::set<StorageKey> storage_keys_;
-};
-
 // Gather storage key info table for quota-internals page.
 //
 // This class is granted ownership of itself when it is passed to
@@ -1107,6 +1087,17 @@
                      weak_factory_.GetWeakPtr(), std::move(callback)));
 }
 
+void QuotaManagerImpl::GetStorageKeysForType(blink::mojom::StorageType type,
+                                             GetStorageKeysCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  LazyInitialize();
+
+  PostTaskAndReplyWithResultForDBThread(
+      base::BindOnce(&GetStorageKeysForTypeOnDBThread, type),
+      base::BindOnce(&QuotaManagerImpl::DidGetStorageKeys,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
 void QuotaManagerImpl::GetUsageInfo(GetUsageInfoCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   LazyInitialize();
@@ -1258,6 +1249,14 @@
                                std::move(callback));
 }
 
+void QuotaManagerImpl::DeleteBucketData(const BucketInfo& bucket,
+                                        QuotaClientTypes quota_client_types,
+                                        StatusCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DeleteBucketDataInternal(bucket, std::move(quota_client_types), false,
+                           std::move(callback));
+}
+
 void QuotaManagerImpl::PerformStorageCleanup(
     StorageType type,
     QuotaClientTypes quota_client_types,
@@ -1393,21 +1392,16 @@
              storage_key.origin().GetURL());
 }
 
-void QuotaManagerImpl::GetStorageKeysModifiedBetween(
-    StorageType type,
-    base::Time begin,
-    base::Time end,
-    GetStorageKeysCallback callback) {
+void QuotaManagerImpl::GetBucketsModifiedBetween(StorageType type,
+                                                 base::Time begin,
+                                                 base::Time end,
+                                                 GetBucketsCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   LazyInitialize();
-  GetModifiedSinceHelper* helper = new GetModifiedSinceHelper;
   PostTaskAndReplyWithResultForDBThread(
-      FROM_HERE,
-      base::BindOnce(&GetModifiedSinceHelper::GetModifiedBetweenOnDBThread,
-                     base::Unretained(helper), type, begin, end),
-      base::BindOnce(&GetModifiedSinceHelper::DidGetModifiedBetween,
-                     base::Owned(helper), weak_factory_.GetWeakPtr(),
-                     std::move(callback), type));
+      base::BindOnce(&GetModifiedBetweenOnDBThread, type, begin, end),
+      base::BindOnce(&QuotaManagerImpl::DidGetModifiedBetween,
+                     weak_factory_.GetWeakPtr(), std::move(callback), type));
 }
 
 bool QuotaManagerImpl::ResetUsageTracker(StorageType type) {
@@ -2138,6 +2132,23 @@
   std::move(callback).Run(std::move(result));
 }
 
+void QuotaManagerImpl::DidGetStorageKeys(
+    GetStorageKeysCallback callback,
+    QuotaErrorOr<std::set<StorageKey>> result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DidDatabaseWork(result.ok() || result.error() != QuotaError::kDatabaseError);
+  std::move(callback).Run(std::move(result.value()));
+}
+
+void QuotaManagerImpl::DidGetModifiedBetween(
+    GetBucketsCallback callback,
+    StorageType type,
+    QuotaErrorOr<std::set<BucketInfo>> result) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DidDatabaseWork(result.ok() || result.error() != QuotaError::kDatabaseError);
+  std::move(callback).Run(result.value(), type);
+}
+
 void QuotaManagerImpl::PostTaskAndReplyWithResultForDBThread(
     const base::Location& from_here,
     base::OnceCallback<bool(QuotaDatabase*)> task,
diff --git a/storage/browser/quota/quota_manager_impl.h b/storage/browser/quota/quota_manager_impl.h
index c44fa64..028a20e 100644
--- a/storage/browser/quota/quota_manager_impl.h
+++ b/storage/browser/quota/quota_manager_impl.h
@@ -187,6 +187,11 @@
                  blink::mojom::StorageType type,
                  base::OnceCallback<void(QuotaErrorOr<BucketInfo>)>);
 
+  // Retrieves all storage keys for `type` that are in the bucket database.
+  // Used for listing storage keys when showing storage key quota usage.
+  void GetStorageKeysForType(blink::mojom::StorageType type,
+                             GetStorageKeysCallback callback);
+
   // Called by clients or webapps. Returns usage per host.
   void GetUsageInfo(GetUsageInfoCallback callback);
 
@@ -265,14 +270,18 @@
 
   // DeleteStorageKeyData and DeleteHostData (surprisingly enough) delete data
   // of a particular blink::mojom::StorageType associated with either a specific
-  // storage key or set of storage keys. Each method additionally requires a
-  // |quota_client_types| which specifies the types of QuotaClients to delete
-  // from the storage key. Pass in QuotaClientType::AllClients() to remove all
-  // clients from the storage key, regardless of type.
+  // storage key or set of storage keys. DeleteBucketData will delete only the
+  // specified bucket. Each method additionally requires a |quota_client_types|
+  // which specifies the types of QuotaClients to delete from the storage key.
+  // Pass in QuotaClientType::AllClients() to remove all clients from the
+  // storage key, regardless of type.
   virtual void DeleteStorageKeyData(const blink::StorageKey& storage_key,
                                     blink::mojom::StorageType type,
                                     QuotaClientTypes quota_client_types,
                                     StatusCallback callback);
+  virtual void DeleteBucketData(const BucketInfo& bucket,
+                                QuotaClientTypes quota_client_types,
+                                StatusCallback callback);
   void DeleteHostData(const std::string& host,
                       blink::mojom::StorageType type,
                       QuotaClientTypes quota_client_types,
@@ -300,10 +309,10 @@
   bool IsStorageUnlimited(const blink::StorageKey& storage_key,
                           blink::mojom::StorageType type) const;
 
-  virtual void GetStorageKeysModifiedBetween(blink::mojom::StorageType type,
-                                             base::Time begin,
-                                             base::Time end,
-                                             GetStorageKeysCallback callback);
+  virtual void GetBucketsModifiedBetween(blink::mojom::StorageType type,
+                                         base::Time begin,
+                                         base::Time end,
+                                         GetBucketsCallback callback);
 
   bool ResetUsageTracker(blink::mojom::StorageType type);
 
@@ -374,7 +383,6 @@
   class BucketDataDeleter;
   class StorageKeyDataDeleter;
   class HostDataDeleter;
-  class GetModifiedSinceHelper;
   class DumpQuotaTableHelper;
   class DumpBucketTableHelper;
   class StorageCleanupHelper;
@@ -451,6 +459,9 @@
                                 bool is_eviction,
                                 StatusCallback callback);
 
+  // Runs StorageKeyDataDeleter which calls QuotaClients to clear all data for
+  // the storage key. Once the task is complete, calls the QuotaDatabase to
+  // delete buckets for the storage key & storage type from the bucket table.
   void DeleteStorageKeyDataInternal(const blink::StorageKey& storage_key,
                                     blink::mojom::StorageType type,
                                     QuotaClientTypes quota_client_types,
@@ -512,6 +523,11 @@
 
   void DidGetBucket(base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback,
                     QuotaErrorOr<BucketInfo> result);
+  void DidGetStorageKeys(GetStorageKeysCallback callback,
+                         QuotaErrorOr<std::set<blink::StorageKey>> result);
+  void DidGetModifiedBetween(GetBucketsCallback callback,
+                             blink::mojom::StorageType type,
+                             QuotaErrorOr<std::set<BucketInfo>> result);
 
   void DeleteOnCorrectThread() const;
 
diff --git a/storage/browser/quota/quota_manager_unittest.cc b/storage/browser/quota/quota_manager_unittest.cc
index 86f901b5..bffd8b3 100644
--- a/storage/browser/quota/quota_manager_unittest.cc
+++ b/storage/browser/quota/quota_manager_unittest.cc
@@ -171,6 +171,15 @@
     run_loop.Run();
   }
 
+  void GetStorageKeysForType(blink::mojom::StorageType storage_type) {
+    base::RunLoop run_loop;
+    quota_manager_impl_->GetStorageKeysForType(
+        storage_type,
+        base::BindOnce(&QuotaManagerImplTest::DidGetStorageKeys,
+                       weak_factory_.GetWeakPtr(), run_loop.QuitClosure()));
+    run_loop.Run();
+  }
+
   void GetUsageInfo() {
     usage_info_.clear();
     quota_manager_impl_->GetUsageInfo(base::BindOnce(
@@ -300,6 +309,15 @@
                        weak_factory_.GetWeakPtr()));
   }
 
+  void DeleteBucketData(const BucketInfo& bucket,
+                        QuotaClientTypes quota_client_types) {
+    quota_status_ = QuotaStatusCode::kUnknown;
+    quota_manager_impl_->DeleteBucketData(
+        bucket, std::move(quota_client_types),
+        base::BindOnce(&QuotaManagerImplTest::StatusCallback,
+                       weak_factory_.GetWeakPtr()));
+  }
+
   void DeleteHostData(const std::string& host,
                       StorageType type,
                       QuotaClientTypes quota_client_types) {
@@ -360,15 +378,17 @@
     quota_manager_impl_->NotifyStorageKeyNoLongerInUse(storage_key);
   }
 
-  void GetStorageKeysModifiedBetween(StorageType type,
-                                     base::Time begin,
-                                     base::Time end) {
-    modified_storage_keys_.clear();
-    modified_storage_keys_type_ = StorageType::kUnknown;
-    quota_manager_impl_->GetStorageKeysModifiedBetween(
+  void GetBucketsModifiedBetween(StorageType type,
+                                 base::Time begin,
+                                 base::Time end) {
+    modified_buckets_.clear();
+    modified_buckets_type_ = StorageType::kUnknown;
+    base::RunLoop run_loop;
+    quota_manager_impl_->GetBucketsModifiedBetween(
         type, begin, end,
-        base::BindOnce(&QuotaManagerImplTest::DidGetModifiedStorageKeys,
-                       weak_factory_.GetWeakPtr()));
+        base::BindOnce(&QuotaManagerImplTest::DidGetModifiedBuckets,
+                       weak_factory_.GetWeakPtr(), run_loop.QuitClosure()));
+    run_loop.Run();
   }
 
   void DumpQuotaTable() {
@@ -389,6 +409,12 @@
     std::move(quit_closure).Run();
   }
 
+  void DidGetStorageKeys(base::OnceClosure quit_closure,
+                         const std::set<StorageKey>& storage_keys) {
+    storage_keys_ = std::move(storage_keys);
+    std::move(quit_closure).Run();
+  }
+
   void DidGetUsageInfo(UsageInfoEntries entries) {
     usage_info_ = std::move(entries);
   }
@@ -465,10 +491,12 @@
            !bucket->storage_key.origin().GetURL().is_empty());
   }
 
-  void DidGetModifiedStorageKeys(const std::set<StorageKey>& storage_keys,
-                                 StorageType type) {
-    modified_storage_keys_ = storage_keys;
-    modified_storage_keys_type_ = type;
+  void DidGetModifiedBuckets(base::OnceClosure quit_closure,
+                             const std::set<BucketInfo>& buckets,
+                             StorageType type) {
+    modified_buckets_ = buckets;
+    modified_buckets_type_ = type;
+    std::move(quit_closure).Run();
   }
 
   void DidDumpQuotaTable(const QuotaTableEntries& entries) {
@@ -535,12 +563,10 @@
   const absl::optional<BucketInfo>& eviction_bucket() const {
     return eviction_bucket_;
   }
-  const std::set<StorageKey>& modified_storage_keys() const {
-    return modified_storage_keys_;
+  const std::set<BucketInfo>& modified_buckets() const {
+    return modified_buckets_;
   }
-  StorageType modified_storage_keys_type() const {
-    return modified_storage_keys_type_;
-  }
+  StorageType modified_buckets_type() const { return modified_buckets_type_; }
   const QuotaTableEntries& quota_entries() const { return quota_entries_; }
   const BucketTableEntries& bucket_entries() const { return bucket_entries_; }
   const QuotaSettings& settings() const { return settings_; }
@@ -551,6 +577,7 @@
   base::test::ScopedFeatureList scoped_feature_list_;
   base::test::TaskEnvironment task_environment_;
   QuotaErrorOr<BucketInfo> bucket_;
+  QuotaErrorOr<std::set<StorageKey>> storage_keys_;
 
   static std::vector<QuotaClientType> AllClients() {
     // TODO(pwnall): Implement using something other than an empty vector?
@@ -577,8 +604,8 @@
   int64_t total_space_;
   int64_t available_space_;
   absl::optional<BucketInfo> eviction_bucket_;
-  std::set<StorageKey> modified_storage_keys_;
-  StorageType modified_storage_keys_type_;
+  std::set<BucketInfo> modified_buckets_;
+  StorageType modified_buckets_type_;
   QuotaTableEntries quota_entries_;
   BucketTableEntries bucket_entries_;
   QuotaSettings settings_;
@@ -666,6 +693,36 @@
   ASSERT_FALSE(is_db_disabled());
 }
 
+TEST_F(QuotaManagerImplTest, GetStorageKeysForType) {
+  StorageKey storage_key_a = ToStorageKey("http://a.com/");
+  StorageKey storage_key_b = ToStorageKey("http://b.com/");
+  StorageKey storage_key_c = ToStorageKey("http://c.com/");
+
+  CreateBucketForTesting(storage_key_a, "bucket_a", kTemp);
+  EXPECT_TRUE(bucket_.ok());
+  BucketInfo bucket_a = bucket_.value();
+
+  CreateBucketForTesting(storage_key_b, "bucket_b", kTemp);
+  EXPECT_TRUE(bucket_.ok());
+  BucketInfo bucket_b = bucket_.value();
+
+  CreateBucketForTesting(storage_key_c, "bucket_c", kPerm);
+  EXPECT_TRUE(bucket_.ok());
+  BucketInfo bucket_c = bucket_.value();
+
+  GetStorageKeysForType(kTemp);
+  EXPECT_EQ(2U, storage_keys_->size());
+  ASSERT_EQ(1U, storage_keys_->count(storage_key_a));
+  ASSERT_EQ(1U, storage_keys_->count(storage_key_b));
+  ASSERT_EQ(0U, storage_keys_->count(storage_key_c));
+
+  GetStorageKeysForType(kPerm);
+  EXPECT_EQ(1U, storage_keys_->size());
+  ASSERT_EQ(0U, storage_keys_->count(storage_key_a));
+  ASSERT_EQ(0U, storage_keys_->count(storage_key_b));
+  ASSERT_EQ(1U, storage_keys_->count(storage_key_c));
+}
+
 TEST_F(QuotaManagerImplTest, GetUsageAndQuota_Simple) {
   static const MockStorageKeyData kData[] = {
       {"http://foo.com/", kTemp, 10},
@@ -2335,6 +2392,225 @@
   EXPECT_EQ(predelete_bar_pers - 1000, usage());
 }
 
+TEST_F(QuotaManagerImplTest, DeleteBucketNoClients) {
+  CreateBucketForTesting(ToStorageKey("http://foo.com"), kDefaultBucketName,
+                         kTemp);
+  ASSERT_TRUE(bucket_.ok());
+
+  DeleteBucketData(bucket_.value(), AllQuotaClientTypes());
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(QuotaStatusCode::kOk, status());
+}
+
+TEST_F(QuotaManagerImplTest, DeleteBucketDataMultiple) {
+  static const MockStorageKeyData kData1[] = {
+      {"http://foo.com/", kTemp, 1},
+      {"http://foo.com:1/", kTemp, 20},
+      {"http://foo.com/", kPerm, 300},
+      {"http://bar.com/", kTemp, 4000},
+  };
+  static const MockStorageKeyData kData2[] = {
+      {"http://foo.com/", kTemp, 50000}, {"http://foo.com:1/", kTemp, 6000},
+      {"http://foo.com/", kPerm, 700},   {"https://foo.com/", kTemp, 80},
+      {"http://bar.com/", kTemp, 9},
+  };
+  CreateAndRegisterClient(kData1, QuotaClientType::kFileSystem,
+                          {blink::mojom::StorageType::kTemporary,
+                           blink::mojom::StorageType::kPersistent});
+  CreateAndRegisterClient(kData2, QuotaClientType::kDatabase,
+                          {blink::mojom::StorageType::kTemporary,
+                           blink::mojom::StorageType::kPersistent});
+
+  CreateBucketForTesting(ToStorageKey("http://foo.com"), kDefaultBucketName,
+                         kTemp);
+  ASSERT_TRUE(bucket_.ok());
+  BucketInfo foo_temp_bucket = bucket_.value();
+
+  CreateBucketForTesting(ToStorageKey("http://bar.com"), kDefaultBucketName,
+                         kTemp);
+  ASSERT_TRUE(bucket_.ok());
+  BucketInfo bar_temp_bucket = bucket_.value();
+
+  GetGlobalUsage(kTemp);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_global_tmp = usage();
+
+  GetHostUsageWithBreakdown("foo.com", kTemp);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_foo_tmp = usage();
+
+  GetHostUsageWithBreakdown("bar.com", kTemp);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_bar_tmp = usage();
+
+  GetHostUsageWithBreakdown("foo.com", kPerm);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_foo_pers = usage();
+
+  GetHostUsageWithBreakdown("bar.com", kPerm);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_bar_pers = usage();
+
+  for (const MockStorageKeyData& data : kData1) {
+    quota_manager_impl()->NotifyStorageAccessed(ToStorageKey(data.origin),
+                                                data.type, base::Time::Now());
+  }
+  for (const MockStorageKeyData& data : kData2) {
+    quota_manager_impl()->NotifyStorageAccessed(ToStorageKey(data.origin),
+                                                data.type, base::Time::Now());
+  }
+  task_environment_.RunUntilIdle();
+
+  reset_status_callback_count();
+  DeleteBucketData(foo_temp_bucket, AllQuotaClientTypes());
+  DeleteBucketData(bar_temp_bucket, AllQuotaClientTypes());
+  DeleteBucketData(foo_temp_bucket, AllQuotaClientTypes());
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(3, status_callback_count());
+
+  DumpBucketTable();
+  task_environment_.RunUntilIdle();
+
+  for (const auto& entry : bucket_entries()) {
+    if (entry.type != kTemp)
+      continue;
+
+    EXPECT_NE(std::string("http://foo.com/"),
+              entry.storage_key.origin().GetURL().spec());
+    EXPECT_NE(std::string("http://bar.com/"),
+              entry.storage_key.origin().GetURL().spec());
+  }
+
+  GetGlobalUsage(kTemp);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_global_tmp - (1 + 4000 + 50000 + 9), usage());
+
+  GetHostUsageWithBreakdown("foo.com", kTemp);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_foo_tmp - (1 + 50000), usage());
+
+  GetHostUsageWithBreakdown("bar.com", kTemp);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_bar_tmp - (4000 + 9), usage());
+
+  GetHostUsageWithBreakdown("foo.com", kPerm);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_foo_pers, usage());
+
+  GetHostUsageWithBreakdown("bar.com", kPerm);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_bar_pers, usage());
+}
+
+TEST_F(QuotaManagerImplTest, DeleteBucketDataMultipleClientsDifferentTypes) {
+  static const MockStorageKeyData kData1[] = {
+      {"http://foo.com/", kPerm, 1},
+      {"http://foo.com:1/", kPerm, 10},
+      {"http://foo.com/", kTemp, 100},
+      {"http://bar.com/", kPerm, 1000},
+  };
+  static const MockStorageKeyData kData2[] = {
+      {"http://foo.com/", kTemp, 10000},
+      {"http://foo.com:1/", kTemp, 100000},
+      {"https://foo.com/", kTemp, 1000000},
+      {"http://bar.com/", kTemp, 10000000},
+  };
+  CreateAndRegisterClient(kData1, QuotaClientType::kFileSystem,
+                          {blink::mojom::StorageType::kTemporary,
+                           blink::mojom::StorageType::kPersistent});
+  CreateAndRegisterClient(kData2, QuotaClientType::kDatabase,
+                          {blink::mojom::StorageType::kTemporary});
+
+  CreateBucketForTesting(ToStorageKey("http://foo.com/"), kDefaultBucketName,
+                         kPerm);
+  ASSERT_TRUE(bucket_.ok());
+  BucketInfo foo_perm_bucket = bucket_.value();
+
+  CreateBucketForTesting(ToStorageKey("http://bar.com/"), kDefaultBucketName,
+                         kPerm);
+  ASSERT_TRUE(bucket_.ok());
+  BucketInfo bar_perm_bucket = bucket_.value();
+
+  GetGlobalUsage(kTemp);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_global_tmp = usage();
+
+  GetHostUsageWithBreakdown("foo.com", kTemp);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_foo_tmp = usage();
+
+  GetHostUsageWithBreakdown("bar.com", kTemp);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_bar_tmp = usage();
+
+  GetGlobalUsage(kPerm);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_global_pers = usage();
+
+  GetHostUsageWithBreakdown("foo.com", kPerm);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_foo_pers = usage();
+
+  GetHostUsageWithBreakdown("bar.com", kPerm);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_bar_pers = usage();
+
+  for (const MockStorageKeyData& data : kData1) {
+    quota_manager_impl()->NotifyStorageAccessed(ToStorageKey(data.origin),
+                                                data.type, base::Time::Now());
+  }
+  for (const MockStorageKeyData& data : kData2) {
+    quota_manager_impl()->NotifyStorageAccessed(ToStorageKey(data.origin),
+                                                data.type, base::Time::Now());
+  }
+  task_environment_.RunUntilIdle();
+
+  reset_status_callback_count();
+  DeleteBucketData(foo_perm_bucket, AllQuotaClientTypes());
+  DeleteBucketData(bar_perm_bucket, AllQuotaClientTypes());
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(2, status_callback_count());
+
+  DumpBucketTable();
+  task_environment_.RunUntilIdle();
+
+  for (const auto& entry : bucket_entries()) {
+    if (entry.type != kPerm)
+      continue;
+
+    EXPECT_NE(std::string("http://foo.com/"),
+              entry.storage_key.origin().GetURL().spec());
+    EXPECT_NE(std::string("http://bar.com/"),
+              entry.storage_key.origin().GetURL().spec());
+  }
+
+  GetGlobalUsage(kTemp);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_global_tmp, usage());
+
+  GetHostUsageWithBreakdown("foo.com", kTemp);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_foo_tmp, usage());
+
+  GetHostUsageWithBreakdown("bar.com", kTemp);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_bar_tmp, usage());
+
+  GetGlobalUsage(kPerm);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_global_pers - (1 + 1000), usage());
+
+  GetHostUsageWithBreakdown("foo.com", kPerm);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_foo_pers - 1, usage());
+
+  GetHostUsageWithBreakdown("bar.com", kPerm);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_bar_pers - 1000, usage());
+}
+
 TEST_F(QuotaManagerImplTest, GetCachedStorageKeys) {
   static const MockStorageKeyData kData[] = {
       {"http://a.com/", kTemp, 1},
@@ -2474,7 +2750,7 @@
   EXPECT_EQ(ToStorageKey("http://a.com/"), eviction_bucket()->storage_key);
 }
 
-TEST_F(QuotaManagerImplTest, GetStorageKeysModifiedBetween) {
+TEST_F(QuotaManagerImplTest, GetBucketsModifiedBetween) {
   static const MockStorageKeyData kData[] = {
       {"http://a.com/", kTemp, 0},  {"http://a.com:1/", kTemp, 0},
       {"https://a.com/", kTemp, 0}, {"http://b.com/", kPerm, 0},  // persistent
@@ -2485,10 +2761,10 @@
                               {blink::mojom::StorageType::kTemporary,
                                blink::mojom::StorageType::kPersistent});
 
-  GetStorageKeysModifiedBetween(kTemp, base::Time(), base::Time::Max());
+  GetBucketsModifiedBetween(kTemp, base::Time(), base::Time::Max());
   task_environment_.RunUntilIdle();
-  EXPECT_TRUE(modified_storage_keys().empty());
-  EXPECT_EQ(modified_storage_keys_type(), kTemp);
+  EXPECT_TRUE(modified_buckets().empty());
+  EXPECT_EQ(modified_buckets_type(), kTemp);
 
   base::Time time1 = client->IncrementMockTime();
   client->ModifyStorageKeyAndNotify(ToStorageKey("http://a.com/"), kTemp, 10);
@@ -2499,31 +2775,38 @@
   client->ModifyStorageKeyAndNotify(ToStorageKey("http://c.com/"), kTemp, 10);
   base::Time time3 = client->IncrementMockTime();
 
-  GetStorageKeysModifiedBetween(kTemp, time1, base::Time::Max());
+  GetBucketsModifiedBetween(kTemp, time1, base::Time::Max());
   task_environment_.RunUntilIdle();
-  EXPECT_EQ(4U, modified_storage_keys().size());
-  EXPECT_EQ(modified_storage_keys_type(), kTemp);
+  EXPECT_EQ(4U, modified_buckets().size());
+  EXPECT_EQ(modified_buckets_type(), kTemp);
   for (const MockStorageKeyData& data : kData) {
-    if (data.type == kTemp)
-      EXPECT_EQ(1U, modified_storage_keys().count(ToStorageKey(data.origin)));
+    if (data.type == kTemp) {
+      auto it =
+          std::find_if(modified_buckets().begin(), modified_buckets().end(),
+                       [data](const BucketInfo& bucket) {
+                         return ToStorageKey(data.origin) == bucket.storage_key;
+                       });
+      EXPECT_NE(it, modified_buckets().end());
+    }
   }
 
-  GetStorageKeysModifiedBetween(kTemp, time2, base::Time::Max());
+  GetBucketsModifiedBetween(kTemp, time2, base::Time::Max());
   task_environment_.RunUntilIdle();
-  EXPECT_EQ(2U, modified_storage_keys().size());
+  EXPECT_EQ(2U, modified_buckets().size());
 
-  GetStorageKeysModifiedBetween(kTemp, time3, base::Time::Max());
+  GetBucketsModifiedBetween(kTemp, time3, base::Time::Max());
   task_environment_.RunUntilIdle();
-  EXPECT_TRUE(modified_storage_keys().empty());
-  EXPECT_EQ(modified_storage_keys_type(), kTemp);
+  EXPECT_TRUE(modified_buckets().empty());
+  EXPECT_EQ(modified_buckets_type(), kTemp);
 
   client->ModifyStorageKeyAndNotify(ToStorageKey("http://a.com/"), kTemp, 10);
 
-  GetStorageKeysModifiedBetween(kTemp, time3, base::Time::Max());
+  GetBucketsModifiedBetween(kTemp, time3, base::Time::Max());
   task_environment_.RunUntilIdle();
-  EXPECT_EQ(1U, modified_storage_keys().size());
-  EXPECT_EQ(1U, modified_storage_keys().count(ToStorageKey("http://a.com/")));
-  EXPECT_EQ(modified_storage_keys_type(), kTemp);
+  EXPECT_EQ(1U, modified_buckets().size());
+  EXPECT_EQ(modified_buckets().begin()->storage_key,
+            ToStorageKey("http://a.com/"));
+  EXPECT_EQ(modified_buckets_type(), kTemp);
 }
 
 TEST_F(QuotaManagerImplTest, DumpQuotaTable) {
@@ -2650,6 +2933,62 @@
   EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
 }
 
+TEST_F(QuotaManagerImplTest, DeleteSpecificClientTypeSingleBucket) {
+  static const MockStorageKeyData kData1[] = {
+      {"http://foo.com/", kTemp, 1},
+  };
+  static const MockStorageKeyData kData2[] = {
+      {"http://foo.com/", kTemp, 2},
+  };
+  static const MockStorageKeyData kData3[] = {
+      {"http://foo.com/", kTemp, 4},
+  };
+  static const MockStorageKeyData kData4[] = {
+      {"http://foo.com/", kTemp, 8},
+  };
+  CreateAndRegisterClient(kData1, QuotaClientType::kFileSystem,
+                          {blink::mojom::StorageType::kTemporary});
+  CreateAndRegisterClient(kData2, QuotaClientType::kAppcache,
+                          {blink::mojom::StorageType::kTemporary});
+  CreateAndRegisterClient(kData3, QuotaClientType::kDatabase,
+                          {blink::mojom::StorageType::kTemporary});
+  CreateAndRegisterClient(kData4, QuotaClientType::kIndexedDatabase,
+                          {blink::mojom::StorageType::kTemporary});
+
+  CreateBucketForTesting(ToStorageKey("http://foo.com"), kDefaultBucketName,
+                         kTemp);
+  ASSERT_TRUE(bucket_.ok());
+  BucketInfo foo_bucket = bucket_.value();
+
+  GetHostUsageWithBreakdown("foo.com", kTemp);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_foo_tmp = usage();
+
+  DeleteBucketData(foo_bucket, {QuotaClientType::kFileSystem});
+  task_environment_.RunUntilIdle();
+  GetHostUsageWithBreakdown("foo.com", kTemp);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_foo_tmp - 1, usage());
+
+  DeleteBucketData(foo_bucket, {QuotaClientType::kAppcache});
+  task_environment_.RunUntilIdle();
+  GetHostUsageWithBreakdown("foo.com", kTemp);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage());
+
+  DeleteBucketData(foo_bucket, {QuotaClientType::kDatabase});
+  task_environment_.RunUntilIdle();
+  GetHostUsageWithBreakdown("foo.com", kTemp);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_foo_tmp - 4 - 2 - 1, usage());
+
+  DeleteBucketData(foo_bucket, {QuotaClientType::kIndexedDatabase});
+  task_environment_.RunUntilIdle();
+  GetHostUsageWithBreakdown("foo.com", kTemp);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
+}
+
 TEST_F(QuotaManagerImplTest, DeleteSpecificClientTypeSingleHost) {
   static const MockStorageKeyData kData1[] = {
       {"http://foo.com:1111/", kTemp, 1},
@@ -2744,6 +3083,52 @@
   EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
 }
 
+TEST_F(QuotaManagerImplTest, DeleteMultipleClientTypesSingleBucket) {
+  static const MockStorageKeyData kData1[] = {
+      {"http://foo.com/", kTemp, 1},
+  };
+  static const MockStorageKeyData kData2[] = {
+      {"http://foo.com/", kTemp, 2},
+  };
+  static const MockStorageKeyData kData3[] = {
+      {"http://foo.com/", kTemp, 4},
+  };
+  static const MockStorageKeyData kData4[] = {
+      {"http://foo.com/", kTemp, 8},
+  };
+  CreateAndRegisterClient(kData1, QuotaClientType::kFileSystem,
+                          {blink::mojom::StorageType::kTemporary});
+  CreateAndRegisterClient(kData2, QuotaClientType::kAppcache,
+                          {blink::mojom::StorageType::kTemporary});
+  CreateAndRegisterClient(kData3, QuotaClientType::kDatabase,
+                          {blink::mojom::StorageType::kTemporary});
+  CreateAndRegisterClient(kData4, QuotaClientType::kIndexedDatabase,
+                          {blink::mojom::StorageType::kTemporary});
+
+  CreateBucketForTesting(ToStorageKey("http://foo.com/"), kDefaultBucketName,
+                         kTemp);
+  ASSERT_TRUE(bucket_.ok());
+  BucketInfo foo_bucket = bucket_.value();
+
+  GetHostUsageWithBreakdown("foo.com", kTemp);
+  task_environment_.RunUntilIdle();
+  const int64_t predelete_foo_tmp = usage();
+
+  DeleteBucketData(foo_bucket,
+                   {QuotaClientType::kFileSystem, QuotaClientType::kDatabase});
+  task_environment_.RunUntilIdle();
+  GetHostUsageWithBreakdown("foo.com", kTemp);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_foo_tmp - 4 - 1, usage());
+
+  DeleteBucketData(foo_bucket, {QuotaClientType::kAppcache,
+                                QuotaClientType::kIndexedDatabase});
+  task_environment_.RunUntilIdle();
+  GetHostUsageWithBreakdown("foo.com", kTemp);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage());
+}
+
 TEST_F(QuotaManagerImplTest, DeleteMultipleClientTypesSingleHost) {
   static const MockStorageKeyData kData1[] = {
       {"http://foo.com:1111/", kTemp, 1},
diff --git a/storage/browser/test/mock_quota_manager.cc b/storage/browser/test/mock_quota_manager.cc
index 202702f..dcf7c01 100644
--- a/storage/browser/test/mock_quota_manager.cc
+++ b/storage/browser/test/mock_quota_manager.cc
@@ -21,22 +21,19 @@
 
 namespace storage {
 
-MockQuotaManager::StorageKeyInfo::StorageKeyInfo(
-    const StorageKey& storage_key,
-    StorageType type,
-    QuotaClientTypes quota_client_types,
-    base::Time modified)
-    : storage_key(storage_key),
-      type(type),
+MockQuotaManager::BucketData::BucketData(const BucketInfo& bucket,
+                                         QuotaClientTypes quota_client_types,
+                                         base::Time modified)
+    : bucket(bucket),
       quota_client_types(std::move(quota_client_types)),
       modified(modified) {}
 
-MockQuotaManager::StorageKeyInfo::~StorageKeyInfo() = default;
+MockQuotaManager::BucketData::~BucketData() = default;
 
-MockQuotaManager::StorageKeyInfo::StorageKeyInfo(
-    MockQuotaManager::StorageKeyInfo&&) = default;
-MockQuotaManager::StorageKeyInfo& MockQuotaManager::StorageKeyInfo::operator=(
-    MockQuotaManager::StorageKeyInfo&&) = default;
+MockQuotaManager::BucketData::BucketData(MockQuotaManager::BucketData&&) =
+    default;
+MockQuotaManager::BucketData& MockQuotaManager::BucketData::operator=(
+    MockQuotaManager::BucketData&&) = default;
 
 MockQuotaManager::StorageInfo::StorageInfo()
     : usage(0), quota(std::numeric_limits<int64_t>::max()) {}
@@ -68,55 +65,74 @@
   usage_and_quota_map_[std::make_pair(storage_key, type)].quota = quota;
 }
 
-bool MockQuotaManager::AddStorageKey(const StorageKey& storage_key,
-                                     StorageType type,
-                                     QuotaClientTypes quota_client_types,
-                                     base::Time modified) {
-  storage_keys_.emplace_back(StorageKeyInfo(
-      storage_key, type, std::move(quota_client_types), modified));
+bool MockQuotaManager::AddBucket(const BucketInfo& bucket,
+                                 QuotaClientTypes quota_client_types,
+                                 base::Time modified) {
+  auto it = std::find_if(
+      buckets_.begin(), buckets_.end(),
+      [bucket](const BucketData& bucket_data) {
+        return bucket.id == bucket_data.bucket.id ||
+               (bucket.name == bucket_data.bucket.name &&
+                bucket.storage_key == bucket_data.bucket.storage_key &&
+                bucket.type == bucket_data.bucket.type);
+      });
+  DCHECK(it == buckets_.end());
+  buckets_.emplace_back(
+      BucketData(bucket, std::move(quota_client_types), modified));
   return true;
 }
 
-bool MockQuotaManager::StorageKeyHasData(const StorageKey& storage_key,
-                                         StorageType type,
-                                         QuotaClientType quota_client) const {
-  for (const auto& info : storage_keys_) {
-    if (info.storage_key == storage_key && info.type == type &&
-        info.quota_client_types.contains(quota_client))
+BucketInfo MockQuotaManager::CreateBucket(const StorageKey& storage_key,
+                                          const std::string& name,
+                                          StorageType type) {
+  return BucketInfo(bucket_id_generator_.GenerateNextId(), storage_key, type,
+                    name, /*expiration=*/base::Time::Max(), /*quota=*/0);
+}
+
+bool MockQuotaManager::BucketHasData(const BucketInfo& bucket,
+                                     QuotaClientType quota_client) const {
+  for (const auto& info : buckets_) {
+    if (info.bucket == bucket && info.quota_client_types.contains(quota_client))
       return true;
   }
   return false;
 }
 
-void MockQuotaManager::GetStorageKeysModifiedBetween(
-    StorageType type,
-    base::Time begin,
-    base::Time end,
-    GetStorageKeysCallback callback) {
-  auto storage_keys_to_return = std::make_unique<std::set<StorageKey>>();
-  for (const auto& info : storage_keys_) {
-    if (info.type == type && info.modified >= begin && info.modified < end)
-      storage_keys_to_return->insert(info.storage_key);
+int MockQuotaManager::BucketDataCount(QuotaClientType quota_client) {
+  return std::count_if(
+      buckets_.begin(), buckets_.end(),
+      [quota_client](const BucketData& bucket) {
+        return bucket.quota_client_types.contains(quota_client);
+      });
+}
+
+void MockQuotaManager::GetBucketsModifiedBetween(StorageType type,
+                                                 base::Time begin,
+                                                 base::Time end,
+                                                 GetBucketsCallback callback) {
+  auto buckets_to_return = std::make_unique<std::set<BucketInfo>>();
+  for (const auto& info : buckets_) {
+    if (info.bucket.type == type && info.modified >= begin &&
+        info.modified < end)
+      buckets_to_return->insert(info.bucket);
   }
 
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(&MockQuotaManager::DidGetModifiedInTimeRange,
                                 weak_factory_.GetWeakPtr(), std::move(callback),
-                                std::move(storage_keys_to_return), type));
+                                std::move(buckets_to_return), type));
 }
 
-void MockQuotaManager::DeleteStorageKeyData(const StorageKey& storage_key,
-                                            StorageType type,
-                                            QuotaClientTypes quota_client_types,
-                                            StatusCallback callback) {
-  for (auto current = storage_keys_.begin(); current != storage_keys_.end();
-       ++current) {
-    if (current->storage_key == storage_key && current->type == type) {
+void MockQuotaManager::DeleteBucketData(const BucketInfo& bucket,
+                                        QuotaClientTypes quota_client_types,
+                                        StatusCallback callback) {
+  for (auto current = buckets_.begin(); current != buckets_.end(); ++current) {
+    if (current->bucket == bucket) {
       // Modify the mask: if it's 0 after "deletion", remove the storage key.
       for (QuotaClientType type : quota_client_types)
         current->quota_client_types.erase(type);
       if (current->quota_client_types.empty())
-        storage_keys_.erase(current);
+        buckets_.erase(current);
       break;
     }
   }
@@ -143,10 +159,10 @@
 }
 
 void MockQuotaManager::DidGetModifiedInTimeRange(
-    GetStorageKeysCallback callback,
-    std::unique_ptr<std::set<StorageKey>> storage_keys,
+    GetBucketsCallback callback,
+    std::unique_ptr<std::set<BucketInfo>> buckets,
     StorageType storage_type) {
-  std::move(callback).Run(*storage_keys, storage_type);
+  std::move(callback).Run(*buckets, storage_type);
 }
 
 void MockQuotaManager::DidDeleteStorageKeyData(
diff --git a/storage/browser/test/mock_quota_manager.h b/storage/browser/test/mock_quota_manager.h
index 95bd3b4..acbaaed 100644
--- a/storage/browser/test/mock_quota_manager.h
+++ b/storage/browser/test/mock_quota_manager.h
@@ -53,20 +53,19 @@
   // allows clients to set up the storage key database that should be queried.
   // This method will only search through the storage keys added explicitly via
   // AddStorageKey.
-  void GetStorageKeysModifiedBetween(blink::mojom::StorageType type,
-                                     base::Time begin,
-                                     base::Time end,
-                                     GetStorageKeysCallback callback) override;
+  void GetBucketsModifiedBetween(blink::mojom::StorageType type,
+                                 base::Time begin,
+                                 base::Time end,
+                                 GetBucketsCallback callback) override;
 
-  // Removes an storage key from the canned list of storage keys, but doesn't
-  // touch anything on disk. The caller must provide `quota_client_types` which
+  // Removes a bucket from the canned list of buckets, but doesn't touch
+  // anything on disk. The caller must provide `quota_client_types` which
   // specifies the types of QuotaClients which should be removed from this
-  // storage key. Setting the mask to AllQuotaClientTypes() will remove all
-  // clients from the storage key, regardless of type.
-  void DeleteStorageKeyData(const blink::StorageKey& storage_key,
-                            blink::mojom::StorageType type,
-                            QuotaClientTypes quota_client_types,
-                            StatusCallback callback) override;
+  // bucket. Setting the mask to AllQuotaClientTypes() will remove all
+  // clients from the bucket, regardless of type.
+  void DeleteBucketData(const BucketInfo& bucket,
+                        QuotaClientTypes quota_client_types,
+                        StatusCallback callback) override;
 
   // Overrides QuotaManager's implementation so that tests can observe
   // calls to this function.
@@ -78,23 +77,31 @@
                 int64_t quota);
 
   // Helper methods for timed-deletion testing:
-  // Adds a storage key to the canned list that will be searched through via
-  // GetStorageKeysModifiedBetween.
-  // `quota_clients` specified the types of QuotaClients this canned storage key
+  // Adds a bucket to the canned list that will be searched through via
+  // GetBucketsModifiedBetween.
+  // `quota_clients` specified the types of QuotaClients this canned bucket
   // contains.
-  bool AddStorageKey(const blink::StorageKey& storage_key,
-                     blink::mojom::StorageType type,
-                     QuotaClientTypes quota_client_types,
-                     base::Time modified);
+  bool AddBucket(const BucketInfo& bucket,
+                 QuotaClientTypes quota_client_types,
+                 base::Time modified);
+
+  // Creates a BucketInfo object with a generated BucketId. Makes sure newly
+  // created buckets are created with a unique id and with the specified
+  // attributes.
+  BucketInfo CreateBucket(const blink::StorageKey& storage_key,
+                          const std::string& name,
+                          blink::mojom::StorageType type);
 
   // Helper methods for timed-deletion testing:
-  // Checks an storage key and type against the storage keys that have been
-  // added via AddStorageKey and removed via DeleteStorageKeyData. If the
-  // storage key exists in the canned list with the proper StorageType and
-  // client, returns true.
-  bool StorageKeyHasData(const blink::StorageKey& storage_key,
-                         blink::mojom::StorageType type,
-                         QuotaClientType quota_client_type) const;
+  // Checks a bucket against the buckets that have been added via AddBucket and
+  // removed via DeleteBucketData. If the bucket exists in the canned list with
+  // the proper client, returns true.
+  bool BucketHasData(const BucketInfo& bucket,
+                     QuotaClientType quota_client_type) const;
+
+  // Returns the count for how many buckets still exist for the client to make
+  // sure there are no buckets that aren't accounted for during testing.
+  int BucketDataCount(QuotaClientType quota_client);
 
   std::map<const blink::StorageKey, int> write_error_tracker() const {
     return write_error_tracker_;
@@ -106,24 +113,23 @@
  private:
   friend class MockQuotaManagerProxy;
 
-  // Contains the essential bits of information about an storage key that the
+  // Contains the essential bits of information about a bucket that the
   // MockQuotaManager needs to understand for time-based deletion:
-  // the storage key itself, the StorageType and its modification time.
-  struct StorageKeyInfo {
-    StorageKeyInfo(const blink::StorageKey& storage_key,
-                   blink::mojom::StorageType type,
-                   QuotaClientTypes quota_clients,
-                   base::Time modified);
-    ~StorageKeyInfo();
+  // the bucket itself, the StorageType, its modification time and its
+  // QuotaClients.
+  struct BucketData {
+    BucketData(const BucketInfo& bucket,
+               QuotaClientTypes quota_clients,
+               base::Time modified);
+    ~BucketData();
 
-    StorageKeyInfo(const StorageKeyInfo&) = delete;
-    StorageKeyInfo& operator=(const StorageKeyInfo&) = delete;
+    BucketData(const BucketData&) = delete;
+    BucketData& operator=(const BucketData&) = delete;
 
-    StorageKeyInfo(StorageKeyInfo&&);
-    StorageKeyInfo& operator=(StorageKeyInfo&&);
+    BucketData(BucketData&&);
+    BucketData& operator=(BucketData&&);
 
-    blink::StorageKey storage_key;
-    blink::mojom::StorageType type;
+    BucketInfo bucket;
     QuotaClientTypes quota_client_types;
     base::Time modified;
   };
@@ -144,15 +150,16 @@
   void UpdateUsage(const blink::StorageKey& storage_key,
                    blink::mojom::StorageType type,
                    int64_t delta);
-  void DidGetModifiedInTimeRange(
-      GetStorageKeysCallback callback,
-      std::unique_ptr<std::set<blink::StorageKey>> storage_keys,
-      blink::mojom::StorageType storage_type);
+  void DidGetModifiedInTimeRange(GetBucketsCallback callback,
+                                 std::unique_ptr<std::set<BucketInfo>> buckets,
+                                 blink::mojom::StorageType storage_type);
   void DidDeleteStorageKeyData(StatusCallback callback,
                                blink::mojom::QuotaStatusCode status);
 
-  // The list of stored storage keys that have been added via AddStorageKey.
-  std::vector<StorageKeyInfo> storage_keys_;
+  BucketId::Generator bucket_id_generator_;
+
+  // The list of stored buckets that have been added via AddBucket.
+  std::vector<BucketData> buckets_;
   std::map<std::pair<blink::StorageKey, blink::mojom::StorageType>, StorageInfo>
       usage_and_quota_map_;
 
diff --git a/storage/browser/test/mock_quota_manager_unittest.cc b/storage/browser/test/mock_quota_manager_unittest.cc
index 3ba6b82..e2c8ec7 100644
--- a/storage/browser/test/mock_quota_manager_unittest.cc
+++ b/storage/browser/test/mock_quota_manager_unittest.cc
@@ -52,33 +52,38 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  void GetModifiedStorageKeys(StorageType type,
-                              base::Time begin,
-                              base::Time end) {
-    manager_->GetStorageKeysModifiedBetween(
+  void GetModifiedBuckets(StorageType type, base::Time begin, base::Time end) {
+    base::RunLoop run_loop;
+    manager_->GetBucketsModifiedBetween(
         type, begin, end,
-        base::BindOnce(&MockQuotaManagerTest::GotModifiedStorageKeys,
-                       weak_factory_.GetWeakPtr()));
+        base::BindOnce(&MockQuotaManagerTest::GotModifiedBuckets,
+                       weak_factory_.GetWeakPtr(), run_loop.QuitClosure()));
+    run_loop.Run();
   }
 
-  void GotModifiedStorageKeys(const std::set<StorageKey>& storage_keys,
-                              StorageType type) {
-    storage_keys_ = storage_keys;
+  void GotModifiedBuckets(base::OnceClosure quit_closure,
+                          const std::set<BucketInfo>& buckets,
+                          StorageType type) {
+    buckets_ = buckets;
     type_ = type;
+    std::move(quit_closure).Run();
   }
 
-  void DeleteStorageKeyData(const StorageKey& storage_key,
-                            StorageType type,
-                            QuotaClientTypes quota_client_types) {
-    manager_->DeleteStorageKeyData(
-        storage_key, type, std::move(quota_client_types),
-        base::BindOnce(&MockQuotaManagerTest::DeletedStorageKeyData,
-                       weak_factory_.GetWeakPtr()));
+  void DeleteBucketData(const BucketInfo& bucket,
+                        QuotaClientTypes quota_client_types) {
+    base::RunLoop run_loop;
+    manager_->DeleteBucketData(
+        bucket, std::move(quota_client_types),
+        base::BindOnce(&MockQuotaManagerTest::DeletedBucketData,
+                       weak_factory_.GetWeakPtr(), run_loop.QuitClosure()));
+    run_loop.Run();
   }
 
-  void DeletedStorageKeyData(blink::mojom::QuotaStatusCode status) {
+  void DeletedBucketData(base::OnceClosure quit_closure,
+                         blink::mojom::QuotaStatusCode status) {
     ++deletion_callback_count_;
     EXPECT_EQ(blink::mojom::QuotaStatusCode::kOk, status);
+    std::move(quit_closure).Run();
   }
 
   int deletion_callback_count() const {
@@ -89,7 +94,7 @@
     return manager_.get();
   }
 
-  const std::set<StorageKey>& storage_keys() const { return storage_keys_; }
+  const std::set<BucketInfo>& buckets() const { return buckets_; }
 
   const StorageType& type() const {
     return type_;
@@ -103,7 +108,7 @@
 
   int deletion_callback_count_;
 
-  std::set<StorageKey> storage_keys_;
+  std::set<BucketInfo> buckets_;
   StorageType type_;
 
   base::WeakPtrFactory<MockQuotaManagerTest> weak_factory_{this};
@@ -111,183 +116,134 @@
   DISALLOW_COPY_AND_ASSIGN(MockQuotaManagerTest);
 };
 
-TEST_F(MockQuotaManagerTest, BasicStorageKeyManipulation) {
+TEST_F(MockQuotaManagerTest, BasicBucketManipulation) {
   const StorageKey kStorageKey1 =
       StorageKey::CreateFromStringForTesting("http://host1:1/");
   const StorageKey kStorageKey2 =
       StorageKey::CreateFromStringForTesting("http://host2:1/");
 
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientDB));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientDB));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientDB));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientDB));
+  const BucketInfo temp_bucket1 =
+      manager()->CreateBucket(kStorageKey1, "temp_host1", kTemporary);
+  const BucketInfo perm_bucket1 =
+      manager()->CreateBucket(kStorageKey1, "perm_host1", kPersistent);
+  const BucketInfo temp_bucket2 =
+      manager()->CreateBucket(kStorageKey2, "temp_host2", kTemporary);
+  const BucketInfo perm_bucket2 =
+      manager()->CreateBucket(kStorageKey2, "perm_host2", kPersistent);
 
-  manager()->AddStorageKey(kStorageKey1, kTemporary, {kClientFile},
-                           base::Time::Now());
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientDB));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientDB));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientDB));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientDB));
+  EXPECT_EQ(manager()->BucketDataCount(kClientFile), 0);
+  EXPECT_EQ(manager()->BucketDataCount(kClientDB), 0);
 
-  manager()->AddStorageKey(kStorageKey1, kPersistent, {kClientFile},
-                           base::Time::Now());
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientDB));
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientDB));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientDB));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientDB));
+  manager()->AddBucket(temp_bucket1, {kClientFile}, base::Time::Now());
+  EXPECT_EQ(manager()->BucketDataCount(kClientFile), 1);
+  EXPECT_EQ(manager()->BucketDataCount(kClientDB), 0);
+  EXPECT_TRUE(manager()->BucketHasData(temp_bucket1, kClientFile));
 
-  manager()->AddStorageKey(kStorageKey2, kTemporary, {kClientFile, kClientDB},
-                           base::Time::Now());
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientDB));
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey1, kPersistent, kClientDB));
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientFile));
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientDB));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kPersistent, kClientDB));
+  manager()->AddBucket(perm_bucket1, {kClientFile}, base::Time::Now());
+  EXPECT_EQ(manager()->BucketDataCount(kClientFile), 2);
+  EXPECT_EQ(manager()->BucketDataCount(kClientDB), 0);
+  EXPECT_TRUE(manager()->BucketHasData(temp_bucket1, kClientFile));
+  EXPECT_TRUE(manager()->BucketHasData(perm_bucket1, kClientFile));
+
+  manager()->AddBucket(temp_bucket2, {kClientFile, kClientDB},
+                       base::Time::Now());
+  EXPECT_EQ(manager()->BucketDataCount(kClientFile), 3);
+  EXPECT_EQ(manager()->BucketDataCount(kClientDB), 1);
+  EXPECT_TRUE(manager()->BucketHasData(temp_bucket1, kClientFile));
+  EXPECT_TRUE(manager()->BucketHasData(perm_bucket1, kClientFile));
+  EXPECT_TRUE(manager()->BucketHasData(temp_bucket2, kClientFile));
+  EXPECT_TRUE(manager()->BucketHasData(temp_bucket2, kClientDB));
+
+  manager()->AddBucket(perm_bucket2, {kClientDB}, base::Time::Now());
+  EXPECT_EQ(manager()->BucketDataCount(kClientFile), 3);
+  EXPECT_EQ(manager()->BucketDataCount(kClientDB), 2);
+  EXPECT_TRUE(manager()->BucketHasData(temp_bucket1, kClientFile));
+  EXPECT_TRUE(manager()->BucketHasData(perm_bucket1, kClientFile));
+  EXPECT_TRUE(manager()->BucketHasData(temp_bucket2, kClientFile));
+  EXPECT_TRUE(manager()->BucketHasData(temp_bucket2, kClientDB));
+  EXPECT_TRUE(manager()->BucketHasData(perm_bucket2, kClientDB));
 }
 
-TEST_F(MockQuotaManagerTest, StorageKeyDeletion) {
-  const StorageKey kStorageKey1 =
-      StorageKey::CreateFromStringForTesting("http://host1:1/");
-  const StorageKey kStorageKey2 =
-      StorageKey::CreateFromStringForTesting("http://host2:1/");
-  const StorageKey kStorageKey3 =
-      StorageKey::CreateFromStringForTesting("http://host3:1/");
+TEST_F(MockQuotaManagerTest, BucketDeletion) {
+  const BucketInfo bucket1 = manager()->CreateBucket(
+      StorageKey::CreateFromStringForTesting("http://host1:1/"),
+      kDefaultBucketName, kTemporary);
+  const BucketInfo bucket2 = manager()->CreateBucket(
+      StorageKey::CreateFromStringForTesting("http://host2:1/"),
+      kDefaultBucketName, kPersistent);
+  const BucketInfo bucket3 = manager()->CreateBucket(
+      StorageKey::CreateFromStringForTesting("http://host3:1/"),
+      kDefaultBucketName, kTemporary);
 
-  manager()->AddStorageKey(kStorageKey1, kTemporary, {kClientFile},
-                           base::Time::Now());
-  manager()->AddStorageKey(kStorageKey2, kTemporary, {kClientFile, kClientDB},
-                           base::Time::Now());
-  manager()->AddStorageKey(kStorageKey3, kTemporary, {kClientFile, kClientDB},
-                           base::Time::Now());
+  manager()->AddBucket(bucket1, {kClientFile}, base::Time::Now());
+  manager()->AddBucket(bucket2, {kClientFile, kClientDB}, base::Time::Now());
+  manager()->AddBucket(bucket3, {kClientFile, kClientDB}, base::Time::Now());
 
-  DeleteStorageKeyData(kStorageKey2, kTemporary, {kClientFile});
-  base::RunLoop().RunUntilIdle();
+  DeleteBucketData(bucket2, {kClientFile});
 
   EXPECT_EQ(1, deletion_callback_count());
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientFile));
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientDB));
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey3, kTemporary, kClientFile));
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey3, kTemporary, kClientDB));
+  EXPECT_EQ(manager()->BucketDataCount(kClientFile), 2);
+  EXPECT_EQ(manager()->BucketDataCount(kClientDB), 2);
+  EXPECT_TRUE(manager()->BucketHasData(bucket1, kClientFile));
+  EXPECT_TRUE(manager()->BucketHasData(bucket2, kClientDB));
+  EXPECT_TRUE(manager()->BucketHasData(bucket3, kClientFile));
+  EXPECT_TRUE(manager()->BucketHasData(bucket3, kClientDB));
 
-  DeleteStorageKeyData(kStorageKey3, kTemporary, {kClientFile, kClientDB});
-  base::RunLoop().RunUntilIdle();
+  DeleteBucketData(bucket3, {kClientFile, kClientDB});
 
   EXPECT_EQ(2, deletion_callback_count());
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey1, kTemporary, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientFile));
-  EXPECT_TRUE(
-      manager()->StorageKeyHasData(kStorageKey2, kTemporary, kClientDB));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey3, kTemporary, kClientFile));
-  EXPECT_FALSE(
-      manager()->StorageKeyHasData(kStorageKey3, kTemporary, kClientDB));
+  EXPECT_EQ(manager()->BucketDataCount(kClientFile), 1);
+  EXPECT_EQ(manager()->BucketDataCount(kClientDB), 1);
+  EXPECT_TRUE(manager()->BucketHasData(bucket1, kClientFile));
+  EXPECT_TRUE(manager()->BucketHasData(bucket2, kClientDB));
 }
 
-TEST_F(MockQuotaManagerTest, ModifiedStorageKeys) {
-  const StorageKey kStorageKey1 =
-      StorageKey::CreateFromStringForTesting("http://host1:1/");
-  const StorageKey kStorageKey2 =
-      StorageKey::CreateFromStringForTesting("http://host2:1/");
+TEST_F(MockQuotaManagerTest, ModifiedBuckets) {
+  const BucketInfo bucket1 = manager()->CreateBucket(
+      StorageKey::CreateFromStringForTesting("http://host1:1/"),
+      kDefaultBucketName, kTemporary);
+  const BucketInfo bucket2 = manager()->CreateBucket(
+      StorageKey::CreateFromStringForTesting("http://host2:1/"),
+      kDefaultBucketName, kTemporary);
 
   base::Time now = base::Time::Now();
   base::Time then = base::Time();
   base::TimeDelta an_hour = base::TimeDelta::FromMilliseconds(3600000);
   base::TimeDelta a_minute = base::TimeDelta::FromMilliseconds(60000);
 
-  GetModifiedStorageKeys(kTemporary, then, base::Time::Max());
-  base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(storage_keys().empty());
+  GetModifiedBuckets(kTemporary, then, base::Time::Max());
+  EXPECT_TRUE(buckets().empty());
 
-  manager()->AddStorageKey(kStorageKey1, kTemporary, {kClientFile},
-                           now - an_hour);
+  manager()->AddBucket(bucket1, {kClientFile}, now - an_hour);
 
-  GetModifiedStorageKeys(kTemporary, then, base::Time::Max());
-  base::RunLoop().RunUntilIdle();
+  GetModifiedBuckets(kTemporary, then, base::Time::Max());
 
   EXPECT_EQ(kTemporary, type());
-  EXPECT_EQ(1UL, storage_keys().size());
-  EXPECT_EQ(1UL, storage_keys().count(kStorageKey1));
-  EXPECT_EQ(0UL, storage_keys().count(kStorageKey2));
+  EXPECT_EQ(1UL, buckets().size());
+  EXPECT_EQ(1UL, buckets().count(bucket1));
+  EXPECT_EQ(0UL, buckets().count(bucket2));
 
-  manager()->AddStorageKey(kStorageKey2, kTemporary, {kClientFile}, now);
+  manager()->AddBucket(bucket2, {kClientFile}, now);
 
-  GetModifiedStorageKeys(kTemporary, then, base::Time::Max());
-  base::RunLoop().RunUntilIdle();
+  GetModifiedBuckets(kTemporary, then, base::Time::Max());
 
   EXPECT_EQ(kTemporary, type());
-  EXPECT_EQ(2UL, storage_keys().size());
-  EXPECT_EQ(1UL, storage_keys().count(kStorageKey1));
-  EXPECT_EQ(1UL, storage_keys().count(kStorageKey2));
+  EXPECT_EQ(2UL, buckets().size());
+  EXPECT_EQ(1UL, buckets().count(bucket1));
+  EXPECT_EQ(1UL, buckets().count(bucket2));
 
-  GetModifiedStorageKeys(kTemporary, then, now);
-  base::RunLoop().RunUntilIdle();
+  GetModifiedBuckets(kTemporary, then, now);
 
   EXPECT_EQ(kTemporary, type());
-  EXPECT_EQ(1UL, storage_keys().size());
-  EXPECT_EQ(1UL, storage_keys().count(kStorageKey1));
-  EXPECT_EQ(0UL, storage_keys().count(kStorageKey2));
+  EXPECT_EQ(1UL, buckets().size());
+  EXPECT_EQ(1UL, buckets().count(bucket1));
+  EXPECT_EQ(0UL, buckets().count(bucket2));
 
-  GetModifiedStorageKeys(kTemporary, now - a_minute, now + a_minute);
-  base::RunLoop().RunUntilIdle();
+  GetModifiedBuckets(kTemporary, now - a_minute, now + a_minute);
 
   EXPECT_EQ(kTemporary, type());
-  EXPECT_EQ(1UL, storage_keys().size());
-  EXPECT_EQ(0UL, storage_keys().count(kStorageKey1));
-  EXPECT_EQ(1UL, storage_keys().count(kStorageKey2));
+  EXPECT_EQ(1UL, buckets().size());
+  EXPECT_EQ(0UL, buckets().count(bucket1));
+  EXPECT_EQ(1UL, buckets().count(bucket2));
 }
 }  // namespace storage
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 0657d8e..e91d9fc 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -11055,7 +11055,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.135"
+              "revision": "version:92.0.4515.137"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -11142,7 +11142,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.23"
+              "revision": "version:93.0.4577.25"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -11316,7 +11316,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.135"
+              "revision": "version:92.0.4515.137"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -11403,7 +11403,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.23"
+              "revision": "version:93.0.4577.25"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 86fff54c..4e0c397c 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -50487,7 +50487,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.135"
+              "revision": "version:92.0.4515.137"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -50575,7 +50575,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.23"
+              "revision": "version:93.0.4577.25"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -50751,7 +50751,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.135"
+              "revision": "version:92.0.4515.137"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -50839,7 +50839,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.23"
+              "revision": "version:93.0.4577.25"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51088,7 +51088,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.135"
+              "revision": "version:92.0.4515.137"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51175,7 +51175,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.23"
+              "revision": "version:93.0.4577.25"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51349,7 +51349,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.135"
+              "revision": "version:92.0.4515.137"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51436,7 +51436,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.23"
+              "revision": "version:93.0.4577.25"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51685,7 +51685,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.135"
+              "revision": "version:92.0.4515.137"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51772,7 +51772,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.23"
+              "revision": "version:93.0.4577.25"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51946,7 +51946,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.135"
+              "revision": "version:92.0.4515.137"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -52033,7 +52033,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.23"
+              "revision": "version:93.0.4577.25"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 8019fe93..fb0557a 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -349,7 +349,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M93',
-          'revision': 'version:93.0.4577.23',
+          'revision': 'version:93.0.4577.25',
         }
       ],
     },
@@ -373,7 +373,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.135',
+          'revision': 'version:92.0.4515.137',
         }
       ],
     },
@@ -421,7 +421,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M93',
-          'revision': 'version:93.0.4577.23',
+          'revision': 'version:93.0.4577.25',
         }
       ],
     },
@@ -445,7 +445,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.135',
+          'revision': 'version:92.0.4515.137',
         }
       ],
     },
@@ -493,7 +493,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M93',
-          'revision': 'version:93.0.4577.23',
+          'revision': 'version:93.0.4577.25',
         }
       ],
     },
@@ -517,7 +517,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.135',
+          'revision': 'version:92.0.4515.137',
         }
       ],
     },
@@ -626,4 +626,4 @@
       ],
     },
   },
-}
+}
\ No newline at end of file
diff --git a/testing/scripts/run_variations_smoke_tests.py b/testing/scripts/run_variations_smoke_tests.py
index 0e34d76..ac8ee34 100755
--- a/testing/scripts/run_variations_smoke_tests.py
+++ b/testing/scripts/run_variations_smoke_tests.py
@@ -3,27 +3,221 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """A smoke test to verify Chrome doesn't crash and basic rendering is functional
-when parsing a newly given Finch seed.
+when parsing a newly given variations seed.
 """
 
 import argparse
 import json
 import logging
 import os
+import shutil
 import sys
+import tempfile
 
 import common
 
+_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
+_SRC_DIR = os.path.join(_THIS_DIR, os.path.pardir, os.path.pardir)
+_WEBDRIVER_PATH = os.path.join(_SRC_DIR, 'third_party', 'webdriver', 'pylib')
+
+sys.path.insert(0, _WEBDRIVER_PATH)
+from selenium import webdriver
+from selenium.webdriver import ChromeOptions
+from selenium.common.exceptions import NoSuchElementException
+from selenium.common.exceptions import WebDriverException
+
+# Constants around the Local State file and variation keys.
+_LOCAL_STATE_FILE_NAME = 'Local State'
+_LOCAL_STATE_SEED_NAME = 'variations_compressed_seed'
+_LOCAL_STATE_SEED_SIGNATURE_NAME = 'variations_seed_signature'
+
+# Test cases to verify web elements can be rendered correctly.
+_TEST_CASES = [
+    {
+        # data:text/html,<h1 id="success">Success</h1>
+        'url': 'data:text/html,%3Ch1%20id%3D%22success%22%3ESuccess%3C%2Fh1%3E',
+        'expected_id': 'success',
+        'expected_text': 'Success',
+    },
+    {
+        # TODO(crbug.com/1234165): Make tests hermetic by using a test http
+        # server or WPR.
+        'url': 'https://chromium.org/',
+        'expected_id': 'sites-chrome-userheader-title',
+        'expected_text': 'The Chromium Projects',
+    },
+]
+
+
+def _parse_test_seed():
+  """Reads and parses the test variations seed.
+
+  For prototypeing propose, a test seed is hard-coded in the
+  variations_smoke_test_data/ directory, and this function should be updated to
+  use the seed provided by the official variations test recipe once it's ready
+  for integration.
+
+  Returns:
+    A tuple of two strings: the compressed seed and the seed signature.
+  """
+  with open(
+      os.path.join(_THIS_DIR, 'variations_smoke_test_data',
+                   'variations_seed_beta_linux.json')) as f:
+    seed_json = json.load(f)
+
+  return (seed_json.get(_LOCAL_STATE_SEED_NAME, None),
+          seed_json.get(_LOCAL_STATE_SEED_NAME, None))
+
+
+def _get_current_seed(user_data_dir):
+  """Gets the current seed.
+
+  Args:
+    user_data_dir (str): Path to the user data directory used to laucn Chrome.
+
+  Returns:
+    A tuple of two strings: the compressed seed and the seed signature.
+  """
+  with open(os.path.join(user_data_dir, _LOCAL_STATE_FILE_NAME)) as f:
+    local_state = json.load(f)
+
+  return local_state.get(_LOCAL_STATE_SEED_NAME, None), local_state.get(
+      _LOCAL_STATE_SEED_SIGNATURE_NAME, None)
+
+
+def _inject_test_seed(seed, signature, user_data_dir):
+  """Injects the given test seed.
+
+  Args:
+    seed (str): A variations seed.
+    signature (str): A seed signature.
+    user_data_dir (str): Path to the user data directory used to laucn Chrome.
+  """
+  with open(os.path.join(user_data_dir, _LOCAL_STATE_FILE_NAME)) as f:
+    local_state = json.load(f)
+
+  local_state[_LOCAL_STATE_SEED_NAME] = seed
+  local_state[_LOCAL_STATE_SEED_SIGNATURE_NAME] = signature
+
+  with open(os.path.join(user_data_dir, _LOCAL_STATE_FILE_NAME), 'w') as f:
+    json.dump(local_state, f)
+
+
+def _run_tests():
+  """Runs the smoke tests.
+
+  Returns:
+    0 if tests passed, otherwise 1.
+  """
+  path_chrome = os.path.join('.', 'chrome')
+  path_chromedriver = os.path.join('.', 'chromedriver')
+
+  user_data_dir = tempfile.mkdtemp()
+  _, log_file = tempfile.mkstemp()
+
+  chrome_options = ChromeOptions()
+  chrome_options.binary_location = path_chrome
+  chrome_options.add_argument('user-data-dir=' + user_data_dir)
+  chrome_options.add_argument('log-file=' + log_file)
+
+  # By default, ChromeDriver passes in --disable-backgroud-networking, however,
+  # fetching variations seeds requires network connection, so override it.
+  chrome_options.add_experimental_option('excludeSwitches',
+                                         ['disable-background-networking'])
+
+  driver = None
+  try:
+    # Starts Chrome without seed.
+    driver = webdriver.Chrome(path_chromedriver, chrome_options=chrome_options)
+    driver.close()
+
+    # Verify a production version of variations seed was fetched successfully.
+    current_seed, current_signature = _get_current_seed(user_data_dir)
+    if not current_seed or not current_signature:
+      logging.error('Failed to fetch variations seed on initial run')
+      return 1
+
+    # Inject the test seed.
+    seed, signature = _parse_test_seed()
+    if not seed or not signature:
+      logging.error(
+          'Ill-formed test seed json file: "%s" and "%s" are required',
+          _LOCAL_STATE_SEED_NAME, _LOCAL_STATE_SEED_SIGNATURE_NAME)
+      return 1
+
+    _inject_test_seed(seed, signature, user_data_dir)
+
+    # Verify the seed has been injected successfully.
+    current_seed, current_signature = _get_current_seed(user_data_dir)
+    if current_seed != seed or current_signature != signature:
+      logging.error('Failed to inject the test seed')
+      return 1
+
+    # Starts Chrome again with the test seed injected.
+    driver = webdriver.Chrome(path_chromedriver, chrome_options=chrome_options)
+
+    # Run test cases: visit urls and verify certain web elements are rendered
+    # correctly.
+    # TODO(crbug.com/1234404): Investigate pixel/layout based testing instead of
+    # DOM based testing to verify that rendering is working properly.
+    for t in _TEST_CASES:
+      driver.get(t['url'])
+      element = driver.find_element_by_id(t['expected_id'])
+      if not element.is_displayed() or t['expected_text'] != element.text:
+        logging.error(
+            'Test failed because element: "%s" is not visibly found after '
+            'visiting url: "%s"', t['expected_text'], t['url'])
+        return 1
+
+    driver.close()
+
+    # Starts Chrome again to allow it to download a seed delta and update the
+    # seed with it.
+    driver = webdriver.Chrome(path_chromedriver, chrome_options=chrome_options)
+    driver.close()
+
+    # Verify seed has been updated successfully and it's different from the
+    # injected test seed.
+    #
+    # TODO(crbug.com/1234171): This test expectation may not work correctly when
+    # a field trial config under test does not affect a platform, so it requires
+    # more investigations to figure out the correct behavior.
+    current_seed, current_signature = _get_current_seed(user_data_dir)
+    if current_seed == seed or current_signature == signature:
+      logging.error('Failed to update seed with a delta')
+      return 1
+  except WebDriverException as e:
+    logging.error('Chrome exited abnormally, likely due to a crash.\n%s', e)
+    return 1
+  except NoSuchElementException as e:
+    logging.error('Failed to find the expected web element.\n%s', e)
+    return 1
+  finally:
+    shutil.rmtree(user_data_dir, ignore_errors=True)
+
+    # Print logs for debugging purpose.
+    with open(log_file) as f:
+      logging.info('Chrome logs for debugging:\n%s', f.read())
+
+    shutil.rmtree(log_file, ignore_errors=True)
+    if driver:
+      driver.quit()
+
+  return 0
+
 
 def main_run(args):
-  """Runs the Finch smoke tests."""
+  """Runs the variations smoke tests."""
   logging.basicConfig(level=logging.INFO)
   parser = argparse.ArgumentParser()
   parser.add_argument('--isolated-script-test-output', type=str, required=True)
   args, _ = parser.parse_known_args()
+  rc = _run_tests()
   with open(args.isolated_script_test_output, 'w') as f:
-    common.record_local_script_results('run_finch_smoke_tests', f, [], True)
-  return 0
+    common.record_local_script_results('run_variations_smoke_tests', f, [],
+                                       rc == 0)
+
+  return rc
 
 
 def main_compile_targets(args):
diff --git a/testing/scripts/variations_smoke_test_data/variations_seed_beta_linux.json b/testing/scripts/variations_smoke_test_data/variations_seed_beta_linux.json
new file mode 100644
index 0000000..4cba279
--- /dev/null
+++ b/testing/scripts/variations_smoke_test_data/variations_seed_beta_linux.json
@@ -0,0 +1 @@
+{"variations_compressed_seed":"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","variations_seed_signature":"MEYCIQDTF0+81NAyjSBmb9DA1BZs9thXcCBEaG+kx/Qa8rZAJwIhAMJAc4FL58G4VIUKYUBFJjSRHat7oAP/zC8Ho0Ua3OZP"}
\ No newline at end of file
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index b75e286..66096f0 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3928,6 +3928,25 @@
             ]
         }
     ],
+    "GridAndGroupAndroidM5": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "enable_launch_bug_fix": "true",
+                        "enable_launch_polish": "true"
+                    },
+                    "enable_features": [
+                        "TabGroupsContinuationAndroid"
+                    ]
+                }
+            ]
+        }
+    ],
     "GroupAutoCreationAndroid": [
         {
             "platforms": [
@@ -5688,25 +5707,6 @@
             ]
         }
     ],
-    "OmniboxPedalsBatch2": [
-        {
-            "platforms": [
-                "windows",
-                "mac",
-                "chromeos",
-                "linux"
-            ],
-            "experiments": [
-                {
-                    "name": "Batch2Colored",
-                    "enable_features": [
-                        "OmniboxPedalsBatch2",
-                        "OmniboxPedalsDefaultIconColored"
-                    ]
-                }
-            ]
-        }
-    ],
     "OmniboxUpdatedConnectionSecurityIndicatorsIPH": [
         {
             "platforms": [
@@ -7504,6 +7504,21 @@
             ]
         }
     ],
+    "SafeBrowsingPasswordCheckIntegrationForSavedPasswordsAndroid": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "SafeBrowsingPasswordCheckIntegrationForSavedPasswordsAndroid"
+                    ]
+                }
+            ]
+        }
+    ],
     "SafeBrowsingPasswordProtectionRequestWithToken": [
         {
             "platforms": [
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 4d4de64..bdb5f10 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -8112,6 +8112,11 @@
     execution_context_->CountUse(feature);
 }
 
+void Document::CountDeprecation(mojom::WebFeature feature) {
+  if (execution_context_)
+    execution_context_->CountDeprecation(feature);
+}
+
 void Document::CountProperty(CSSPropertyID property) const {
   if (DocumentLoader* loader = Loader()) {
     loader->GetUseCounter().Count(
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 62564aaa..5e8a699 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -1606,6 +1606,7 @@
 
   // Use counter related functions.
   void CountUse(mojom::WebFeature feature) final;
+  void CountDeprecation(mojom::WebFeature feature) final;
   void CountUse(mojom::WebFeature feature) const;
   void CountProperty(CSSPropertyID property_id) const;
   void CountAnimatedProperty(CSSPropertyID property_id) const;
diff --git a/third_party/blink/renderer/core/execution_context/execution_context.cc b/third_party/blink/renderer/core/execution_context/execution_context.cc
index 181698e..20fa8ff 100644
--- a/third_party/blink/renderer/core/execution_context/execution_context.cc
+++ b/third_party/blink/renderer/core/execution_context/execution_context.cc
@@ -180,6 +180,10 @@
   ContextLifecycleNotifier::NotifyContextDestroyed();
 }
 
+void ExecutionContext::CountDeprecation(WebFeature feature) {
+  Deprecation::CountDeprecation(this, feature);
+}
+
 HeapObserverSet<ContextLifecycleObserver>&
 ExecutionContext::ContextLifecycleObserverSet() {
   return ContextLifecycleNotifier::observers();
diff --git a/third_party/blink/renderer/core/execution_context/execution_context.h b/third_party/blink/renderer/core/execution_context/execution_context.h
index 5e8c470..7d5894c 100644
--- a/third_party/blink/renderer/core/execution_context/execution_context.h
+++ b/third_party/blink/renderer/core/execution_context/execution_context.h
@@ -251,6 +251,8 @@
   virtual void AddInspectorIssue(mojom::blink::InspectorIssueInfoPtr) = 0;
   virtual void AddInspectorIssue(AuditsIssue) = 0;
 
+  void CountDeprecation(WebFeature feature) override;
+
   bool IsContextPaused() const;
   LoaderFreezeMode GetLoaderFreezeMode() const;
   bool IsContextDestroyed() const { return is_context_destroyed_; }
diff --git a/third_party/blink/renderer/core/frame/deprecation.cc b/third_party/blink/renderer/core/frame/deprecation.cc
index de3093c5..ef37dc7 100644
--- a/third_party/blink/renderer/core/frame/deprecation.cc
+++ b/third_party/blink/renderer/core/frame/deprecation.cc
@@ -586,6 +586,12 @@
     case WebFeature::kXHRJSONEncodingDetection:
       return {"XHRJSONEncodingDetection", kM93,
               "UTF-16 is not supported by response json in XMLHttpRequest"};
+
+    case WebFeature::kAuthorizationCoveredByWildcard:
+      return {"AuthorizationCoveredByWildcard", kM97,
+              "\"Authorization\" will not be covered by the wildcard symbol (*)"
+              "in CORS \"Access-Control-Allow-Headers\" handling."};
+
     // Features that aren't deprecated don't have a deprecation message.
     default:
       return {"NotDeprecated", kUnknown, ""};
diff --git a/third_party/blink/renderer/core/frame/history.cc b/third_party/blink/renderer/core/frame/history.cc
index 4ad6068..4de925a 100644
--- a/third_party/blink/renderer/core/frame/history.cc
+++ b/third_party/blink/renderer/core/frame/history.cc
@@ -196,6 +196,7 @@
       if (Page* page = DomWindow()->GetFrame()->GetPage())
         page->HistoryNavigationVirtualTimePauser().PauseVirtualTime();
     }
+    DomWindow()->document()->Loader()->DidTriggerBackForwardNavigation();
   } else {
     // We intentionally call reload() for the current frame if delta is zero.
     // Otherwise, navigation happens on the root frame.
diff --git a/third_party/blink/renderer/core/html/resources/html.css b/third_party/blink/renderer/core/html/resources/html.css
index 93f608e..5bc0aaf 100644
--- a/third_party/blink/renderer/core/html/resources/html.css
+++ b/third_party/blink/renderer/core/html/resources/html.css
@@ -27,31 +27,6 @@
     display: block;
 }
 
-/* children of the <head> element all have display:none */
-head {
-    display: none
-}
-
-meta {
-    display: none
-}
-
-title {
-    display: none
-}
-
-link {
-    display: none
-}
-
-style {
-    display: none
-}
-
-script {
-    display: none
-}
-
 /* generic block-level elements */
 
 body {
@@ -459,10 +434,6 @@
   font-family: monospace;
 }
 
-input[type="hidden" i] {
-    display: none
-}
-
 input {
     appearance: auto;
     padding:1px 0;
@@ -801,22 +772,23 @@
     outline-offset: -1px;
 }
 
-datalist {
-    display: none
-}
-
+/* https://html.spec.whatwg.org/multipage/rendering.html#hidden-elements */
+/* TODO(crbug.com/1231263): <area> should be display:none. */
 area {
-    display: inline;
+  display: inline;
+}
+base, basefont, datalist, head, link, meta, noembed,
+noframes, param, rp, script, style, template, title {
+  display: none;
+}
+input[type="hidden" i] {
+  display: none !important;
 }
 
 area:-webkit-any-link {
     cursor: pointer;
 }
 
-param {
-    display: none
-}
-
 input[type="checkbox" i] {
     appearance: auto;
     box-sizing: border-box;
@@ -1348,16 +1320,8 @@
     text-align: start;
 }
 
-rp {
-    display: none;
-}
-
 /* other elements */
 
-noframes {
-    display: none
-}
-
 frameset, frame {
     display: block
 }
@@ -1397,10 +1361,6 @@
     list-style-type: disclosure-open;
 }
 
-template {
-    display: none
-}
-
 bdi, output {
     unicode-bidi: isolate;
 }
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
index 80e4adb..df939940 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
@@ -1727,7 +1727,12 @@
           // NGInlineItem.
           continue;
         }
-        if (item.Type() == NGInlineItem::kAtomicInline) {
+#if DCHECK_IS_ON()
+        if (item.Type() == NGInlineItem::kBlockInInline)
+          DCHECK(line_info.HasForcedBreak());
+#endif
+        if (item.Type() == NGInlineItem::kAtomicInline ||
+            item.Type() == NGInlineItem::kBlockInInline) {
           // The max-size for atomic inlines are cached in |max_size_cache|.
           unsigned item_index = &item - items_data.items.begin();
           position += max_size_cache[item_index];
@@ -1747,10 +1752,6 @@
             continue;
           }
         }
-#if DCHECK_IS_ON()
-        if (item.Type() == NGInlineItem::kBlockInInline)
-          DCHECK(line_info.HasForcedBreak());
-#endif
         position += result.inline_size;
       }
       // Compute the forced break after all results were handled, because
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
index 0adff8f..5972df5 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
@@ -1899,39 +1899,10 @@
                    item_result->layout_result->PhysicalFragment())
             .InlineSize();
     item_result->inline_size += inline_margins;
-  } else if (mode_ == NGLineBreakerMode::kMaxContent && max_size_cache_) {
-    unsigned item_index = &item - Items().begin();
-    item_result->inline_size = (*max_size_cache_)[item_index];
   } else {
-    DCHECK(mode_ == NGLineBreakerMode::kMinContent || !max_size_cache_);
-    NGBlockNode child(To<LayoutBox>(item.GetLayoutObject()));
-
-    NGMinMaxConstraintSpaceBuilder builder(constraint_space_, node_.Style(),
-                                           child, /* is_new_fc */ true);
-    builder.SetAvailableBlockSize(constraint_space_.AvailableSize().block_size);
-    builder.SetPercentageResolutionBlockSize(
-        constraint_space_.PercentageResolutionBlockSize());
-    builder.SetReplacedPercentageResolutionBlockSize(
-        constraint_space_.ReplacedPercentageResolutionBlockSize());
-    const auto space = builder.ToConstraintSpace();
-
-    MinMaxSizesResult result =
-        ComputeMinAndMaxContentContribution(node_.Style(), child, space);
-    if (mode_ == NGLineBreakerMode::kMinContent) {
-      item_result->inline_size = result.sizes.min_size + inline_margins;
-      if (depends_on_block_constraints_out_) {
-        *depends_on_block_constraints_out_ |=
-            result.depends_on_block_constraints;
-      }
-      if (max_size_cache_) {
-        if (max_size_cache_->IsEmpty())
-          max_size_cache_->resize(Items().size());
-        unsigned item_index = &item - Items().begin();
-        (*max_size_cache_)[item_index] = result.sizes.max_size + inline_margins;
-      }
-    } else {
-      item_result->inline_size = result.sizes.max_size + inline_margins;
-    }
+    DCHECK(mode_ == NGLineBreakerMode::kMaxContent ||
+           mode_ == NGLineBreakerMode::kMinContent);
+    ComputeMinMaxContentSizeForBlockChild(item, item_result);
   }
 
   item_result->should_create_line_box = true;
@@ -1958,6 +1929,49 @@
   MoveToNextOf(item);
 }
 
+void NGLineBreaker::ComputeMinMaxContentSizeForBlockChild(
+    const NGInlineItem& item,
+    NGInlineItemResult* item_result) {
+  DCHECK(mode_ == NGLineBreakerMode::kMaxContent ||
+         mode_ == NGLineBreakerMode::kMinContent);
+  if (mode_ == NGLineBreakerMode::kMaxContent && max_size_cache_) {
+    const unsigned item_index = &item - Items().begin();
+    item_result->inline_size = (*max_size_cache_)[item_index];
+    return;
+  }
+
+  DCHECK(mode_ == NGLineBreakerMode::kMinContent || !max_size_cache_);
+  NGBlockNode child(To<LayoutBox>(item.GetLayoutObject()));
+
+  NGMinMaxConstraintSpaceBuilder builder(constraint_space_, node_.Style(),
+                                         child, /* is_new_fc */ true);
+  builder.SetAvailableBlockSize(constraint_space_.AvailableSize().block_size);
+  builder.SetPercentageResolutionBlockSize(
+      constraint_space_.PercentageResolutionBlockSize());
+  builder.SetReplacedPercentageResolutionBlockSize(
+      constraint_space_.ReplacedPercentageResolutionBlockSize());
+  const auto space = builder.ToConstraintSpace();
+
+  const MinMaxSizesResult result =
+      ComputeMinAndMaxContentContribution(node_.Style(), child, space);
+  const LayoutUnit inline_margins = item_result->margins.InlineSum();
+  if (mode_ == NGLineBreakerMode::kMinContent) {
+    item_result->inline_size = result.sizes.min_size + inline_margins;
+    if (depends_on_block_constraints_out_)
+      *depends_on_block_constraints_out_ |= result.depends_on_block_constraints;
+    if (max_size_cache_) {
+      if (max_size_cache_->IsEmpty())
+        max_size_cache_->resize(Items().size());
+      const unsigned item_index = &item - Items().begin();
+      (*max_size_cache_)[item_index] = result.sizes.max_size + inline_margins;
+    }
+    return;
+  }
+
+  DCHECK(mode_ == NGLineBreakerMode::kMaxContent && !max_size_cache_);
+  item_result->inline_size = result.sizes.max_size + inline_margins;
+}
+
 void NGLineBreaker::HandleBlockInInline(const NGInlineItem& item,
                                         NGLineInfo* line_info) {
   DCHECK_EQ(item.Type(), NGInlineItem::kBlockInInline);
@@ -1968,30 +1982,36 @@
     return;
   }
 
-  // TODO(crbug.com/716930): Support MinMax.
-
   NGInlineItemResult* item_result = AddItem(item, line_info);
-  const NGBlockBreakToken* block_break_token =
-      break_token_ ? break_token_->BlockInInlineBreakToken() : nullptr;
-  scoped_refptr<const NGLayoutResult> layout_result =
-      NGBlockNode(To<LayoutBox>(item.GetLayoutObject()))
-          .Layout(constraint_space_, block_break_token);
-  if (layout_result->Status() != NGLayoutResult::kSuccess) {
-    line_info->SetAbortedLayoutResult(std::move(layout_result));
-    state_ = LineBreakState::kDone;
-    return;
-  }
-  const NGPhysicalFragment& fragment = layout_result->PhysicalFragment();
-  item_result->inline_size =
-      NGFragment(constraint_space_.GetWritingDirection(), fragment)
-          .InlineSize();
-  position_ += item_result->inline_size;
-  item_result->should_create_line_box = !layout_result->IsSelfCollapsing();
-  item_result->layout_result = std::move(layout_result);
+  if (mode_ == NGLineBreakerMode::kContent) {
+    const NGBlockBreakToken* block_break_token =
+        break_token_ ? break_token_->BlockInInlineBreakToken() : nullptr;
+    scoped_refptr<const NGLayoutResult> layout_result =
+        NGBlockNode(To<LayoutBox>(item.GetLayoutObject()))
+            .Layout(constraint_space_, block_break_token);
+    if (layout_result->Status() != NGLayoutResult::kSuccess) {
+      line_info->SetAbortedLayoutResult(std::move(layout_result));
+      state_ = LineBreakState::kDone;
+      return;
+    }
+    const NGPhysicalFragment& fragment = layout_result->PhysicalFragment();
+    item_result->inline_size =
+        NGFragment(constraint_space_.GetWritingDirection(), fragment)
+            .InlineSize();
 
-  DCHECK(!line_info->BlockInInlineBreakToken());
-  line_info->SetBlockInInlineBreakToken(
-      To<NGBlockBreakToken>(fragment.BreakToken()));
+    item_result->should_create_line_box = !layout_result->IsSelfCollapsing();
+    item_result->layout_result = std::move(layout_result);
+
+    DCHECK(!line_info->BlockInInlineBreakToken());
+    line_info->SetBlockInInlineBreakToken(
+        To<NGBlockBreakToken>(fragment.BreakToken()));
+  } else {
+    DCHECK(mode_ == NGLineBreakerMode::kMaxContent ||
+           mode_ == NGLineBreakerMode::kMinContent);
+    ComputeMinMaxContentSizeForBlockChild(item, item_result);
+  }
+
+  position_ += item_result->inline_size;
   line_info->SetIsBlockInInline();
   line_info->SetHasForcedBreak();
   is_after_forced_break_ = true;
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
index fc2de55..e17cbf0 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
@@ -175,6 +175,8 @@
   void HandleBidiControlItem(const NGInlineItem&, NGLineInfo*);
   void HandleAtomicInline(const NGInlineItem&, NGLineInfo*);
   void HandleBlockInInline(const NGInlineItem&, NGLineInfo*);
+  void ComputeMinMaxContentSizeForBlockChild(const NGInlineItem&,
+                                             NGInlineItemResult*);
 
   bool CanBreakAfterAtomicInline(const NGInlineItem& item) const;
   bool CanBreakAfter(const NGInlineItem& item) const;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
index 4dc13a4..31aebf7 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
@@ -711,7 +711,10 @@
     input.override_inline_size = fragment.InlineSize();
     input.override_block_size = fragment.BlockSize();
     box_->ComputeAndSetBlockDirectionMargins(box_->ContainingBlock());
-    box_->ForceLayout();
+    if (box_->NeedsLayout())
+      box_->LayoutIfNeeded();
+    else
+      box_->ForceLayout();
     DCHECK_EQ(box_->Size(), physical_fragment.Size().ToLayoutSize())
         << "Legacy layout was supposed to use the size that NG computed";
   }
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 420e25ec..7bd0d58 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -315,6 +315,7 @@
   WebVector<WebHistoryItem> app_history_forward_entries;
   mojo::Remote<blink::mojom::CodeCacheHost> code_cache_host;
   HashSet<KURL> early_hints_preloaded_resources_;
+  bool scroll_offset_changed_since_last_history_navigation_triggered_;
 };
 
 // Asserts size of DocumentLoader, so that whenever a new attribute is added to
@@ -642,6 +643,7 @@
     GetLocalFrameClient().DidObserveInputDelay(input_delay);
   }
 }
+
 void DocumentLoader::DidObserveLoadingBehavior(LoadingBehaviorFlag behavior) {
   if (frame_) {
     DCHECK_GE(state_, kCommitted);
@@ -649,6 +651,18 @@
   }
 }
 
+void DocumentLoader::DidTriggerBackForwardNavigation() {
+  scroll_offset_changed_since_last_history_navigation_triggered_ = false;
+}
+
+void DocumentLoader::DidChangeScrollOffset() {
+  // The scroll offset changed. If a pending history navigation commits in this
+  // document later on, we should keep the updated scroll offset instead of
+  // trying to restore the scroll offset from the session history entry.
+  // See also https://crbug.com/1209717.
+  scroll_offset_changed_since_last_history_navigation_triggered_ = true;
+}
+
 // static
 WebHistoryCommitType LoadTypeToCommitType(WebFrameLoadType type) {
   switch (type) {
@@ -1396,6 +1410,10 @@
   // to check again if frame_ is null.
   if (!frame_ || !frame_->GetPage())
     return;
+
+  bool may_restore_scroll_offset =
+      history_item &&
+      !scroll_offset_changed_since_last_history_navigation_triggered_;
   GetFrameLoader().SaveScrollState();
 
   KURL old_url = frame_->GetDocument()->Url();
@@ -1435,8 +1453,8 @@
   // to a same ISN when a history navigation targets a frame that no longer
   // exists (https://crbug.com/705550).
   if (!same_item_sequence_number) {
-    GetFrameLoader().DidFinishSameDocumentNavigation(url, frame_load_type,
-                                                     history_item);
+    GetFrameLoader().DidFinishSameDocumentNavigation(
+        url, frame_load_type, history_item, may_restore_scroll_offset);
   }
 }
 
@@ -2566,6 +2584,10 @@
   return use_counter_.Count(feature, GetFrame());
 }
 
+void DocumentLoader::CountDeprecation(mojom::WebFeature feature) {
+  return use_counter_.Count(feature, GetFrame());
+}
+
 void DocumentLoader::RecordUseCountersForCommit() {
   // Pre-commit state, count usage the use counter associated with "this"
   // (provisional document loader) instead of frame_'s document loader.
diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h
index 5a01205..d12a375 100644
--- a/third_party/blink/renderer/core/loader/document_loader.h
+++ b/third_party/blink/renderer/core/loader/document_loader.h
@@ -165,6 +165,9 @@
   void DidObserveInputDelay(base::TimeDelta input_delay);
   void DidObserveLoadingBehavior(LoadingBehaviorFlag);
 
+  void DidTriggerBackForwardNavigation();
+  void DidChangeScrollOffset();
+
   // https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps
   void RunURLAndHistoryUpdateSteps(
       const KURL&,
@@ -304,6 +307,7 @@
 
   // UseCounter
   void CountUse(mojom::WebFeature) override;
+  void CountDeprecation(mojom::WebFeature) override;
 
   void SetApplicationCacheHostForTesting(ApplicationCacheHostForFrame* host) {
     application_cache_host_ = host;
@@ -650,6 +654,11 @@
   mojo::Remote<blink::mojom::CodeCacheHost> code_cache_host_;
 
   HashSet<KURL> early_hints_preloaded_resources_;
+
+  // Tracks whether the scroll offset changed since the last time a history
+  // navigation was triggered from this document, so that we won't attempt to do
+  // scroll restoration when the navigation commits.
+  bool scroll_offset_changed_since_last_history_navigation_triggered_ = false;
 };
 
 DECLARE_WEAK_IDENTIFIER_MAP(DocumentLoader);
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index 2d63d0d..9a9bdce 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -293,8 +293,15 @@
   // scroll offsets. In order to avoid keeping around a stale anchor, we clear
   // it when the saved scroll offset changes.
   history_item->SetScrollAnchorData(ScrollAnchorData());
-  if (ScrollableArea* layout_scrollable_area = frame_->View()->LayoutViewport())
+  if (ScrollableArea* layout_scrollable_area =
+          frame_->View()->LayoutViewport()) {
+    if (!history_item->GetViewState() ||
+        layout_scrollable_area->GetScrollOffset() !=
+            history_item->GetViewState()->scroll_offset_) {
+      document_loader_->DidChangeScrollOffset();
+    }
     history_item->SetScrollOffset(layout_scrollable_area->GetScrollOffset());
+  }
   history_item->SetVisualViewportScrollOffset(ToScrollOffset(
       frame_->GetPage()->GetVisualViewport().VisibleRect().Location()));
 
@@ -422,7 +429,8 @@
 void FrameLoader::DidFinishSameDocumentNavigation(
     const KURL& url,
     WebFrameLoadType frame_load_type,
-    HistoryItem* history_item) {
+    HistoryItem* history_item,
+    bool may_restore_scroll_offset) {
   // If we have a state object, we cannot also be a new navigation.
   scoped_refptr<SerializedScriptValue> state_object =
       history_item ? history_item->StateObject() : nullptr;
@@ -438,7 +446,7 @@
                                        ? std::move(state_object)
                                        : SerializedScriptValue::NullValue());
 
-  if (view_state) {
+  if (view_state && may_restore_scroll_offset) {
     RestoreScrollPositionAndViewState(frame_load_type, *view_state,
                                       history_item->ScrollRestorationType());
   }
diff --git a/third_party/blink/renderer/core/loader/frame_loader.h b/third_party/blink/renderer/core/loader/frame_loader.h
index e5995b1..4c313f5 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.h
+++ b/third_party/blink/renderer/core/loader/frame_loader.h
@@ -177,7 +177,8 @@
 
   void DidFinishSameDocumentNavigation(const KURL&,
                                        WebFrameLoadType,
-                                       HistoryItem*);
+                                       HistoryItem*,
+                                       bool may_restore_scroll_offset);
 
   // This will attempt to detach the current document. It will dispatch unload
   // events and abort XHR requests. Returns true if the frame is ready to
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
index 2e4a02b..8a176f4 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
@@ -45,6 +45,7 @@
 #include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
 #include "third_party/blink/renderer/core/paint/rounded_border_geometry.h"
 #include "third_party/blink/renderer/core/paint/scoped_paint_state.h"
+#include "third_party/blink/renderer/core/paint/scoped_svg_paint_state.h"
 #include "third_party/blink/renderer/core/paint/scrollable_area_painter.h"
 #include "third_party/blink/renderer/core/paint/theme_painter.h"
 #include "third_party/blink/renderer/core/paint/url_metadata_utils.h"
@@ -1655,7 +1656,14 @@
     return;
   }
 
+  const LayoutObject* layout_object = child_fragment.GetLayoutObject();
   if (child_fragment.IsInlineBox()) {
+    if (layout_object->IsSVGInline()) {
+      ScopedSVGPaintState paint_state(*layout_object, paint_info);
+      NGInlineBoxFragmentPainter(cursor, item, child_fragment)
+          .Paint(paint_info, paint_offset);
+      return;
+    }
     NGInlineBoxFragmentPainter(cursor, item, child_fragment)
         .Paint(paint_info, paint_offset);
     return;
@@ -1663,7 +1671,7 @@
 
   // Block-in-inline
   DCHECK(RuntimeEnabledFeatures::LayoutNGBlockInInlineEnabled());
-  DCHECK(!child_fragment.GetLayoutObject()->IsInline());
+  DCHECK(!layout_object->IsInline());
   PaintInfo paint_info_for_descendants = paint_info.ForDescendants();
   paint_info_for_descendants.SetIsInFragmentTraversal();
   PaintBlockChild({&child_fragment, item.OffsetInContainerFragment()},
diff --git a/third_party/blink/renderer/core/testing/null_execution_context.h b/third_party/blink/renderer/core/testing/null_execution_context.h
index 87546b36..4b6c28b 100644
--- a/third_party/blink/renderer/core/testing/null_execution_context.h
+++ b/third_party/blink/renderer/core/testing/null_execution_context.h
@@ -54,6 +54,7 @@
   scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner(TaskType) override;
 
   void CountUse(mojom::WebFeature) override {}
+  void CountDeprecation(mojom::WebFeature) override {}
 
   const BrowserInterfaceBrokerProxy& GetBrowserInterfaceBroker() const override;
 
diff --git a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
index 70a2257..d076eb3 100644
--- a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
+++ b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
@@ -252,6 +252,10 @@
   ReportingProxy().CountFeature(feature);
 }
 
+void WorkerOrWorkletGlobalScope::CountDeprecation(WebFeature feature) {
+  Deprecation::CountDeprecation(this, feature);
+}
+
 ResourceLoadScheduler::ThrottleOptionOverride
 WorkerOrWorkletGlobalScope::GetThrottleOptionOverride() const {
   return ResourceLoadScheduler::ThrottleOptionOverride::kNone;
diff --git a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h
index 9f6948b2..a7e2f9d 100644
--- a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h
+++ b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h
@@ -90,6 +90,7 @@
 
   // UseCounter
   void CountUse(WebFeature feature) final;
+  void CountDeprecation(WebFeature feature) final;
 
   // May return nullptr if this global scope is not threaded (i.e.,
   // WorkletGlobalScope for the main thread) or after Dispose() is called.
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.cc b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.cc
index 94b3564..54c677d 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.cc
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope.cc
@@ -74,7 +74,7 @@
     const Vector<absl::optional<base::TimeDelta>>& local_times,
     const Vector<Timing>& timings,
     const Vector<Timing::NormalizedTiming>& normalized_timings) {
-  DCHECK(!animators_.at(animation_id));
+  DCHECK(!animators_.DeprecatedAtOrEmptyValue(animation_id));
   Animator* animator = CreateInstance(name, options, serialized_state,
                                       local_times, timings, normalized_timings);
   if (!animator)
@@ -137,7 +137,7 @@
 
   for (const auto& animation : input.added_and_updated_animations) {
     int id = animation.worklet_animation_id.animation_id;
-    Animator* animator = animators_.at(id);
+    Animator* animator = animators_.DeprecatedAtOrEmptyValue(id);
     // We don't try to create an animator if there isn't any.
     // This can only happen if constructing an animator instance has failed
     // e.g., the constructor throws an exception.
@@ -150,7 +150,7 @@
 
   for (const auto& animation : input.updated_animations) {
     int id = animation.worklet_animation_id.animation_id;
-    Animator* animator = animators_.at(id);
+    Animator* animator = animators_.DeprecatedAtOrEmptyValue(id);
     // We don't try to create an animator if there isn't any.
     if (!animator || !predicate(animator))
       continue;
@@ -333,7 +333,7 @@
 
 AnimatorDefinition* AnimationWorkletGlobalScope::FindDefinitionForTest(
     const String& name) {
-  return animator_definitions_.at(name);
+  return animator_definitions_.DeprecatedAtOrEmptyValue(name);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
index 073dc53e..0fc71f4 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -194,14 +194,14 @@
     PopAndRestore();
   }
 
-  DCHECK_GE(state_stack_.back()->GetSaveType(),
-            CanvasRenderingContext2DState::SaveType::kBeginEndLayer);
+  DCHECK(state_stack_.back()->GetSaveType() ==
+         CanvasRenderingContext2DState::SaveType::kBeginEndLayer);
   PopAndRestore();
   layer_count_--;
 }
 
 void BaseRenderingContext2D::PopAndRestore() {
-  DCHECK_GE(state_stack_.size(), 1u);
+  DCHECK_GT(state_stack_.size(), 1u);
 
   state_stack_.pop_back();
   state_stack_.back()->ClearResolvedFilter();
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h
index 6d2d7c4..1860316e 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h
@@ -38,8 +38,9 @@
  public:
   enum ClipListCopyMode { kCopyClipList, kDontCopyClipList };
   // SaveType indicates whether the state was pushed to the state stack by Save
-  // or by BeginLayer. By default, it is set to kSaveRestore.
-  enum class SaveType { kSaveRestore, kBeginEndLayer };
+  // or by BeginLayer. The first state on the state stack, which is created in 
+  // the canvas constructor and not by Save or BeginLayer, has SaveType kIntiial.
+  enum class SaveType { kSaveRestore, kBeginEndLayer, kInitial };
 
   CanvasRenderingContext2DState();
   CanvasRenderingContext2DState(const CanvasRenderingContext2DState&,
@@ -307,7 +308,7 @@
 
   ClipList clip_list_;
 
-  const SaveType save_type_ = SaveType::kSaveRestore;
+  const SaveType save_type_ = SaveType::kInitial;
 
   DISALLOW_COPY_AND_ASSIGN(CanvasRenderingContext2DState);
 };
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.cc
index e207fc4b..7f6a296 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.cc
@@ -36,6 +36,7 @@
 namespace {
 
 const int kThumbRadius = 6;
+const base::TimeDelta kRenderTimelineInterval = base::TimeDelta::FromSeconds(1);
 
 // Only respond to main button of primary pointer(s).
 bool IsValidPointerEvent(const blink::Event& event) {
@@ -82,6 +83,13 @@
 
 void MediaControlTimelineElement::SetPosition(double current_time,
                                               bool suppress_aria) {
+  if (is_live_ && !live_anchor_time_ && current_time != 0) {
+    live_anchor_time_.emplace();
+    live_anchor_time_->clock_time_ = base::TimeTicks::Now();
+    live_anchor_time_->media_time_ = MediaElement().currentTime();
+  }
+
+  MaybeUpdateTimelineInterval();
   setValue(String::Number(current_time));
 
   if (!suppress_aria)
@@ -91,8 +99,11 @@
 }
 
 void MediaControlTimelineElement::SetDuration(double duration) {
-  double duration_value = std::isfinite(duration) ? duration : 0;
-  SetFloatingPointAttribute(html_names::kMaxAttr, duration_value);
+  is_live_ = std::isinf(duration);
+  double duration_value = duration;
+  SetFloatingPointAttribute(html_names::kMaxAttr,
+                            is_live_ ? 0.0 : duration_value);
+  SetFloatingPointAttribute(html_names::kMinAttr, 0.0);
   RenderBarSegments();
 }
 
@@ -109,10 +120,12 @@
   if (BeginScrubbingEvent(event)) {
     Platform::Current()->RecordAction(
         UserMetricsAction("Media.Controls.ScrubbingBegin"));
+    is_scrubbing_ = true;
     GetMediaControls().BeginScrubbing(MediaControlsImpl::IsTouchEvent(&event));
   } else if (EndScrubbingEvent(event)) {
     Platform::Current()->RecordAction(
         UserMetricsAction("Media.Controls.ScrubbingEnd"));
+    is_scrubbing_ = false;
     GetMediaControls().EndScrubbing();
   }
 
@@ -163,6 +176,58 @@
       event, GetLayoutObject());
 }
 
+void MediaControlTimelineElement::OnMediaPlaying() {
+  if (!is_live_)
+    return;
+
+  if (render_timeline_timer_.IsRunning())
+    render_timeline_timer_.Stop();
+}
+
+void MediaControlTimelineElement::OnMediaStoppedPlaying() {
+  if (!is_live_ || is_scrubbing_ || !live_anchor_time_)
+    return;
+
+  render_timeline_timer_.Start(
+      FROM_HERE, kRenderTimelineInterval,
+      WTF::BindRepeating(&MediaControlTimelineElement::UpdateLiveTimeline,
+                         WrapWeakPersistent(this)));
+}
+
+void MediaControlTimelineElement::OnProgress() {
+  MaybeUpdateTimelineInterval();
+  RenderBarSegments();
+}
+
+void MediaControlTimelineElement::UpdateLiveTimeline() {
+  MaybeUpdateTimelineInterval();
+  RenderBarSegments();
+}
+
+void MediaControlTimelineElement::MaybeUpdateTimelineInterval() {
+  if (!is_live_ || !MediaElement().seekable()->length() || !live_anchor_time_)
+    return;
+
+  int last_seekable = MediaElement().seekable()->length() - 1;
+  double seekable_start =
+      MediaElement().seekable()->start(last_seekable, ASSERT_NO_EXCEPTION);
+  double seekable_end =
+      MediaElement().seekable()->end(last_seekable, ASSERT_NO_EXCEPTION);
+  double expected_media_time_now =
+      live_anchor_time_->media_time_ +
+      (base::TimeTicks::Now() - live_anchor_time_->clock_time_).InSecondsF();
+
+  // Cap the current live time in seekable range.
+  if (expected_media_time_now > seekable_end) {
+    live_anchor_time_->media_time_ = seekable_end;
+    live_anchor_time_->clock_time_ = base::TimeTicks::Now();
+    expected_media_time_now = seekable_end;
+  }
+
+  SetFloatingPointAttribute(html_names::kMinAttr, seekable_start);
+  SetFloatingPointAttribute(html_names::kMaxAttr, expected_media_time_now);
+}
+
 void MediaControlTimelineElement::RenderBarSegments() {
   SetupBarSegments();
 
@@ -174,6 +239,16 @@
   // buffered range containing the current play head.
   TimeRanges* buffered_time_ranges = MediaElement().buffered();
   DCHECK(buffered_time_ranges);
+
+  // Calculate |current_time| and |duration| for live media base on the timeline
+  // value since timeline's minimum value is not necessarily zero.
+  if (is_live_) {
+    current_time =
+        value().ToDouble() - GetFloatingPointAttribute(html_names::kMinAttr);
+    duration = GetFloatingPointAttribute(html_names::kMaxAttr) -
+               GetFloatingPointAttribute(html_names::kMinAttr);
+  }
+
   if (std::isnan(duration) || std::isinf(duration) || !duration ||
       std::isnan(current_time)) {
     SetBeforeSegmentPosition(MediaControlSliderElement::Position(0, 0));
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.h b/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.h
index c40ec8d..f0c62f0 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.h
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_timeline_element.h
@@ -5,6 +5,9 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CONTROLS_ELEMENTS_MEDIA_CONTROL_TIMELINE_ELEMENT_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CONTROLS_ELEMENTS_MEDIA_CONTROL_TIMELINE_ELEMENT_H_
 
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/renderer/modules/media_controls/elements/media_control_slider_element.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 
@@ -28,6 +31,10 @@
 
   void OnMediaKeyboardEvent(Event* event) { DefaultEventHandler(*event); }
 
+  void OnMediaPlaying();
+  void OnMediaStoppedPlaying();
+  void OnProgress();
+
   void RenderBarSegments();
 
   // Inform the timeline that the Media Controls have been shown or hidden.
@@ -40,9 +47,18 @@
   const char* GetNameForHistograms() const override;
 
  private:
+  // Struct used to track the current live time.
+  struct LiveAnchorTime {
+    base::TimeTicks clock_time_;
+    double media_time_ = 0;
+  };
+
   void DefaultEventHandler(Event&) override;
   bool KeepEventInNode(const Event&) const override;
 
+  void UpdateLiveTimeline();
+  void MaybeUpdateTimelineInterval();
+
   // Checks if we can begin or end a scrubbing event. If the event is a pointer
   // event then it needs to start and end with valid pointer events. If the
   // event is a pointer event followed by a touch event then it can only be
@@ -55,6 +71,14 @@
   bool is_touching_ = false;
 
   bool controls_hidden_ = false;
+
+  bool is_scrubbing_ = false;
+
+  bool is_live_ = false;
+
+  absl::optional<LiveAnchorTime> live_anchor_time_;
+
+  base::RepeatingTimer render_timeline_timer_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc b/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
index a260d500..1b17f93 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_impl.cc
@@ -1864,11 +1864,13 @@
 void MediaControlsImpl::OnPlaying() {
   StartHideMediaControlsTimer();
   UpdateCSSClassFromState();
+  timeline_->OnMediaPlaying();
 }
 
 void MediaControlsImpl::OnPause() {
   UpdatePlayState();
   UpdateTimeIndicators();
+  timeline_->OnMediaStoppedPlaying();
   MakeOpaque();
 
   StopHideMediaControlsTimer();
@@ -1993,7 +1995,7 @@
 }
 
 void MediaControlsImpl::OnLoadingProgress() {
-  timeline_->RenderBarSegments();
+  timeline_->OnProgress();
 }
 
 void MediaControlsImpl::ComputeWhichControlsFit() {
@@ -2193,6 +2195,7 @@
 }
 
 void MediaControlsImpl::OnWaiting() {
+  timeline_->OnMediaStoppedPlaying();
   UpdateCSSClassFromState();
 }
 
diff --git a/third_party/blink/renderer/modules/webgpu/gpu.cc b/third_party/blink/renderer/modules/webgpu/gpu.cc
index ab48663..47cdbed 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu.cc
@@ -203,8 +203,7 @@
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
   ScriptPromise promise = resolver->Promise();
 
-  if (!dawn_control_client_ || dawn_control_client_->IsContextLost() ||
-      !dawn_control_client_->GetContextProviderWeakPtr()) {
+  if (!dawn_control_client_ || dawn_control_client_->IsContextLost()) {
     ExecutionContext* execution_context = ExecutionContext::From(script_state);
 
     // TODO(natlee@microsoft.com): if GPU process is lost, wait for the GPU
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_adapter.cc b/third_party/blink/renderer/modules/webgpu/gpu_adapter.cc
index 4483958..44ea86fe 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_adapter.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_adapter.cc
@@ -91,11 +91,12 @@
   return features_;
 }
 
-void GPUAdapter::OnRequestDeviceCallback(ScriptPromiseResolver* resolver,
+void GPUAdapter::OnRequestDeviceCallback(ScriptState* script_state,
+                                         ScriptPromiseResolver* resolver,
                                          const GPUDeviceDescriptor* descriptor,
                                          WGPUDevice dawn_device) {
   if (dawn_device) {
-    ExecutionContext* execution_context = resolver->GetExecutionContext();
+    ExecutionContext* execution_context = ExecutionContext::From(script_state);
     auto* device = MakeGarbageCollected<GPUDevice>(execution_context,
                                                    GetDawnControlClient(), this,
                                                    dawn_device, descriptor);
@@ -181,7 +182,8 @@
     context_provider->ContextProvider()->WebGPUInterface()->RequestDeviceAsync(
         adapter_service_id_, requested_device_properties,
         WTF::Bind(&GPUAdapter::OnRequestDeviceCallback, WrapPersistent(this),
-                  WrapPersistent(resolver), WrapPersistent(descriptor)));
+                  WrapPersistent(script_state), WrapPersistent(resolver),
+                  WrapPersistent(descriptor)));
   } else {
     resolver->Reject(MakeGarbageCollected<DOMException>(
         DOMExceptionCode::kOperationError, "WebGPU context lost"));
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_adapter.h b/third_party/blink/renderer/modules/webgpu/gpu_adapter.h
index dce988b..1bdb75d 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_adapter.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_adapter.h
@@ -52,7 +52,8 @@
                          const char* message);
 
  private:
-  void OnRequestDeviceCallback(ScriptPromiseResolver* resolver,
+  void OnRequestDeviceCallback(ScriptState* script_state,
+                               ScriptPromiseResolver* resolver,
                                const GPUDeviceDescriptor* descriptor,
                                WGPUDevice dawn_device);
   void InitializeFeatureNameList();
diff --git a/third_party/blink/renderer/modules/webtransport/web_transport.cc b/third_party/blink/renderer/modules/webtransport/web_transport.cc
index 9873082..37832e8 100644
--- a/third_party/blink/renderer/modules/webtransport/web_transport.cc
+++ b/third_party/blink/renderer/modules/webtransport/web_transport.cc
@@ -840,7 +840,7 @@
                                           bool fin_received) {
   DVLOG(1) << "WebTransport::OnIncomingStreamClosed(" << stream_id << ", "
            << fin_received << ") this=" << this;
-  WebTransportStream* stream = stream_map_.at(stream_id);
+  WebTransportStream* stream = stream_map_.DeprecatedAtOrEmptyValue(stream_id);
   // |stream| can be unset because of races between different ways of closing
   // |bidirectional streams.
   if (stream) {
diff --git a/third_party/blink/renderer/platform/audio/hrtf_database_loader.cc b/third_party/blink/renderer/platform/audio/hrtf_database_loader.cc
index 034ded0..01cb98a 100644
--- a/third_party/blink/renderer/platform/audio/hrtf_database_loader.cc
+++ b/third_party/blink/renderer/platform/audio/hrtf_database_loader.cc
@@ -86,6 +86,8 @@
 void HRTFDatabaseLoader::LoadAsynchronously() {
   DCHECK(IsMainThread());
 
+  MutexLocker locker(lock_);
+
   // m_hrtfDatabase and m_thread should both be unset because this should be a
   // new HRTFDatabaseLoader object that was just created by
   // createAndLoadAsynchronouslyIfNecessary and because we haven't started
@@ -122,6 +124,10 @@
 }
 
 void HRTFDatabaseLoader::WaitForLoaderThreadCompletion() {
+  // We can lock this because this is called from either the main thread or
+  // the offline audio rendering thread.
+  MutexLocker locker(lock_);
+
   if (!thread_)
     return;
 
diff --git a/third_party/blink/renderer/platform/audio/hrtf_database_loader.h b/third_party/blink/renderer/platform/audio/hrtf_database_loader.h
index 3ce476f..a94997b4 100644
--- a/third_party/blink/renderer/platform/audio/hrtf_database_loader.h
+++ b/third_party/blink/renderer/platform/audio/hrtf_database_loader.h
@@ -64,8 +64,8 @@
   // must be called from the audio thread.
   bool IsLoaded() { return Database(); }
 
-  // waitForLoaderThreadCompletion() may be called more than once and is
-  // thread-safe.
+  // May be called from both main and audio thread, and also can be called more
+  // than once.
   void WaitForLoaderThreadCompletion();
 
   // Returns the database or nullptr if the database doesn't yet exist.  Must
@@ -87,11 +87,10 @@
   void LoadTask();
   void CleanupTask(base::WaitableEvent*);
 
-  // Holding a m_lock is required when accessing m_hrtfDatabase since we access
-  // it from multiple threads.
+  // |lock_| MUST be held when accessing |hrtf_database_| or |thread_| because
+  // it can be accessed by multiple threads (e.g multiple AudioContexts).
   Mutex lock_;
   std::unique_ptr<HRTFDatabase> hrtf_database_;
-
   std::unique_ptr<Thread> thread_;
 
   float database_sample_rate_;
diff --git a/third_party/blink/renderer/platform/bindings/dom_wrapper_world.cc b/third_party/blink/renderer/platform/bindings/dom_wrapper_world.cc
index 1f6e80bb0f..d38eaa4 100644
--- a/third_party/blink/renderer/platform/bindings/dom_wrapper_world.cc
+++ b/third_party/blink/renderer/platform/bindings/dom_wrapper_world.cc
@@ -226,7 +226,8 @@
 
 String DOMWrapperWorld::NonMainWorldHumanReadableName() const {
   DCHECK(!IsMainWorld());
-  return IsolatedWorldHumanReadableNames().at(GetWorldId());
+  return IsolatedWorldHumanReadableNames().DeprecatedAtOrEmptyValue(
+      GetWorldId());
 }
 
 void DOMWrapperWorld::SetNonMainWorldHumanReadableName(
diff --git a/third_party/blink/renderer/platform/bindings/v8_global_value_map.h b/third_party/blink/renderer/platform/bindings/v8_global_value_map.h
index ac7c7a2..ee71262 100644
--- a/third_party/blink/renderer/platform/bindings/v8_global_value_map.h
+++ b/third_party/blink/renderer/platform/bindings/v8_global_value_map.h
@@ -48,7 +48,7 @@
     return old_value;
   }
   static v8::PersistentContainerValue Get(const Impl* impl, KeyType key) {
-    return impl->at(key);
+    return impl->DeprecatedAtOrEmptyValue(key);
   }
 
   static v8::PersistentContainerValue Remove(Impl* impl, KeyType key) {
diff --git a/third_party/blink/renderer/platform/bindings/v8_per_context_data.cc b/third_party/blink/renderer/platform/bindings/v8_per_context_data.cc
index 8d92ce43..e747045 100644
--- a/third_party/blink/renderer/platform/bindings/v8_per_context_data.cc
+++ b/third_party/blink/renderer/platform/bindings/v8_per_context_data.cc
@@ -171,7 +171,7 @@
 }
 
 V8PerContextData::Data* V8PerContextData::GetData(const char* key) {
-  return data_map_.at(key);
+  return data_map_.DeprecatedAtOrEmptyValue(key);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.cc b/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.cc
index c699c8a..65a72b04 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.cc
@@ -5,7 +5,7 @@
 #include "third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h"
 
 #include "base/check.h"
-#include "gpu/command_buffer/client/webgpu_interface.h"
+#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_resource_provider_cache.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 
 namespace blink {
@@ -18,9 +18,8 @@
       base::MakeRefCounted<DawnControlClientHolder>(std::move(context_provider),
                                                     std::move(task_runner));
   dawn_control_client_holder->context_provider_->ContextProvider()
-      ->SetLostContextCallback(
-          WTF::BindRepeating(&DawnControlClientHolder::SetContextLost,
-                             dawn_control_client_holder));
+      ->SetLostContextCallback(WTF::BindRepeating(
+          &DawnControlClientHolder::Destroy, dawn_control_client_holder));
   return dawn_control_client_holder;
 }
 
@@ -29,16 +28,35 @@
     scoped_refptr<base::SingleThreadTaskRunner> task_runner)
     : context_provider_(std::make_unique<WebGraphicsContext3DProviderWrapper>(
           std::move(context_provider))),
-      procs_(
-          context_provider_->ContextProvider()->WebGPUInterface()->GetProcs()),
+      task_runner_(task_runner),
+      api_channel_(context_provider_->ContextProvider()
+                       ->WebGPUInterface()
+                       ->GetAPIChannel()),
+      procs_(api_channel_->GetProcs()),
       recyclable_resource_cache_(GetContextProviderWeakPtr(), task_runner) {}
 
+DawnControlClientHolder::~DawnControlClientHolder() = default;
+
 void DawnControlClientHolder::Destroy() {
-  SetContextLost();
+  api_channel_->Disconnect();
+
+  // Destroy the WebGPU context.
+  // This ensures that GPU resources are eagerly reclaimed.
+  // Because we have disconnected the wire client, any JavaScript which uses
+  // WebGPU will do nothing.
   if (context_provider_) {
-    context_provider_->ContextProvider()
-        ->WebGPUInterface()
-        ->DisconnectContextAndDestroyServer();
+    // If the context provider is destroyed during a real lost context event, it
+    // causes the CommandBufferProxy that the context provider owns, which is
+    // what issued the lost context event in the first place, to be destroyed
+    // before the event is done being handled. This causes a crash when an
+    // outstanding AutoLock goes out of scope. To avoid this, we create a no-op
+    // task to hold a reference to the context provider until this function is
+    // done executing, and drop it after.
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce([](std::unique_ptr<WebGraphicsContext3DProviderWrapper>
+                              context_provider) {},
+                       std::move(context_provider_)));
   }
 }
 
@@ -50,12 +68,8 @@
   return context_provider_->GetWeakPtr();
 }
 
-void DawnControlClientHolder::SetContextLost() {
-  lost_ = true;
-}
-
 bool DawnControlClientHolder::IsContextLost() const {
-  return lost_;
+  return !context_provider_;
 }
 
 std::unique_ptr<RecyclableCanvasResource>
diff --git a/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h b/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h
index e30657c0..fd58ce9 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h
@@ -8,24 +8,26 @@
 #include <dawn/dawn_proc_table.h>
 #include <dawn/webgpu.h>
 
+#include "gpu/command_buffer/client/webgpu_interface.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/webgpu_resource_provider_cache.h"
 #include "third_party/blink/renderer/platform/graphics/web_graphics_context_3d_provider_wrapper.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
 
-namespace gpu {
-namespace webgpu {
+namespace base {
 
-class WebGPUInterface;
+class SingleThreadTaskRunner;
 
-}  // namespace webgpu
-}  // namespace gpu
+}  // namespace base
 
 namespace blink {
 
-// This class holds the WebGPUInterface and a |destroyed_| flag.
-// DawnControlClientHolder::Destroy() should be called to destroy the backing
-// WebGPUInterface.
+// This class holds the WebGraphicsContext3DProviderWrapper and a strong
+// reference to the WebGPU APIChannel.
+// DawnControlClientHolder::Destroy() should be called to destroy the
+// context which will free all command buffer and GPU resources. As long
+// as the reference to the APIChannel is held, calling WebGPU procs is
+// valid.
 class PLATFORM_EXPORT DawnControlClientHolder
     : public RefCounted<DawnControlClientHolder> {
  public:
@@ -45,7 +47,6 @@
   base::WeakPtr<WebGraphicsContext3DProviderWrapper> GetContextProviderWeakPtr()
       const;
   const DawnProcTable& GetProcs() const { return procs_; }
-  void SetContextLost();
   bool IsContextLost() const;
   std::unique_ptr<RecyclableCanvasResource> GetOrCreateCanvasResource(
       const IntSize& size,
@@ -54,11 +55,12 @@
 
  private:
   friend class RefCounted<DawnControlClientHolder>;
-  ~DawnControlClientHolder() = default;
+  ~DawnControlClientHolder();
 
   std::unique_ptr<WebGraphicsContext3DProviderWrapper> context_provider_;
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+  scoped_refptr<gpu::webgpu::APIChannel> api_channel_;
   DawnProcTable procs_;
-  bool lost_ = false;
   WebGPURecyclableResourceCache recyclable_resource_cache_;
 };
 
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_image_bitmap_handler_test.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_image_bitmap_handler_test.cc
index 15ff68fa..afcac98 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_image_bitmap_handler_test.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_image_bitmap_handler_test.cc
@@ -59,13 +59,11 @@
 class MockWebGPUInterface : public gpu::webgpu::WebGPUInterfaceStub {
  public:
   MockWebGPUInterface() {
-    procs_ = {};
-
     // WebGPU functions the tests will call. No-op them since we don't have a
     // real WebGPU device.
-    procs_.deviceReference = [](WGPUDevice) {};
-    procs_.deviceRelease = [](WGPUDevice) {};
-    procs_.textureRelease = [](WGPUTexture) {};
+    procs()->deviceReference = [](WGPUDevice) {};
+    procs()->deviceRelease = [](WGPUDevice) {};
+    procs()->textureRelease = [](WGPUTexture) {};
   }
 
   MOCK_METHOD(gpu::webgpu::ReservedTexture,
@@ -82,11 +80,6 @@
   MOCK_METHOD(void,
               DissociateMailbox,
               (GLuint texture_id, GLuint texture_generation));
-
-  const DawnProcTable& GetProcs() const override { return procs_; }
-
- private:
-  DawnProcTable procs_;
 };
 
 // The six reference pixels are: red, green, blue, white, black.
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
index 35039d1..5980266 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
@@ -21,12 +21,10 @@
 class MockWebGPUInterface : public gpu::webgpu::WebGPUInterfaceStub {
  public:
   MockWebGPUInterface() {
-    procs_ = {};
-
     // WebGPU functions the tests will call. No-op them since we don't have a
     // real WebGPU device.
-    procs_.deviceReference = [](WGPUDevice) {};
-    procs_.deviceRelease = [](WGPUDevice) {};
+    procs()->deviceReference = [](WGPUDevice) {};
+    procs()->deviceRelease = [](WGPUDevice) {};
   }
 
   MOCK_METHOD1(ReserveTexture, gpu::webgpu::ReservedTexture(WGPUDevice device));
@@ -51,13 +49,10 @@
     memcpy(&most_recent_waited_token, sync_token_data, sizeof(gpu::SyncToken));
   }
 
-  const DawnProcTable& GetProcs() const override { return procs_; }
-
   gpu::SyncToken most_recent_generated_token;
   gpu::SyncToken most_recent_waited_token;
 
  private:
-  DawnProcTable procs_;
   uint64_t token_id_ = 42;
 };
 
diff --git a/third_party/blink/renderer/platform/instrumentation/use_counter.h b/third_party/blink/renderer/platform/instrumentation/use_counter.h
index e93eac92..bd1ffcb 100644
--- a/third_party/blink/renderer/platform/instrumentation/use_counter.h
+++ b/third_party/blink/renderer/platform/instrumentation/use_counter.h
@@ -50,6 +50,10 @@
 
   // Counts a use of the given feature. Repeated calls are ignored.
   virtual void CountUse(mojom::WebFeature feature) = 0;
+
+  // Counts a use of the given feature which is being deprecated. Repeated
+  // calls are ignored.
+  virtual void CountDeprecation(mojom::WebFeature feature) = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc b/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc
index f4db817..b0ca0bd5 100644
--- a/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc
+++ b/third_party/blink/renderer/platform/loader/allowed_by_nosniff_test.cc
@@ -30,6 +30,7 @@
   }
 
   MOCK_METHOD1(CountUse, void(mojom::WebFeature));
+  MOCK_METHOD1(CountDeprecation, void(mojom::WebFeature));
 };
 
 class MockConsoleLogger : public GarbageCollected<MockConsoleLogger>,
diff --git a/third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h b/third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h
index a2478953..20e1fd0 100644
--- a/third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h
+++ b/third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h
@@ -24,6 +24,11 @@
       use_counter_->CountUse(feature);
     }
   }
+  void CountDeprecation(mojom::WebFeature feature) override {
+    if (use_counter_) {
+      use_counter_->CountDeprecation(feature);
+    }
+  }
   void Trace(Visitor* visitor) const override { visitor->Trace(use_counter_); }
 
   void Detach() { use_counter_ = nullptr; }
diff --git a/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc b/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc
index 7ceb5eb..5a4c5d9 100644
--- a/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/memory_cache.cc
@@ -170,7 +170,8 @@
   TRACE_EVENT1("blink", "MemoryCache::evict", "resource",
                resource->Url().GetString().Utf8());
 
-  ResourceMap* resources = resource_maps_.at(resource->CacheIdentifier());
+  ResourceMap* resources =
+      resource_maps_.DeprecatedAtOrEmptyValue(resource->CacheIdentifier());
   if (!resources)
     return;
 
@@ -196,11 +197,12 @@
 bool MemoryCache::Contains(const Resource* resource) const {
   if (!resource || resource->Url().IsEmpty())
     return false;
-  const ResourceMap* resources = resource_maps_.at(resource->CacheIdentifier());
+  const ResourceMap* resources =
+      resource_maps_.DeprecatedAtOrEmptyValue(resource->CacheIdentifier());
   if (!resources)
     return false;
   KURL url = RemoveFragmentIdentifierIfNeeded(resource->Url());
-  MemoryCacheEntry* entry = resources->at(url);
+  MemoryCacheEntry* entry = resources->DeprecatedAtOrEmptyValue(url);
   return entry && resource == entry->GetResource();
 }
 
@@ -214,11 +216,12 @@
   if (!resource_url.IsValid() || resource_url.IsNull())
     return nullptr;
   DCHECK(!cache_identifier.IsNull());
-  const ResourceMap* resources = resource_maps_.at(cache_identifier);
+  const ResourceMap* resources =
+      resource_maps_.DeprecatedAtOrEmptyValue(cache_identifier);
   if (!resources)
     return nullptr;
-  MemoryCacheEntry* entry =
-      resources->at(RemoveFragmentIdentifierIfNeeded(resource_url));
+  MemoryCacheEntry* entry = resources->DeprecatedAtOrEmptyValue(
+      RemoveFragmentIdentifierIfNeeded(resource_url));
   if (!entry)
     return nullptr;
   return entry->GetResource();
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index 8fc72ba..7eba67fe 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -538,7 +538,8 @@
   if (resource_url.IsEmpty())
     return nullptr;
   KURL url = MemoryCache::RemoveFragmentIdentifierIfNeeded(resource_url);
-  const WeakMember<Resource>& resource = cached_resources_map_.at(url);
+  const WeakMember<Resource>& resource =
+      cached_resources_map_.DeprecatedAtOrEmptyValue(url);
   return resource.Get();
 }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
index db1aa6b..0916745 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
@@ -739,7 +739,7 @@
   DCHECK(!passed_redirect_response.IsNull());
 
   if (passed_redirect_response.HasAuthorizationCoveredByWildcardOnPreflight()) {
-    fetcher_->GetUseCounter().CountUse(
+    fetcher_->GetUseCounter().CountDeprecation(
         mojom::WebFeature::kAuthorizationCoveredByWildcard);
   }
 
@@ -922,7 +922,7 @@
   const ResourceRequestHead& request = resource_->GetResourceRequest();
 
   if (response.HasAuthorizationCoveredByWildcardOnPreflight()) {
-    fetcher_->GetUseCounter().CountUse(
+    fetcher_->GetUseCounter().CountDeprecation(
         mojom::WebFeature::kAuthorizationCoveredByWildcard);
   }
 
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
index 23d4055..0f5c331 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
@@ -224,6 +224,16 @@
         spatial_layers,
     media::VideoEncodeAccelerator::Config::InterLayerPredMode*
         inter_layer_pred) {
+  if (codec_settings.codecType == webrtc::kVideoCodecH264 &&
+      codec_settings.H264().numberOfTemporalLayers > 1 &&
+      !RTCVideoEncoder::H264HwSupportForTemporalLayers()) {
+    DVLOG(1) << "H264 temporal layers not yet supported by HW codecs, but use"
+             << " HW codecs and leave the fallback decision to a webrtc client"
+             << " by seeing metadata in webrtc::CodecSpecificInfo";
+
+    return true;
+  }
+
   if (codec_settings.codecType == webrtc::kVideoCodecVP8 &&
       codec_settings.mode == webrtc::VideoCodecMode::kScreensharing &&
       codec_settings.VP8().numberOfTemporalLayers > 1) {
@@ -249,6 +259,22 @@
 
   // We fill SpatialLayer only in temporal layer or spatial layer encoding.
   switch (codec_settings.codecType) {
+    case webrtc::kVideoCodecH264:
+      if (codec_settings.H264().numberOfTemporalLayers > 1) {
+        // Though we don't support H264 SVC. We allocate 1 element in
+        // spatial_layers for temporal layer encoding.
+        spatial_layers->resize(1u);
+        auto& sl = (*spatial_layers)[0];
+        sl.width = codec_settings.width;
+        sl.height = codec_settings.height;
+        if (!ConvertKbpsToBps(codec_settings.startBitrate, &sl.bitrate_bps))
+          return false;
+        sl.framerate = codec_settings.maxFramerate;
+        sl.max_qp = base::saturated_cast<uint8_t>(codec_settings.qpMax);
+        sl.num_of_temporal_layers = base::saturated_cast<uint8_t>(
+            codec_settings.H264().numberOfTemporalLayers);
+      }
+      break;
     case webrtc::kVideoCodecVP8:
       if (codec_settings.VP8().numberOfTemporalLayers > 1) {
         // Though there is no SVC in VP8 spec. We allocate 1 element in
@@ -993,6 +1019,18 @@
   webrtc::CodecSpecificInfo info;
   info.codecType = video_codec_type_;
   switch (video_codec_type_) {
+    case webrtc::kVideoCodecH264: {
+      webrtc::CodecSpecificInfoH264& h264 = info.codecSpecific.H264;
+      h264.packetization_mode = webrtc::H264PacketizationMode::NonInterleaved;
+      h264.idr_frame = metadata.key_frame;
+      if (metadata.h264) {
+        h264.temporal_idx = metadata.h264->temporal_idx;
+        h264.base_layer_sync = metadata.h264->layer_sync;
+      } else {
+        h264.temporal_idx = webrtc::kNoTemporalIdx;
+        h264.base_layer_sync = false;
+      }
+    } break;
     case webrtc::kVideoCodecVP8:
       info.codecSpecific.VP8.keyIdx = -1;
       break;
@@ -1599,10 +1637,13 @@
 }
 
 // static
+bool RTCVideoEncoder::H264HwSupportForTemporalLayers() {
+  // Temporal layers are not supported by hardware encoders.
+  return false;
+}
+
+// static
 bool RTCVideoEncoder::Vp9HwSupportForSpatialLayers() {
-  // TODO(crbug.com/1217919): Query spatial scalability support for the HW
-  // encoder instead of checking OS.
-  // Underlying encoder will check the spatial SVC encoding capability on CrOS.
 #if defined(ARCH_CPU_X86_FAMILY) && BUILDFLAG(IS_CHROMEOS_ASH)
   return base::FeatureList::IsEnabled(media::kVaapiVp9kSVCHWEncoding);
 #endif
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h
index f3c86392..1ff816a8 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h
@@ -64,6 +64,8 @@
       const webrtc::VideoEncoder::RateControlParameters& parameters) override;
   EncoderInfo GetEncoderInfo() const override;
 
+  // Returns true if there's H264 HW support for temporal layers.
+  static bool H264HwSupportForTemporalLayers();
   // Returns true if there's VP9 HW support for spatial layers.
   static bool Vp9HwSupportForSpatialLayers();
 
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
index 6fc44c91..eb4a7f3 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
@@ -111,14 +111,14 @@
 
     EXPECT_CALL(*mock_gpu_factories_.get(), GetTaskRunner())
         .WillRepeatedly(Return(encoder_thread_.task_runner()));
-    mock_vea_ = ExpectCreateInitAndDestroyVEA();
   }
 
   void TearDown() override {
     DVLOG(3) << __func__;
     EXPECT_TRUE(encoder_thread_.IsRunning());
     RunUntilIdle();
-    rtc_encoder_->Release();
+    if (rtc_encoder_)
+      rtc_encoder_->Release();
     RunUntilIdle();
     encoder_thread_.Stop();
   }
@@ -148,6 +148,8 @@
         ADD_FAILURE() << "Unexpected codec type: " << codec_type;
         media_profile = media::VIDEO_CODEC_PROFILE_UNKNOWN;
     }
+
+    mock_vea_ = ExpectCreateInitAndDestroyVEA();
     rtc_encoder_ = std::make_unique<RTCVideoEncoder>(media_profile, false,
                                                      mock_gpu_factories_.get());
   }
@@ -180,8 +182,8 @@
     return codec;
   }
 
-  webrtc::VideoCodec GetSVCLayerCodec(size_t num_spatial_layers) {
-    const webrtc::VideoCodecType codec_type = webrtc::kVideoCodecVP9;
+  webrtc::VideoCodec GetSVCLayerCodec(webrtc::VideoCodecType codec_type,
+                                      size_t num_spatial_layers) {
     webrtc::VideoCodec codec{};
     codec.codecType = codec_type;
     codec.width = kInputFrameWidth;
@@ -194,23 +196,34 @@
     codec.qpMax = 30;
     codec.numberOfSimulcastStreams = 1;
     codec.mode = webrtc::VideoCodecMode::kRealtimeVideo;
-    webrtc::VideoCodecVP9& vp9 = *codec.VP9();
-    vp9.numberOfTemporalLayers = 3;
-    vp9.numberOfSpatialLayers = num_spatial_layers;
-    num_spatial_layers_ = num_spatial_layers;
-    constexpr int kDenom[] = {4, 2, 1};
-    for (size_t sid = 0; sid < num_spatial_layers; ++sid) {
-      webrtc::SpatialLayer& sl = codec.spatialLayers[sid];
-      const int denom = kDenom[sid];
-      sl.width = kInputFrameWidth / denom;
-      sl.height = kInputFrameHeight / denom;
-      sl.maxFramerate = 24;
-      sl.numberOfTemporalLayers = vp9.numberOfTemporalLayers;
-      sl.targetBitrate = kStartBitrate / denom;
-      sl.maxBitrate = sl.targetBitrate / denom;
-      sl.minBitrate = sl.targetBitrate / denom;
-      sl.qpMax = 30;
-      sl.active = true;
+
+    switch (codec_type) {
+      case webrtc::kVideoCodecH264: {
+        webrtc::VideoCodecH264& h264 = *codec.H264();
+        h264.numberOfTemporalLayers = 2;
+      } break;
+      case webrtc::kVideoCodecVP9: {
+        webrtc::VideoCodecVP9& vp9 = *codec.VP9();
+        vp9.numberOfTemporalLayers = 3;
+        vp9.numberOfSpatialLayers = num_spatial_layers;
+        num_spatial_layers_ = num_spatial_layers;
+        constexpr int kDenom[] = {4, 2, 1};
+        for (size_t sid = 0; sid < num_spatial_layers; ++sid) {
+          webrtc::SpatialLayer& sl = codec.spatialLayers[sid];
+          const int denom = kDenom[sid];
+          sl.width = kInputFrameWidth / denom;
+          sl.height = kInputFrameHeight / denom;
+          sl.maxFramerate = 24;
+          sl.numberOfTemporalLayers = vp9.numberOfTemporalLayers;
+          sl.targetBitrate = kStartBitrate / denom;
+          sl.maxBitrate = sl.targetBitrate / denom;
+          sl.minBitrate = sl.targetBitrate / denom;
+          sl.qpMax = 30;
+          sl.active = true;
+        }
+      } break;
+      default:
+        NOTREACHED();
     }
     return codec;
   }
@@ -349,18 +362,25 @@
             rtc_encoder_->InitEncode(&codec, kVideoEncoderSettings));
 }
 
-INSTANTIATE_TEST_SUITE_P(CodecProfiles,
-                         RTCVideoEncoderTest,
-                         Values(webrtc::kVideoCodecVP8,
-                                webrtc::kVideoCodecH264));
-
-TEST_F(RTCVideoEncoderTest, CreateAndInitSucceedsForTemporalLayer) {
-  webrtc::VideoCodec tl_codec = GetSVCLayerCodec(/*num_spatial_layers=*/1);
+TEST_P(RTCVideoEncoderTest, CreateAndInitSucceedsForTemporalLayer) {
+  const webrtc::VideoCodecType codec_type = GetParam();
+  if (codec_type == webrtc::kVideoCodecVP8) {
+    GTEST_SKIP() << "VP8 temporal layer encoding is not supported";
+    return;
+  }
+  webrtc::VideoCodec tl_codec = GetSVCLayerCodec(codec_type,
+                                                 /*num_spatial_layers=*/1);
   CreateEncoder(tl_codec.codecType);
   EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
             rtc_encoder_->InitEncode(&tl_codec, kVideoEncoderSettings));
 }
 
+INSTANTIATE_TEST_SUITE_P(CodecProfiles,
+                         RTCVideoEncoderTest,
+                         Values(webrtc::kVideoCodecH264,
+                                webrtc::kVideoCodecVP8,
+                                webrtc::kVideoCodecVP9));
+
 // Checks that WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE is returned when there is
 // platform error.
 TEST_F(RTCVideoEncoderTest, SoftwareFallbackAfterError) {
@@ -477,8 +497,9 @@
             rtc_encoder_->Encode(rtc_frame, &frame_types));
 }
 
-TEST_F(RTCVideoEncoderTest, EncodeTemporalLayer) {
-  webrtc::VideoCodec tl_codec = GetSVCLayerCodec(/*num_spatial_layers=*/1);
+TEST_F(RTCVideoEncoderTest, EncodeVP9TemporalLayer) {
+  webrtc::VideoCodec tl_codec = GetSVCLayerCodec(webrtc::kVideoCodecVP9,
+                                                 /*num_spatial_layers=*/1);
   CreateEncoder(tl_codec.codecType);
   EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
             rtc_encoder_->InitEncode(&tl_codec, kVideoEncoderSettings));
@@ -512,7 +533,8 @@
 
 // http://crbug.com/1226875
 TEST_F(RTCVideoEncoderTest, EncodeSpatialLayer) {
-  webrtc::VideoCodec sl_codec = GetSVCLayerCodec(/*num_spatial_layers=*/3);
+  webrtc::VideoCodec sl_codec = GetSVCLayerCodec(webrtc::kVideoCodecVP9,
+                                                 /*num_spatial_layers=*/3);
   CreateEncoder(sl_codec.codecType);
   EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
             rtc_encoder_->InitEncode(&sl_codec, kVideoEncoderSettings));
@@ -540,8 +562,9 @@
   }
 }
 
-TEST_F(RTCVideoEncoderTest, CreateAndInitThreeLayerSvc) {
-  webrtc::VideoCodec tl_codec = GetSVCLayerCodec(/*num_spatial_layers=*/3);
+TEST_F(RTCVideoEncoderTest, CreateAndInitVP9ThreeLayerSvc) {
+  webrtc::VideoCodec tl_codec = GetSVCLayerCodec(webrtc::kVideoCodecVP9,
+                                                 /*num_spatial_layers=*/3);
   CreateEncoder(tl_codec.codecType);
 
   EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
@@ -558,8 +581,9 @@
                       Field(&SpatialLayer::height, kInputFrameHeight)))));
 }
 
-TEST_F(RTCVideoEncoderTest, CreateAndInitSvcSinglecast) {
-  webrtc::VideoCodec tl_codec = GetSVCLayerCodec(/*num_spatial_layers=*/3);
+TEST_F(RTCVideoEncoderTest, CreateAndInitVP9SvcSinglecast) {
+  webrtc::VideoCodec tl_codec = GetSVCLayerCodec(webrtc::kVideoCodecVP9,
+                                                 /*num_spatial_layers=*/3);
   tl_codec.spatialLayers[1].active = false;
   tl_codec.spatialLayers[2].active = false;
   CreateEncoder(tl_codec.codecType);
@@ -573,8 +597,10 @@
                         Field(&SpatialLayer::height, kInputFrameHeight / 4)))));
 }
 
-TEST_F(RTCVideoEncoderTest, CreateAndInitSvcSinglecastWithoutTemporalLayers) {
-  webrtc::VideoCodec tl_codec = GetSVCLayerCodec(/*num_spatial_layers=*/3);
+TEST_F(RTCVideoEncoderTest,
+       CreateAndInitVP9SvcSinglecastWithoutTemporalLayers) {
+  webrtc::VideoCodec tl_codec = GetSVCLayerCodec(webrtc::kVideoCodecVP9,
+                                                 /*num_spatial_layers=*/3);
   tl_codec.spatialLayers[1].active = false;
   tl_codec.spatialLayers[2].active = false;
   tl_codec.spatialLayers[0].numberOfTemporalLayers = 1;
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-features=BackForwardCache b/third_party/blink/web_tests/FlagExpectations/enable-features=BackForwardCache
index 78465d4..e60d01b 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-features=BackForwardCache
+++ b/third_party/blink/web_tests/FlagExpectations/enable-features=BackForwardCache
@@ -2,9 +2,6 @@
 # tags: [ Release Debug ]
 # results: [ Timeout Crash Pass Failure Slow Skip ]
 
-# TODO(peria): Make these tests work with same-site back-forward cache.
-crbug.com/1155125 http/tests/history/replacestate-post-to-get-2.html [ Timeout ]
-
 # TODO(peria): Make these tests work with same-site browsing instance swap.
 crbug.com/1136383 http/tests/inspector-protocol/network/interception-download.js [ Failure ]
 crbug.com/1136383 virtual/sxg-subresource-disabled/http/tests/loading/sxg/sxg-subresource-origin-trial.https.html [ Failure ]
diff --git a/third_party/blink/web_tests/OWNERS b/third_party/blink/web_tests/OWNERS
index 72e8ffc..3dfdc72 100644
--- a/third_party/blink/web_tests/OWNERS
+++ b/third_party/blink/web_tests/OWNERS
@@ -1 +1,4 @@
 *
+
+per-file VirtualTestSuites=set noparent
+per-file VirtualTestSuites=file://third_party/blink/web_tests/VIRTUAL_OWNERS
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index b839ed5..dfa4547 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2491,13 +2491,17 @@
 crbug.com/27659 external/wpt/css/css-ruby/block-ruby-001.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/block-ruby-002.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/block-ruby-005.html [ Failure ]
+crbug.com/27659 external/wpt/css/css-ruby/empty-ruby-base-container.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/empty-ruby-text-container-float.html [ Failure ]
+crbug.com/27659 external/wpt/css/css-ruby/improperly-contained-annotation-001.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/root-block-ruby.xhtml [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/root-ruby.xhtml [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/ruby-align-001.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/ruby-align-001a.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/ruby-align-002.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/ruby-align-002a.html [ Failure ]
+crbug.com/27659 external/wpt/css/css-ruby/ruby-base-container-abs.html [ Failure ]
+crbug.com/27659 external/wpt/css/css-ruby/ruby-base-container-float.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/ruby-bidi-002.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/ruby-bidi-003.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/ruby-box-generation-001.html [ Failure ]
@@ -2509,6 +2513,7 @@
 crbug.com/27659 external/wpt/css/css-ruby/ruby-float-handling-001.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/ruby-intrinsic-isize-001.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/ruby-intrinsic-isize-002.html [ Failure ]
+crbug.com/27659 external/wpt/css/css-ruby/ruby-intrinsic-isize-003.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/ruby-justification-002.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/ruby-lang-specific-style-001.html [ Failure ]
 crbug.com/27659 external/wpt/css/css-ruby/ruby-line-break-suppression-001.html [ Failure ]
@@ -2620,12 +2625,8 @@
 crbug.com/626703 external/wpt/service-workers/service-worker/worker-interception.https.html [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
-crbug.com/626703 external/wpt/css/css-ruby/improperly-contained-annotation-001.html [ Failure ]
 crbug.com/626703 [ Mac10.14 ] virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/valid-image-after-load.https.html [ Failure ]
-crbug.com/626703 external/wpt/css/css-ruby/ruby-base-container-abs.html [ Failure ]
-crbug.com/626703 external/wpt/css/css-ruby/empty-ruby-base-container.html [ Failure ]
 crbug.com/626703 [ Mac11.0 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-local-time-null-2.https.html [ Failure ]
-crbug.com/626703 external/wpt/css/css-ruby/ruby-base-container-float.html [ Failure ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/webrtc/simulcast/getStats.https.html [ Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/html/cross-origin-opener-policy/historical/coep-navigate-popup-unsafe-inherit.https.html [ Timeout ]
 crbug.com/626703 [ Mac10.14 ] external/wpt/html/cross-origin-opener-policy/historical/coep-navigate-popup-unsafe-inherit.https.html [ Timeout ]
@@ -3143,7 +3144,6 @@
 crbug.com/626703 external/wpt/websockets/stream/tentative/close.any.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/626703 external/wpt/websockets/stream/tentative/close.any.sharedworker.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/626703 external/wpt/websockets/stream/tentative/close.any.serviceworker.html?wpt_flags=h2 [ Failure Timeout ]
-crbug.com/626703 external/wpt/css/css-ruby/ruby-intrinsic-isize-003.html [ Failure ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/selection/contenteditable/modifying-selection-with-primary-mouse-button.tentative.html [ Crash Failure ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/streams/readable-streams/tee.any.html [ Crash Failure ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/streams/readable-streams/tee.any.worker.html [ Failure ]
@@ -4130,10 +4130,10 @@
 crbug.com/1179585 virtual/layout_ng_svg_text/svg/custom/empty-mask.svg [ Failure ]
 crbug.com/1179585 virtual/layout_ng_svg_text/svg/custom/text-match-highlight.html [ Failure ]
 crbug.com/1179585 virtual/layout_ng_svg_text/svg/custom/visibility-collapse.html [ Failure ]
+crbug.com/1179585 virtual/layout_ng_svg_text/svg/dynamic-updates/SVGTextElement-dom-textLength-attr.html [ Failure ]
+crbug.com/1179585 virtual/layout_ng_svg_text/svg/dynamic-updates/SVGTextElement-svgdom-textLength-prop.html [ Failure ]
 crbug.com/1179585 virtual/layout_ng_svg_text/svg/dynamic-updates/SVGTextElement-dom-lengthAdjust-attr.html [ Failure ]
 crbug.com/1179585 virtual/layout_ng_svg_text/svg/dynamic-updates/SVGTextElement-svgdom-lengthAdjust-prop.html [ Failure ]
-crbug.com/1179585 virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text.svg [ Failure ]
-crbug.com/1179585 virtual/layout_ng_svg_text/svg/filters/filter-on-tspan.svg [ Failure ]
 crbug.com/1179585 virtual/layout_ng_svg_text/svg/stroke/non-scaling-stroke-text-decoration.html [ Failure ]
 crbug.com/1179585 virtual/layout_ng_svg_text/svg/stroke/non-scaling-stroke-text-decoration-dashed.html [ Failure ]
 crbug.com/1179585 virtual/layout_ng_svg_text/svg/text/bbox-with-glyph-overflow.html [ Failure ]
@@ -6293,7 +6293,6 @@
 
 # Sheriff 2018-01-25
 crbug.com/893869 css3/masking/mask-repeat-space-padding.html [ Failure Pass ]
-crbug.com/1155125 [ Linux ] http/tests/history/replacestate-post-to-get-2.html [ Pass Timeout ]
 
 # V8 roll
 crbug.com/961059 fast/workers/worker-shared-asm-buffer.html [ Skip ]
diff --git a/third_party/blink/web_tests/VIRTUAL_OWNERS b/third_party/blink/web_tests/VIRTUAL_OWNERS
new file mode 100644
index 0000000..38edf3e
--- /dev/null
+++ b/third_party/blink/web_tests/VIRTUAL_OWNERS
@@ -0,0 +1,21 @@
+# These owners approve changes to VirtualTestSuites.
+#
+# The following things should be checked whether they are justified in the
+# CL description:
+# - If the CL adds virtual tests (by adding new virtual suites or new
+#   directories to existing virtual suties), how many tests the CL adds.
+# - If the number of new tests is big (e.g. >=1000),
+#   - Whether the new tests can be skipped on some platforms / OS versions.
+#   - Whether the new directories are essential for test coverage.
+#   - Whether other alternatives [1] are more suitable than virtual suites.
+#   - How much more time of the swarm jobs for the blink_web_tests step on
+#     bots will take.
+#
+# [1] https://chromium.googlesource.com/chromium/src/+/main/docs/testing/web_tests.md#testing-runtime-flags
+
+ikilpatrick@chromium.org
+kojii@chromium.org
+masonf@chromium.org
+pdr@chromium.org
+tkent@chromium.org
+wangxianzhu@chromium.org
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 0106a5a..e742a6d 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1161,3 +1161,4 @@
     "args": ["--enable-blink-features=VirtualKeyboard"]
   }
 ]
+
diff --git a/third_party/blink/web_tests/WebGPUExpectations b/third_party/blink/web_tests/WebGPUExpectations
index caa3d789..20cf373 100644
--- a/third_party/blink/web_tests/WebGPUExpectations
+++ b/third_party/blink/web_tests/WebGPUExpectations
@@ -131,9 +131,6 @@
 crbug.com/1215024 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createComputePipeline:enrty_point_name_must_match:stageEntryPoint="main%5Cu0000";* [ Failure ]
 crbug.com/1215024 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createComputePipeline:enrty_point_name_must_match:stageEntryPoint="main%5Cu0000a";* [ Failure ]
 
-# Crash when creating the bindgroup. Most likely because we are using an invalid VkImageView
-crbug.com/dawn/1043 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createBindGroup:texture,resource_state:* [ Crash Failure ]
-
 # Flaky crash on shutdown
 crbug.com/1236132 wpt_internal/webgpu/cts.html?q=webgpu:web_platform,canvas,readbackFromWebGPUCanvas:onscreenCanvas,uploadToWebGL:* [ RetryOnFailure Crash ]
 
@@ -266,18 +263,6 @@
 # Nvidia only, worker only, very flaky
 [ Linux ] wpt_internal/webgpu/cts.html?worker=1&q=webgpu:api,operation,render_pass,storeop2:* [ Failure ]
 
-# Vulkan drivers incorrectly interpret shader modules with multiple entrypoints.
-crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:buffers_with_varying_step_mode:* [ Crash Failure ]
-crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:non_zero_array_stride_and_attribute_offset:* [ Crash Failure ]
-crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:setVertexBuffer_offset_and_attribute_offset:* [ Crash Failure ]
-crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:vertex_format_to_shader_format_conversion:* [ Crash Failure ]
-crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:discontiguous_location_and_attribs:* [ Crash Failure ]
-crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:max_buffers_and_attribs:* [ Crash Failure ]
-crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:overlapping_attributes:* [ Crash Failure ]
-crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:array_stride_zero:* [ Crash Failure ]
-crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:vertex_buffer_used_multiple_times_interleaved:* [ Crash Failure ]
-crbug.com/dawn/956 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,operation,vertex_state,correctness:vertex_buffer_used_multiple_times_overlapped:* [ Crash Failure ]
-
 # and possibly crbug.com/1231840
 crbug.com/1213657 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:web_platform,copyToTexture,canvas:copy_contents_from_gl_context_canvas:* [ Slow Crash Failure Timeout ]
 
@@ -287,8 +272,6 @@
 # Crashes on pipeline compilation in the driver.
 crbug.com/tint/993 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:shader,execution,robust_access:linear_memory:storageClass="workgroup";atomic=false;baseType="bool";* [ Skip ]
 
-crbug.com/dawn/1013 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,validation,resource_usages,texture,in_pass_encoder:unused_bindings_in_pipeline:* [ Crash Failure ]
-
 # Crashes or fails with "Backing is being accessed by both GL and Vulkan"
 crbug.com/1234041 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,validation,queue,copyToTexture,CopyExternalImageToTexture:* [ Skip ]
 
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements-expected.txt b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements-expected.txt
index 449e57b..bba3d84 100644
--- a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements-expected.txt
@@ -1,21 +1,21 @@
 This is a testharness.js-based test.
-PASS source should not be hidden
-PASS track should not be hidden
-FAIL area should be hidden assert_equals: expected "none" but got "inline"
-FAIL base should be hidden assert_equals: expected "none" but got "inline"
-FAIL basefont should be hidden assert_equals: expected "none" but got "inline"
-PASS datalist should be hidden
-PASS head should be hidden
-PASS link should be hidden
-PASS meta should be hidden
-FAIL noembed should be hidden assert_equals: expected "none" but got "inline"
-PASS noframes should be hidden
-PASS param should be hidden
-PASS rp should be hidden
-PASS script should be hidden
-PASS style should be hidden
-PASS template should be hidden
-PASS title should be hidden
-PASS [hidden] element should be hidden
+PASS source should not be display:none
+PASS track should not be display:none
+FAIL area should be display:none assert_true: expected true got false
+PASS base should be display:none
+PASS basefont should be display:none
+PASS datalist should be display:none
+PASS head should be display:none
+PASS link should be display:none
+PASS meta should be display:none
+PASS noembed should be display:none
+PASS noframes should be display:none
+PASS param should be display:none
+PASS rp should be display:none
+PASS script should be display:none
+PASS style should be display:none
+PASS template should be display:none
+PASS title should be display:none
+PASS [hidden] element should be display:none
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements.html b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements.html
index 4286681..577f32d 100644
--- a/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements.html
+++ b/third_party/blink/web_tests/external/wpt/html/rendering/non-replaced-elements/hidden-elements.html
@@ -13,23 +13,27 @@
   "noframes", "param", "rp", "script", "style", "template", "title",
 ];
 
+function isDisplayNone(element) {
+  return getComputedStyle(element).display === "none";
+}
+
 for (let name of kNotHiddenElementLocalNames) {
   test(function() {
     let element = document.createElement(name);
     document.body.appendChild(element);
-    assert_equals(getComputedStyle(element).display, "inline");
-  }, `${name} should not be hidden`);
+    assert_false(isDisplayNone(element));
+  }, `${name} should not be display:none`);
 }
 
 for (let name of kHiddenElementLocalNames) {
   test(function() {
     let element = document.createElement(name);
     document.body.appendChild(element);
-    assert_equals(getComputedStyle(element).display, "none");
-  }, `${name} should be hidden`);
+    assert_true(isDisplayNone(element));
+  }, `${name} should be display:none`);
 }
 
 test(function() {
-  assert_equals(getComputedStyle(document.querySelector("[hidden]")).display, "none");
-}, `[hidden] element should be hidden`);
+  assert_true(isDisplayNone(document.querySelector("[hidden]")));
+}, `[hidden] element should be display:none`);
 </script>
diff --git a/third_party/blink/web_tests/fast/forms/access-key-for-all-elements.html b/third_party/blink/web_tests/fast/forms/access-key-for-all-elements.html
index ed1388a..a25bea1 100644
--- a/third_party/blink/web_tests/fast/forms/access-key-for-all-elements.html
+++ b/third_party/blink/web_tests/fast/forms/access-key-for-all-elements.html
@@ -29,7 +29,7 @@
 
 const focusableTagNames = [
   "a", "abbr", "acronym", "address", "area", "article", "aside", "audio",
-  "b", "base", "basefont", "bdo", "bgsound", "big", "blockquote", "br",
+  "b", "bdo", "bgsound", "big", "blockquote", "br",
   "canvas", "caption", "center", "cite", "code", "col", "colgroup",
   "dd", "del", "details", "dfn", "dir", "div", "dl", "dt",
   "em", "fieldset", "figcaption", "figure", "font", "footer", "form",
@@ -44,7 +44,7 @@
   "table", "tbody", "td", "tfoot", "th", "thead", "tr", "track", "tt",
   "u", "ul", "var", "video", "wbr", "xmp"];
 
-const nonFocusableTagNames = ["embed", "head", "link", "meta", "noembed", "noscript", "param", "script", "style", "title"];
+const nonFocusableTagNames = ["base", "basefont", "embed", "head", "link", "meta", "noembed", "noscript", "param", "script", "style", "title"];
 
 function testPressAccessKeyOnElement(element) {
   var clicked = false;
diff --git a/third_party/blink/web_tests/fast/loader/scroll-restore-does-not-happen-if-page-scrolled-expected.txt b/third_party/blink/web_tests/fast/loader/scroll-restore-does-not-happen-if-page-scrolled-expected.txt
new file mode 100644
index 0000000..c848543
--- /dev/null
+++ b/third_party/blink/web_tests/fast/loader/scroll-restore-does-not-happen-if-page-scrolled-expected.txt
@@ -0,0 +1,18 @@
+Test ensures that frame scroll position is not restored if the page scrolled away before the history navigation finished.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS document.scrollingElement.scrollLeft is 100
+PASS document.scrollingElement.scrollTop is 100
+PASS document.scrollingElement.scrollLeft is 200
+PASS document.scrollingElement.scrollTop is 200
+PASS document.scrollingElement.scrollLeft is 300
+PASS document.scrollingElement.scrollTop is 300
+Verify that scroll position is kept correctly during navigation.
+PASS document.scrollingElement.scrollLeft is 300
+PASS document.scrollingElement.scrollTop is 300
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
diff --git a/third_party/blink/web_tests/fast/loader/scroll-restore-does-not-happen-if-page-scrolled.html b/third_party/blink/web_tests/fast/loader/scroll-restore-does-not-happen-if-page-scrolled.html
new file mode 100644
index 0000000..4e15eba
--- /dev/null
+++ b/third_party/blink/web_tests/fast/loader/scroll-restore-does-not-happen-if-page-scrolled.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<style>
+#content {
+  height: 2000px;
+  width: 2000px;
+}
+</style>
+
+<div id='console'></div>
+<div id='content'></div>
+
+<script src="../../resources/js-test.js"></script>
+<script>
+jsTestIsAsync = true;
+setPrintTestResultsLazily();
+description('Test ensures that frame scroll position is not restored if the ' +
+            'page scrolled away before the history navigation finished.');
+
+// Scroll to (100, 100).
+window.scrollTo(100, 100);
+shouldBe('document.scrollingElement.scrollLeft', '100');
+shouldBe('document.scrollingElement.scrollTop', '100');
+
+// Add a new session history entry with URL fragment #test.
+// The previous session history entry will save the current scroll offset.
+history.pushState(null, null, '#test');
+
+setTimeout(() => {
+  // Scroll to (200, 200).
+  window.scrollTo(200, 200);
+  shouldBe('document.scrollingElement.scrollLeft', '200');
+  shouldBe('document.scrollingElement.scrollTop', '200');
+
+  // Navigate back to the first session history entry. This would normally do a
+  // scroll restoration back to (100, 100) when the navigation commits.
+  history.back();
+  // However, we immediately scroll to (300, 300).
+  window.scrollTo(300, 300);
+  shouldBe('document.scrollingElement.scrollLeft', '300');
+  shouldBe('document.scrollingElement.scrollTop', '300');
+}, 0);
+
+window.addEventListener('hashchange', _ => {
+  // After the back navigation committed, the scroll position stays at (300, 300).
+  debug('Verify that scroll position is kept correctly during navigation.');
+  shouldBe('document.scrollingElement.scrollLeft', '300');
+  shouldBe('document.scrollingElement.scrollTop', '300');
+
+  finishJSTest();
+});
+</script>
diff --git a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/svg/tspan-pattern-update-expected.txt b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/svg/tspan-pattern-update-expected.txt
index 406950be..59b5c22d 100644
--- a/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/svg/tspan-pattern-update-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/composite-after-paint/paint/invalidation/svg/tspan-pattern-update-expected.txt
@@ -6,7 +6,7 @@
       "contentsOpaque": true,
       "backgroundColor": "#FFFFFF",
       "invalidations": [
-        [8, 8, 300, 100]
+        [108, 8, 100, 100]
       ]
     }
   ]
diff --git a/third_party/blink/web_tests/http/tests/history/.htaccess b/third_party/blink/web_tests/http/tests/history/.htaccess
index 0c64a7af..fbcc3fab 100644
--- a/third_party/blink/web_tests/http/tests/history/.htaccess
+++ b/third_party/blink/web_tests/http/tests/history/.htaccess
@@ -13,3 +13,8 @@
 <Files "dynamic-frame-creation-order-onload-handler.html">
 Header set Cache-Control "no-store"
 </Files>
+# replacestate-post-to-get-2.html is not compatible with back-forward cache.
+# The following setting disables back-forward cache.
+<Files "replacestate-post-to-get-2.html">
+Header set Cache-Control "no-store"
+</Files>
diff --git a/third_party/blink/web_tests/http/tests/media/media-controls-live-timeline.html b/third_party/blink/web_tests/http/tests/media/media-controls-live-timeline.html
new file mode 100644
index 0000000..85a6729
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/media/media-controls-live-timeline.html
@@ -0,0 +1,39 @@
+<title>Test timeline work properly when playing live video.</title>
+<script src="/w3c/resources/testharness.js"></script>
+<script src="/w3c/resources/testharnessreport.js"></script>
+<script src="media-source/mediasource-util.js"></script>
+<script src="../../media-resources/media-controls.js"></script>
+<video controls></video>
+<script>
+  const epsilon = 0.1;
+  const pause_delay = 1;  // 1 second.
+
+  mediasource_testafterdataloaded(function(test, mediaElement, mediaSource, segmentInfo, sourceBuffer, mediaData){
+    mediaElement.play();
+    mediaSource.duration = +Infinity;
+    let timeline = timelineElement(mediaElement);
+
+    // Append all media data for complete playback.
+    test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer end update.');
+    test.expectEvent(mediaElement, 'loadedmetadata', 'Reached HAVE_METADATA');
+    test.expectEvent(mediaElement, 'playing', 'Playing media.');
+    sourceBuffer.appendBuffer(mediaData);
+
+    test.waitForExpectedEvents(function() {
+      test.waitForCurrentTimeChange(mediaElement, function() {
+        assert_approx_equals(Number(timeline.value), Number(timeline.max),
+                             epsilon, "timline thumb should be near the end");
+
+        mediaElement.onpause = test.step_func(function() {
+          setTimeout(test.step_func_done(function() {
+            assert_approx_equals(Number(timeline.max) - Number(timeline.value),
+                                 pause_delay, epsilon,
+                                 "timeline thumb should be 1s away from the end");
+          }), pause_delay * 1000 /* convert to milliseconds */);
+        });
+
+        mediaElement.pause();
+      });
+    });
+  }, "Timeline work properly when playing live video");
+</script>
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/tspan-pattern-update-expected.txt b/third_party/blink/web_tests/paint/invalidation/svg/tspan-pattern-update-expected.txt
index 56d07a3..7fc8c61 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/tspan-pattern-update-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/svg/tspan-pattern-update-expected.txt
@@ -6,7 +6,7 @@
       "contentsOpaque": true,
       "backgroundColor": "#FFFFFF",
       "invalidations": [
-        [8, 8, 300, 100]
+        [108, 8, 100, 100]
       ]
     }
   ]
diff --git a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png
new file mode 100644
index 0000000..5246f64
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png b/third_party/blink/web_tests/platform/mac/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png
new file mode 100644
index 0000000..b73270cb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/layout_ng_svg_text/svg/filters/filter-on-tspan-expected.png b/third_party/blink/web_tests/platform/mac/virtual/layout_ng_svg_text/svg/filters/filter-on-tspan-expected.png
new file mode 100644
index 0000000..0d67598
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/layout_ng_svg_text/svg/filters/filter-on-tspan-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png b/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png
new file mode 100644
index 0000000..7bb37b7
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/filters/filter-on-filter-for-text-expected.png
Binary files differ
diff --git a/third_party/libusb/BUILD.gn b/third_party/libusb/BUILD.gn
index c77e2e2..c60bf106a 100644
--- a/third_party/libusb/BUILD.gn
+++ b/third_party/libusb/BUILD.gn
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 
 # libusb is only used by //services/device/usb on Windows and macOS.
-assert(is_mac)
+assert(is_win || is_mac)
 
 import("//build/config/chromeos/ui_mode.gni")
 import("//build/config/features.gni")
diff --git a/third_party/metrics_proto/README.chromium b/third_party/metrics_proto/README.chromium
index b0b2832..4a7547c 100644
--- a/third_party/metrics_proto/README.chromium
+++ b/third_party/metrics_proto/README.chromium
@@ -1,8 +1,8 @@
 Name: Metrics Protos
 Short Name: metrics_proto
 URL: This is the canonical public repository
-Version: 384565628
-Date: 2021/07/13 UTC
+Version: 388133545
+Date: 2021/08/02 UTC
 License: BSD
 Security Critical: Yes
 
diff --git a/third_party/metrics_proto/structured_data.proto b/third_party/metrics_proto/structured_data.proto
index 8ce594e..09bb1fb 100644
--- a/third_party/metrics_proto/structured_data.proto
+++ b/third_party/metrics_proto/structured_data.proto
@@ -12,7 +12,7 @@
 // One structured metrics event, containing several hashed and unhashed metrics
 // related to a single event type, tied to a single pseudonymous user.
 //
-// Next tag: 4
+// Next tag: 5
 message StructuredEventProto {
   // A per-client, per-profile, per-project ID that is used only for structured
   // metrics. For projects recorded from Chrome OS's platform2 repository, this
@@ -31,10 +31,14 @@
   // 1. Metric name. This is a string, and the first 8 bytes of its MD5 hash is
   //    recorded as name_hash.
   //
-  // 2. Kind. Each metric can store two kinds of values.
+  // 2. Kind. Each metric can store three kinds of values.
   //
   //    - int64. The client supplies an int64 value for the metric, and that
-  //      value is recorded as-is in value_raw.
+  //      value is recorded as-is in value_int64.
+  //
+  //    - string. The client supplies a string value for the metric, which is
+  //      recorded as-is in value_string. This is sometimes referred to as a
+  //      "raw string" to differentiate from the following.
   //
   //    - hashed-string. The client supplies an arbitrary string for the metric.
   //      The string itself is not recorded, instead, value_hmac records the
@@ -50,9 +54,21 @@
     oneof value {
       fixed64 value_hmac = 2;
       int64 value_int64 = 3;
+      string value_string = 4;
     }
   }
   repeated Metric metrics = 3;
+
+  // Type of this event, which determines which log source the event is saved
+  // into. An event should have type RAW_STRING if and only if the event may
+  // contain raw string metrics, ie. strings that have not been HMAC'd. The
+  // UNKNOWN value is considered an error and should never be sent.
+  enum EventType {
+    UNKNOWN = 0;
+    REGULAR = 1;
+    RAW_STRING = 2;
+  }
+  optional EventType event_type = 4;
 }
 
 // The top-level proto for structured metrics. One StructuredDataProto is
diff --git a/third_party/metrics_proto/ukm/source.proto b/third_party/metrics_proto/ukm/source.proto
index c58c6ba..abbb756 100644
--- a/third_party/metrics_proto/ukm/source.proto
+++ b/third_party/metrics_proto/ukm/source.proto
@@ -34,7 +34,7 @@
 }
 
 // Source contains data related to a top-level navigation.
-// Next tag: 19
+// Next tag: 20
 message Source {
   // The URL scheme, such as HTTP, HTTPS, CHROME_EXTENSION, etc.
   enum UrlScheme {
diff --git a/tools/android/avd/proto/creation/generic_android27.textpb b/tools/android/avd/proto/creation/generic_android27.textpb
index fc437b0..e1c69e8 100644
--- a/tools/android/avd/proto/creation/generic_android27.textpb
+++ b/tools/android/avd/proto/creation/generic_android27.textpb
@@ -6,7 +6,7 @@
 
 emulator_package {
   package_name: "chromium/third_party/android_sdk/public/emulator"
-  version: "qrZePjKjcYAEId2PuvAx2MQsYIfhgn_DV1qfYiFrGMUC"  # 30.6.5
+  version: "Dogc_gNCYNb3fIG-ovlMkV5EhjaYwdA_Jw9goUpl3A8C"  # 30.7.5
   dest_path: ".emulator_sdk"
 }
 
@@ -29,4 +29,8 @@
     height: 1920
     width: 1080
   }
+  advanced_features {
+    key: "GLESDynamicVersion"
+    value: "on"
+  }
 }
diff --git a/tools/android/avd/proto/creation/generic_android28.textpb b/tools/android/avd/proto/creation/generic_android28.textpb
index 64039176..37438ff 100644
--- a/tools/android/avd/proto/creation/generic_android28.textpb
+++ b/tools/android/avd/proto/creation/generic_android28.textpb
@@ -6,7 +6,7 @@
 
 emulator_package {
   package_name: "chromium/third_party/android_sdk/public/emulator"
-  version: "qrZePjKjcYAEId2PuvAx2MQsYIfhgn_DV1qfYiFrGMUC"  # 30.6.5
+  version: "Dogc_gNCYNb3fIG-ovlMkV5EhjaYwdA_Jw9goUpl3A8C"  # 30.7.5
   dest_path: ".emulator_sdk"
 }
 
@@ -29,4 +29,8 @@
     height: 1920
     width: 1080
   }
+  advanced_features {
+    key: "GLESDynamicVersion"
+    value: "on"
+  }
 }
diff --git a/tools/android/avd/proto/creation/generic_android29.textpb b/tools/android/avd/proto/creation/generic_android29.textpb
index c511386..af7d475 100644
--- a/tools/android/avd/proto/creation/generic_android29.textpb
+++ b/tools/android/avd/proto/creation/generic_android29.textpb
@@ -6,7 +6,7 @@
 
 emulator_package {
   package_name: "chromium/third_party/android_sdk/public/emulator"
-  version: "qrZePjKjcYAEId2PuvAx2MQsYIfhgn_DV1qfYiFrGMUC"  # 30.6.5
+  version: "Dogc_gNCYNb3fIG-ovlMkV5EhjaYwdA_Jw9goUpl3A8C"  # 30.7.5
   dest_path: ".emulator_sdk"
 }
 
@@ -29,4 +29,8 @@
     height: 1920
     width: 1080
   }
+  advanced_features {
+    key: "GLESDynamicVersion"
+    value: "on"
+  }
 }
diff --git a/tools/android/avd/proto/creation/generic_playstore_android27.textpb b/tools/android/avd/proto/creation/generic_playstore_android27.textpb
index c1c40cb..94e5a7e4 100644
--- a/tools/android/avd/proto/creation/generic_playstore_android27.textpb
+++ b/tools/android/avd/proto/creation/generic_playstore_android27.textpb
@@ -6,7 +6,7 @@
 
 emulator_package {
   package_name: "chromium/third_party/android_sdk/public/emulator"
-  version: "qrZePjKjcYAEId2PuvAx2MQsYIfhgn_DV1qfYiFrGMUC"  # 30.6.5
+  version: "Dogc_gNCYNb3fIG-ovlMkV5EhjaYwdA_Jw9goUpl3A8C"  # 30.7.5
   dest_path: ".emulator_sdk"
 }
 
@@ -29,4 +29,8 @@
     height: 1920
     width: 1080
   }
+  advanced_features {
+    key: "GLESDynamicVersion"
+    value: "on"
+  }
 }
diff --git a/tools/android/avd/proto/creation/generic_playstore_android28.textpb b/tools/android/avd/proto/creation/generic_playstore_android28.textpb
index 4b252d3..e58b56f 100644
--- a/tools/android/avd/proto/creation/generic_playstore_android28.textpb
+++ b/tools/android/avd/proto/creation/generic_playstore_android28.textpb
@@ -6,7 +6,7 @@
 
 emulator_package {
   package_name: "chromium/third_party/android_sdk/public/emulator"
-  version: "qrZePjKjcYAEId2PuvAx2MQsYIfhgn_DV1qfYiFrGMUC"  # 30.6.5
+  version: "Dogc_gNCYNb3fIG-ovlMkV5EhjaYwdA_Jw9goUpl3A8C"  # 30.7.5
   dest_path: ".emulator_sdk"
 }
 
@@ -29,4 +29,8 @@
     height: 1920
     width: 1080
   }
+  advanced_features {
+    key: "GLESDynamicVersion"
+    value: "on"
+  }
 }
diff --git a/tools/gritsettings/translation_expectations.pyl b/tools/gritsettings/translation_expectations.pyl
index cfd696e..dac4277 100644
--- a/tools/gritsettings/translation_expectations.pyl
+++ b/tools/gritsettings/translation_expectations.pyl
@@ -89,9 +89,11 @@
     ],
   },
   # The omnibox_pedal_synonyms are translated only into a subset of the languages.
+  # Note, the list here must match the <translations> list in
+  # components/omnibox/resources/omnibox_pedal_synonyms.grd or TC pipeline breaks.
   "omnibox_pedal_synonyms_grd": {
     "languages": [
-      "ar", "es-419", "fr", "ja", "zh-TW",
+      "ar", "de", "es-419", "fr", "ja", "zh-CN", "zh-TW",
     ],
     "files": [
       "components/omnibox/resources/omnibox_pedal_synonyms.grd",
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 9d7ed52..ec7ef07 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -5377,6 +5377,15 @@
   <description>The user selected 'share' option for a Feed card.</description>
 </action>
 
+<action name="ContentSuggestions.Feed.CardAction.TapPreview">
+  <owner>tinazwang@chromium.org</owner>
+  <owner>feed@chromium.org</owner>
+  <description>
+    After long-pressing the feed card and seeing the preview, the user tapped on
+    preview.
+  </description>
+</action>
+
 <action name="ContentSuggestions.Feed.DeviceOrientationChanged.Landscape">
   <owner>adamta@google.org</owner>
   <owner>sczs@chromium.org</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index c8d842b..0cdaf0c 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -1870,6 +1870,7 @@
   <int value="10" label="DINO_PAGE_OFFLINE_CONTENT"/>
   <int value="11" label="OFFLINE_INDICATOR"/>
   <int value="12" label="OFFLINE_CONTENT_NOTIFICATION"/>
+  <int value="13" label="DOWNLOAD_PROGRESS_MESSAGE"/>
 </enum>
 
 <enum name="AndroidEnhancedProtectionPromoAction">
@@ -34437,6 +34438,9 @@
   <int value="38"
       label="After following an active web feed, the user tapped to dismiss
              the post-follow help dialog."/>
+  <int value="39"
+      label="After long-pressing on the feed and seeing the preview, the user
+             tapped on the preview."/>
 </enum>
 
 <enum name="FeedUserCommandType">
@@ -54858,6 +54862,7 @@
   <int value="13" label="GroupedPermission"/>
   <int value="14" label="PermissionUpdate"/>
   <int value="15" label="AdsBlocked"/>
+  <int value="16" label="DownloadProgress"/>
 </enum>
 
 <enum name="MessageLoopProblems">
@@ -81922,25 +81927,59 @@
 
 <enum name="TranslationStatus">
   <int value="0" label="Uninitialized"/>
-  <int value="1" label="Succeeded, from manual translation"/>
+  <int value="1"
+      label="Deprecated 07/2021. Succeeded, from manual translation"/>
   <int value="2" label="Succeeded, automatic translation by user preference"/>
   <int value="3" label="Succeeded, automatic translation by link"/>
-  <int value="4" label="Reverted by user, manual translation"/>
+  <int value="4"
+      label="Deprecated 07/2021. Reverted by user, manual translation"/>
   <int value="5" label="Reverted by user, automatic translation"/>
   <int value="6" label="New translation requested by user"/>
   <int value="7" label="Abandoned"/>
-  <int value="8" label="Failed with no error, manual translation"/>
+  <int value="8"
+      label="Deprecated 07/2021. Failed with no error, manual translation"/>
   <int value="9" label="Failed with no error, automatic translation"/>
-  <int value="10" label="Failed with an error, manual translation"/>
+  <int value="10"
+      label="Deprecated 07/2021. Failed with an error, manual translation"/>
   <int value="11" label="Failed with an error, automatic translation"/>
+  <int value="12" label="Succeeded, from manual translation from Translate UI"/>
+  <int value="13"
+      label="Reverted by user, manual translation from Translate UI"/>
+  <int value="14"
+      label="Failed with no error, manual translation from Translate UI"/>
+  <int value="15"
+      label="Failed with an error, manual translation from Translate UI"/>
+  <int value="16" label="Succeeded, from manual translation from Context Menu"/>
+  <int value="17"
+      label="Reverted by user, manual translation from Context Menu"/>
+  <int value="18"
+      label="Failed with no error, manual translation from Context Menu"/>
+  <int value="19"
+      label="Failed with an error, manual translation from Context Menu"/>
 </enum>
 
 <enum name="TranslationType">
   <int value="0" label="Uninitialized"/>
-  <int value="1" label="Manual translation, first translation of page load"/>
-  <int value="2" label="Manual translation, after page was already translated"/>
+  <int value="1"
+      label="Deprecated 07/2021. Manual translation, first translation of
+             page load"/>
+  <int value="2"
+      label="Deprecated 07/2021. Manual translation, after page was already
+             translated"/>
   <int value="3" label="Automatic translation by user preference"/>
   <int value="4" label="Automatic translation by link"/>
+  <int value="5"
+      label="Manual translation from Translate UI, first translation of page
+             load"/>
+  <int value="6"
+      label="Manual translation from Translate UI, after page was already
+             translated"/>
+  <int value="7"
+      label="Manual translation from Context Menu, first translation of page
+             load"/>
+  <int value="8"
+      label="Manual translation Context Menu, after page was already
+             translated"/>
 </enum>
 
 <enum name="TrendingTileEvent">
@@ -87612,6 +87651,13 @@
   <int value="2" label="Well-Known supported"/>
 </enum>
 
+<enum name="WhatsNewLoadEvent">
+  <int value="0" label="Load started"/>
+  <int value="1" label="Load succeeded"/>
+  <int value="2" label="Load failed and error page whas shown"/>
+  <int value="3" label="Load failed and was redirected to NTP"/>
+</enum>
+
 <enum name="WhitelistedDownloadType">
   <int value="0" label="Does not match any whitelists"/>
   <int value="1" label="URL whitelist"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index 47dab0d..34bfd9e 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -82,6 +82,7 @@
   <variant name=".AddToHomescreenIPH"/>
   <variant name=".AdsBlocked"/>
   <variant name=".ChromeSurvey"/>
+  <variant name=".DownloadProgress"/>
   <variant name=".GeneratedPasswordSaved"/>
   <variant name=".GroupedPermission"/>
   <variant name=".MerchantTrust"/>
diff --git a/tools/metrics/histograms/metadata/borealis/histograms.xml b/tools/metrics/histograms/metadata/borealis/histograms.xml
index 36268ec..15b405a 100644
--- a/tools/metrics/histograms/metadata/borealis/histograms.xml
+++ b/tools/metrics/histograms/metadata/borealis/histograms.xml
@@ -21,6 +21,16 @@
 
 <histograms>
 
+<histogram name="Borealis.AppsInstalledAtLogin" units="apps"
+    expires_after="2022-12-26">
+  <owner>joelhockey@google.com</owner>
+  <owner>src/chrome/browser/ash/borealis/OWNERS</owner>
+  <summary>
+    Records the number of Broealis apps in the registry at login time. This only
+    logs if Borealis has already benn enabled for the current user.
+  </summary>
+</histogram>
+
 <histogram name="Borealis.Disk.Client.AvailableSpaceAtRequest" units="MB"
     expires_after="2022-04-08">
   <owner>danielng@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index cf4258d..a432a33 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -13707,8 +13707,8 @@
 <histogram name="ReportingAndNEL.BackingStoreUpdateOutcome"
     enum="NetReportingAndNelBackingStoreUpdateOutcome"
     expires_after="2020-09-09">
-  <owner>chlily@chromium.org</owner>
-  <owner>sburnett@chromium.org</owner>
+  <owner>yhirano@chromium.org</owner>
+  <owner>src/net/reporting/OWNERS</owner>
   <summary>
     The outcome of updating the backing store. This is recorded whenever a batch
     of pending operations is committed to the backing store. Success occurs when
@@ -13753,8 +13753,8 @@
 
 <histogram name="ReportingAndNEL.InitializeDBOutcome"
     enum="NetReportingAndNelInitializeDbOutcome" expires_after="2020-09-09">
-  <owner>chlily@chromium.org</owner>
-  <owner>sburnett@chromium.org</owner>
+  <owner>yhirano@chromium.org</owner>
+  <owner>src/net/reporting/OWNERS</owner>
   <summary>
     The outcome of initializing the SQLite database. This is recorded upon the
     first load request after startup, which will typically occur upon the first
@@ -13764,8 +13764,8 @@
 
 <histogram name="ReportingAndNEL.KillDatabaseResult" enum="BooleanSuccess"
     expires_after="2020-09-09">
-  <owner>chlily@chromium.org</owner>
-  <owner>sburnett@chromium.org</owner>
+  <owner>yhirano@chromium.org</owner>
+  <owner>src/net/reporting/OWNERS</owner>
   <summary>
     Whether killing the database because it was corrupted beyond repair
     succeeded. This is recorded when a fatal SQLite error is detected.
@@ -13774,8 +13774,8 @@
 
 <histogram name="ReportingAndNEL.NumberOfLoadedNELPolicies"
     units="policy count" expires_after="2020-09-09">
-  <owner>chlily@chromium.org</owner>
-  <owner>sburnett@chromium.org</owner>
+  <owner>yhirano@chromium.org</owner>
+  <owner>src/net/reporting/OWNERS</owner>
   <summary>
     The number of NEL policies loaded from the store. This is recorded when the
     NetworkErrorLoggingService requests a load from the backing store, which
@@ -13785,8 +13785,8 @@
 
 <histogram name="ReportingAndNEL.NumberOfLoadedReportingEndpointGroups"
     units="endpoint group count" expires_after="2020-09-09">
-  <owner>chlily@chromium.org</owner>
-  <owner>sburnett@chromium.org</owner>
+  <owner>yhirano@chromium.org</owner>
+  <owner>src/net/reporting/OWNERS</owner>
   <summary>
     The number of Reporting endpoint groups loaded from the store. This is
     recorded when the ReportingCache requests a load from the backing store,
@@ -13797,8 +13797,8 @@
 
 <histogram name="ReportingAndNEL.NumberOfLoadedReportingEndpoints"
     units="endpoint count" expires_after="2020-09-09">
-  <owner>chlily@chromium.org</owner>
-  <owner>sburnett@chromium.org</owner>
+  <owner>yhirano@chromium.org</owner>
+  <owner>src/net/reporting/OWNERS</owner>
   <summary>
     The number of Reporting endpoints loaded from the store. This is recorded
     when the ReportingCache requests a load from the backing store, which
@@ -13809,8 +13809,8 @@
 
 <histogram name="ReportingAndNEL.TimeInitializeDB" units="ms"
     expires_after="2020-09-09">
-  <owner>chlily@chromium.org</owner>
-  <owner>sburnett@chromium.org</owner>
+  <owner>yhirano@chromium.org</owner>
+  <owner>src/net/reporting/OWNERS</owner>
   <summary>
     The time (ms) taken to initialize the Reporting and NEL DB. This is recorded
     when the DB is initialized, which typically occurs upon the first network
@@ -19868,6 +19868,19 @@
   </summary>
 </histogram>
 
+<histogram name="WhatsNew.LoadEvent" enum="WhatsNewLoadEvent"
+    expires_after="2022-01-09">
+  <owner>rbpotter@chromium.org</owner>
+  <owner>mahmadi@chromium.org</owner>
+  <summary>
+    Records load events for the What's New page when a load event occurs. This
+    tracks how many times loading remote content is attempted, and whether it
+    succeeds, fails and results in an error page, or fails and redirects to the
+    New Tab Page. Load is attempted when the page is first opened, and success
+    or failure will occur when the network request completes. Desktop only.
+  </summary>
+</histogram>
+
 <histogram name="WheelScrolling.WasLatched" enum="BooleanLatched"
     expires_after="M77">
   <owner>flackr@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/sb_client/histograms.xml b/tools/metrics/histograms/metadata/sb_client/histograms.xml
index f1f4615f..c7dd245 100644
--- a/tools/metrics/histograms/metadata/sb_client/histograms.xml
+++ b/tools/metrics/histograms/metadata/sb_client/histograms.xml
@@ -182,6 +182,9 @@
 
 <histogram name="SBClientDownload.ExtractDmgFeaturesTimeMedium" units="ms"
     expires_after="2021-07-21">
+  <obsolete>
+    Removed 08-2021 due to lack of use.
+  </obsolete>
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <owner>rsesek@chromium.org</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 763fff0..7dd4fa25 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -9,8 +9,8 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/win/b846a827ab0b47d0299bc3776d1fb5c067c50d94/trace_processor_shell.exe"
         },
         "mac": {
-            "hash": "e7000eeeefed60f263b77eea1156606a18304379",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/a6966e3445f5dc2a0d9f735b8c689c5078e4c33e/trace_processor_shell"
+            "hash": "8399e9e79d05c0012dd60be2ea9542bf9df11e0d",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/b846a827ab0b47d0299bc3776d1fb5c067c50d94/trace_processor_shell"
         },
         "linux_arm64": {
             "hash": "5074025a2898ec41a872e70a5719e417acb0a380",
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 0a750fd..ce9de1a 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -78,6 +78,7 @@
  <item id="data_reduction_proxy_secure_proxy_check" added_in_milestone="62" hash_code="131236802" type="0" deprecated="2020-03-13" content_hash_code="122297136" file_path=""/>
  <item id="data_reduction_proxy_warmup" added_in_milestone="62" hash_code="8250451" type="0" deprecated="2020-03-13" content_hash_code="6321249" file_path=""/>
  <item id="desktop_ios_promotion" added_in_milestone="63" hash_code="13694792" type="0" deprecated="2018-11-04" content_hash_code="19776951" file_path=""/>
+ <item id="desktop_screenshot_save" added_in_milestone="94" hash_code="18870110" type="0" content_hash_code="26509513" os_list="linux,windows" file_path="chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc"/>
  <item id="device_geolocation_request" added_in_milestone="62" hash_code="77673751" type="0" deprecated="2017-10-20" content_hash_code="97181773" file_path=""/>
  <item id="device_management_service" added_in_milestone="62" hash_code="117782019" type="0" content_hash_code="104419970" os_list="linux,windows" file_path="components/policy/core/common/cloud/device_management_service.cc"/>
  <item id="devtools_cdp_network_resource" added_in_milestone="87" hash_code="60744935" type="0" content_hash_code="85833926" os_list="linux,windows" file_path="content/browser/devtools/protocol/devtools_network_resource_loader.cc"/>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index f91c34c..81279ca 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -96,6 +96,7 @@
       <traffic_annotation unique_id="proxy_config_settings"/>
       <traffic_annotation unique_id="video_tutorial_fetcher"/>
       <traffic_annotation unique_id="qr_code_save"/>
+      <traffic_annotation unique_id="desktop_screenshot_save"/>
       <traffic_annotation unique_id="safe_browsing_cache_collector"/>
       <traffic_annotation unique_id="safety_check_update_connectivity"/>
       <traffic_annotation unique_id="open_search"/>
diff --git a/tools/typescript/definitions/settings_private.d.ts b/tools/typescript/definitions/settings_private.d.ts
new file mode 100644
index 0000000..d324129c
--- /dev/null
+++ b/tools/typescript/definitions/settings_private.d.ts
@@ -0,0 +1,49 @@
+// Copyright 2021 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 Definitions for chrome.settingsPrivate API */
+// TODO(crbug.com/1203307): Auto-generate this file.
+
+declare namespace chrome {
+  export namespace settingsPrivate {
+    export enum PrefType {
+      BOOLEAN = 'BOOLEAN',
+      NUMBER = 'NUMBER',
+      STRING = 'STRING',
+      URL = 'URL',
+      LIST = 'LIST',
+      DICTIONARY = 'DICTIONARY',
+    }
+
+    export enum ControlledBy {
+      DEVICE_POLICY = 'DEVICE_POLICY',
+      USER_POLICY = 'USER_POLICY',
+      OWNER = 'OWNER',
+      PRIMARY_USER = 'PRIMARY_USER',
+      EXTENSION = 'EXTENSION',
+      PARENT = 'PARENT',
+      CHILD_RESTRICTION = 'CHILD_RESTRICTION',
+    }
+
+    export enum Enforcement {
+      ENFORCED = 'ENFORCED',
+      RECOMMENDED = 'RECOMMENDED',
+      PARENT_SUPERVISED = 'PARENT_SUPERVISED',
+    }
+
+    export interface PrefObject {
+      key: string;
+      type: PrefType;
+      value: any;
+      controlledBy?: ControlledBy;
+      controlledByName?: string;
+      enforcement?: Enforcement;
+      recommendedValue?: any;
+      userSelectableValues?: Array<any>;
+      userControlDisabled?: boolean;
+      extensionId?: string;
+      extensionCanBeDisabled?: boolean;
+    }
+  }
+}
diff --git a/ui/base/cocoa/focus_tracker.h b/ui/base/cocoa/focus_tracker.h
index a676e08..c89d08a 100644
--- a/ui/base/cocoa/focus_tracker.h
+++ b/ui/base/cocoa/focus_tracker.h
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifndef UI_BASE_COCOA_FOCUS_TRACKER_H_
+#define UI_BASE_COCOA_FOCUS_TRACKER_H_
+
 #import <Cocoa/Cocoa.h>
 
 #include "base/component_export.h"
@@ -27,3 +30,5 @@
 // longer in the view hierarchy under |window|.
 - (BOOL)restoreFocusInWindow:(NSWindow*)window;
 @end
+
+#endif  // UI_BASE_COCOA_FOCUS_TRACKER_H_
\ No newline at end of file
diff --git a/ui/file_manager/file_manager/foreground/js/banner_controller.js b/ui/file_manager/file_manager/foreground/js/banner_controller.js
index 5f29452..e02836b4 100644
--- a/ui/file_manager/file_manager/foreground/js/banner_controller.js
+++ b/ui/file_manager/file_manager/foreground/js/banner_controller.js
@@ -98,14 +98,14 @@
         this.localStorageCache_[key] = storedValue;
       }
     }
-    this.reconcile();
+    await this.reconcile();
   }
 
   /**
    * Loops through all the banners and checks whether they should be shown or
    * not. If shown, picks the highest priority banner.
    */
-  reconcile() {
+  async reconcile() {
     this.currentVolume_ = this.directoryModel_.getCurrentVolumeInfo();
 
     /** @type {?Banner} */
@@ -117,7 +117,7 @@
         this.warningBanners_.concat(this.educationalBanners_);
     for (const banner of orderedBanners) {
       if (!this.shouldShowBanner_(banner)) {
-        this.maybeHideBanner_(banner);
+        this.hideBannerIfShown_(banner);
         continue;
       }
 
@@ -127,7 +127,7 @@
     }
 
     if (bannerToShow) {
-      this.showBanner_(bannerToShow);
+      await this.showBanner_(bannerToShow);
     }
   }
 
@@ -143,6 +143,13 @@
       return false;
     }
 
+    const showLimit = banner.showLimit();
+    const timesShown =
+        this.localStorageCache_[`${banner.tagName}_${VIEW_COUNTER_SUFFIX}`];
+    if (showLimit && timesShown >= showLimit) {
+      return false;
+    }
+
     return true;
   }
 
@@ -151,9 +158,15 @@
    * @param {!Banner} banner The banner to hide.
    * @private
    */
-  showBanner_(banner) {
+  async showBanner_(banner) {
     if (banner.parentElement !== this.container_) {
       this.container_.appendChild(/** @type {Node} */ (banner));
+
+      // Views are set when the banner is first appended to the DOM. This
+      // denotes a new app session.
+      const localStorageKey = `${banner.tagName}_${VIEW_COUNTER_SUFFIX}`;
+      await this.setLocalStorage_(
+          localStorageKey, this.localStorageCache_[localStorageKey] + 1);
     }
 
     banner.setAttribute('hidden', false);
@@ -167,7 +180,7 @@
    * @param {!Banner} banner The banner to hide.
    * @private
    */
-  maybeHideBanner_(banner) {
+  hideBannerIfShown_(banner) {
     if (banner.parentElement !== this.container_) {
       return;
     }
@@ -201,6 +214,26 @@
   }
 
   /**
+   * Writes through the localStorage cache to localStorage to ensure values
+   * are immediately available.
+   * @param {string} key The key in localStorage to set.
+   * @param {number} value The value to set the key to in localStorage.
+   * @private
+   */
+  async setLocalStorage_(key, value) {
+    if (!this.localStorageCache_.hasOwnProperty(key)) {
+      console.error(`Key ${key} not found in localStorage cache`);
+      return;
+    }
+    this.localStorageCache_[key] = value;
+    try {
+      await xfm.storage.local.setAsync({[key]: value});
+    } catch (e) {
+      console.warn(e.message);
+    }
+  }
+
+  /**
    * Listens for localStorage changes to ensure instance cache is in sync.
    * @param {!Object<string, !StorageChange>} changes Changes that occurred.
    * @param {string} areaName One of "sync"|"local"|"managed".
@@ -247,3 +280,28 @@
   }
   return false;
 }
+
+/**
+ * Checks if the current sizeStats are below the threshold required to trigger
+ * the banner to show.
+ * @param {!Banner.DiskThresholdMinRatio|!Banner.DiskThresholdMinSize|undefined}
+ *     threshold
+ * @param {?chrome.fileManagerPrivate.MountPointSizeStats|undefined} sizeStats
+ * @returns {boolean}
+ */
+export function isBelowThreshold(threshold, sizeStats) {
+  if (!threshold || !sizeStats) {
+    return false;
+  }
+  if (!sizeStats.remainingSize || !sizeStats.totalSize) {
+    return false;
+  }
+  if (threshold.minSize < sizeStats.remainingSize) {
+    return false;
+  }
+  const currentRatio = sizeStats.remainingSize / sizeStats.totalSize;
+  if (threshold.minRatio < currentRatio) {
+    return false;
+  }
+  return true;
+}
diff --git a/ui/file_manager/file_manager/foreground/js/banner_controller_unittest.m.js b/ui/file_manager/file_manager/foreground/js/banner_controller_unittest.m.js
index 70394ae..9a96b04 100644
--- a/ui/file_manager/file_manager/foreground/js/banner_controller_unittest.m.js
+++ b/ui/file_manager/file_manager/foreground/js/banner_controller_unittest.m.js
@@ -25,10 +25,11 @@
 
 /**
  * @typedef {{
- *   setAllowedVolumeTypes: function(!Array<!Banner.AllowedVolumeType>),
- *   reset: function(),
- *   tagName: string
- *  }}
+ *    setAllowedVolumeTypes: function(!Array<!Banner.AllowedVolumeType>),
+ *    setShowLimit: function(number),
+ *    reset: function(),
+ *    tagName: string,
+ * }}
  */
 let TestBanner;
 
@@ -61,8 +62,8 @@
 /**
  * @type {!Banner.AllowedVolumeType}
  */
-const myFilesAllowedVolumeType = {
-  type: VolumeManagerCommon.VolumeType.MY_FILES,
+const driveAllowedVolumeType = {
+  type: VolumeManagerCommon.VolumeType.DRIVE,
   id: null,
 };
 
@@ -82,10 +83,17 @@
   /** @type {!Array<!Banner.AllowedVolumeType>} */
   let allowedVolumeTypes = [];
 
+  /** @type {number|undefined} */
+  let showLimit;
+
   class FakeBanner extends Banner {
     allowedVolumeTypes() {
       return allowedVolumeTypes;
     }
+
+    showLimit() {
+      return showLimit;
+    }
   }
 
   customElements.define(tagName, FakeBanner);
@@ -96,6 +104,10 @@
     },
     reset: () => {
       allowedVolumeTypes = [];
+      showLimit = undefined;
+    },
+    setShowLimit: (limit) => {
+      showLimit = limit;
     },
     // Element.tagName returns uppercase.
     tagName: tagName.toUpperCase(),
@@ -125,7 +137,6 @@
  * Asserts that all banners on the DOM are hidden.
  */
 function assertNoVisibleBanners() {
-  const elements = bannerContainer.children;
   for (const element of bannerContainer.children) {
     assertEquals(element.getAttribute('hidden'), 'true');
     assertEquals(element.getAttribute('aria-hidden'), 'true');
@@ -257,10 +268,10 @@
 
   // Set the following banners to show:
   //  First Warning Banner shows on Downloads.
-  //  Second Warning Banner shows on My files.
+  //  Second Warning Banner shows on Drive.
   //  First Educational Banner shows on Android Files.
   testWarningBanners[0].setAllowedVolumeTypes([downloadsAllowedVolumeType]);
-  testWarningBanners[1].setAllowedVolumeTypes([myFilesAllowedVolumeType]);
+  testWarningBanners[1].setAllowedVolumeTypes([driveAllowedVolumeType]);
   testEducationalBanners[0].setAllowedVolumeTypes(
       [androidFilesAllowedVolumeType]);
 
@@ -268,16 +279,16 @@
   await controller.initialize();
   assertBannerVisible(testWarningBanners[0]);
 
-  // Change volume to My files and verify the second warning banner is showing.
+  // Change volume to Drives and verify the second warning banner is showing.
   changeCurrentVolume(
-      VolumeManagerCommon.VolumeType.MY_FILES, /* volumeId */ null);
-  controller.reconcile();
+      VolumeManagerCommon.VolumeType.DRIVE, /* volumeId */ null);
+  await controller.reconcile();
   assertBannerVisible(testWarningBanners[1]);
 
   // Change volume to Android files and verify the educational banner is shown.
   changeCurrentVolume(
       VolumeManagerCommon.VolumeType.ANDROID_FILES, /* volumeId */ null);
-  controller.reconcile();
+  await controller.reconcile();
   assertBannerVisible(testEducationalBanners[0]);
 }
 
@@ -294,6 +305,114 @@
 
   // Change current volume to a null volume type and assert no banner is shown.
   changeCurrentVolume(/* volumeType */ null, /* volumeId */ null);
-  controller.reconcile();
+  await controller.reconcile();
+  assertNoVisibleBanners();
+}
+
+/**
+ * Test that banners that have hit their show limit are not shown again.
+ */
+export async function testBannersChangeAfterShowLimitReached() {
+  // Add 2 educational banners.
+  controller.setEducationalBannersInOrder([
+    testEducationalBanners[0].tagName,
+    testEducationalBanners[1].tagName,
+  ]);
+
+  // Set the showLimit for the educational banners.
+  testEducationalBanners[0].setShowLimit(1);
+  testEducationalBanners[0].setAllowedVolumeTypes([downloadsAllowedVolumeType]);
+  testEducationalBanners[1].setShowLimit(3);
+  testEducationalBanners[1].setAllowedVolumeTypes([downloadsAllowedVolumeType]);
+
+  // The first reconciliation should increment the counter and append to DOM.
+  await controller.initialize();
+  assertBannerVisible(testEducationalBanners[0]);
+
+  // Clearing the DOM should imitate a new Files app session.
+  bannerContainer.textContent = '';
+  await controller.reconcile();
+
+  // After a new Files app session has started, the previous counter has
+  // exceeded it's show limit, assert the next priority banner is shown.
+  assertBannerVisible(testEducationalBanners[1]);
+}
+
+/**
+ * Test that the show limit is increased only on a per app session.
+ */
+export async function testChangingVolumesDoesntIncreaseShowTimes() {
+  // Add 1 educational banner.
+  controller.setEducationalBannersInOrder([testEducationalBanners[0].tagName]);
+
+  const bannerShowLimit = 3;
+  testEducationalBanners[0].setAllowedVolumeTypes([downloadsAllowedVolumeType]);
+  testEducationalBanners[0].setShowLimit(bannerShowLimit);
+
+  await controller.initialize();
+  assertBannerVisible(testEducationalBanners[0]);
+
+  // Change directory one more times than the show limit to verify the show
+  // limit doesn't increase.
+  for (let i = 0; i < bannerShowLimit + 1; i++) {
+    // Change directory and verify no banner shown.
+    changeCurrentVolume(VolumeManagerCommon.VolumeType.DRIVE, null);
+    await controller.reconcile();
+    assertNoVisibleBanners();
+
+    // Change back to DOWNLOADS and verify banner has been shown.
+    changeCurrentVolume(VolumeManagerCommon.VolumeType.DOWNLOADS, null);
+    await controller.reconcile();
+    assertBannerVisible(testEducationalBanners[0]);
+  }
+}
+
+/**
+ * Test that multiple banners with different allowedVolumeTypes and show limits
+ * are show at the right stages. This also asserts that banners that don't
+ * implement showLimit still are shown as expected.
+ */
+export async function testMultipleBannersAllowedVolumeTypesAndShowLimit() {
+  controller.setWarningBannersInOrder([
+    testWarningBanners[0].tagName,
+  ]);
+  controller.setEducationalBannersInOrder([
+    testEducationalBanners[0].tagName,
+  ]);
+
+  // Set the allowed volume types for the warning banners.
+  testWarningBanners[0].setAllowedVolumeTypes([driveAllowedVolumeType]);
+
+  // Set the showLimit and allowed volume types for the educational banner.
+  testEducationalBanners[0].setShowLimit(2);
+  testEducationalBanners[0].setAllowedVolumeTypes([downloadsAllowedVolumeType]);
+
+  // The first reconciliation should increment the counter and append to DOM.
+  await controller.initialize();
+  assertBannerVisible(testEducationalBanners[0]);
+
+  // Change the directory to Drive.
+  changeCurrentVolume(VolumeManagerCommon.VolumeType.DRIVE, null);
+  await controller.reconcile();
+  assertBannerVisible(testWarningBanners[0]);
+
+  // Start a new Files app session (clearing the container emulates this).
+  // Change the directory back Downloads.
+  bannerContainer.textContent = '';
+  changeCurrentVolume(VolumeManagerCommon.VolumeType.DOWNLOADS, null);
+  await controller.reconcile();
+  assertBannerVisible(testEducationalBanners[0]);
+
+  // Change back to Drive, warning banner should still be showing.
+  changeCurrentVolume(VolumeManagerCommon.VolumeType.DRIVE, null);
+  await controller.reconcile();
+  assertBannerVisible(testWarningBanners[0]);
+
+  // Emulate starting a new Files app session and change back to Downloads
+  // volume. This time the educational banner should no longer be showing as it
+  // has exceeded it's show limit.
+  bannerContainer.textContent = '';
+  changeCurrentVolume(VolumeManagerCommon.VolumeType.DOWNLOADS, null);
+  await controller.reconcile();
   assertNoVisibleBanners();
 }
diff --git a/ui/file_manager/file_manager/foreground/js/banner_util_unittest.m.js b/ui/file_manager/file_manager/foreground/js/banner_util_unittest.m.js
index 70af03c5..ae0daac3 100644
--- a/ui/file_manager/file_manager/foreground/js/banner_util_unittest.m.js
+++ b/ui/file_manager/file_manager/foreground/js/banner_util_unittest.m.js
@@ -8,7 +8,7 @@
 import {Banner} from '../../externs/banner.js';
 import {VolumeInfo} from '../../externs/volume_info.js';
 
-import {isAllowedVolume} from './banner_controller.js';
+import {isAllowedVolume, isBelowThreshold} from './banner_controller.js';
 
 /** @type {!Array<!Banner.AllowedVolumeType>} */
 let allowedVolumeTypes = [];
@@ -30,6 +30,18 @@
   return /** @type {!VolumeInfo} */ (new FakeVolumeInfo());
 }
 
+/**
+ * Creates a chrome.fileManagerPrivate.MountPointSizeStats type.
+ * @param {number} remainingSize
+ * @returns {!chrome.fileManagerPrivate.MountPointSizeStats}
+ */
+function createRemainingSizeStats(remainingSize) {
+  return {
+    totalSize: 20 * 1024 * 1024 * 1024,  // 20 GB
+    remainingSize,
+  };
+}
+
 export function tearDown() {
   allowedVolumeTypes = [];
 }
@@ -116,3 +128,78 @@
 
   assertFalse(isAllowedVolume(currentVolume, allowedVolumeTypes));
 }
+
+/**
+ * Test undefined types return false.
+ */
+export function testUndefinedThresholdAndSizeStats() {
+  const testMinSizeThreshold = {
+    type: VolumeManagerCommon.VolumeType.DOWNLOADS,
+    minSize: 1 * 1024 * 1024 * 1024,  // 1 GB
+  };
+  const testMinRatioThreshold = {
+    type: VolumeManagerCommon.VolumeType.DOWNLOADS,
+    minSize: 0.1,
+  };
+  const testSizeStats = {
+    totalSize: 20 * 1024 * 1024 * 1024,     // 20 GB
+    remainingSize: 1 * 1024 * 1024 * 1024,  // 1 GB
+  };
+
+  assertFalse(isBelowThreshold(undefined, undefined));
+  assertFalse(isBelowThreshold(testMinSizeThreshold, undefined));
+  assertFalse(isBelowThreshold(testMinRatioThreshold, undefined));
+  assertFalse(isBelowThreshold(undefined, testSizeStats));
+}
+
+/**
+ * Test that below, equal to and above the minSize threshold returns correct
+ * results.
+ */
+export function testMinSizeReturnsCorrectly() {
+  const createMinSizeThreshold = (minSize) => ({
+    type: VolumeManagerCommon.VolumeType.DOWNLOADS,
+    minSize,
+  });
+
+  // Remaining Size: 512 KB, Min Size: 1 GB
+  assertTrue(isBelowThreshold(
+      createMinSizeThreshold(1 * 1024 * 1024 * 1024 /* 1 GB */),
+      createRemainingSizeStats(512 * 1024 * 1024 /* 512 KB */)));
+
+  // Remaining Size: 1 GB, Min Size: 1 GB
+  assertTrue(isBelowThreshold(
+      createMinSizeThreshold(1 * 1024 * 1024 * 1024 /* 1 GB */),
+      createRemainingSizeStats(1 * 1024 * 1024 * 1024 /* 1 GB */)));
+
+  // Remaining Size: 2 GB, Min Size: 1 GB
+  assertFalse(isBelowThreshold(
+      createMinSizeThreshold(1 * 1024 * 1024 * 1024 /* 1 GB */),
+      createRemainingSizeStats(2 * 1024 * 1024 * 1024 /* 2 GB */)));
+}
+
+/**
+ * Test that below, equal to and above the minRatio threshold returns correct
+ * results.
+ */
+export function testMinRatioReturnsCorrectly() {
+  const createMinRatioThreshold = (minRatio) => ({
+    type: VolumeManagerCommon.VolumeType.DOWNLOADS,
+    minRatio,
+  });
+
+  // Remaining Size Ratio: 0.1, Threshold: 0.2
+  assertTrue(isBelowThreshold(
+      createMinRatioThreshold(0.2),
+      createRemainingSizeStats(2 * 1024 * 1024 * 1024 /* 2 GB */)));
+
+  // Remaining Size Ratio: 0.1, Threshold: 0.1
+  assertTrue(isBelowThreshold(
+      createMinRatioThreshold(0.1),
+      createRemainingSizeStats(2 * 1024 * 1024 * 1024 /* 2 GB */)));
+
+  // Remaining Size Ratio: 0.1, Threshold: 0.05
+  assertFalse(isBelowThreshold(
+      createMinRatioThreshold(0.05),
+      createRemainingSizeStats(2 * 1024 * 1024 * 1024 /* 2 GB */)));
+}
diff --git a/ui/gfx/icon_util_unittests_resource.h b/ui/gfx/icon_util_unittests_resource.h
index 0a50edfd..560a0de9 100644
--- a/ui/gfx/icon_util_unittests_resource.h
+++ b/ui/gfx/icon_util_unittests_resource.h
@@ -2,4 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifndef UI_GFX_ICON_UTIL_UNITTESTS_RESOURCE_H_
+#define UI_GFX_ICON_UTIL_UNITTESTS_RESOURCE_H_
+
 #define IDR_MAINFRAME               101
+
+#endif  // UI_GFX_ICON_UTIL_UNITTESTS_RESOURCE_H_
\ No newline at end of file
diff --git a/ui/gfx/test/icc_profiles.h b/ui/gfx/test/icc_profiles.h
index 356beaf..8b13d4a 100644
--- a/ui/gfx/test/icc_profiles.h
+++ b/ui/gfx/test/icc_profiles.h
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifndef UI_GFX_TEST_ICC_PROFILES_H_
+#define UI_GFX_TEST_ICC_PROFILES_H_
+
 #include "ui/gfx/icc_profile.h"
 
 namespace gfx {
@@ -21,3 +24,5 @@
 ICCProfile ICCProfileForTestingOvershoot();
 
 }  // namespace gfx
+
+#endif  // UI_GFX_TEST_ICC_PROFILES_H_
\ No newline at end of file
diff --git a/ui/ozone/demo/surfaceless_gl_renderer.cc b/ui/ozone/demo/surfaceless_gl_renderer.cc
index b854a48..5d7a037 100644
--- a/ui/ozone/demo/surfaceless_gl_renderer.cc
+++ b/ui/ozone/demo/surfaceless_gl_renderer.cc
@@ -11,6 +11,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
+#include "base/cxx17_backports.h"
 #include "base/macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/trace_event/trace_event.h"
@@ -174,7 +175,7 @@
     base::StringToInt(
         command_line->GetSwitchValueASCII("enable-overlay").c_str(),
         &requested_overlay_cnt);
-    overlay_cnt_ = std::max(1, std::min(kMaxLayers, requested_overlay_cnt));
+    overlay_cnt_ = base::clamp(requested_overlay_cnt, 1, kMaxLayers);
 
     const gfx::Size overlay_size =
         gfx::Size(size_.width() / 8, size_.height() / 8);
diff --git a/ui/views/animation/animation_builder.cc b/ui/views/animation/animation_builder.cc
index 2247d61..eb13e91 100644
--- a/ui/views/animation/animation_builder.cc
+++ b/ui/views/animation/animation_builder.cc
@@ -34,39 +34,6 @@
     a.first->layer()->GetAnimator()->StartTogether(a.second);
 }
 
-AnimationBuilder& AnimationBuilder::SetDuration(base::TimeDelta duration) {
-  duration_ = duration;
-  return *this;
-}
-
-AnimationBuilder& AnimationBuilder::SetOpacity(View* view,
-                                               float target_opacity) {
-  AnimationKey key = {view, ui::LayerAnimationElement::OPACITY};
-  AddAnimation(key, ui::LayerAnimationElement::CreateOpacityElement(
-                        target_opacity, duration_));
-  return *this;
-}
-
-AnimationBuilder& AnimationBuilder::SetRoundedCorners(
-    View* view,
-    gfx::RoundedCornersF& rounded_corners) {
-  AnimationKey key = {view, ui::LayerAnimationElement::ROUNDED_CORNERS};
-  AddAnimation(key, ui::LayerAnimationElement::CreateRoundedCornersElement(
-                        rounded_corners, duration_));
-  return *this;
-}
-
-AnimationBuilder& AnimationBuilder::Repeat() {
-  // Go through all empty sequences added in StartSequence() and set the correct
-  // repeating behavior.
-  is_sequence_repeating_ = true;
-  for (auto& animation : animation_sequences_) {
-    animation_sequences_[animation.first].back()->set_is_repeating(
-        is_sequence_repeating_);
-  }
-  return *this;
-}
-
 AnimationBuilder& AnimationBuilder::NewSequence() {
   // Add an empty sequence for all existing views. If the same property is
   // animated at the same time in different sequences PreemptionStrategy will
@@ -90,6 +57,43 @@
   return *this;
 }
 
+AnimationBuilder& AnimationBuilder::SetDuration(base::TimeDelta duration) {
+  duration_ = duration;
+  return *this;
+}
+
+AnimationBuilder& AnimationBuilder::Repeat() {
+  // Go through all empty sequences added in StartSequence() and set the correct
+  // repeating behavior.
+  is_sequence_repeating_ = true;
+  for (auto& animation : animation_sequences_) {
+    animation_sequences_[animation.first].back()->set_is_repeating(
+        is_sequence_repeating_);
+  }
+  return *this;
+}
+
+AnimationBuilder& AnimationBuilder::Then() {
+  return *this;
+}
+
+AnimationBuilder& AnimationBuilder::SetOpacity(View* view,
+                                               float target_opacity) {
+  AnimationKey key = {view, ui::LayerAnimationElement::OPACITY};
+  AddAnimation(key, ui::LayerAnimationElement::CreateOpacityElement(
+                        target_opacity, duration_));
+  return *this;
+}
+
+AnimationBuilder& AnimationBuilder::SetRoundedCorners(
+    View* view,
+    gfx::RoundedCornersF& rounded_corners) {
+  AnimationKey key = {view, ui::LayerAnimationElement::ROUNDED_CORNERS};
+  AddAnimation(key, ui::LayerAnimationElement::CreateRoundedCornersElement(
+                        rounded_corners, duration_));
+  return *this;
+}
+
 void AnimationBuilder::OnStarted(base::OnceClosure callback) {}
 
 void AnimationBuilder::OnEnded(base::OnceClosure callback) {}
diff --git a/ui/views/animation/animation_builder.h b/ui/views/animation/animation_builder.h
index 2091ac9..0678b411 100644
--- a/ui/views/animation/animation_builder.h
+++ b/ui/views/animation/animation_builder.h
@@ -27,18 +27,22 @@
   AnimationBuilder();
   ~AnimationBuilder();
 
+  AnimationBuilder& NewSequence();
+  AnimationBuilder& EndSequence();
+
   AnimationBuilder& SetDuration(base::TimeDelta duration);
 
+  // No effect if called before NewSequence();
+  AnimationBuilder& Repeat();
+
+  AnimationBuilder& Then();
+
   // These methods should be changed to OnSetXXX if we integrate with the View
   // base class.
   AnimationBuilder& SetOpacity(View* view, float target_opacity);
   AnimationBuilder& SetRoundedCorners(views::View* view,
                                       gfx::RoundedCornersF& rounded_corners);
 
-  // No effect if called before NewSequence();
-  AnimationBuilder& Repeat();
-  AnimationBuilder& NewSequence();
-  AnimationBuilder& EndSequence();
 
   // Called when the animation starts.
   void OnStarted(base::OnceClosure callback);
diff --git a/ui/views/examples/animation_example.cc b/ui/views/examples/animation_example.cc
index 7c1f460..2a90f26b 100644
--- a/ui/views/examples/animation_example.cc
+++ b/ui/views/examples/animation_example.cc
@@ -176,9 +176,8 @@
           .NewSequence()
           .Repeat()
           .SetDuration(base::TimeDelta::FromSeconds(2))
-          // TODO(elainechien): These two opacity changes will be separated by a
-          // .Then() call as they happen in sequence.
           .SetOpacity(view, 0.4f)
+          .Then()
           .SetOpacity(view, 0.9f)
           .EndSequence();
     }
diff --git a/weblayer/shell/app/resource.h b/weblayer/shell/app/resource.h
index e50a3ae8..aed36c7 100644
--- a/weblayer/shell/app/resource.h
+++ b/weblayer/shell/app/resource.h
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifndef WEBLAYER_SHELL_APP_RESOURCE_H_
+#define WEBLAYER_SHELL_APP_RESOURCE_H_
+
 //{{NO_DEPENDENCIES}}
 // Microsoft Visual C++ generated include file.
 // Used by shell.rc
@@ -37,3 +40,5 @@
 #define _APS_NEXT_SYMED_VALUE 117
 #endif
 #endif
+
+#endif  // WEBLAYER_SHELL_APP_RESOURCE_H_
\ No newline at end of file