diff --git a/.eslintrc.js b/.eslintrc.js
index be406d5..387c492 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -17,6 +17,7 @@
     'brace-style': ['error', '1tbs'],
     'curly': ['error', 'multi-line', 'consistent'],
     'new-parens': 'error',
+    'no-array-constructor': 'error',
     'no-console': ['error', {allow: ['info', 'warn', 'error', 'assert']}],
     'no-extra-boolean-cast': 'error',
     'no-extra-semi': 'error',
diff --git a/DEPS b/DEPS
index 99fc140..8da762d 100644
--- a/DEPS
+++ b/DEPS
@@ -280,7 +280,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': 'a10e6c1025274a5f2cc4958c39a102755b45bb34',
+  'skia_revision': '2292a55e385c2c22ee4b0cf2e34fa4f5304b6241',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -359,7 +359,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'd2741940ef1cf0bc2572e52e7e9677b74166416d',
+  'devtools_frontend_revision': '6c4b06be17e8a7ae237290a5589eacc6f89a47bf',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -395,7 +395,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': 'dadf11909a2efb8dea2d88261f6aa9715513d61c',
+  'dawn_revision': '2f1b0dc47d8316f3db6e5b9a55b873cd3079cea5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1133,7 +1133,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'f2b987bce92c3db8f1bc6f8c482c45c62fedcedf',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '2a5efd3e0fa04ba2f4408b1514e7211715480043',
       'condition': 'checkout_chromeos',
   },
 
@@ -1156,7 +1156,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '2f3c96d5e4eeef5bc0992fc7140c2d198b71f669',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '418f021a0eddf7a207d8e5b68cccf0588c3a9724',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1553,7 +1553,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '2a59c7427cfd780bcb98dfad01772b44468edc7a',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '4d2a825326de4078f54adf4035ba2eff9901a052',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1796,7 +1796,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@b30e642420249994535e6b8d9cc292b07d4034f7',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@651b84b4189ae15e0d560044ed653dd0798f5bb3',
     'condition': 'checkout_src_internal',
   },
 
@@ -1826,7 +1826,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': '-6tfPiC-tl4DErnlvar3SY-mesNNq9Y4YR7Hq1Vb-F4C',
+        'version': 'SIWoxdNNhtEW34ggbOoHdo5pi6nqFHv4keKLTelV8DYC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1837,7 +1837,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'WsmCtJ_DmkP5Pbw9a5dbqXendI4L_kIJX-S9ykYePXsC',
+        'version': '3DDMcfjqwB9lHOJNbjxvwVfZ2QyJOwXFk65zXhh1kLcC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/browser/aw_feature_list_creator.cc b/android_webview/browser/aw_feature_list_creator.cc
index f30a3a6d..a7ab41d 100644
--- a/android_webview/browser/aw_feature_list_creator.cc
+++ b/android_webview/browser/aw_feature_list_creator.cc
@@ -47,6 +47,7 @@
 #include "components/variations/pref_names.h"
 #include "components/variations/service/safe_seed_manager.h"
 #include "components/variations/service/variations_service.h"
+#include "components/variations/variations_switches.h"
 #include "content/public/common/content_switch_dependent_feature_overrides.h"
 #include "net/base/features.h"
 #include "net/nqe/pref_names.h"
@@ -247,14 +248,17 @@
       aw_feature_entries::RegisterEnabledFeatureEntries(feature_list.get());
 
   auto* metrics_client = AwMetricsServiceClient::GetInstance();
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
   // Populate FieldTrialList. Since |low_entropy_provider| is null, it will fall
   // back to the provider we previously gave to FieldTrialList, which is a low
   // entropy provider. The X-Client-Data header is not reported on WebView, so
   // we pass an empty object as the |low_entropy_source_value|.
   variations_field_trial_creator_->SetUpFieldTrials(
       variation_ids,
-      GetSwitchDependentFeatureOverrides(
-          *base::CommandLine::ForCurrentProcess()),
+      command_line->GetSwitchValueASCII(
+          variations::switches::kForceVariationIds),
+      GetSwitchDependentFeatureOverrides(*command_line),
       /*low_entropy_provider=*/nullptr, std::move(feature_list),
       metrics_client->metrics_state_manager(), aw_field_trials_.get(),
       &ignored_safe_seed_manager, /*low_entropy_source_value=*/absl::nullopt);
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index abf8ba9..5bb0d3d 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1889,6 +1889,8 @@
     "wm/event_client_impl.h",
     "wm/float/float_controller.cc",
     "wm/float/float_controller.h",
+    "wm/float/tablet_mode_float_window_resizer.cc",
+    "wm/float/tablet_mode_float_window_resizer.h",
     "wm/fullscreen_window_finder.cc",
     "wm/fullscreen_window_finder.h",
     "wm/gestures/back_gesture/back_gesture_affordance.cc",
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index 91d98f0..84a5e95 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -824,7 +824,7 @@
   DCHECK(chromeos::wm::features::IsFloatWindowEnabled());
   aura::Window* window = window_util::GetActiveWindow();
   DCHECK(window);
-  // TODO(sammiequon|shidi): Add some UI like a bounce if a window cannnot be
+  // TODO(sammiequon|shidi): Add some UI like a bounce if a window cannot be
   // floated.
   chromeos::ToggleFloating(window);
   base::RecordAction(UserMetricsAction("Accel_Toggle_Floating"));
diff --git a/ash/frame/non_client_frame_view_ash.cc b/ash/frame/non_client_frame_view_ash.cc
index 4143595..b273b09 100644
--- a/ash/frame/non_client_frame_view_ash.cc
+++ b/ash/frame/non_client_frame_view_ash.cc
@@ -98,7 +98,8 @@
     if (window_state_->IsFullscreen())
       return;
     if (Shell::Get()->tablet_mode_controller()->ShouldAutoHideTitlebars(
-            widget_)) {
+            widget_) &&
+        !window_state_->IsFloated()) {
       ImmersiveFullscreenController::EnableForWidget(widget_, true);
     }
   }
@@ -127,7 +128,7 @@
         Shell::Get()->tablet_mode_controller() &&
         Shell::Get()->tablet_mode_controller()->ShouldAutoHideTitlebars(
             widget)) {
-      if (window_state->IsMinimized())
+      if (window_state->IsMinimized() || window_state->IsFloated())
         ImmersiveFullscreenController::EnableForWidget(widget_, false);
       else if (window_state->IsMaximized())
         ImmersiveFullscreenController::EnableForWidget(widget_, true);
diff --git a/ash/login/ui/login_expanded_public_account_view_unittest.cc b/ash/login/ui/login_expanded_public_account_view_unittest.cc
index 8ec136c..b5d2c8c 100644
--- a/ash/login/ui/login_expanded_public_account_view_unittest.cc
+++ b/ash/login/ui/login_expanded_public_account_view_unittest.cc
@@ -18,7 +18,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/events/test/event_generator.h"
-#include "ui/views/controls/link.h"
+#include "ui/views/controls/link_fragment.h"
 #include "ui/views/layout/box_layout_view.h"
 #include "ui/views/test/combobox_test_api.h"
 #include "ui/views/widget/widget.h"
@@ -192,8 +192,9 @@
 
   // Tap on the learn more link.
   const auto& children = test_api.learn_more_label()->children();
-  const auto it = base::ranges::find(children, views::Link::kViewClassName,
-                                     &views::View::GetClassName);
+  const auto it =
+      base::ranges::find(children, views::LinkFragment::kViewClassName,
+                         &views::View::GetClassName);
   DCHECK(it != children.cend());
   TapOnView(*it);
   ASSERT_NE(test_api.learn_more_dialog(), nullptr);
diff --git a/ash/public/cpp/wallpaper/wallpaper_controller_client.h b/ash/public/cpp/wallpaper/wallpaper_controller_client.h
index 40293427..95cde1d3 100644
--- a/ash/public/cpp/wallpaper/wallpaper_controller_client.h
+++ b/ash/public/cpp/wallpaper/wallpaper_controller_client.h
@@ -28,10 +28,6 @@
   // Opens the wallpaper picker window.
   virtual void OpenWallpaperPicker() = 0;
 
-  // Closes the app side of the wallpaper preview (top header bar) if it is
-  // currently open.
-  virtual void MaybeClosePreviewWallpaper() = 0;
-
   // Sets the default wallpaper and removes the file for the previous wallpaper.
   virtual void SetDefaultWallpaper(
       const AccountId& account_id,
diff --git a/ash/wallpaper/test_wallpaper_controller_client.cc b/ash/wallpaper/test_wallpaper_controller_client.cc
index 8a723e1..dd9ce9b 100644
--- a/ash/wallpaper/test_wallpaper_controller_client.cc
+++ b/ash/wallpaper/test_wallpaper_controller_client.cc
@@ -45,7 +45,6 @@
 
 void TestWallpaperControllerClient::ResetCounts() {
   open_count_ = 0;
-  close_preview_count_ = 0;
   set_default_wallpaper_count_ = 0;
   migrate_collection_id_from_chrome_app_count_ = 0;
   fetch_daily_refresh_wallpaper_param_ = std::string();
@@ -61,10 +60,6 @@
   open_count_++;
 }
 
-void TestWallpaperControllerClient::MaybeClosePreviewWallpaper() {
-  close_preview_count_++;
-}
-
 void TestWallpaperControllerClient::SetDefaultWallpaper(
     const AccountId& account_id,
     bool show_wallpaper,
diff --git a/ash/wallpaper/test_wallpaper_controller_client.h b/ash/wallpaper/test_wallpaper_controller_client.h
index 47dfb662..cdcb20c 100644
--- a/ash/wallpaper/test_wallpaper_controller_client.h
+++ b/ash/wallpaper/test_wallpaper_controller_client.h
@@ -35,7 +35,6 @@
                      const std::vector<backdrop::Image>& images);
 
   size_t open_count() const { return open_count_; }
-  size_t close_preview_count() const { return close_preview_count_; }
   size_t set_default_wallpaper_count() const {
     return set_default_wallpaper_count_;
   }
@@ -80,7 +79,6 @@
 
   // WallpaperControllerClient:
   void OpenWallpaperPicker() override;
-  void MaybeClosePreviewWallpaper() override;
   void SetDefaultWallpaper(
       const AccountId& account_id,
       bool show_wallpaper,
@@ -117,7 +115,6 @@
 
  private:
   size_t open_count_ = 0;
-  size_t close_preview_count_ = 0;
   size_t set_default_wallpaper_count_ = 0;
   size_t migrate_collection_id_from_chrome_app_count_ = 0;
   size_t fetch_images_for_collection_count_ = 0;
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index 0b935e6..8e2258e8 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -764,9 +764,6 @@
     DCHECK(!reload_preview_wallpaper_callback_);
     return;
   }
-  // May be null in tests.
-  if (wallpaper_controller_client_)
-    wallpaper_controller_client_->MaybeClosePreviewWallpaper();
   CancelPreviewWallpaper();
 }
 
diff --git a/ash/wallpaper/wallpaper_controller_impl.h b/ash/wallpaper/wallpaper_controller_impl.h
index 348f6e5..98aa3d8 100644
--- a/ash/wallpaper/wallpaper_controller_impl.h
+++ b/ash/wallpaper/wallpaper_controller_impl.h
@@ -157,10 +157,8 @@
   // always return true thereafter.
   bool HasShownAnyWallpaper() const;
 
-  // Ash cannot close the chrome side of the wallpaper preview so this function
-  // tells the chrome side to do so. Also Ash cannot tell whether or not the
-  // wallpaper picker is currently open so this will close the wallpaper preview
-  // if it is open and do nothing if it is not open.
+  // Exit wallpaper preview state if it is open and do nothing if it is not
+  // open.
   void MaybeClosePreviewWallpaper();
 
   // Shows the wallpaper and alerts observers of changes.
diff --git a/ash/wallpaper/wallpaper_controller_unittest.cc b/ash/wallpaper/wallpaper_controller_unittest.cc
index 6170c10..bfcbdd2 100644
--- a/ash/wallpaper/wallpaper_controller_unittest.cc
+++ b/ash/wallpaper/wallpaper_controller_unittest.cc
@@ -387,11 +387,20 @@
   void OnWallpaperColorsChanged() override { ++colors_changed_count_; }
   void OnWallpaperBlurChanged() override { ++blur_changed_count_; }
   void OnFirstWallpaperShown() override { ++first_shown_count_; }
+  void OnWallpaperPreviewStarted() override {
+    DCHECK(!is_in_wallpaper_preview_);
+    is_in_wallpaper_preview_ = true;
+  }
+  void OnWallpaperPreviewEnded() override {
+    DCHECK(is_in_wallpaper_preview_);
+    is_in_wallpaper_preview_ = false;
+  }
 
   int colors_changed_count() const { return colors_changed_count_; }
   int blur_changed_count() const { return blur_changed_count_; }
   int first_shown_count() const { return first_shown_count_; }
   int wallpaper_changed_count() const { return wallpaper_changed_count_; }
+  bool is_in_wallpaper_preview() const { return is_in_wallpaper_preview_; }
 
  private:
   WallpaperController* controller_;
@@ -399,6 +408,7 @@
   int blur_changed_count_ = 0;
   int first_shown_count_ = 0;
   int wallpaper_changed_count_ = 0;
+  bool is_in_wallpaper_preview_ = false;
 };
 
 }  // namespace
@@ -2531,9 +2541,12 @@
   gfx::ImageSkia custom_wallpaper = CreateImage(640, 480, kWallpaperColor);
   EXPECT_NE(kWallpaperColor, GetWallpaperColor());
   ClearWallpaperCount();
+
+  TestWallpaperControllerObserver observer(controller_);
   controller_->SetCustomWallpaper(account_id_1, file_name_1, layout,
                                   custom_wallpaper, true /*preview_mode=*/);
   RunAllTasksUntilIdle();
+  EXPECT_TRUE(observer.is_in_wallpaper_preview());
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
   // Verify that the user wallpaper info remains unchanged during the preview.
@@ -2547,12 +2560,12 @@
   ClearWallpaperCount();
   EnterOverview();
   RunAllTasksUntilIdle();
+  EXPECT_FALSE(observer.is_in_wallpaper_preview());
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_NE(kWallpaperColor, GetWallpaperColor());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDefault);
   EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
   EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
-  EXPECT_EQ(1u, client_.close_preview_count());
 }
 
 TEST_F(WallpaperControllerTest, ClosePreviewWallpaperOnWindowCycleStart) {
@@ -2576,6 +2589,8 @@
       CreateTestWindow(gfx::Rect(0, 0, 100, 100)));
   WindowState::Get(wallpaper_picker_window.get())->Activate();
 
+  TestWallpaperControllerObserver observer(controller_);
+
   // Set a custom wallpaper for the user and enable preview. Verify that the
   // wallpaper is changed to the expected color.
   const WallpaperLayout layout = WALLPAPER_LAYOUT_CENTER;
@@ -2585,6 +2600,7 @@
   controller_->SetCustomWallpaper(account_id_1, file_name_1, layout,
                                   custom_wallpaper, true /*preview_mode=*/);
   RunAllTasksUntilIdle();
+  EXPECT_TRUE(observer.is_in_wallpaper_preview());
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
   // Verify that the user wallpaper info remains unchanged during the preview.
@@ -2598,12 +2614,12 @@
   Shell::Get()->window_cycle_controller()->HandleCycleWindow(
       WindowCycleController::WindowCyclingDirection::kForward);
   RunAllTasksUntilIdle();
+  EXPECT_FALSE(observer.is_in_wallpaper_preview());
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_NE(kWallpaperColor, GetWallpaperColor());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDefault);
   EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
   EXPECT_TRUE(Shell::Get()->window_cycle_controller()->IsCycling());
-  EXPECT_EQ(1u, client_.close_preview_count());
 }
 
 TEST_F(WallpaperControllerTest,
@@ -2628,6 +2644,8 @@
       CreateTestWindow(gfx::Rect(0, 0, 100, 100)));
   WindowState::Get(wallpaper_picker_window.get())->Activate();
 
+  TestWallpaperControllerObserver observer(controller_);
+
   // Set a custom wallpaper for the user and enable preview. Verify that the
   // wallpaper is changed to the expected color.
   const WallpaperLayout layout = WALLPAPER_LAYOUT_CENTER;
@@ -2637,6 +2655,7 @@
   controller_->SetCustomWallpaper(account_id_1, file_name_1, layout,
                                   custom_wallpaper, true /*preview_mode=*/);
   RunAllTasksUntilIdle();
+  EXPECT_TRUE(observer.is_in_wallpaper_preview());
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_EQ(kWallpaperColor, GetWallpaperColor());
   // Verify that the user wallpaper info remains unchanged during the preview.
@@ -2650,13 +2669,13 @@
   SimulateUserLogin(account_id_2);
   controller_->ShowUserWallpaper(account_id_2);
   RunAllTasksUntilIdle();
+  EXPECT_FALSE(observer.is_in_wallpaper_preview());
   EXPECT_EQ(1, GetWallpaperCount());
   EXPECT_NE(kWallpaperColor, GetWallpaperColor());
   EXPECT_EQ(controller_->GetWallpaperType(), WallpaperType::kDefault);
   EXPECT_TRUE(
       controller_->GetUserWallpaperInfo(account_id_2, &user_wallpaper_info));
   EXPECT_EQ(user_wallpaper_info, default_wallpaper_info);
-  EXPECT_EQ(1u, client_.close_preview_count());
 }
 
 TEST_F(WallpaperControllerTest, ConfirmPreviewWallpaper) {
diff --git a/ash/webui/os_feedback_ui/untrusted_resources/untrusted_index.html b/ash/webui/os_feedback_ui/untrusted_resources/untrusted_index.html
index 3a1da19..eb5b3719 100644
--- a/ash/webui/os_feedback_ui/untrusted_resources/untrusted_index.html
+++ b/ash/webui/os_feedback_ui/untrusted_resources/untrusted_index.html
@@ -12,6 +12,9 @@
       margin: 0;
     }
   </style>
+  <link rel="stylesheet" href="//resources/chromeos/colors/cros_styles.css">
+  <link rel="stylesheet" href="//resources/css/text_defaults.css">
+  <link rel="stylesheet" href="//resources/css/md_colors.css">
 </head>
 
 <body>
diff --git a/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.html b/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.html
index 2f69a36..23b896efa 100644
--- a/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.html
+++ b/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.html
@@ -9,7 +9,7 @@
       '. label   .'
       '. options .'
       '. .       .';
-    grid-template-columns: 8px 1fr 20px;
+    grid-template-columns: 12px 1fr 20px;
     grid-template-rows: auto 1fr 20px;
   }
 
@@ -22,7 +22,7 @@
     grid-area: label;
     justify-content: space-between;
     margin-block-start: 20px;
-    margin-inline-start: 12px;
+    margin-inline-start: 8px;
   }
 
   #keyboardBacklightLabel > p {
@@ -72,7 +72,7 @@
   }
 
   .color-inner-container svg {
-    fill: var(--cros-text-color-primary);
+    fill: var(--cros-icon-color-primary);
   }
 
   .color-container:focus-visible {
diff --git a/ash/wm/float/float_controller_unittest.cc b/ash/wm/float/float_controller_unittest.cc
index b812a1c..d3204094 100644
--- a/ash/wm/float/float_controller_unittest.cc
+++ b/ash/wm/float/float_controller_unittest.cc
@@ -10,6 +10,7 @@
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/test/scoped_feature_list.h"
+#include "chromeos/ui/frame/immersive/immersive_fullscreen_controller.h"
 #include "chromeos/ui/wm/features.h"
 #include "ui/aura/test/test_window_delegate.h"
 #include "ui/wm/core/window_util.h"
@@ -130,4 +131,26 @@
   EXPECT_FALSE(controller->IsFloated(window.get()));
 }
 
+// Tests that windows floated in tablet mode have immersive mode disabled,
+// showing their title bars.
+TEST_F(WindowFloatTest, TabletImmersiveMode) {
+  // Create a test app window that has a header.
+  auto window = CreateAppWindow();
+  auto* immersive_controller = chromeos::ImmersiveFullscreenController::Get(
+      views::Widget::GetWidgetForNativeView(window.get()));
+
+  // Enter tablet mode and float `window`.
+  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
+  EXPECT_TRUE(immersive_controller->IsEnabled());
+
+  PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
+  EXPECT_FALSE(immersive_controller->IsEnabled());
+
+  PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
+  EXPECT_TRUE(immersive_controller->IsEnabled());
+
+  // TODO(crbug.com/1339489): Add tests to check immersive mode when transition
+  // to tablet from clamshell and vice versa.
+}
+
 }  // namespace ash
diff --git a/ash/wm/float/tablet_mode_float_window_resizer.cc b/ash/wm/float/tablet_mode_float_window_resizer.cc
new file mode 100644
index 0000000..65aa07d
--- /dev/null
+++ b/ash/wm/float/tablet_mode_float_window_resizer.cc
@@ -0,0 +1,41 @@
+// Copyright 2022 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 "ash/wm/float/tablet_mode_float_window_resizer.h"
+
+#include "ash/wm/window_state.h"
+#include "chromeos/ui/wm/features.h"
+#include "ui/aura/window.h"
+
+namespace ash {
+
+TabletModeFloatWindowResizer::TabletModeFloatWindowResizer(
+    WindowState* window_state)
+    : WindowResizer(window_state) {
+  DCHECK(chromeos::wm::features::IsFloatWindowEnabled());
+}
+
+TabletModeFloatWindowResizer::~TabletModeFloatWindowResizer() {
+  window_state_->DeleteDragDetails();
+}
+
+void TabletModeFloatWindowResizer::Drag(const gfx::PointF& location_in_parent,
+                                        int event_flags) {
+  gfx::Rect bounds = CalculateBoundsForDrag(location_in_parent);
+  if (bounds != GetTarget()->bounds())
+    SetBoundsDuringResize(bounds);
+}
+
+void TabletModeFloatWindowResizer::CompleteDrag() {
+  // Keep the bounds where it was last dragged to.
+  // TODO(crbug.com/1338715): Magnetize to the closest corner on release.
+}
+
+void TabletModeFloatWindowResizer::RevertDrag() {
+  GetTarget()->SetBounds(details().initial_bounds_in_parent);
+}
+
+void TabletModeFloatWindowResizer::FlingOrSwipe(ui::GestureEvent* event) {}
+
+}  // namespace ash
diff --git a/ash/wm/float/tablet_mode_float_window_resizer.h b/ash/wm/float/tablet_mode_float_window_resizer.h
new file mode 100644
index 0000000..31d5f815
--- /dev/null
+++ b/ash/wm/float/tablet_mode_float_window_resizer.h
@@ -0,0 +1,34 @@
+// Copyright 2022 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 ASH_WM_FLOAT_TABLET_MODE_FLOAT_WINDOW_RESIZER_H_
+#define ASH_WM_FLOAT_TABLET_MODE_FLOAT_WINDOW_RESIZER_H_
+
+#include "ash/wm/window_resizer.h"
+
+namespace ash {
+
+class WindowState;
+
+// WindowResizer implementation for floated windows in tablet mode.
+// TODO(crbug.com/1338715): This resizer adds the most basic dragging. It needs
+// to stick to edges and magnetize to corners on release.
+class TabletModeFloatWindowResizer : public WindowResizer {
+ public:
+  explicit TabletModeFloatWindowResizer(WindowState* window_state);
+  TabletModeFloatWindowResizer(const TabletModeFloatWindowResizer&) = delete;
+  TabletModeFloatWindowResizer& operator=(const TabletModeFloatWindowResizer&) =
+      delete;
+  ~TabletModeFloatWindowResizer() override;
+
+  // WindowResizer:
+  void Drag(const gfx::PointF& location_in_parent, int event_flags) override;
+  void CompleteDrag() override;
+  void RevertDrag() override;
+  void FlingOrSwipe(ui::GestureEvent* event) override;
+};
+
+}  // namespace ash
+
+#endif  // ASH_WM_FLOAT_TABLET_MODE_FLOAT_WINDOW_RESIZER_H_
diff --git a/ash/wm/workspace/workspace_window_resizer.cc b/ash/wm/workspace/workspace_window_resizer.cc
index 244a4ea..4988e8c 100644
--- a/ash/wm/workspace/workspace_window_resizer.cc
+++ b/ash/wm/workspace/workspace_window_resizer.cc
@@ -21,6 +21,7 @@
 #include "ash/wm/default_window_resizer.h"
 #include "ash/wm/desks/desks_util.h"
 #include "ash/wm/drag_window_resizer.h"
+#include "ash/wm/float/tablet_mode_float_window_resizer.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/pip/pip_window_resizer.h"
 #include "ash/wm/tablet_mode/tablet_mode_browser_window_drag_delegate.h"
@@ -299,14 +300,23 @@
     aura::Window* window,
     const gfx::PointF& point_in_parent,
     int window_component,
-    ::wm::WindowMoveSource source) {
+    wm::WindowMoveSource source) {
+  WindowState* window_state = WindowState::Get(window);
+
+  // Dragging floated windows in tablet mode is allowed.
+  // TODO(crbug.com/1338715): Investigate if we need to wrap the resizer in a
+  // DragWindowResizer.
+  if (window_state->IsFloated() && window_component == HTCAPTION) {
+    window_state->CreateDragDetails(point_in_parent, HTCAPTION, source);
+    return std::make_unique<TabletModeFloatWindowResizer>(window_state);
+  }
+
   // Window dragging from top and tab dragging are disabled if "WebUITabStrip"
   // feature is enabled. "WebUITabStrip" will be enabled on 81 for Krane and on
   // 82 for all other boards.
   if (features::IsWebUITabStripEnabled())
     return nullptr;
 
-  WindowState* window_state = WindowState::Get(window);
   // Only maximized/fullscreen/snapped window can be dragged from the top of
   // the screen.
   if (!window_state->IsMaximized() && !window_state->IsFullscreen() &&
diff --git a/base/check.cc b/base/check.cc
index a62f5fc..349579b 100644
--- a/base/check.cc
+++ b/base/check.cc
@@ -14,11 +14,71 @@
 #endif
 
 #include "base/check_op.h"
+#include "base/debug/dump_without_crashing.h"
 #include "base/logging.h"
+#include "base/thread_annotations.h"
 #include "build/build_config.h"
 
+#include <atomic>
+
 namespace logging {
 
+namespace {
+
+#if defined(DCHECK_IS_CONFIGURABLE)
+void DCheckDumpOnceWithoutCrashing() {
+  // Best-effort gate to prevent multiple DCHECKs from being dumped. This will
+  // race if multiple threads DCHECK at the same time, but we'll eventually stop
+  // reporting and at most report once per thread.
+  static std::atomic<bool> has_dumped = false;
+  if (!has_dumped.load(std::memory_order_relaxed)) {
+    // Note that dumping may fail if the crash handler hasn't been set yet. In
+    // that case we want to try again on the next failing DCHECK.
+    if (base::debug::DumpWithoutCrashingUnthrottled())
+      has_dumped.store(true, std::memory_order_relaxed);
+  }
+}
+
+class DCheckLogMessage : public LogMessage {
+ public:
+  using LogMessage::LogMessage;
+  ~DCheckLogMessage() override {
+    if (severity() != logging::LOGGING_FATAL)
+      DCheckDumpOnceWithoutCrashing();
+  }
+};
+
+#if BUILDFLAG(IS_WIN)
+class DCheckWin32ErrorLogMessage : public Win32ErrorLogMessage {
+ public:
+  using Win32ErrorLogMessage::Win32ErrorLogMessage;
+  ~DCheckWin32ErrorLogMessage() override {
+    if (severity() != logging::LOGGING_FATAL)
+      DCheckDumpOnceWithoutCrashing();
+  }
+};
+#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
+class DCheckErrnoLogMessage : public ErrnoLogMessage {
+ public:
+  using ErrnoLogMessage::ErrnoLogMessage;
+  ~DCheckErrnoLogMessage() override {
+    if (severity() != logging::LOGGING_FATAL)
+      DCheckDumpOnceWithoutCrashing();
+  }
+};
+#endif  // BUILDFLAG(IS_WIN)
+#else
+static_assert(logging::LOGGING_DCHECK == logging::LOGGING_FATAL);
+typedef LogMessage DCheckLogMessage;
+#if BUILDFLAG(IS_WIN)
+typedef Win32ErrorLogMessage DCheckWin32ErrorLogMessage;
+#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
+typedef ErrnoLogMessage DCheckErrnoLogMessage;
+#endif  // BUILDFLAG(IS_WIN)
+#endif  // defined(DCHECK_IS_CONFIGURABLE)
+
+}  // namespace
+
 CheckError CheckError::Check(const char* file,
                              int line,
                              const char* condition) {
@@ -40,7 +100,7 @@
 CheckError CheckError::DCheck(const char* file,
                               int line,
                               const char* condition) {
-  auto* const log_message = new LogMessage(file, line, LOGGING_DCHECK);
+  auto* const log_message = new DCheckLogMessage(file, line, LOGGING_DCHECK);
   log_message->stream() << "Check failed: " << condition << ". ";
   return CheckError(log_message);
 }
@@ -48,7 +108,7 @@
 CheckError CheckError::DCheckOp(const char* file,
                                 int line,
                                 CheckOpResult* check_op_result) {
-  auto* const log_message = new LogMessage(file, line, LOGGING_DCHECK);
+  auto* const log_message = new DCheckLogMessage(file, line, LOGGING_DCHECK);
   log_message->stream() << "Check failed: " << check_op_result->message_;
   free(check_op_result->message_);
   check_op_result->message_ = nullptr;
@@ -80,10 +140,10 @@
   SystemErrorCode err_code = logging::GetLastSystemErrorCode();
 #if BUILDFLAG(IS_WIN)
   auto* const log_message =
-      new Win32ErrorLogMessage(file, line, LOGGING_DCHECK, err_code);
+      new DCheckWin32ErrorLogMessage(file, line, LOGGING_DCHECK, err_code);
 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
   auto* const log_message =
-      new ErrnoLogMessage(file, line, LOGGING_DCHECK, err_code);
+      new DCheckErrnoLogMessage(file, line, LOGGING_DCHECK, err_code);
 #endif
   log_message->stream() << "Check failed: " << condition << ". ";
   return CheckError(log_message);
diff --git a/cc/animation/animation_host_unittest.cc b/cc/animation/animation_host_unittest.cc
index fa79bed..bebb3bc 100644
--- a/cc/animation/animation_host_unittest.cc
+++ b/cc/animation/animation_host_unittest.cc
@@ -337,9 +337,7 @@
 
   // Create scroll timeline that links scroll animation and worklet animation
   // together.
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(0);
-  scroll_offsets.push_back(100);
+  ScrollTimeline::ScrollOffsets scroll_offsets(0, 100);
   auto scroll_timeline = ScrollTimeline::Create(
       element_id, ScrollTimeline::ScrollDown, scroll_offsets);
 
@@ -380,9 +378,7 @@
 
   // Create scroll timeline that links scroll animation and scroll-linked
   // animation together.
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(0);
-  scroll_offsets.push_back(100);
+  ScrollTimeline::ScrollOffsets scroll_offsets(0, 100);
   auto scroll_timeline = ScrollTimeline::Create(
       element_id_, ScrollTimeline::ScrollDown, scroll_offsets);
 
@@ -458,10 +454,7 @@
   timeline_->AttachAnimation(mock_scroll_animation);
   host_impl_->AddToTicking(mock_scroll_animation);
 
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(0);
-  scroll_offsets.push_back(100);
-
+  ScrollTimeline::ScrollOffsets scroll_offsets(0, 100);
   auto scroll_timeline = ScrollTimeline::Create(
       element_id_, ScrollTimeline::ScrollDown, scroll_offsets);
 
diff --git a/cc/animation/scroll_offset_animation_curve_factory.cc b/cc/animation/scroll_offset_animation_curve_factory.cc
index 761b549..553bffe 100644
--- a/cc/animation/scroll_offset_animation_curve_factory.cc
+++ b/cc/animation/scroll_offset_animation_curve_factory.cc
@@ -35,7 +35,7 @@
   if (scroll_type == ScrollType::kAutoScroll)
     return CreateLinearAnimation(target_value);
 
-  if (base::FeatureList::IsEnabled(features::kImpulseScrollAnimations))
+  if (features::IsImpulseScrollAnimationEnabled())
     return CreateImpulseAnimation(target_value);
 
   return CreateEaseInOutAnimation(
diff --git a/cc/animation/scroll_timeline.cc b/cc/animation/scroll_timeline.cc
index f8d2481..06f54a4 100644
--- a/cc/animation/scroll_timeline.cc
+++ b/cc/animation/scroll_timeline.cc
@@ -27,33 +27,23 @@
          direction == ScrollTimeline::ScrollLeft;
 }
 
-bool ValidateScrollOffsets(const std::vector<double>& scroll_offsets) {
-  return scroll_offsets.empty() || scroll_offsets.size() >= 2.0;
-}
-
 }  // namespace
 
-template double ComputeProgress<std::vector<double>>(
-    double,
-    const std::vector<double>&);
-
 ScrollTimeline::ScrollTimeline(absl::optional<ElementId> scroller_id,
                                ScrollDirection direction,
-                               const std::vector<double> scroll_offsets,
+                               absl::optional<ScrollOffsets> scroll_offsets,
                                int animation_timeline_id)
     : AnimationTimeline(animation_timeline_id),
       pending_id_(scroller_id),
       direction_(direction),
-      scroll_offsets_(scroll_offsets) {
-  DCHECK(ValidateScrollOffsets(scroll_offsets_));
-}
+      pending_offsets_(scroll_offsets) {}
 
 ScrollTimeline::~ScrollTimeline() = default;
 
 scoped_refptr<ScrollTimeline> ScrollTimeline::Create(
     absl::optional<ElementId> scroller_id,
     ScrollTimeline::ScrollDirection direction,
-    const std::vector<double> scroll_offsets) {
+    absl::optional<ScrollOffsets> scroll_offsets) {
   return base::WrapRefCounted(
       new ScrollTimeline(scroller_id, direction, scroll_offsets,
                          AnimationIdProvider::NextTimelineId()));
@@ -61,15 +51,16 @@
 
 scoped_refptr<AnimationTimeline> ScrollTimeline::CreateImplInstance() const {
   return base::WrapRefCounted(
-      new ScrollTimeline(pending_id_, direction_, scroll_offsets_, id()));
+      new ScrollTimeline(pending_id_, direction_, pending_offsets_, id()));
 }
 
 bool ScrollTimeline::IsActive(const ScrollTree& scroll_tree,
                               bool is_active_tree) const {
   // Blink passes empty scroll offsets when the timeline is inactive.
-  if (scroll_offsets_.empty()) {
+  if ((is_active_tree && !active_offsets_) ||
+      (!is_active_tree && !pending_offsets_))
     return false;
-  }
+
   // If pending tree with our scroller hasn't been activated, or the scroller
   // has been removed (e.g. if it is no longer composited).
   if ((is_active_tree && !active_id_) || (!is_active_tree && !pending_id_))
@@ -113,35 +104,26 @@
   DCHECK_GE(max_offset, 0);
   DCHECK_GE(current_offset, 0);
 
-  DCHECK_GE(scroll_offsets_.size(), 2u);
-  double resolved_start_scroll_offset = scroll_offsets_[0];
-  double resolved_end_scroll_offset =
-      scroll_offsets_[scroll_offsets_.size() - 1];
-
-  // TODO(crbug.com/1060384): Once the spec has been updated to state what the
-  // expected result is when startScrollOffset >= endScrollOffset, we might need
-  // to add a special case here. See
-  // https://github.com/WICG/scroll-animations/issues/20
-
-  // 3. If current scroll offset is less than startScrollOffset:
-  if (current_offset < resolved_start_scroll_offset) {
-    return base::TimeTicks();
+  double start_offset = 0;
+  double end_offset = 0;
+  if (is_active_tree) {
+    DCHECK(active_offsets_);
+    start_offset = active_offsets_->start;
+    end_offset = active_offsets_->end;
+  } else {
+    DCHECK(pending_offsets_);
+    start_offset = pending_offsets_->start;
+    end_offset = pending_offsets_->end;
   }
 
-  // 4. If current scroll offset is greater than or equal to endScrollOffset:
-  if (current_offset >= resolved_end_scroll_offset) {
-    return base::TimeTicks() + base::Milliseconds(kScrollTimelineDurationMs);
-  }
-
-  // Otherwise,
-  // 5.1 Let progress be a result of applying calculate scroll timeline progress
-  // procedure for current scroll offset.
-  // 5.2 The current time is the result of evaluating the following expression:
-  //                progress × timeline duration to get the percentage
+  // TODO(crbug.com/1338167): Update once
+  // github.com/w3c/csswg-drafts/issues/7401 is resolved.
+  double progress =
+      end_offset == start_offset
+          ? 1
+          : (current_offset - start_offset) / (end_offset - start_offset);
   return base::TimeTicks() +
-         base::Milliseconds(ComputeProgress<std::vector<double>>(
-                                current_offset, scroll_offsets_) *
-                            kScrollTimelineDurationMs);
+         base::Milliseconds(progress * kScrollTimelineDurationMs);
 }
 
 void ScrollTimeline::PushPropertiesTo(AnimationTimeline* impl_timeline) {
@@ -149,16 +131,12 @@
   DCHECK(impl_timeline);
   ScrollTimeline* scroll_timeline = ToScrollTimeline(impl_timeline);
   scroll_timeline->pending_id_ = pending_id_;
-  // TODO(smcgruer): This leads to incorrect behavior in the current design,
-  // because we end up using the pending start/end scroll offset for the active
-  // tree too. Instead we need to either split these (like pending_id_ and
-  // active_id_) or have a ScrollTimeline per tree.
-  scroll_timeline->scroll_offsets_ = scroll_offsets_;
-  DCHECK(ValidateScrollOffsets(scroll_timeline->scroll_offsets_));
+  scroll_timeline->pending_offsets_ = pending_offsets_;
 }
 
 void ScrollTimeline::ActivateTimeline() {
   active_id_ = pending_id_;
+  active_offsets_ = pending_offsets_;
   for (auto& kv : id_to_animation_map_) {
     auto& animation = kv.second;
     if (animation->IsWorkletAnimation())
@@ -200,17 +178,15 @@
 
 void ScrollTimeline::UpdateScrollerIdAndScrollOffsets(
     absl::optional<ElementId> pending_id,
-    const std::vector<double> scroll_offsets) {
-  if (pending_id_ == pending_id && scroll_offsets_ == scroll_offsets) {
+    absl::optional<ScrollOffsets> pending_offsets) {
+  if (pending_id_ == pending_id && pending_offsets_ == pending_offsets)
     return;
-  }
 
   // When the scroller id changes it will first be modified in the pending tree.
   // Then later (when the pending tree is promoted to active)
   // |ActivateTimeline| will be called and will set the |active_id_|.
   pending_id_ = pending_id;
-  scroll_offsets_ = scroll_offsets;
-  DCHECK(ValidateScrollOffsets(scroll_offsets_));
+  pending_offsets_ = pending_offsets;
 
   SetNeedsPushProperties();
 }
diff --git a/cc/animation/scroll_timeline.h b/cc/animation/scroll_timeline.h
index a654855..482a874 100644
--- a/cc/animation/scroll_timeline.h
+++ b/cc/animation/scroll_timeline.h
@@ -35,19 +35,31 @@
     ScrollRight,
   };
 
+  struct ScrollOffsets {
+    ScrollOffsets(double start_offset, double end_offset) {
+      start = start_offset;
+      end = end_offset;
+    }
+    bool operator==(const ScrollOffsets& other) const {
+      return start == other.start && end == other.end;
+    }
+    double start = 0;
+    double end = 0;
+  };
+
   // 100% is represented as 100s or 100000ms. We store it here in Milliseconds
   // because that is the time unit returned by functions like CurrentTime.
   static constexpr double kScrollTimelineDurationMs = 100000;
 
   ScrollTimeline(absl::optional<ElementId> scroller_id,
                  ScrollDirection direction,
-                 const std::vector<double> scroll_offsets,
+                 absl::optional<ScrollOffsets> scroll_offsets,
                  int animation_timeline_id);
 
   static scoped_refptr<ScrollTimeline> Create(
       absl::optional<ElementId> scroller_id,
       ScrollDirection direction,
-      const std::vector<double> scroll_offsets);
+      absl::optional<ScrollOffsets> scroll_offsets);
 
   // Create a copy of this ScrollTimeline intended for the impl thread in the
   // compositor.
@@ -68,7 +80,7 @@
 
   void UpdateScrollerIdAndScrollOffsets(
       absl::optional<ElementId> scroller_id,
-      const std::vector<double> scroll_offsets);
+      absl::optional<ScrollOffsets> scroll_offsets);
 
   void PushPropertiesTo(AnimationTimeline* impl_timeline) override;
   void ActivateTimeline() override;
@@ -82,14 +94,14 @@
   absl::optional<ElementId> GetPendingIdForTest() const { return pending_id_; }
   ScrollDirection GetDirectionForTest() const { return direction_; }
   absl::optional<double> GetStartScrollOffsetForTest() const {
-    if (scroll_offsets_.empty())
+    if (!pending_offsets_)
       return absl::nullopt;
-    return scroll_offsets_[0];
+    return pending_offsets_->start;
   }
   absl::optional<double> GetEndScrollOffsetForTest() const {
-    if (scroll_offsets_.empty())
+    if (!pending_offsets_)
       return absl::nullopt;
-    return scroll_offsets_[1];
+    return pending_offsets_->end;
   }
 
   bool IsScrollTimeline() const override;
@@ -108,9 +120,8 @@
   // it should base its current time on, and where the origin point is.
   ScrollDirection direction_;
 
-  // This defines scroll ranges of the scroller that the ScrollTimeline is
-  // active within. If no ranges are defined the timeline is inactive.
-  std::vector<double> scroll_offsets_;
+  absl::optional<ScrollOffsets> pending_offsets_;
+  absl::optional<ScrollOffsets> active_offsets_;
 };
 
 inline ScrollTimeline* ToScrollTimeline(AnimationTimeline* timeline) {
@@ -124,53 +135,6 @@
   return static_cast<const ScrollTimeline*>(timeline);
 }
 
-// https://drafts.csswg.org/scroll-animations-1/#progress-calculation-algorithm
-template <typename T>
-double ComputeProgress(double current_offset, const T& resolved_offsets) {
-  // 1. Let scroll offsets be the result of applying the procedure to resolve
-  // scroll timeline offsets for scrollOffsets.
-  DCHECK_GE(resolved_offsets.size(), 2u);
-  // When start offset is greater than end offset, current time is calculated
-  // outside of this method.
-  DCHECK_LT(resolved_offsets[0], resolved_offsets[resolved_offsets.size() - 1]);
-  // When animation is in before or after phase, current time is calculated
-  // outside of this method.
-  DCHECK_GE(current_offset, resolved_offsets[0]);
-  DCHECK_LT(current_offset, resolved_offsets[resolved_offsets.size() - 1]);
-  // Traverse scroll offsets from the back to find first interval that
-  // contains the current offset. In case of overlapping offsets, last matching
-  // interval in the list is used to calculate the current time. The rational
-  // for choosing last matching offset is to be consistent with CSS property
-  // overrides.
-
-  // 2. Let offset index correspond to the position of the last offset in scroll
-  // offsets whose value is less than or equal to offset and the value at the
-  // following position greater than offset
-  int offset_index;
-  for (offset_index = resolved_offsets.size() - 2;
-       offset_index > 0 && resolved_offsets[offset_index] > current_offset;
-       offset_index--) {
-    DCHECK_LT(current_offset, resolved_offsets[offset_index + 1]);
-  }
-  // 3. Let start offset be the offset value at position offset index in
-  // scroll offsets.
-  double start_offset = resolved_offsets[offset_index];
-  // 4. Let end offset be the value of next offset in scroll offsets after
-  // start offset.
-  double end_offset = resolved_offsets[offset_index + 1];
-  // 5. Let size be the number of offsets in scroll offsets.
-  unsigned int size = resolved_offsets.size();
-  // 6. Let offset weight be the result of evaluating 1 / (size - 1).
-  double offset_weight = 1.0 / (size - 1);
-  // 7. Let interval progress be the result of evaluating
-  // (offset - start offset) / (end offset - start offset).
-  double interval_progress =
-      (current_offset - start_offset) / (end_offset - start_offset);
-  // 8. Return the result of evaluating
-  // (offset index + interval progress) × offset weight.
-  return (offset_index + interval_progress) * offset_weight;
-}
-
 }  // namespace cc
 
 #endif  // CC_ANIMATION_SCROLL_TIMELINE_H_
diff --git a/cc/animation/scroll_timeline_unittest.cc b/cc/animation/scroll_timeline_unittest.cc
index 6d66c54..5318a05 100644
--- a/cc/animation/scroll_timeline_unittest.cc
+++ b/cc/animation/scroll_timeline_unittest.cc
@@ -24,6 +24,12 @@
 #define EXPECT_SCROLL_TIMELINE_TIME_NEAR(expected, value) \
   EXPECT_NEAR(expected, ToDouble(value), time_error_ms)
 
+#define EXPECT_SCROLL_TIMELINE_BEFORE_START(value) \
+  EXPECT_LT(ToDouble(value), 0);
+
+#define EXPECT_SCROLL_TIMELINE_AFTER_END(value) \
+  EXPECT_GT(ToDouble(value), ScrollTimeline::kScrollTimelineDurationMs);
+
 void SetScrollOffset(PropertyTrees* property_trees,
                      ElementId scroller_id,
                      gfx::PointF offset) {
@@ -123,9 +129,7 @@
 };
 
 TEST_F(ScrollTimelineTest, BasicCurrentTimeCalculations) {
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(0);
-  scroll_offsets.push_back(100);
+  ScrollTimeline::ScrollOffsets scroll_offsets(0, 100);
 
   scoped_refptr<ScrollTimeline> vertical_timeline = ScrollTimeline::Create(
       scroller_id(), ScrollTimeline::ScrollDown, scroll_offsets);
@@ -150,118 +154,6 @@
       horizontal_timeline->CurrentTime(scroll_tree(), false));
 }
 
-TEST_F(ScrollTimelineTest, MultipleScrollOffsetsCurrentTimeCalculations) {
-  double scroll_size =
-      content_size().height() - container_size().height();  // 400
-
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(0);
-  scroll_offsets.push_back(100.0);
-  scroll_offsets.push_back(250.0);
-  scroll_offsets.push_back(scroll_size);
-
-  scoped_refptr<ScrollTimeline> vertical_timeline = ScrollTimeline::Create(
-      scroller_id(), ScrollTimeline::ScrollDown, scroll_offsets);
-
-  unsigned int offset = 0;
-  double w = 1.0 / 3.0;  // offset weight
-  double p = 0;          // progress within the offset
-
-  // Scale necessary to convert absolute unit times to progress based values
-  double scale = ScrollTimeline::kScrollTimelineDurationMs / scroll_size;
-
-  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF());
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(
-      (offset + p) * w * scroll_size * scale,
-      vertical_timeline->CurrentTime(scroll_tree(), false));
-
-  p = (70.0 - 0.0) / (100.0 - 0.0);
-  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 70));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(
-      (offset + p) * w * scroll_size * scale,
-      vertical_timeline->CurrentTime(scroll_tree(), false));
-
-  offset = 1;
-  p = 0;
-  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 100));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(
-      (offset + p) * w * scroll_size * scale,
-      vertical_timeline->CurrentTime(scroll_tree(), false));
-
-  p = (150.0 - 100.0) / (250.0 - 100.0);
-  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 150));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(
-      (offset + p) * w * scroll_size * scale,
-      vertical_timeline->CurrentTime(scroll_tree(), false));
-
-  offset = 2;
-  p = 0;
-  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 250));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(
-      (offset + p) * w * scroll_size * scale,
-      vertical_timeline->CurrentTime(scroll_tree(), false));
-
-  p = (350.0 - 250.0) / (400.0 - 250.0);
-  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 350));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(
-      (offset + p) * w * scroll_size * scale,
-      vertical_timeline->CurrentTime(scroll_tree(), false));
-
-  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 400));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(
-      ScrollTimeline::kScrollTimelineDurationMs,
-      vertical_timeline->CurrentTime(scroll_tree(), false));
-}
-
-TEST_F(ScrollTimelineTest, OverlappingScrollOffsets) {
-  double scroll_size = 100.0;
-
-  // Start offset is greater than end offset ==> animation progress is
-  // either 0% or 100%.
-  std::vector<double> scroll_offsets = {350.0, 200.0, 50.0};
-
-  scoped_refptr<ScrollTimeline> vertical_timeline = ScrollTimeline::Create(
-      scroller_id(), ScrollTimeline::ScrollDown, scroll_offsets);
-
-  // Offset is less than start offset ==> current time is 0.
-  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 300));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(
-      0, vertical_timeline->CurrentTime(scroll_tree(), false));
-
-  // Scale necessary to convert absolute unit times to progress based values
-  double scale = ScrollTimeline::kScrollTimelineDurationMs / scroll_size;
-
-  // Offset is greater than end offset ==> current time is 100%.
-  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 360));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(
-      scroll_size * scale,
-      vertical_timeline->CurrentTime(scroll_tree(), false));
-
-  scroll_offsets = {0.0, 400.0, 200.0};
-
-  vertical_timeline = ScrollTimeline::Create(
-      scroller_id(), ScrollTimeline::ScrollDown, scroll_offsets);
-
-  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 100));
-  // Scroll offset is 25% of [0, 400) range, which maps to [0% 50%) of the
-  // entire scroll range.
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(
-      scroll_size * 0.5 * 0.25 * scale,
-      vertical_timeline->CurrentTime(scroll_tree(), false));
-
-  scroll_offsets = {200.0, 0.0, 400.0};
-
-  vertical_timeline = ScrollTimeline::Create(
-      scroller_id(), ScrollTimeline::ScrollDown, scroll_offsets);
-
-  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 300));
-  // Scroll offset is 75% of [0, 400) range, which maps to [50% 100%) of the
-  // entire scroll range.
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(
-      scroll_size * (0.5 + 0.5 * 0.75) * scale,
-      vertical_timeline->CurrentTime(scroll_tree(), false));
-}
-
 // This test ensures that the ScrollTimeline's active scroller id is correct. We
 // had a few crashes caused by assuming that the id would be available in the
 // active tree before the activation happened; see http://crbug.com/853231
@@ -283,9 +175,7 @@
                          container_size());
 
   double scroll_size = content_size().height() - container_size().height();
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(0);
-  scroll_offsets.push_back(scroll_size);
+  ScrollTimeline::ScrollOffsets scroll_offsets(0, scroll_size);
 
   double halfwayY = scroll_size / 2.;
   double expectedTime = 0.5 * ScrollTimeline::kScrollTimelineDurationMs;
@@ -321,9 +211,7 @@
 
 TEST_F(ScrollTimelineTest, CurrentTimeIsAdjustedForPixelSnapping) {
   double scroll_size = content_size().height() - container_size().height();
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(0);
-  scroll_offsets.push_back(scroll_size);
+  ScrollTimeline::ScrollOffsets scroll_offsets(0, scroll_size);
   scoped_refptr<ScrollTimeline> timeline = ScrollTimeline::Create(
       scroller_id(), ScrollTimeline::ScrollDown, scroll_offsets);
 
@@ -346,21 +234,20 @@
 TEST_F(ScrollTimelineTest, CurrentTimeHandlesStartScrollOffset) {
   double scroll_size = content_size().height() - container_size().height();
   const double start_scroll_offset = 20;
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(start_scroll_offset);
-  scroll_offsets.push_back(scroll_size);
+  ScrollTimeline::ScrollOffsets scroll_offsets(start_scroll_offset,
+                                               scroll_size);
   scoped_refptr<ScrollTimeline> timeline = ScrollTimeline::Create(
       scroller_id(), ScrollTimeline::ScrollDown, scroll_offsets);
 
-  // Unscrolled, the timeline should read a current time of 0 since the current
-  // offset (0) will be less than the startScrollOffset.
+  // Unscrolled, the timeline should read a current time of < 0 since the
+  // current offset (0) will be less than the startScrollOffset.
   SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF());
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(0,
-                                   timeline->CurrentTime(scroll_tree(), false));
+  EXPECT_SCROLL_TIMELINE_BEFORE_START(
+      timeline->CurrentTime(scroll_tree(), false));
 
   SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 19));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(0,
-                                   timeline->CurrentTime(scroll_tree(), false));
+  EXPECT_SCROLL_TIMELINE_BEFORE_START(
+      timeline->CurrentTime(scroll_tree(), false).value());
 
   SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 20));
   EXPECT_SCROLL_TIMELINE_TIME_NEAR(0,
@@ -380,16 +267,13 @@
 TEST_F(ScrollTimelineTest, CurrentTimeHandlesEndScrollOffset) {
   double scroll_size = content_size().height() - container_size().height();
   const double end_scroll_offset = scroll_size - 20;
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(0);  // should be absl::nullopt
-  scroll_offsets.push_back(end_scroll_offset);
+  ScrollTimeline::ScrollOffsets scroll_offsets(0, end_scroll_offset);
   scoped_refptr<ScrollTimeline> timeline = ScrollTimeline::Create(
       scroller_id(), ScrollTimeline::ScrollDown, scroll_offsets);
 
   SetScrollOffset(&property_trees(), scroller_id(),
                   gfx::PointF(0, scroll_size));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(ScrollTimeline::kScrollTimelineDurationMs,
-                                   timeline->CurrentTime(scroll_tree(), false));
+  EXPECT_SCROLL_TIMELINE_AFTER_END(timeline->CurrentTime(scroll_tree(), false));
 
   SetScrollOffset(&property_trees(), scroller_id(),
                   gfx::PointF(0, scroll_size - 20));
@@ -413,9 +297,8 @@
   double scroll_size = content_size().height() - container_size().height();
   double start_scroll_offset = 20;
   double end_scroll_offset = scroll_size - 50;
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(start_scroll_offset);
-  scroll_offsets.push_back(end_scroll_offset);
+  ScrollTimeline::ScrollOffsets scroll_offsets(start_scroll_offset,
+                                               end_scroll_offset);
   scoped_refptr<ScrollTimeline> timeline = ScrollTimeline::Create(
       scroller_id(), ScrollTimeline::ScrollDown, scroll_offsets);
   SetScrollOffset(&property_trees(), scroller_id(),
@@ -427,11 +310,15 @@
 }
 
 TEST_F(ScrollTimelineTest, CurrentTimeHandlesEqualStartAndEndScrollOffset) {
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(20);
-  scroll_offsets.push_back(20);
+  ScrollTimeline::ScrollOffsets scroll_offsets(20, 20);
   scoped_refptr<ScrollTimeline> timeline = ScrollTimeline::Create(
       scroller_id(), ScrollTimeline::ScrollDown, scroll_offsets);
+
+  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 20));
+
+  EXPECT_SCROLL_TIMELINE_TIME_NEAR(ScrollTimeline::kScrollTimelineDurationMs,
+                                   timeline->CurrentTime(scroll_tree(), false));
+
   SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 150));
 
   EXPECT_SCROLL_TIMELINE_TIME_NEAR(ScrollTimeline::kScrollTimelineDurationMs,
@@ -440,19 +327,22 @@
 
 TEST_F(ScrollTimelineTest,
        CurrentTimeHandlesStartOffsetLargerThanEndScrollOffset) {
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(50);
-  scroll_offsets.push_back(10);
+  ScrollTimeline::ScrollOffsets scroll_offsets(50, 10);
   scoped_refptr<ScrollTimeline> timeline = ScrollTimeline::Create(
       scroller_id(), ScrollTimeline::ScrollDown, scroll_offsets);
-  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 40));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(0,
-                                   timeline->CurrentTime(scroll_tree(), false));
+
+  // Timeline direction reversed.
+  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 0));
+  EXPECT_SCROLL_TIMELINE_AFTER_END(timeline->CurrentTime(scroll_tree(), false));
+
+  SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 30));
+  EXPECT_SCROLL_TIMELINE_TIME_NEAR(
+      ScrollTimeline::kScrollTimelineDurationMs / 2,
+      timeline->CurrentTime(scroll_tree(), false));
 
   SetScrollOffset(&property_trees(), scroller_id(), gfx::PointF(0, 150));
-
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(ScrollTimeline::kScrollTimelineDurationMs,
-                                   timeline->CurrentTime(scroll_tree(), false));
+  EXPECT_SCROLL_TIMELINE_BEFORE_START(
+      timeline->CurrentTime(scroll_tree(), false));
 }
 
 TEST_F(ScrollTimelineTest, CurrentTimeHandlesScrollOffsets) {
@@ -460,18 +350,17 @@
   const double scroller_height =
       content_size().height() - container_size().height();
   const double end_scroll_offset = scroller_height - 20;
-  std::vector<double> scroll_offsets;
-  scroll_offsets.push_back(start_scroll_offset);
-  scroll_offsets.push_back(end_scroll_offset);
+  ScrollTimeline::ScrollOffsets scroll_offsets(start_scroll_offset,
+                                               end_scroll_offset);
 
   scoped_refptr<ScrollTimeline> timeline = ScrollTimeline::Create(
       scroller_id(), ScrollTimeline::ScrollDown, scroll_offsets);
 
-  // Before the start_scroll_offset the current time should be 0
+  // Before the start_scroll_offset the current time should be < 0
   SetScrollOffset(&property_trees(), scroller_id(),
                   gfx::PointF(0, start_scroll_offset - 10));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(0,
-                                   timeline->CurrentTime(scroll_tree(), false));
+  EXPECT_SCROLL_TIMELINE_BEFORE_START(
+      timeline->CurrentTime(scroll_tree(), false));
 
   // At the end_scroll_offset the current time should be 100%
   SetScrollOffset(&property_trees(), scroller_id(),
@@ -479,19 +368,16 @@
   EXPECT_SCROLL_TIMELINE_TIME_NEAR(ScrollTimeline::kScrollTimelineDurationMs,
                                    timeline->CurrentTime(scroll_tree(), false));
 
-  // After the end_scroll_offset the current time should be 100%
+  // After the end_scroll_offset the current time should be > 100%
   SetScrollOffset(&property_trees(), scroller_id(),
                   gfx::PointF(0, end_scroll_offset + 10));
-  EXPECT_SCROLL_TIMELINE_TIME_NEAR(ScrollTimeline::kScrollTimelineDurationMs,
-                                   timeline->CurrentTime(scroll_tree(), false));
+  EXPECT_SCROLL_TIMELINE_AFTER_END(timeline->CurrentTime(scroll_tree(), false));
 }
 
 TEST_F(ScrollTimelineTest, Activeness) {
   // ScrollTimeline with zero scroller id is inactive.
-  std::vector<double> scroll_offsets;
   double scroll_size = content_size().height() - container_size().height();
-  scroll_offsets.push_back(0);
-  scroll_offsets.push_back(scroll_size);
+  ScrollTimeline::ScrollOffsets scroll_offsets(0, scroll_size);
   scoped_refptr<ScrollTimeline> inactive_timeline1 = ScrollTimeline::Create(
       absl::nullopt, ScrollTimeline::ScrollDown, scroll_offsets);
   EXPECT_FALSE(
@@ -511,9 +397,9 @@
       inactive_timeline2->IsActive(scroll_tree(), true /*is_active_tree*/));
 
   // ScrollTimeline with empty scroll offsets is inactive.
-  std::vector<double> empty_scroll_offsets;
-  scoped_refptr<ScrollTimeline> inactive_timeline3 = ScrollTimeline::Create(
-      scroller_id(), ScrollTimeline::ScrollDown, empty_scroll_offsets);
+  scoped_refptr<ScrollTimeline> inactive_timeline3 =
+      ScrollTimeline::Create(scroller_id(), ScrollTimeline::ScrollDown,
+                             /* scroll_offsets */ absl::nullopt);
   EXPECT_FALSE(
       inactive_timeline3->IsActive(scroll_tree(), false /*is_active_tree*/));
   EXPECT_FALSE(
diff --git a/cc/animation/worklet_animation_unittest.cc b/cc/animation/worklet_animation_unittest.cc
index 7cfe9e8..823ae30 100644
--- a/cc/animation/worklet_animation_unittest.cc
+++ b/cc/animation/worklet_animation_unittest.cc
@@ -60,7 +60,7 @@
   MockScrollTimeline()
       : ScrollTimeline(ElementId(),
                        ScrollTimeline::ScrollDown,
-                       std::vector<double>(),
+                       /* scroll_offsets */ absl::nullopt,
                        AnimationIdProvider::NextTimelineId()) {}
   MOCK_CONST_METHOD2(CurrentTime,
                      absl::optional<base::TimeTicks>(const ScrollTree&, bool));
diff --git a/cc/paint/skottie_wrapper_impl.cc b/cc/paint/skottie_wrapper_impl.cc
index 70357530..85901b8 100644
--- a/cc/paint/skottie_wrapper_impl.cc
+++ b/cc/paint/skottie_wrapper_impl.cc
@@ -295,6 +295,7 @@
 
   void Seek(float t, FrameDataCallback frame_data_cb) override
       LOCKS_EXCLUDED(lock_) {
+    TRACE_EVENT1("cc", "SkottieWrapperImpl::Seek", "timestamp", t);
     base::AutoLock lock(lock_);
     // There's no need to reset |current_frame_data_cb_| to null when finished.
     // The callback is guaranteed to only be invoked synchronously during calls
@@ -310,6 +311,7 @@
             const SkottieColorMap& color_map,
             const SkottieTextPropertyValueMap& text_map) override
       LOCKS_EXCLUDED(lock_) {
+    TRACE_EVENT1("cc", "SkottieWrapperImpl::Draw", "timestamp", t);
     base::AutoLock lock(lock_);
     current_frame_data_cb_ = std::move(frame_data_cb);
     property_manager_->color_property_manager().SetNewValues(color_map);
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 3ecb4ac..75fbdd9 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -852,6 +852,7 @@
     "//chrome/browser/segmentation_platform:jni_headers",
     "//chrome/browser/tab:jni_headers",
     "//chrome/browser/touch_to_fill/android:jni_headers",
+    "//chrome/browser/ui/android/fast_checkout:jni_headers",
     "//chrome/browser/ui/android/favicon:jni_headers",
     "//chrome/browser/ui/android/logo:jni_headers",
     "//chrome/browser/ui/android/omnibox:jni_headers",
@@ -1357,6 +1358,7 @@
     "javatests/src/org/chromium/chrome/browser/ntp/IncognitoDescriptionViewRenderTest.java",
     "javatests/src/org/chromium/chrome/browser/ntp/TitleUtilTest.java",
     "javatests/src/org/chromium/chrome/browser/tab/WebContentsStateBridgeTest.java",
+    "javatests/src/org/chromium/chrome/browser/tab/state/CouponPersistedTabDataTest.java",
     "javatests/src/org/chromium/chrome/browser/tab/state/FilePersistedTabDataStorageTest.java",
     "javatests/src/org/chromium/chrome/browser/tab/state/PersistedTabDataTest.java",
     "javatests/src/org/chromium/chrome/browser/tab/state/PriceDropMetricsLoggerTest.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 932d985..ad30221 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -86,6 +86,7 @@
   "junit/src/org/chromium/chrome/browser/cryptids/ProbabilisticCryptidRendererUnitTest.java",
   "junit/src/org/chromium/chrome/browser/customtabs/ClientManagerTest.java",
   "junit/src/org/chromium/chrome/browser/customtabs/CloseButtonNavigatorTest.java",
+  "junit/src/org/chromium/chrome/browser/customtabs/CustomTabActivityLifecycleUmaTrackerUnitTest.java",
   "junit/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProviderTest.java",
   "junit/src/org/chromium/chrome/browser/customtabs/CustomTabNavigationBarControllerTest.java",
   "junit/src/org/chromium/chrome/browser/customtabs/CustomTabNightModeStateControllerTest.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java b/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
index 44831e96..5e24942 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
@@ -28,6 +28,8 @@
 import org.chromium.chrome.browser.metrics.VariationsSession;
 import org.chromium.chrome.browser.notifications.chime.ChimeDelegate;
 import org.chromium.chrome.browser.omaha.RequestGenerator;
+import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmark;
+import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksDelegateImpl;
 import org.chromium.chrome.browser.password_manager.GooglePasswordManagerUIProvider;
 import org.chromium.chrome.browser.policy.PolicyAuditor;
 import org.chromium.chrome.browser.rlz.RevenueStats;
@@ -238,6 +240,14 @@
     }
 
     /**
+     * @return An iterator of partner bookmarks.
+     */
+    @Nullable
+    public PartnerBookmark.BookmarkIterator getPartnerBookmarkIterator() {
+        return new PartnerBookmarksDelegateImpl().createIterator();
+    }
+
+    /**
      * @return A new {@link DigitalWellbeingClient} instance.
      */
     public DigitalWellbeingClient createDigitalWellbeingClient() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
index 44cad04..3381564 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
@@ -366,6 +366,10 @@
 
         // Create and fire a launch intent.
         Intent launchIntent = createCustomTabActivityIntent(mActivity, mIntent);
+        Uri extraReferrer = mActivity.getReferrer();
+        if (extraReferrer != null) {
+            launchIntent.putExtra(IntentHandler.EXTRA_ACTIVITY_REFERRER, extraReferrer.toString());
+        }
 
         // Allow disk writes during startActivity() to avoid strict mode violations on some
         // Samsung devices, see https://crbug.com/796548.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivityLifecycleUmaTracker.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivityLifecycleUmaTracker.java
index 1229db6..5bd5d1ad 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivityLifecycleUmaTracker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivityLifecycleUmaTracker.java
@@ -6,14 +6,25 @@
 
 import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.SAVED_INSTANCE_SUPPLIER;
 
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
 import android.os.Bundle;
+import android.os.SystemClock;
+import android.text.TextUtils;
 
+import androidx.annotation.StringDef;
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.base.IntentUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
+import org.chromium.chrome.browser.customtabs.features.TabInteractionRecorder;
 import org.chromium.chrome.browser.dependency_injection.ActivityScope;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
 import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver;
@@ -21,6 +32,9 @@
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.webapps.WebappCustomTabTimeSpentLogger;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 import javax.inject.Inject;
 import javax.inject.Named;
 
@@ -30,10 +44,23 @@
 @ActivityScope
 public class CustomTabActivityLifecycleUmaTracker implements PauseResumeWithNativeObserver,
         NativeInitObserver {
+    /**
+     * Identifier used for last CCT client App. Used as suffix for histogram
+     * "CustomTabs.RetainableSessions.TimeBetweenLaunch".
+     */
+    @StringDef({ClientIdentifierType.DIFFERENT, ClientIdentifierType.MIXED,
+            ClientIdentifierType.REFERRER, ClientIdentifierType.PACKAGE_NAME})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ClientIdentifierType {
+        String DIFFERENT = ".Different";
+        String MIXED = ".Mixed";
+        String REFERRER = ".Referrer";
+        String PACKAGE_NAME = ".PackageName";
+    }
 
     private final BrowserServicesIntentDataProvider mIntentDataProvider;
-    private final CustomTabsConnection mConnection;
     private final Supplier<Bundle> mSavedInstanceStateSupplier;
+    private final Activity mActivity;
 
     private WebappCustomTabTimeSpentLogger mWebappTimeSpentLogger;
     private boolean mIsInitialResume = true;
@@ -88,11 +115,10 @@
 
     @Inject
     public CustomTabActivityLifecycleUmaTracker(ActivityLifecycleDispatcher lifecycleDispatcher,
-            BrowserServicesIntentDataProvider intentDataProvider,
-            CustomTabsConnection customTabsConnection,
+            BrowserServicesIntentDataProvider intentDataProvider, Activity activity,
             @Named(SAVED_INSTANCE_SUPPLIER) Supplier<Bundle> savedInstanceStateSupplier) {
         mIntentDataProvider = intentDataProvider;
-        mConnection = customTabsConnection;
+        mActivity = activity;
         mSavedInstanceStateSupplier = savedInstanceStateSupplier;
 
         lifecycleDispatcher.register(this);
@@ -111,15 +137,26 @@
             String lastUrl =
                     preferences.readString(ChromePreferenceKeys.CUSTOM_TABS_LAST_URL, null);
             String urlToLoad = mIntentDataProvider.getUrlToLoad();
-            if (lastUrl != null && lastUrl.equals(urlToLoad)) {
+            boolean launchWithSameUrl = lastUrl != null && lastUrl.equals(urlToLoad);
+            if (launchWithSameUrl) {
                 RecordUserAction.record("CustomTabsMenuOpenSameUrl");
             } else {
                 preferences.writeString(ChromePreferenceKeys.CUSTOM_TABS_LAST_URL, urlToLoad);
             }
 
+            if (ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_RETAINING_STATE)) {
+                String clientPackage = mIntentDataProvider.getClientPackageName();
+                String referrer = getReferrerUriString(mActivity);
+                int taskId = mActivity.getTaskId();
+
+                recordForRetainableSessions(
+                        clientPackage, referrer, taskId, preferences, launchWithSameUrl);
+                TabInteractionRecorder.resetTabInteractionRecords();
+            }
             recordUserAction();
             recordMetrics();
         }
+
         mIsInitialResume = false;
 
         mWebappTimeSpentLogger = WebappCustomTabTimeSpentLogger.createInstanceAndStartTimer(
@@ -141,4 +178,94 @@
             mWebappTimeSpentLogger.onPause();
         }
     }
+
+    /**
+     * Record shared pref and histogram when an retainable CCT session is launched back to back with
+     * same embedded app and URL, and user action has seen in the previous CCT. The embedded app is
+     * determine through taskId + package name combination. For the package name to use, this
+     * function will bias clientPackage if provided, otherwise fallback to referrer.
+     *
+     * @param clientPackage Package name get from CCT service
+     * @param referrer Referrer of the CCT activity.
+     * @param taskId The task Id of CCT activity.
+     * @param preferences Instance from {@link SharedPreferencesManager#getInstance()}.
+     */
+    @VisibleForTesting
+    static void recordForRetainableSessions(String clientPackage, String referrer, int taskId,
+            SharedPreferencesManager preferences, boolean launchWithSameUrl) {
+        String prevClientPackage =
+                preferences.readString(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLIENT_PACKAGE, null);
+        String prevReferrer =
+                preferences.readString(ChromePreferenceKeys.CUSTOM_TABS_LAST_REFERRER, null);
+        int prevTaskId = preferences.readInt(ChromePreferenceKeys.CUSTOM_TABS_LAST_TASK_ID);
+
+        preferences.writeInt(ChromePreferenceKeys.CUSTOM_TABS_LAST_TASK_ID, taskId);
+        if (TextUtils.isEmpty(clientPackage)) {
+            preferences.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLIENT_PACKAGE);
+            preferences.writeString(ChromePreferenceKeys.CUSTOM_TABS_LAST_REFERRER, referrer);
+        } else {
+            preferences.writeString(
+                    ChromePreferenceKeys.CUSTOM_TABS_LAST_CLIENT_PACKAGE, clientPackage);
+            preferences.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_REFERRER);
+        }
+
+        if (!launchWithSameUrl || prevTaskId != taskId
+                || !preferences.readBoolean(
+                        ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION, false)) {
+            return;
+        }
+
+        boolean hasClientPackage = !TextUtils.isEmpty(clientPackage);
+        String suffix = ClientIdentifierType.DIFFERENT;
+        if (hasClientPackage && TextUtils.equals(clientPackage, prevClientPackage)) {
+            suffix = ClientIdentifierType.PACKAGE_NAME;
+        } else if (!TextUtils.isEmpty(referrer) && TextUtils.equals(referrer, prevReferrer)) {
+            suffix = ClientIdentifierType.REFERRER;
+        } else {
+            String currentPackage =
+                    hasClientPackage ? clientPackage : Uri.parse(referrer).getHost();
+            String prevPackage = !TextUtils.isEmpty(prevClientPackage)
+                    ? prevClientPackage
+                    : Uri.parse(prevReferrer).getHost();
+
+            if (TextUtils.equals(currentPackage, prevPackage)) {
+                suffix = ClientIdentifierType.MIXED;
+            }
+        }
+
+        String histogramPrefix = "CustomTabs.RetainableSessions.TimeBetweenLaunch";
+        long time = SystemClock.uptimeMillis();
+        long lastClosedTime =
+                preferences.readLong(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TIMESTAMP);
+        if (lastClosedTime != 0 && lastClosedTime < time) {
+            RecordHistogram.recordLongTimesHistogram(
+                    histogramPrefix + suffix, time - lastClosedTime);
+        }
+    }
+
+    /**
+     * Get the referrer for the given activity. If the activity is launched through launcher
+     * activity, the referrer is set through {@link IntentHandler#EXTRA_ACTIVITY_REFERRER}; if not,
+     * check {@link Activity#getReferrer()}; if both return empty, fallback to
+     * {@link IntentHandler#getReferrerPolicyFromIntent(Intent)}.
+     */
+    @VisibleForTesting
+    static String getReferrerUriString(Activity activity) {
+        if (activity == null || activity.getIntent() == null) {
+            return "";
+        }
+
+        Intent intent = activity.getIntent();
+        String extraReferrer =
+                IntentUtils.safeGetStringExtra(intent, IntentHandler.EXTRA_ACTIVITY_REFERRER);
+        if (extraReferrer != null) {
+            return extraReferrer;
+        }
+
+        Uri activityReferrer = activity.getReferrer();
+        if (activityReferrer != null) {
+            return activityReferrer.toString();
+        }
+        return IntentHandler.getReferrerUrlIncludingExtraHeaders(intent);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/TabInteractionRecorder.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/TabInteractionRecorder.java
index e240b92..dbcc16a6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/TabInteractionRecorder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/TabInteractionRecorder.java
@@ -87,6 +87,15 @@
         RecordHistogram.recordBooleanHistogram("CustomTabs.HadInteractionOnClose", hadInteraction);
     }
 
+    /**
+     *  Remove all the shared preferences related to tab interactions.
+     */
+    public static void resetTabInteractionRecords() {
+        SharedPreferencesManager pref = SharedPreferencesManager.getInstance();
+        pref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TIMESTAMP);
+        pref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION);
+    }
+
     @NativeMethods
     interface Natives {
         TabInteractionRecorder getFromTab(Tab tab);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/partnerbookmarks/PartnerBookmarksShim.java b/chrome/android/java/src/org/chromium/chrome/browser/partnerbookmarks/PartnerBookmarksShim.java
index d105a447..34b1cd3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/partnerbookmarks/PartnerBookmarksShim.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/partnerbookmarks/PartnerBookmarksShim.java
@@ -7,6 +7,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 
+import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations;
 import org.chromium.components.version_info.VersionInfo;
 
@@ -33,7 +34,7 @@
 
         PartnerBookmarksReader reader =
                 new PartnerBookmarksReader(context, PartnerBrowserCustomizations.getInstance(),
-                        new PartnerBookmarksDelegateImpl()::createIterator);
+                        AppHooks.get()::getPartnerBookmarkIterator);
 
         boolean systemOrPreStable =
                 (context.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 1
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileGroup.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileGroup.java
index d3e4fbe..f4c877f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileGroup.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileGroup.java
@@ -291,7 +291,7 @@
     @Override
     public void onIconMadeAvailable(GURL siteUrl) {
         for (Tile tile : findTilesForUrl(siteUrl)) {
-            mTileRenderer.updateIcon(tile, () -> mObserver.onTileIconChanged(tile));
+            mTileRenderer.updateIcon(tile, mTileSetupDelegate);
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java
index de93a772..dc3a623 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java
@@ -214,9 +214,8 @@
 
         // Note: It is important that the callbacks below don't keep a reference to the tile or
         // modify them as there is no guarantee that the same tile would be used to update the view.
-        if (mImageFetcher != null && tile.getSource() != TileSource.EXPLORE) {
-            updateIcon(tile, setupDelegate.createIconLoadCallback(tile));
-        }
+        updateIcon(tile, setupDelegate);
+        updateContentDescription(tile, tileView);
 
         TileGroup.TileInteractionDelegate delegate = setupDelegate.createInteractionDelegate(tile);
         if (tile.getSource() == TileSource.HOMEPAGE) {
@@ -237,20 +236,52 @@
         return tileView;
     }
 
-    public void updateIcon(final Tile tile, final Runnable iconCallback) {
+    /** @return True, if the tile represents a Search query. */
+    private boolean isSearchTile(Tile tile) {
         TemplateUrlService searchService = TemplateUrlServiceFactory.get();
-        if (searchService != null
-                && searchService.isSearchResultsPageFromDefaultSearchProvider(tile.getData().url)) {
+        return searchService != null
+                && searchService.isSearchResultsPageFromDefaultSearchProvider(tile.getUrl());
+    }
+
+    /**
+     * Given a Tile data and TileView, apply appropriate content description that will be announced
+     * when the view is focused for accessibility.
+     * The objective of the description is to offer audible guidance that helps users differentiate
+     * navigation (open www.site.com) and search (search www.site.com).
+     *
+     * @param tile Tile data that carries information about the destination URL.
+     * @param tileView The view that should receive updated content description.
+     */
+    private void updateContentDescription(Tile tile, SuggestionsTileView tileView) {
+        if (isSearchTile(tile)) {
+            tileView.setContentDescription(mContext.getString(
+                    R.string.accessibility_omnibox_most_visited_tile_search, tile.getTitle()));
+        } else {
+            tileView.setContentDescription(
+                    mContext.getString(R.string.accessibility_omnibox_most_visited_tile_navigate,
+                            tile.getTitle(), tile.getUrl().getHost()));
+        }
+    }
+
+    /**
+     * Update tile decoration.
+     *
+     * @param tile Tile data that carries information about the target site.
+     * @param setupDelegate The delegate used to setup callbacks and listeners for the new view.
+     */
+    public void updateIcon(final Tile tile, TileGroup.TileSetupDelegate setupDelegate) {
+        if (isSearchTile(tile)) {
             // We already have an icon, and could trigger the update instantly.
             // Problem is, the TileView is likely not attached yet and the update would not be
             // properly reflected. Yield.
+            final Runnable iconCallback = setupDelegate.createIconLoadCallback(tile);
             PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
                 setTileIconFromRes(tile, R.drawable.ic_suggestion_magnifier);
                 if (iconCallback != null) iconCallback.run();
             });
-        } else {
-            mImageFetcher.makeLargeIconRequest(tile.getData().url, mMinIconSize,
-                    new LargeIconCallbackImpl(tile, iconCallback));
+        } else if (mImageFetcher != null && tile.getSource() != TileSource.EXPLORE) {
+            mImageFetcher.makeLargeIconRequest(tile.getUrl(), mMinIconSize,
+                    new LargeIconCallbackImpl(tile, setupDelegate.createIconLoadCallback(tile)));
         }
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
index 7cfae9e..d6c7b52 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/bookmarks/BookmarkTest.java
@@ -1283,6 +1283,7 @@
     @SmallTest
     @Features.EnableFeatures({ChromeFeatureList.READ_LATER + ":use_cct/false"})
     @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
+    @DisabledTest(message = "crbug.com/1339976")
     public void testReadingListOpenInRegularTab() throws Exception {
         addReadingListBookmark(TEST_PAGE_TITLE_GOOGLE, mTestUrlA);
         BookmarkPromoHeader.forcePromoStateForTests(SyncPromoState.NO_PROMO);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityRenderTest.java
index 3329a7c..10cdb883 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityRenderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityRenderTest.java
@@ -190,7 +190,7 @@
     public void custom_color_red() throws IOException {
         Context context = InstrumentationRegistry.getContext();
         mIntent = CustomTabsIntentTestUtils.createCustomTabIntent(
-                context, mUrl, builder -> { builder.setToolbarColor(Color.RED); });
+                context, mUrl, true, builder -> { builder.setToolbarColor(Color.RED); });
 
         startActivityAndRenderToolbar("cct_red" + mRunWithHttps);
     }
@@ -201,7 +201,7 @@
     public void custom_color_black() throws IOException {
         Context context = InstrumentationRegistry.getContext();
         mIntent = CustomTabsIntentTestUtils.createCustomTabIntent(
-                context, mUrl, builder -> { builder.setToolbarColor(Color.BLACK); });
+                context, mUrl, true, builder -> { builder.setToolbarColor(Color.BLACK); });
 
         startActivityAndRenderToolbar("cct_black" + mRunWithHttps);
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index 0430fadb..1e98575 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -38,6 +38,7 @@
 import android.os.SystemClock;
 import android.provider.Browser;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.lifecycle.Stage;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -72,6 +73,7 @@
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.task.PostTask;
+import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
@@ -95,6 +97,7 @@
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
 import org.chromium.chrome.browser.browserservices.verification.OriginVerifier;
 import org.chromium.chrome.browser.contextmenu.ContextMenuCoordinator;
+import org.chromium.chrome.browser.customtabs.CustomTabActivityLifecycleUmaTracker.ClientIdentifierType;
 import org.chromium.chrome.browser.customtabs.CustomTabsIntentTestUtils.OnFinishedForTest;
 import org.chromium.chrome.browser.customtabs.content.CustomTabActivityNavigationController.FinishReason;
 import org.chromium.chrome.browser.customtabs.features.toolbar.CustomTabToolbar;
@@ -143,6 +146,7 @@
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.net.test.util.TestWebServer;
 import org.chromium.ui.mojom.WindowOpenDisposition;
+import org.chromium.ui.test.util.BlankUiTestActivity;
 import org.chromium.ui.test.util.UiRestriction;
 import org.chromium.ui.util.ColorUtils;
 import org.chromium.url.GURL;
@@ -163,6 +167,7 @@
                 + "Unit test conversion tracked in crbug.com/1217031")
 public class CustomTabActivityTest {
     private static final int TIMEOUT_PAGE_LOAD_SECONDS = 10;
+    private static final String TEST_PACKAGE = "org.chromium.chrome.tests";
     private static final String TEST_PAGE = "/chrome/test/data/android/google.html";
     private static final String TEST_PAGE_2 = "/chrome/test/data/android/test.html";
     private static final String FRAGMENT_TEST_PAGE = "/chrome/test/data/android/fragment.html";
@@ -226,6 +231,10 @@
     @After
     public void tearDown() {
         TestThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false));
+        SharedPreferencesManager pref = SharedPreferencesManager.getInstance();
+        pref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_TASK_ID);
+        pref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_URL);
+        pref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLIENT_PACKAGE);
 
         SharedPreferencesManager.getInstance().removeKey(
                 ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION);
@@ -663,6 +672,74 @@
 
     @Test
     @SmallTest
+    @Features.EnableFeatures(ChromeFeatureList.CCT_RETAINING_STATE)
+    public void testRecordRetainableSession_WithCctSession() throws Exception {
+        Activity emptyActivity = startBlankUiTestActivity();
+
+        // Write shared pref as it there's a previous CCT launch with sessions.
+        SharedPreferencesManager pref = SharedPreferencesManager.getInstance();
+        pref.writeString(ChromePreferenceKeys.CUSTOM_TABS_LAST_URL, mTestPage);
+        pref.writeString(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLIENT_PACKAGE, TEST_PACKAGE);
+        pref.writeInt(ChromePreferenceKeys.CUSTOM_TABS_LAST_TASK_ID, emptyActivity.getTaskId());
+        pref.writeLong(
+                ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TIMESTAMP, SystemClock.uptimeMillis());
+        pref.writeBoolean(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION, true);
+
+        Intent intent = CustomTabsIntentTestUtils.createCustomTabIntent(
+                InstrumentationRegistry.getContext(), mTestPage, false, builder -> {});
+        CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait();
+        CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent);
+        connection.newSession(token);
+        intent.setData(Uri.parse(mTestPage));
+
+        CustomTabActivity cctActivity = ApplicationTestUtils.waitForActivityWithClass(
+                CustomTabActivity.class, Stage.CREATED, () -> emptyActivity.startActivity(intent));
+        mCustomTabActivityTestRule.setActivity(cctActivity);
+        mCustomTabActivityTestRule.waitForActivityCompletelyLoaded();
+
+        assertLastLaunchedClientAppRecorded(ClientIdentifierType.PACKAGE_NAME, TEST_PACKAGE,
+                mTestPage, cctActivity.getTaskId(), true);
+    }
+
+    @Test
+    @SmallTest
+    @Features.EnableFeatures(ChromeFeatureList.CCT_RETAINING_STATE)
+    public void testRecordRetainableSession_WithoutWarmupAndSession() {
+        Context context = InstrumentationRegistry.getContext();
+        Activity emptyActivity = startBlankUiTestActivity();
+
+        Intent intent =
+                CustomTabsIntentTestUtils
+                        .createCustomTabIntent(context, mTestPage,
+                                /*launchAsNewTask=*/false, builder -> {})
+                        .putExtra(IntentHandler.EXTRA_ACTIVITY_REFERRER, context.getPackageName());
+
+        CustomTabActivity cctActivity = ApplicationTestUtils.waitForActivityWithClass(
+                CustomTabActivity.class, Stage.CREATED, () -> emptyActivity.startActivity(intent));
+        mCustomTabActivityTestRule.setActivity(cctActivity);
+        mCustomTabActivityTestRule.waitForActivityCompletelyLoaded();
+
+        assertLastLaunchedClientAppRecorded(
+                ClientIdentifierType.REFERRER, "", mTestPage, cctActivity.getTaskId(), false);
+        TestThreadUtils.runOnUiThreadBlocking(() -> getActivity().finish());
+        CriteriaHelper.pollUiThread(() -> getActivity().isDestroyed());
+
+        // Write shared prefs as it the last CCT session has saw tab interactions.
+        SharedPreferencesManager pref = SharedPreferencesManager.getInstance();
+        pref.writeBoolean(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION, true);
+
+        // Start another CCT with same intent right away.
+        Intent newIntent = new Intent(intent);
+        cctActivity = ApplicationTestUtils.waitForActivityWithClass(CustomTabActivity.class,
+                Stage.CREATED, () -> emptyActivity.startActivity(newIntent));
+        mCustomTabActivityTestRule.setActivity(cctActivity);
+        mCustomTabActivityTestRule.waitForActivityCompletelyLoaded();
+        assertLastLaunchedClientAppRecorded(
+                ClientIdentifierType.REFERRER, "", mTestPage, getActivity().getTaskId(), true);
+    }
+
+    @Test
+    @SmallTest
     @DisabledTest(message = "https://crbug.com/1308065")
     public void testLoadNewUrlWithSession() throws Exception {
         final Context context = InstrumentationRegistry.getTargetContext();
@@ -1920,6 +1997,13 @@
                 InstrumentationRegistry.getTargetContext(), mTestPage));
     }
 
+    private Activity startBlankUiTestActivity() {
+        Context context = InstrumentationRegistry.getContext();
+        Intent emptyIntent = new Intent(context, BlankUiTestActivity.class);
+        emptyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        return InstrumentationRegistry.getInstrumentation().startActivitySync(emptyIntent);
+    }
+
     private static class ElementContentCriteria implements Runnable {
         private final Tab mTab;
         private final String mJsFunction;
@@ -1963,4 +2047,20 @@
     private SessionDataHolder getSessionDataHolder() {
         return ChromeApplicationImpl.getComponent().resolveSessionDataHolder();
     }
+
+    private void assertLastLaunchedClientAppRecorded(String histogramSuffix, String clientPackage,
+            String url, int taskId, boolean umaRecorded) {
+        SharedPreferencesManager pref = SharedPreferencesManager.getInstance();
+        String histogramName = "CustomTabs.RetainableSessions.TimeBetweenLaunch" + histogramSuffix;
+
+        Assert.assertEquals("Client package name in shared pref is different.", clientPackage,
+                pref.readString(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLIENT_PACKAGE, ""));
+        Assert.assertEquals("Url in shared pref is different.", url,
+                pref.readString(ChromePreferenceKeys.CUSTOM_TABS_LAST_URL, ""));
+        Assert.assertEquals("Task id in shared pref is different.", taskId,
+                pref.readInt(ChromePreferenceKeys.CUSTOM_TABS_LAST_TASK_ID, 0));
+        Assert.assertEquals(String.format("<%s> not recorded correctly.", histogramName),
+                umaRecorded ? 1 : 0,
+                RecordHistogram.getHistogramTotalCountForTesting(histogramName));
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabsIntentTestUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabsIntentTestUtils.java
index ed3a291..248a3e8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabsIntentTestUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabsIntentTestUtils.java
@@ -68,14 +68,14 @@
      * the {@link CustomTabActivity}.
      */
     public static Intent createMinimalCustomTabIntent(Context context, String url) {
-        return createCustomTabIntent(context, url, builder -> {});
+        return createCustomTabIntent(context, url, /*launchAsNewTask=*/true, builder -> {});
     }
 
     /**
      * Creates an Intent that launches a CustomTabActivity, allows some customization.
      */
-    public static Intent createCustomTabIntent(
-            Context context, String url, Callback<CustomTabsIntent.Builder> customizer) {
+    public static Intent createCustomTabIntent(Context context, String url, boolean launchAsNewTask,
+            Callback<CustomTabsIntent.Builder> customizer) {
         CustomTabsIntent.Builder builder =
                 new CustomTabsIntent.Builder(CustomTabsSession.createMockSessionForTesting(
                         new ComponentName(context, ChromeLauncherActivity.class)));
@@ -84,7 +84,7 @@
         Intent intent = customTabsIntent.intent;
         intent.setAction(Intent.ACTION_VIEW);
         intent.setData(Uri.parse(url));
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        if (launchAsNewTask) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         return intent;
     }
 
@@ -112,7 +112,7 @@
      */
     public static Intent createMinimalCustomTabIntentWithTheme(
             Context context, String url, boolean inNightMode) {
-        return createCustomTabIntent(context, url, builder -> {
+        return createCustomTabIntent(context, url, /*launchAsNewTask=*/true, builder -> {
             builder.setColorScheme(inNightMode ? CustomTabsIntent.COLOR_SCHEME_DARK
                                                : CustomTabsIntent.COLOR_SCHEME_LIGHT);
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderTest.java
index 7f98330..b3786bb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderTest.java
@@ -20,8 +20,6 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
-import org.chromium.chrome.browser.omnibox.geo.VisibleNetworks.VisibleCell;
-import org.chromium.chrome.browser.omnibox.geo.VisibleNetworks.VisibleWifi;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -33,9 +31,6 @@
 import org.chromium.components.content_settings.ContentSettingsType;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
-import java.util.Arrays;
-import java.util.HashSet;
-
 /**
  * Tests for GeolocationHeader and GeolocationTracker.
  */
@@ -55,33 +50,12 @@
     private static final double LOCATION_LAT = 20.3;
     private static final double LOCATION_LONG = 155.8;
     private static final float LOCATION_ACCURACY = 20f;
-    private static final VisibleWifi VISIBLE_WIFI1 =
-            VisibleWifi.create("ssid1", "11:11:11:11:11:11", -1, 10L);
-    private static final VisibleWifi VISIBLE_WIFI3 =
-            VisibleWifi.create("ssid3", "11:11:11:11:11:13", -30, 30L);
-    private static final VisibleCell VISIBLE_CELL1 = VisibleCell.builder(VisibleCell.RadioType.CDMA)
-                                                             .setCellId(10)
-                                                             .setLocationAreaCode(11)
-                                                             .setMobileCountryCode(12)
-                                                             .setMobileNetworkCode(13)
-                                                             .setTimestamp(10L)
-                                                             .build();
-    private static final VisibleCell VISIBLE_CELL2 = VisibleCell.builder(VisibleCell.RadioType.GSM)
-                                                             .setCellId(20)
-                                                             .setLocationAreaCode(21)
-                                                             .setMobileCountryCode(22)
-                                                             .setMobileNetworkCode(23)
-                                                             .setTimestamp(20L)
-                                                             .build();
 
     @Before
     public void setUp() throws InterruptedException {
         mActivityTestRule.startMainActivityOnBlankPage();
         mOmniboxTestUtils = new OmniboxTestUtils(mActivityTestRule.getActivity());
-        VisibleNetworks visibleNetworks = VisibleNetworks.create(VISIBLE_WIFI1, VISIBLE_CELL1,
-                new HashSet<>(Arrays.asList(VISIBLE_WIFI3)),
-                new HashSet<>(Arrays.asList(VISIBLE_CELL2)));
-        VisibleNetworksTracker.setVisibleNetworksForTesting(visibleNetworks);
+        GeolocationHeader.setAppPermissionGrantedForTesting(true);
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/CouponPersistedTabDataTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/CouponPersistedTabDataTest.java
new file mode 100644
index 0000000..49f77b6
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/CouponPersistedTabDataTest.java
@@ -0,0 +1,101 @@
+// Copyright 2022 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.tab.state;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.chrome.browser.endpoint_fetcher.EndpointFetcher;
+import org.chromium.chrome.browser.endpoint_fetcher.EndpointFetcherJni;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tab.MockTab;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Test relating to {@link CouponPersistedTabData}
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+@Batch(Batch.UNIT_TESTS)
+public class CouponPersistedTabDataTest {
+    @Rule
+    public JniMocker mMocker = new JniMocker();
+
+    @Rule
+    public TestRule mProcessor = new Features.InstrumentationProcessor();
+
+    @Mock
+    private EndpointFetcher.Natives mEndpointFetcherJniMock;
+
+    @Mock
+    private Profile mProfileMock;
+
+    private static final String SERIALIZE_DESERIALIZE_NAME = "25% Off";
+    private static final String SERIALIZE_DESERIALIZE_CODE = "DISCOUNT25";
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mMocker.mock(EndpointFetcherJni.TEST_HOOKS, mEndpointFetcherJniMock);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> PersistedTabDataConfiguration.setUseTestConfig(true));
+        Profile.setLastUsedProfileForTesting(mProfileMock);
+    }
+
+    @After
+    public void tearDown() {
+        Profile.setLastUsedProfileForTesting(null);
+        PersistedTabDataConfiguration.setUseTestConfig(false);
+    }
+
+    @SmallTest
+    @Test
+    public void testSerializeDeserialize() throws ExecutionException {
+        Tab tab = TestThreadUtils.runOnUiThreadBlocking(() -> new MockTab(1, false));
+        CouponPersistedTabData couponPersistedTabData = new CouponPersistedTabData(tab,
+                new CouponPersistedTabData.Coupon(
+                        SERIALIZE_DESERIALIZE_NAME, SERIALIZE_DESERIALIZE_CODE));
+        ByteBuffer serialized = couponPersistedTabData.getSerializeSupplier().get();
+        CouponPersistedTabData deserialized = new CouponPersistedTabData(tab);
+        Assert.assertTrue(deserialized.deserialize(serialized));
+        Assert.assertEquals(SERIALIZE_DESERIALIZE_NAME, deserialized.getCoupon().couponName);
+        Assert.assertEquals(SERIALIZE_DESERIALIZE_CODE, deserialized.getCoupon().promoCode);
+    }
+
+    @SmallTest
+    @Test
+    public void testSerializeDeserializeNull() throws ExecutionException {
+        Tab tab = TestThreadUtils.runOnUiThreadBlocking(() -> new MockTab(1, false));
+        CouponPersistedTabData deserialized = new CouponPersistedTabData(tab, null);
+        Assert.assertFalse(deserialized.deserialize(null));
+    }
+
+    @SmallTest
+    @Test
+    public void testSerializeDeserializeNoRemainingBytes() throws ExecutionException {
+        Tab tab = TestThreadUtils.runOnUiThreadBlocking(() -> new MockTab(1, false));
+        CouponPersistedTabData couponPersistedTabData = new CouponPersistedTabData(tab, null);
+        ByteBuffer serialized = couponPersistedTabData.getSerializeSupplier().get();
+        CouponPersistedTabData deserialized = new CouponPersistedTabData(tab);
+        Assert.assertFalse(serialized.hasRemaining());
+        Assert.assertFalse(deserialized.deserialize(serialized));
+    }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabActivityLifecycleUmaTrackerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabActivityLifecycleUmaTrackerUnitTest.java
new file mode 100644
index 0000000..771c5157
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabActivityLifecycleUmaTrackerUnitTest.java
@@ -0,0 +1,211 @@
+// Copyright 2022 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.customtabs;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.text.TextUtils;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowSystemClock;
+
+import org.chromium.base.metrics.test.ShadowRecordHistogram;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.customtabs.CustomTabActivityLifecycleUmaTracker.ClientIdentifierType;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit test for {@link CustomTabActivityLifecycleUmaTracker}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(shadows = {ShadowRecordHistogram.class, ShadowSystemClock.class})
+public class CustomTabActivityLifecycleUmaTrackerUnitTest {
+    private static final String PACKAGE_A = "com.example.test.package";
+    private static final String PACKAGE_B = "org.test.mypackage";
+    private static final String REFERRER_A = "android-app://" + PACKAGE_A;
+    private static final String REFERRER_B = "android-app://" + PACKAGE_B;
+    private static final int TASK_ID_123 = 123;
+
+    private SharedPreferencesManager mPref;
+
+    @Before
+    public void setUp() {
+        mPref = SharedPreferencesManager.getInstance();
+        mPref.writeLong(
+                ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TIMESTAMP, SystemClock.uptimeMillis());
+        mPref.writeBoolean(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION, true);
+
+        ShadowSystemClock.advanceBy(1, TimeUnit.MINUTES);
+    }
+
+    @After
+    public void tearDown() {
+        mPref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION);
+        mPref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLIENT_PACKAGE);
+        mPref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TIMESTAMP);
+        mPref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_REFERRER);
+        mPref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_TASK_ID);
+        mPref.removeKey(ChromePreferenceKeys.CUSTOM_TABS_LAST_URL);
+
+        ShadowRecordHistogram.reset();
+        ShadowSystemClock.reset();
+    }
+
+    @Test
+    public void testRecord_SamePackageName() {
+        recordPrefForTesting(PACKAGE_A, null, TASK_ID_123);
+        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
+                PACKAGE_A, REFERRER_A, TASK_ID_123, mPref, /*launchWithSameUri=*/true);
+        assertInteractionRecorded(ClientIdentifierType.PACKAGE_NAME);
+    }
+
+    @Test
+    public void testRecord_DiffPackageName() {
+        recordPrefForTesting(PACKAGE_A, null, TASK_ID_123);
+        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
+                PACKAGE_B, REFERRER_B, TASK_ID_123, mPref, /*launchWithSameUri=*/true);
+        assertInteractionRecorded(ClientIdentifierType.DIFFERENT);
+    }
+
+    @Test
+    public void testRecord_SameReferrer() {
+        recordPrefForTesting(null, REFERRER_A, TASK_ID_123);
+        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
+                null, REFERRER_A, TASK_ID_123, mPref, /*launchWithSameUri=*/true);
+        assertInteractionRecorded(ClientIdentifierType.REFERRER);
+    }
+
+    @Test
+    public void testRecord_DiffReferrer() {
+        recordPrefForTesting(null, REFERRER_A, TASK_ID_123);
+        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
+                null, REFERRER_B, TASK_ID_123, mPref, /*launchWithSameUri=*/true);
+        assertInteractionRecorded(ClientIdentifierType.DIFFERENT);
+    }
+
+    @Test
+    public void testRecord_Mixed_ReferrerThenPackage() {
+        recordPrefForTesting(null, REFERRER_A, TASK_ID_123);
+        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
+                PACKAGE_A, "Random referral", TASK_ID_123, mPref, /*launchWithSameUri=*/true);
+        assertInteractionRecorded(ClientIdentifierType.MIXED);
+    }
+
+    @Test
+    public void testRecord_Mixed_PackageThenReferrer() {
+        recordPrefForTesting(PACKAGE_A, null, TASK_ID_123);
+        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
+                null, REFERRER_A, TASK_ID_123, mPref, /*launchWithSameUri=*/true);
+        assertInteractionRecorded(ClientIdentifierType.MIXED);
+    }
+
+    @Test
+    public void testRecord_DiffUri() {
+        recordPrefForTesting(null, REFERRER_A, TASK_ID_123);
+        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
+                PACKAGE_A, REFERRER_A, TASK_ID_123, mPref, /*launchWithSameUri=*/false);
+        assertNoInteractionRecorded();
+    }
+
+    @Test
+    public void testRecord_DiffTaskId() {
+        recordPrefForTesting(PACKAGE_A, null, TASK_ID_123);
+        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
+                PACKAGE_A, REFERRER_A, 99, mPref, /*launchWithSameUri=*/true);
+        assertNoInteractionRecorded();
+    }
+
+    @Test
+    public void testRecord_NoInteraction() {
+        mPref.writeBoolean(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION, false);
+
+        recordPrefForTesting(PACKAGE_A, null, TASK_ID_123);
+        CustomTabActivityLifecycleUmaTracker.recordForRetainableSessions(
+                PACKAGE_A, REFERRER_A, TASK_ID_123, mPref, /*launchWithSameUri=*/true);
+        assertNoInteractionRecorded();
+    }
+
+    @Test
+    public void testGetReferrer() {
+        String extraActivityReferrer = "android-app://extra.activity.referrer";
+        Uri activityReferrer = Uri.parse("android-app://activity.referrer");
+        String extraReferrerName = "android-app://extra.referrer.name";
+
+        Assert.assertEquals("IntentHandler.EXTRA_ACTIVITY_REFERRER should be used.",
+                extraActivityReferrer,
+                getReferrer(buildMockActivity(
+                        extraActivityReferrer, activityReferrer, extraReferrerName)));
+        Assert.assertEquals("Activity#getReferrer should be used.", activityReferrer.toString(),
+                getReferrer(buildMockActivity(null, activityReferrer, extraReferrerName)));
+        Assert.assertEquals("Intent.EXTRA_REFERRER should be used.", extraReferrerName,
+                getReferrer(buildMockActivity(null, null, extraReferrerName)));
+    }
+
+    @Test
+    public void testGetReferrer_InvalidInputs() {
+        Assert.assertTrue(TextUtils.isEmpty(getReferrer(null)));
+        Assert.assertTrue(TextUtils.isEmpty(getReferrer(mock(Activity.class))));
+
+        Activity activityWithNoReferral = buildMockActivity(null, null, null);
+        Assert.assertTrue(TextUtils.isEmpty(getReferrer(activityWithNoReferral)));
+    }
+
+    private void recordPrefForTesting(String prefPackageName, String prefReferrer, int prefTaskId) {
+        mPref.writeString(ChromePreferenceKeys.CUSTOM_TABS_LAST_CLIENT_PACKAGE, prefPackageName);
+        mPref.writeString(ChromePreferenceKeys.CUSTOM_TABS_LAST_REFERRER, prefReferrer);
+        mPref.writeInt(ChromePreferenceKeys.CUSTOM_TABS_LAST_TASK_ID, prefTaskId);
+    }
+
+    private void assertInteractionRecorded(@ClientIdentifierType String expectedSuffix) {
+        String prefix = "CustomTabs.RetainableSessions.TimeBetweenLaunch";
+        String[] suffixes = {ClientIdentifierType.REFERRER, ClientIdentifierType.PACKAGE_NAME,
+                ClientIdentifierType.MIXED, ClientIdentifierType.DIFFERENT};
+
+        for (String suffix : suffixes) {
+            String histogram = prefix + suffix;
+            Assert.assertEquals("<" + histogram + "> record is different.",
+                    expectedSuffix.equals(suffix) ? 1 : 0,
+                    ShadowRecordHistogram.getHistogramTotalCountForTesting(histogram));
+        }
+    }
+
+    private void assertNoInteractionRecorded() {
+        assertInteractionRecorded("");
+    }
+
+    // For test readability
+    private String getReferrer(Activity activity) {
+        return CustomTabActivityLifecycleUmaTracker.getReferrerUriString(activity);
+    }
+
+    private Activity buildMockActivity(
+            String extraActivityReferrer, Uri activityReferrer, String extraReferrerName) {
+        Activity activity = mock(Activity.class);
+        Intent intent = mock(Intent.class);
+
+        doReturn(intent).when(activity).getIntent();
+        doReturn(activityReferrer).when(activity).getReferrer();
+        doReturn(extraActivityReferrer)
+                .when(intent)
+                .getStringExtra(IntentHandler.EXTRA_ACTIVITY_REFERRER);
+        doReturn(extraReferrerName).when(intent).getStringExtra(Intent.EXTRA_REFERRER_NAME);
+
+        return activity;
+    }
+}
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 006b71e95..e919e4a 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -12287,6 +12287,13 @@
       <message name="IDS_WEBAUTHN_BLUETOOTH_POWER_ON_MANUAL_NEXT" desc="Button text. The user clicks this once they manually turned on Bluetooth, so that Chrome would retry connecting to security keys over Bluetooth.">
         Try again
       </message>
+
+      <if expr="is_macosx">
+        <message name="IDS_WEBAUTHN_BLUETOOTH_PERMISSION" desc="Body text in a dialog that appears when the user has requested some function that depends on Bluetooth, but Chrome doesn't have permission to use Bluetooth. The action on this dialog is a button that will open the macOS System Preferences so that the user can grant permission.">
+          Chrome needs permission to use Bluetooth to connect to your device
+        </message>
+      </if>
+
       <message name="IDS_WEBAUTHN_TRANSPORT_POPUP_LABEL" desc="Menu item text. The user selects this to verify their identity on a web site (i.e. sign in) using a different hardware-based authentication mechanism from what they have selected previously.">
         Choose another option
       </message>
diff --git a/chrome/app/generated_resources_grd/IDS_WEBAUTHN_BLUETOOTH_PERMISSION.png.sha1 b/chrome/app/generated_resources_grd/IDS_WEBAUTHN_BLUETOOTH_PERMISSION.png.sha1
new file mode 100644
index 0000000..fc29fd3
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_WEBAUTHN_BLUETOOTH_PERMISSION.png.sha1
@@ -0,0 +1 @@
+06813bb2bca58aa7df2c91be2659a6e796912e44
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index e9f0512c..b4aa4b5 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -775,8 +775,8 @@
     "memory/memory_ablation_study.h",
     "memory_details.cc",
     "memory_details.h",
-    "metrics/bluetooth_available_utility.cc",
-    "metrics/bluetooth_available_utility.h",
+    "metrics/bluetooth_metrics_provider.cc",
+    "metrics/bluetooth_metrics_provider.h",
     "metrics/chrome_browser_main_extra_parts_metrics.cc",
     "metrics/chrome_browser_main_extra_parts_metrics.h",
     "metrics/chrome_feature_list_creator.cc",
@@ -4412,6 +4412,7 @@
       "//chrome/services/media_gallery_util/public/cpp",
       "//components/accuracy_tips",
       "//components/app_constants",
+      "//components/autofill_assistant/browser/public:proto",
       "//components/commerce/core:public",
       "//components/constrained_window",
       "//components/device_signals/core/browser",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 98eb3c5..34be555 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1154,17 +1154,17 @@
         {"RichAutocompletionAutocompleteTitlesMinChar", "3"},
         {"RichAutocompletionAutocompleteNonPrefixMinChar", "5"}};
 const FeatureEntry::FeatureParam kOmniboxRichAutocompletionAggressive2[] = {
-    {"RichAutocompletionAutocompleteTitles", "true"},
+    {"RichAutocompletionAutocompleteTitlesShortcutProvider", "true"},
     {"RichAutocompletionAutocompleteTitlesMinChar", "2"},
     {"RichAutocompletionAutocompleteShortcutText", "true"},
     {"RichAutocompletionAutocompleteShortcutTextMinChar", "2"}};
 const FeatureEntry::FeatureParam kOmniboxRichAutocompletionAggressive3[] = {
-    {"RichAutocompletionAutocompleteTitles", "true"},
+    {"RichAutocompletionAutocompleteTitlesShortcutProvider", "true"},
     {"RichAutocompletionAutocompleteTitlesMinChar", "3"},
     {"RichAutocompletionAutocompleteShortcutText", "true"},
     {"RichAutocompletionAutocompleteShortcutTextMinChar", "3"}};
 const FeatureEntry::FeatureParam kOmniboxRichAutocompletionAggressive4[] = {
-    {"RichAutocompletionAutocompleteTitles", "true"},
+    {"RichAutocompletionAutocompleteTitlesShortcutProvider", "true"},
     {"RichAutocompletionAutocompleteTitlesMinChar", "4"},
     {"RichAutocompletionAutocompleteShortcutText", "true"},
     {"RichAutocompletionAutocompleteShortcutTextMinChar", "4"}};
@@ -7749,7 +7749,7 @@
     {flag_descriptions::kEnableLensFullscreenSearchFlagId,
      flag_descriptions::kEnableLensFullscreenSearchName,
      flag_descriptions::kEnableLensFullscreenSearchDescription, kOsDesktop,
-     FEATURE_VALUE_TYPE(lens::features::kLensFullscreenSearch)},
+     FEATURE_VALUE_TYPE(lens::features::kLensSearchOptimizations)},
     {flag_descriptions::kEnableLensStandaloneFlagId,
      flag_descriptions::kEnableLensStandaloneName,
      flag_descriptions::kEnableLensStandaloneDescription, kOsDesktop,
@@ -8662,6 +8662,11 @@
      FEATURE_VALUE_TYPE(blink::features::kSystemColorChooser)},
 #endif  // BUILDFLAG(IS_MAC)
 
+    {"ignore-sync-encryption-keys-long-missing",
+     flag_descriptions::kIgnoreSyncEncryptionKeysLongMissingName,
+     flag_descriptions::kIgnoreSyncEncryptionKeysLongMissingDescription, kOsAll,
+     FEATURE_VALUE_TYPE(syncer::kIgnoreSyncEncryptionKeysLongMissing)},
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/app_controller_mac_browsertest.mm b/chrome/browser/app_controller_mac_browsertest.mm
index 3864676..57a5ffb 100644
--- a/chrome/browser/app_controller_mac_browsertest.mm
+++ b/chrome/browser/app_controller_mac_browsertest.mm
@@ -1131,7 +1131,8 @@
 
   // Test that switching tabs updates the handoff URL.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(g_handoff_url, test_url1);
   EXPECT_TRUE(base::EndsWith(g_handoff_title, u"title1.html"));
 
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index b03fa3c..72b98f2 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -2303,6 +2303,7 @@
     "//chromeos/dbus/oobe_config",
     "//chromeos/dbus/permission_broker",
     "//chromeos/dbus/shill",
+    "//chromeos/dbus/smbprovider",
     "//chromeos/dbus/u2f",
     "//chromeos/dbus/util",
     "//chromeos/dbus/virtual_file_provider",
diff --git a/chrome/browser/ash/arc/fileapi/arc_file_system_bridge.cc b/chrome/browser/ash/arc/fileapi/arc_file_system_bridge.cc
index e1eabbd..1e7b7a13 100644
--- a/chrome/browser/ash/arc/fileapi/arc_file_system_bridge.cc
+++ b/chrome/browser/ash/arc/fileapi/arc_file_system_bridge.cc
@@ -32,7 +32,6 @@
 #include "chrome/browser/ash/file_manager/path_util.h"
 #include "chrome/browser/chromeos/fileapi/external_file_url_util.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/virtual_file_provider/virtual_file_provider_client.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -401,12 +400,10 @@
     std::move(callback).Run(absl::nullopt);
     return;
   }
-  chromeos::DBusThreadManager::Get()
-      ->GetVirtualFileProviderClient()
-      ->GenerateVirtualFileId(
-          size, base::BindOnce(&ArcFileSystemBridge::OnGenerateVirtualFileId,
-                               weak_ptr_factory_.GetWeakPtr(), url_decoded,
-                               std::move(callback)));
+  chromeos::VirtualFileProviderClient::Get()->GenerateVirtualFileId(
+      size, base::BindOnce(&ArcFileSystemBridge::OnGenerateVirtualFileId,
+                           weak_ptr_factory_.GetWeakPtr(), url_decoded,
+                           std::move(callback)));
 }
 
 void ArcFileSystemBridge::OnGenerateVirtualFileId(
@@ -431,12 +428,10 @@
     return;
   }
 
-  chromeos::DBusThreadManager::Get()
-      ->GetVirtualFileProviderClient()
-      ->OpenFileById(id.value(),
-                     base::BindOnce(&ArcFileSystemBridge::OnOpenFileById,
-                                    weak_ptr_factory_.GetWeakPtr(), url_decoded,
-                                    std::move(callback), id.value()));
+  chromeos::VirtualFileProviderClient::Get()->OpenFileById(
+      id.value(), base::BindOnce(&ArcFileSystemBridge::OnOpenFileById,
+                                 weak_ptr_factory_.GetWeakPtr(), url_decoded,
+                                 std::move(callback), id.value()));
 }
 
 void ArcFileSystemBridge::OnOpenFileById(const GURL& url_decoded,
diff --git a/chrome/browser/ash/arc/fileapi/arc_file_system_bridge_unittest.cc b/chrome/browser/ash/arc/fileapi/arc_file_system_bridge_unittest.cc
index d884323..12d7242 100644
--- a/chrome/browser/ash/arc/fileapi/arc_file_system_bridge_unittest.cc
+++ b/chrome/browser/ash/arc/fileapi/arc_file_system_bridge_unittest.cc
@@ -27,8 +27,8 @@
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/virtual_file_provider/fake_virtual_file_provider_client.h"
+#include "chromeos/dbus/virtual_file_provider/virtual_file_provider_client.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_utils.h"
 #include "storage/browser/file_system/external_mount_points.h"
@@ -57,8 +57,8 @@
 
   void SetUp() override {
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-    chromeos::DBusThreadManager::Initialize();
     ash::ConciergeClient::InitializeFake(/*fake_cicerone_client=*/nullptr);
+    chromeos::VirtualFileProviderClient::InitializeFake();
     profile_manager_ = std::make_unique<TestingProfileManager>(
         TestingBrowserProcess::GetGlobal());
     ASSERT_TRUE(profile_manager_->SetUp());
@@ -82,8 +82,8 @@
     arc_bridge_service_.file_system()->CloseInstance(&fake_file_system_);
     arc_file_system_bridge_.reset();
     profile_manager_.reset();
+    chromeos::VirtualFileProviderClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::DBusThreadManager::Shutdown();
   }
 
  protected:
@@ -219,7 +219,7 @@
   // Set up fake virtual file provider client.
   constexpr char kId[] = "testfile";
   auto* fake_client = static_cast<chromeos::FakeVirtualFileProviderClient*>(
-      chromeos::DBusThreadManager::Get()->GetVirtualFileProviderClient());
+      chromeos::VirtualFileProviderClient::Get());
   fake_client->set_expected_size(kTestFileSize);
   fake_client->set_result_id(kId);
 
@@ -253,7 +253,7 @@
 
   constexpr char kId[] = "testfile";
   auto* fake_client = static_cast<chromeos::FakeVirtualFileProviderClient*>(
-      chromeos::DBusThreadManager::Get()->GetVirtualFileProviderClient());
+      chromeos::VirtualFileProviderClient::Get());
   fake_client->set_expected_size(kTestFileSize);
   fake_client->set_result_id(kId);
   fake_client->set_result_fd(base::ScopedFD(temp_file.TakePlatformFile()));
diff --git a/chrome/browser/ash/borealis/borealis_context_unittest.cc b/chrome/browser/ash/borealis/borealis_context_unittest.cc
index c77da45..df2bc27 100644
--- a/chrome/browser/ash/borealis/borealis_context_unittest.cc
+++ b/chrome/browser/ash/borealis/borealis_context_unittest.cc
@@ -26,7 +26,6 @@
 #include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/fake_seneschal_client.h"
 #include "chromeos/dbus/chunneld/fake_chunneld_client.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "components/exo/shell_surface_util.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -140,7 +139,7 @@
 
 TEST_F(BorealisContextTest, ChunneldFailure) {
   auto* chunneld_client = static_cast<chromeos::FakeChunneldClient*>(
-      chromeos::DBusThreadManager::Get()->GetChunneldClient());
+      chromeos::ChunneldClient::Get());
 
   chunneld_client->NotifyChunneldStopped();
   histogram_tester_.ExpectUniqueSample(
diff --git a/chrome/browser/ash/crostini/ansible/ansible_management_service_unittest.cc b/chrome/browser/ash/crostini/ansible/ansible_management_service_unittest.cc
index e107120..2147a47 100644
--- a/chrome/browser/ash/crostini/ansible/ansible_management_service_unittest.cc
+++ b/chrome/browser/ash/crostini/ansible/ansible_management_service_unittest.cc
@@ -12,6 +12,7 @@
 #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/test/browser_task_environment.h"
@@ -34,6 +35,7 @@
  public:
   AnsibleManagementServiceTest() {
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -67,6 +69,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_disk_unittest.cc b/chrome/browser/ash/crostini/crostini_disk_unittest.cc
index ae68fb6..227d466 100644
--- a/chrome/browser/ash/crostini/crostini_disk_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_disk_unittest.cc
@@ -17,6 +17,7 @@
 #include "chromeos/ash/components/dbus/concierge/concierge_service.pb.h"
 #include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
@@ -54,6 +55,7 @@
  public:
   CrostiniDiskTestDbus() {
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -73,6 +75,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_export_import_unittest.cc b/chrome/browser/ash/crostini/crostini_export_import_unittest.cc
index e48c4cd..ecaf3ff7 100644
--- a/chrome/browser/ash/crostini/crostini_export_import_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_export_import_unittest.cc
@@ -21,6 +21,7 @@
 #include "chromeos/ash/components/dbus/seneschal/fake_seneschal_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_service.pb.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/test/browser_task_environment.h"
@@ -125,6 +126,7 @@
       : default_container_id_(DefaultContainerId()),
         custom_container_id_(kCrostiniDefaultVmType, "MyVM", "MyContainer") {
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -139,6 +141,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_installer_unittest.cc b/chrome/browser/ash/crostini/crostini_installer_unittest.cc
index 0384a99..006c7ca 100644
--- a/chrome/browser/ash/crostini/crostini_installer_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_installer_unittest.cc
@@ -27,6 +27,7 @@
 #include "chromeos/ash/components/dbus/concierge/concierge_service.pb.h"
 #include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/dlcservice/dlcservice_client.h"
 #include "content/public/test/browser_task_environment.h"
@@ -116,6 +117,7 @@
 
     chromeos::DlcserviceClient::InitializeFake();
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     SetOSRelease();
     waiting_fake_concierge_client_ =
@@ -153,6 +155,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
     chromeos::DlcserviceClient::Shutdown();
 
diff --git a/chrome/browser/ash/crostini/crostini_manager_unittest.cc b/chrome/browser/ash/crostini/crostini_manager_unittest.cc
index 482d068..d4d764e 100644
--- a/chrome/browser/ash/crostini/crostini_manager_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_manager_unittest.cc
@@ -47,6 +47,7 @@
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
 #include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"
 #include "chromeos/ash/components/dbus/userdataauth/fake_cryptohome_misc_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/dlcservice/dlcservice_client.h"
 #include "components/account_id/account_id.h"
@@ -193,6 +194,7 @@
         browser_part_(g_browser_process->platform_part()) {
     chromeos::DBusThreadManager::Initialize();
     ash::AnomalyDetectorClient::InitializeFake();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -211,6 +213,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_package_notification_unittest.cc b/chrome/browser/ash/crostini/crostini_package_notification_unittest.cc
index 7eb16791..13d116e 100644
--- a/chrome/browser/ash/crostini/crostini_package_notification_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_package_notification_unittest.cc
@@ -14,6 +14,7 @@
 #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -35,6 +36,7 @@
 
   void SetUp() override {
     DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -57,6 +59,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_package_service_unittest.cc b/chrome/browser/ash/crostini/crostini_package_service_unittest.cc
index f3a7ea9..bd2650d 100644
--- a/chrome/browser/ash/crostini/crostini_package_service_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_package_service_unittest.cc
@@ -25,6 +25,7 @@
 #include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/fake_seneschal_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/vm_applications/apps.pb.h"
 #include "content/public/test/browser_task_environment.h"
@@ -169,7 +170,7 @@
 
   void SetUp() override {
     DBusThreadManager::Initialize();
-
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -231,6 +232,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_port_forwarder_unittest.cc b/chrome/browser/ash/crostini/crostini_port_forwarder_unittest.cc
index c35f521..b60bf5074 100644
--- a/chrome/browser/ash/crostini/crostini_port_forwarder_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_port_forwarder_unittest.cc
@@ -11,6 +11,7 @@
 #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/permission_broker/fake_permission_broker_client.h"
 #include "content/public/test/browser_task_environment.h"
@@ -46,6 +47,7 @@
 
   void SetUp() override {
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -72,6 +74,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_sshfs_unittest.cc b/chrome/browser/ash/crostini/crostini_sshfs_unittest.cc
index cd6ef2cb8..85644b42 100644
--- a/chrome/browser/ash/crostini/crostini_sshfs_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_sshfs_unittest.cc
@@ -25,6 +25,7 @@
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/cros_disks/cros_disks_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/vm_applications/apps.pb.h"
@@ -66,6 +67,7 @@
  public:
   CrostiniSshfsHelperTest() {
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -105,6 +107,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_upgrade_available_notification_unittest.cc b/chrome/browser/ash/crostini/crostini_upgrade_available_notification_unittest.cc
index c10ccb5..f5473a2 100644
--- a/chrome/browser/ash/crostini/crostini_upgrade_available_notification_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_upgrade_available_notification_unittest.cc
@@ -21,6 +21,7 @@
 #include "chromeos/ash/components/dbus/cicerone/cicerone_service.pb.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -46,6 +47,7 @@
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -63,6 +65,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_util_unittest.cc b/chrome/browser/ash/crostini/crostini_util_unittest.cc
index d46214f..bea1208b 100644
--- a/chrome/browser/ash/crostini/crostini_util_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_util_unittest.cc
@@ -19,6 +19,7 @@
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/dlcservice/dlcservice_client.h"
 #include "content/public/test/browser_task_environment.h"
@@ -47,6 +48,7 @@
             TestingBrowserProcess::GetGlobal())),
         browser_part_(g_browser_process->platform_part()) {
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -58,6 +60,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/dbus/ash_dbus_helper.cc b/chrome/browser/ash/dbus/ash_dbus_helper.cc
index 6ff90bc..03d85c1 100644
--- a/chrome/browser/ash/dbus/ash_dbus_helper.cc
+++ b/chrome/browser/ash/dbus/ash_dbus_helper.cc
@@ -51,6 +51,7 @@
 #include "chromeos/dbus/arc/arc_sensor_service_client.h"
 #include "chromeos/dbus/attestation/attestation_client.h"
 #include "chromeos/dbus/cdm_factory_daemon/cdm_factory_daemon_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/constants/dbus_paths.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/dlcservice/dlcservice_client.h"
@@ -65,9 +66,11 @@
 #include "chromeos/dbus/permission_broker/permission_broker_client.h"
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "chromeos/dbus/resourced/resourced_client.h"
+#include "chromeos/dbus/smbprovider/smb_provider_client.h"
 #include "chromeos/dbus/tpm_manager/tpm_manager_client.h"
 #include "chromeos/dbus/u2f/u2f_client.h"
 #include "chromeos/dbus/update_engine/update_engine_client.h"
+#include "chromeos/dbus/virtual_file_provider/virtual_file_provider_client.h"
 #include "chromeos/dbus/vm_plugin_dispatcher/vm_plugin_dispatcher_client.h"
 #include "device/bluetooth/dbus/bluez_dbus_manager.h"
 #include "device/bluetooth/floss/floss_dbus_manager.h"
@@ -123,6 +126,7 @@
   InitializeDBusClient<AuthPolicyClient>(bus);
   InitializeDBusClient<BiodClient>(bus);  // For device::Fingerprint.
   InitializeDBusClient<chromeos::CdmFactoryDaemonClient>(bus);
+  InitializeDBusClient<chromeos::ChunneldClient>(bus);
   InitializeDBusClient<CiceroneClient>(bus);
   // ConciergeClient depends on CiceroneClient.
   InitializeDBusClient<ConciergeClient>(bus);
@@ -154,6 +158,7 @@
   InitializeDBusClient<chromeos::ResourcedClient>(bus);
   InitializeDBusClient<SeneschalClient>(bus);
   InitializeDBusClient<SessionManagerClient>(bus);
+  InitializeDBusClient<SmbProviderClient>(bus);
   InitializeDBusClient<SpacedClient>(bus);
   InitializeDBusClient<SystemClockClient>(bus);
   InitializeDBusClient<SystemProxyClient>(bus);
@@ -163,6 +168,7 @@
   InitializeDBusClient<chromeos::UpdateEngineClient>(bus);
   InitializeDBusClient<UserDataAuthClient>(bus);
   InitializeDBusClient<UpstartClient>(bus);
+  InitializeDBusClient<chromeos::VirtualFileProviderClient>(bus);
   InitializeDBusClient<chromeos::VmPluginDispatcherClient>(bus);
 
   // Initialize the device settings service so that we'll take actions per
@@ -220,6 +226,7 @@
   }
   // Other D-Bus clients are shut down, also in reverse order of initialization.
   chromeos::VmPluginDispatcherClient::Shutdown();
+  chromeos::VirtualFileProviderClient::Shutdown();
   UpstartClient::Shutdown();
   UserDataAuthClient::Shutdown();
   chromeos::UpdateEngineClient::Shutdown();
@@ -229,6 +236,7 @@
   SystemProxyClient::Shutdown();
   SystemClockClient::Shutdown();
   SpacedClient::Shutdown();
+  SmbProviderClient::Shutdown();
   SessionManagerClient::Shutdown();
   SeneschalClient::Shutdown();
   chromeos::ResourcedClient::Shutdown();
@@ -265,6 +273,7 @@
   CrasAudioClient::Shutdown();
   ConciergeClient::Shutdown();
   CiceroneClient::Shutdown();
+  chromeos::ChunneldClient::Shutdown();
   chromeos::CdmFactoryDaemonClient::Shutdown();
   BiodClient::Shutdown();
   AuthPolicyClient::Shutdown();
diff --git a/chrome/browser/ash/exo/chrome_data_exchange_delegate_unittest.cc b/chrome/browser/ash/exo/chrome_data_exchange_delegate_unittest.cc
index de8cb87..e466de92 100644
--- a/chrome/browser/ash/exo/chrome_data_exchange_delegate_unittest.cc
+++ b/chrome/browser/ash/exo/chrome_data_exchange_delegate_unittest.cc
@@ -28,6 +28,7 @@
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/fake_seneschal_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "components/exo/shell_surface_util.h"
 #include "content/public/common/drop_data.h"
@@ -73,6 +74,7 @@
  public:
   void SetUp() override {
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ConciergeClient::InitializeFake();
     SeneschalClient::InitializeFake();
@@ -120,6 +122,7 @@
     SeneschalClient::Shutdown();
     ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/file_manager/path_util_unittest.cc b/chrome/browser/ash/file_manager/path_util_unittest.cc
index 305863f..49e5453d 100644
--- a/chrome/browser/ash/file_manager/path_util_unittest.cc
+++ b/chrome/browser/ash/file_manager/path_util_unittest.cc
@@ -46,6 +46,7 @@
 #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/cros_disks/cros_disks_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "components/account_id/account_id.h"
@@ -330,6 +331,7 @@
 
   // Initialize DBUS and running container.
   chromeos::DBusThreadManager::Initialize();
+  chromeos::ChunneldClient::InitializeFake();
   ash::CiceroneClient::InitializeFake();
   ash::ConciergeClient::InitializeFake();
   ash::SeneschalClient::InitializeFake();
@@ -506,6 +508,7 @@
   profile_.reset();
   ash::SeneschalClient::Shutdown();
   ash::ConciergeClient::Shutdown();
+  chromeos::ChunneldClient::Shutdown();
   chromeos::DBusThreadManager::Shutdown();
 }
 
diff --git a/chrome/browser/ash/file_manager/trash_unittest_base.cc b/chrome/browser/ash/file_manager/trash_unittest_base.cc
index 09dd8bd..02e6ec9b 100644
--- a/chrome/browser/ash/file_manager/trash_unittest_base.cc
+++ b/chrome/browser/ash/file_manager/trash_unittest_base.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/ash/file_manager/volume_manager_factory.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "storage/browser/file_system/external_mount_points.h"
 #include "storage/browser/test/test_file_system_context.h"
@@ -61,6 +62,7 @@
   ASSERT_TRUE(base::CreateDirectory(downloads_dir_));
 
   chromeos::DBusThreadManager::Initialize();
+  chromeos::ChunneldClient::InitializeFake();
   ash::CiceroneClient::InitializeFake();
   ash::ConciergeClient::InitializeFake();
   ash::SeneschalClient::InitializeFake();
@@ -98,6 +100,7 @@
   ash::SeneschalClient::Shutdown();
   ash::ConciergeClient::Shutdown();
   ash::CiceroneClient::Shutdown();
+  chromeos::ChunneldClient::Shutdown();
   chromeos::DBusThreadManager::Shutdown();
 }
 
diff --git a/chrome/browser/ash/guest_os/dbus_test_helper.cc b/chrome/browser/ash/guest_os/dbus_test_helper.cc
index 71a6a3f..9878161 100644
--- a/chrome/browser/ash/guest_os/dbus_test_helper.cc
+++ b/chrome/browser/ash/guest_os/dbus_test_helper.cc
@@ -7,6 +7,7 @@
 #include "chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h"
 #include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/fake_seneschal_client.h"
+#include "chromeos/dbus/chunneld/fake_chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/dlcservice/fake_dlcservice_client.h"
 
@@ -73,10 +74,20 @@
   return ash::FakeConciergeClient::Get();
 }
 
+FakeChunneldHelper::FakeChunneldHelper(BasicDBusHelper* basic_helper) {
+  DCHECK(basic_helper);
+  chromeos::ChunneldClient::InitializeFake();
+}
+
+FakeChunneldHelper::~FakeChunneldHelper() {
+  chromeos::ChunneldClient::Shutdown();
+}
+
 FakeVmServicesHelper::FakeVmServicesHelper()
     : FakeCiceroneHelper(this),
       FakeSeneschalHelper(this),
       FakeDlcserviceHelper(this),
-      FakeConciergeHelper(this) {}
+      FakeConciergeHelper(this),
+      FakeChunneldHelper(this) {}
 
 }  // namespace guest_os
diff --git a/chrome/browser/ash/guest_os/dbus_test_helper.h b/chrome/browser/ash/guest_os/dbus_test_helper.h
index 693676c8..55e3a84 100644
--- a/chrome/browser/ash/guest_os/dbus_test_helper.h
+++ b/chrome/browser/ash/guest_os/dbus_test_helper.h
@@ -58,6 +58,12 @@
   ash::FakeConciergeClient* FakeConciergeClient();
 };
 
+class FakeChunneldHelper {
+ public:
+  explicit FakeChunneldHelper(BasicDBusHelper* basic_helper);
+  ~FakeChunneldHelper();
+};
+
 // A class for less boilerplate in VM tests. Have your fixture inherit from this
 // class, and the dbus services common to most VMs get initialised with fakes
 // during before your test and torn down correctly after.
@@ -66,7 +72,8 @@
                              public FakeCiceroneHelper,
                              public FakeSeneschalHelper,
                              public FakeDlcserviceHelper,
-                             public FakeConciergeHelper {
+                             public FakeConciergeHelper,
+                             public FakeChunneldHelper {
  public:
   FakeVmServicesHelper();
 };
diff --git a/chrome/browser/ash/guest_os/guest_os_share_path_unittest.cc b/chrome/browser/ash/guest_os/guest_os_share_path_unittest.cc
index ee91a18e..8699039 100644
--- a/chrome/browser/ash/guest_os/guest_os_share_path_unittest.cc
+++ b/chrome/browser/ash/guest_os/guest_os_share_path_unittest.cc
@@ -39,6 +39,7 @@
 #include "chromeos/ash/components/dbus/seneschal/fake_seneschal_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_service.pb.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/dlcservice/dlcservice_client.h"
 #include "chromeos/dbus/vm_plugin_dispatcher/vm_plugin_dispatcher_client.h"
@@ -225,6 +226,7 @@
             TestingBrowserProcess::GetGlobal())),
         browser_part_(g_browser_process->platform_part()) {
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -242,6 +244,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/guest_os/guest_os_stability_monitor.cc b/chrome/browser/ash/guest_os/guest_os_stability_monitor.cc
index c80d5cb..6c39106 100644
--- a/chrome/browser/ash/guest_os/guest_os_stability_monitor.cc
+++ b/chrome/browser/ash/guest_os/guest_os_stability_monitor.cc
@@ -5,7 +5,7 @@
 #include "chrome/browser/ash/guest_os/guest_os_stability_monitor.h"
 
 #include "base/metrics/histogram_functions.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 
 namespace guest_os {
 
@@ -33,8 +33,7 @@
       base::BindOnce(&GuestOsStabilityMonitor::SeneschalStarted,
                      weak_ptr_factory_.GetWeakPtr()));
 
-  auto* chunneld_client =
-      chromeos::DBusThreadManager::Get()->GetChunneldClient();
+  auto* chunneld_client = chromeos::ChunneldClient::Get();
   DCHECK(chunneld_client);
   chunneld_client->WaitForServiceToBeAvailable(
       base::BindOnce(&GuestOsStabilityMonitor::ChunneldStarted,
@@ -70,8 +69,7 @@
 void GuestOsStabilityMonitor::ChunneldStarted(bool is_available) {
   DCHECK(is_available);
 
-  auto* chunneld_client =
-      chromeos::DBusThreadManager::Get()->GetChunneldClient();
+  auto* chunneld_client = chromeos::ChunneldClient::Get();
   DCHECK(chunneld_client);
   chunneld_observer_.Observe(chunneld_client);
 }
diff --git a/chrome/browser/ash/guest_os/guest_os_stability_monitor_unittest.cc b/chrome/browser/ash/guest_os/guest_os_stability_monitor_unittest.cc
index bd57da7..6b64246e 100644
--- a/chrome/browser/ash/guest_os/guest_os_stability_monitor_unittest.cc
+++ b/chrome/browser/ash/guest_os/guest_os_stability_monitor_unittest.cc
@@ -20,6 +20,7 @@
 #include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/fake_seneschal_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/chunneld/fake_chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "content/public/test/browser_task_environment.h"
@@ -31,6 +32,7 @@
  public:
   GuestOsStabilityMonitorTest() : task_env_() {
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -58,6 +60,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
@@ -127,7 +130,7 @@
 
 TEST_F(GuestOsStabilityMonitorTest, ChunneldFailure) {
   auto* chunneld_client = static_cast<chromeos::FakeChunneldClient*>(
-      chromeos::DBusThreadManager::Get()->GetChunneldClient());
+      chromeos::ChunneldClient::Get());
 
   chunneld_client->NotifyChunneldStopped();
   histogram_tester_.ExpectUniqueSample(crostini::kCrostiniStabilityHistogram,
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_files_unittest.cc b/chrome/browser/ash/plugin_vm/plugin_vm_files_unittest.cc
index 9a4a8f0..8115155 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_files_unittest.cc
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_files_unittest.cc
@@ -26,6 +26,7 @@
 #include "chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/vm_applications/apps.pb.h"
 #include "chromeos/dbus/vm_plugin_dispatcher/vm_plugin_dispatcher_client.h"
@@ -102,6 +103,7 @@
       ash::CiceroneClient::InitializeFake();
       ash::ConciergeClient::InitializeFake();
       ash::SeneschalClient::InitializeFake();
+      chromeos::ChunneldClient::InitializeFake();
       chromeos::VmPluginDispatcherClient::InitializeFake();
     }
     ~ScopedDBusThreadManager() {
@@ -109,6 +111,7 @@
       ash::SeneschalClient::Shutdown();
       ash::ConciergeClient::Shutdown();
       ash::CiceroneClient::Shutdown();
+      chromeos::ChunneldClient::Shutdown();
       chromeos::DBusThreadManager::Shutdown();
     }
   } dbus_thread_manager_;
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_manager_impl_unittest.cc b/chrome/browser/ash/plugin_vm/plugin_vm_manager_impl_unittest.cc
index 35bdc889..eb55068d 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_manager_impl_unittest.cc
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_manager_impl_unittest.cc
@@ -28,6 +28,7 @@
 #include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/fake_seneschal_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/dlcservice/fake_dlcservice_client.h"
 #include "chromeos/dbus/vm_plugin_dispatcher/fake_vm_plugin_dispatcher_client.h"
@@ -50,6 +51,7 @@
  public:
   PluginVmManagerImplTest() {
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -95,6 +97,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/policy/active_directory/active_directory_policy_manager.cc b/chrome/browser/ash/policy/active_directory/active_directory_policy_manager.cc
index 9cdb0de9..8ac5a5e 100644
--- a/chrome/browser/ash/policy/active_directory/active_directory_policy_manager.cc
+++ b/chrome/browser/ash/policy/active_directory/active_directory_policy_manager.cc
@@ -29,8 +29,7 @@
 namespace {
 
 // List of policies where variables like ${MACHINE_NAME} should be expanded.
-constexpr const char* kPoliciesToExpand[] = {key::kNativePrinters,
-                                             key::kPrinters};
+constexpr const char* kPoliciesToExpand[] = {key::kPrinters};
 
 // Fetch policy every 90 minutes which matches the Windows default:
 // https://technet.microsoft.com/en-us/library/cc940895.aspx
diff --git a/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc b/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc
index 50e1394..aeca94b 100644
--- a/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc
+++ b/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc
@@ -32,6 +32,7 @@
 #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/dlp/dlp_client.h"
 #include "chromeos/dbus/dlp/dlp_service.pb.h"
@@ -405,6 +406,7 @@
     crostini_features.set_enabled(true);
 
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -440,6 +442,7 @@
     DlpFilesControllerTest::TearDown();
 
     chromeos::DBusThreadManager::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     ash::CiceroneClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::SeneschalClient::Shutdown();
diff --git a/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc b/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc
index 66c563c..a541192 100644
--- a/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc
+++ b/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc
@@ -28,6 +28,7 @@
 #include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
 #include "chromeos/ash/components/dbus/userdataauth/fake_cryptohome_misc_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/vm_plugin_dispatcher/vm_plugin_dispatcher_client.h"
 #include "components/account_id/account_id.h"
@@ -51,7 +52,7 @@
     // This is required before Concierge tests start calling
     // DBusThreadManager::Get() for GuestOsStabilityMonitor.
     chromeos::DBusThreadManager::Initialize();
-
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -109,6 +110,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/smb_client/smb_service.cc b/chrome/browser/ash/smb_client/smb_service.cc
index 2920d361..97d6776 100644
--- a/chrome/browser/ash/smb_client/smb_service.cc
+++ b/chrome/browser/ash/smb_client/smb_service.cc
@@ -426,13 +426,7 @@
 }
 
 SmbProviderClient* SmbService::GetSmbProviderClient() const {
-  // If the DBusThreadManager or the SmbProviderClient aren't available,
-  // there isn't much we can do. This should only happen when running tests.
-  if (!chromeos::DBusThreadManager::IsInitialized() ||
-      !chromeos::DBusThreadManager::Get()) {
-    return nullptr;
-  }
-  return chromeos::DBusThreadManager::Get()->GetSmbProviderClient();
+  return chromeos::SmbProviderClient::Get();
 }
 
 void SmbService::RestoreMounts() {
diff --git a/chrome/browser/ash/smb_client/smb_service_unittest.cc b/chrome/browser/ash/smb_client/smb_service_unittest.cc
index 90ac0f4c4..56eaee2 100644
--- a/chrome/browser/ash/smb_client/smb_service_unittest.cc
+++ b/chrome/browser/ash/smb_client/smb_service_unittest.cc
@@ -44,8 +44,8 @@
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/smbprovider/fake_smb_provider_client.h"
+#include "chromeos/dbus/smbprovider/smb_provider_client.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "content/public/test/browser_task_environment.h"
 #include "storage/browser/file_system/external_mount_points.h"
@@ -171,10 +171,7 @@
     user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>(
         std::move(user_manager_temp));
 
-    // This isn't used, but still needs to exist.
-    chromeos::DBusThreadManager::Initialize();
-    chromeos::DBusThreadManager::GetSetterForTesting()->SetSmbProviderClient(
-        std::make_unique<FakeSmbProviderClient>());
+    SmbProviderClient::InitializeFake();
     ConciergeClient::InitializeFake(/*fake_cicerone_client=*/nullptr);
 
     // Takes ownership of |disk_mount_manager_|, but Shutdown() must be called.
@@ -187,7 +184,7 @@
     profile_manager_.reset();
     disks::DiskMountManager::Shutdown();
     ConciergeClient::Shutdown();
-    chromeos::DBusThreadManager::Shutdown();
+    SmbProviderClient::Shutdown();
   }
 
   void CreateService(TestingProfile* profile) {
diff --git a/chrome/browser/ash/smb_client/smb_share_finder_unittest.cc b/chrome/browser/ash/smb_client/smb_share_finder_unittest.cc
index 0f9c5766..63580e1 100644
--- a/chrome/browser/ash/smb_client/smb_share_finder_unittest.cc
+++ b/chrome/browser/ash/smb_client/smb_share_finder_unittest.cc
@@ -133,6 +133,8 @@
         std::make_unique<InMemoryHostLocator>(should_run_synchronously);
     host_locator_ = host_locator.get();
 
+    // If re-initializing the client, ensure the old client is destroyed first.
+    fake_client_.reset();
     fake_client_ =
         std::make_unique<FakeSmbProviderClient>(should_run_synchronously);
     share_finder_ = std::make_unique<SmbShareFinder>(fake_client_.get());
diff --git a/chrome/browser/ash/usb/cros_usb_detector_unittest.cc b/chrome/browser/ash/usb/cros_usb_detector_unittest.cc
index 1c9ccb4a..aa80397 100644
--- a/chrome/browser/ash/usb/cros_usb_detector_unittest.cc
+++ b/chrome/browser/ash/usb/cros_usb_detector_unittest.cc
@@ -39,6 +39,7 @@
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/cros_disks/cros_disks_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/vm_plugin_dispatcher/fake_vm_plugin_dispatcher_client.h"
@@ -136,6 +137,7 @@
   CrosUsbDetectorTest() {
     // Needed for ChunneldClient via GuestOsStabilityMonitor.
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ConciergeClient::InitializeFake();
     SeneschalClient::InitializeFake();
@@ -160,6 +162,7 @@
     SeneschalClient::Shutdown();
     ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/autofill_assistant/password_change/apc_external_action_delegate.cc b/chrome/browser/autofill_assistant/password_change/apc_external_action_delegate.cc
index 6169010..98ab0c51 100644
--- a/chrome/browser/autofill_assistant/password_change/apc_external_action_delegate.cc
+++ b/chrome/browser/autofill_assistant/password_change/apc_external_action_delegate.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/autofill_assistant/password_change/apc_external_action_delegate.h"
 
+#include <algorithm>
 #include <string>
 #include <vector>
 
@@ -16,6 +17,8 @@
 #include "chrome/browser/ui/autofill_assistant/password_change/password_change_run_controller.h"
 #include "chrome/browser/ui/autofill_assistant/password_change/password_change_run_display.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/autofill_assistant/browser/public/external_action.pb.h"
+#include "components/autofill_assistant/browser/public/external_action_delegate.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
 
@@ -157,7 +160,7 @@
     return;
   }
 
-  DCHECK(choice_index < base_prompt_return_values_.size());
+  CHECK(choice_index < base_prompt_return_values_.size());
   autofill_assistant::password_change::BasePromptSpecification::Result result;
   result.set_selected_tag(base_prompt_return_values_[choice_index]);
 
@@ -247,10 +250,16 @@
         specification) {
   base_prompt_should_send_payload_ = specification.has_output_key();
 
-  // TODO(crbug.com/1331202): Set up DOM checking and matching of DOM conditions
-  // to return values.
-
+  // TODO(crbug.com/1331202): If this causes flickering, separate prompt
+  // argument extraction and showing the base prompt.
   ShowBasePrompt(specification);
+
+  // `this` outlives the script controller, therefore we can pass an unretained
+  // pointer.
+  std::move(start_dom_checks_callback_)
+      .Run(base::BindRepeating(
+          &ApcExternalActionDelegate::OnBasePromptDomUpdateReceived,
+          base::Unretained(this)));
 }
 
 void ApcExternalActionDelegate::HandleGeneratedPasswordPrompt(
@@ -267,6 +276,31 @@
   EndAction(false);
 }
 
+void ApcExternalActionDelegate::OnBasePromptDomUpdateReceived(
+    const autofill_assistant::external::ElementConditionsUpdate& update) {
+  // To ensure predictable behavior, we always choose the condition with the
+  // smallest index if there are multiple fulfilled conditions.
+  size_t minimum_satisfied_index = base_prompt_return_values_.size();
+
+  for (const auto& condition : update.results()) {
+    if (condition.satisfied()) {
+      // Ids must be within the range of the return values vector.
+      if (condition.id() < 0 || static_cast<size_t>(condition.id()) >=
+                                    base_prompt_return_values_.size()) {
+        DLOG(ERROR) << "dom condition id is out of bounds";
+        EndAction(false);
+        return;
+      }
+      minimum_satisfied_index = std::min(minimum_satisfied_index,
+                                         static_cast<size_t>(condition.id()));
+    }
+  }
+
+  if (minimum_satisfied_index < base_prompt_return_values_.size()) {
+    OnBasePromptChoiceSelected(minimum_satisfied_index);
+  }
+}
+
 base::WeakPtr<PasswordChangeRunController>
 ApcExternalActionDelegate::GetWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
diff --git a/chrome/browser/autofill_assistant/password_change/apc_external_action_delegate.h b/chrome/browser/autofill_assistant/password_change/apc_external_action_delegate.h
index e181ec1..50d2a12 100644
--- a/chrome/browser/autofill_assistant/password_change/apc_external_action_delegate.h
+++ b/chrome/browser/autofill_assistant/password_change/apc_external_action_delegate.h
@@ -10,6 +10,7 @@
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/autofill_assistant/password_change/proto/extensions.pb.h"
 #include "chrome/browser/ui/autofill_assistant/password_change/password_change_run_controller.h"
+#include "components/autofill_assistant/browser/public/external_action.pb.h"
 #include "components/autofill_assistant/browser/public/external_action_delegate.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -88,6 +89,9 @@
       const autofill_assistant::password_change::UpdateSidePanelSpecification&
           specification);
 
+  void OnBasePromptDomUpdateReceived(
+      const autofill_assistant::external::ElementConditionsUpdate& update);
+
   // The callback that terminates the current action.
   base::OnceCallback<void(const autofill_assistant::external::Result& result)>
       end_action_callback_;
diff --git a/chrome/browser/autofill_assistant/password_change/apc_external_action_delegate_unittest.cc b/chrome/browser/autofill_assistant/password_change/apc_external_action_delegate_unittest.cc
index 4c02237..216e605 100644
--- a/chrome/browser/autofill_assistant/password_change/apc_external_action_delegate_unittest.cc
+++ b/chrome/browser/autofill_assistant/password_change/apc_external_action_delegate_unittest.cc
@@ -5,6 +5,8 @@
 #include "chrome/browser/autofill_assistant/password_change/apc_external_action_delegate.h"
 
 #include <memory>
+#include <utility>
+#include <vector>
 
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
@@ -14,6 +16,7 @@
 #include "chrome/browser/ui/autofill_assistant/password_change/mock_password_change_run_display.h"
 #include "chrome/browser/ui/autofill_assistant/password_change/password_change_run_display.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/autofill_assistant/browser/public/external_action.pb.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -47,6 +50,17 @@
 
 constexpr char kUrl[] = "https://wwww.example.com";
 
+autofill_assistant::external::ElementConditionsUpdate CreateDomUpdate(
+    const std::vector<std::pair<int, bool>>& updates) {
+  autofill_assistant::external::ElementConditionsUpdate proto;
+  for (const auto& [id, satisfied] : updates) {
+    auto* result = proto.add_results();
+    result->set_id(id);
+    result->set_satisfied(satisfied);
+  }
+  return proto;
+}
+
 // Helper function to create a sample proto for a base prompt.
 autofill_assistant::password_change::BasePromptSpecification
 CreateBasePrompt() {
@@ -213,8 +227,7 @@
   EXPECT_FALSE(result.has_result_info());
 }
 
-TEST_F(ApcExternalActionDelegateTest,
-       ReceiveBasePromptAction_WithoutDomConditions) {
+TEST_F(ApcExternalActionDelegateTest, ReceiveBasePromptAction_FromViewClick) {
   base::MockOnceCallback<void(
       const autofill_assistant::external::Result& result)>
       result_callback;
@@ -228,8 +241,8 @@
   autofill_assistant::external::Result result;
   EXPECT_CALL(result_callback, Run).WillOnce(SaveArg<0>(&result));
 
-  // DOM checks will never be started.
-  EXPECT_CALL(start_dom_checks_callback, Run).Times(0);
+  // DOM checks are always started.
+  EXPECT_CALL(start_dom_checks_callback, Run);
 
   autofill_assistant::password_change::BasePromptSpecification proto =
       CreateBasePrompt();
@@ -266,7 +279,91 @@
 }
 
 TEST_F(ApcExternalActionDelegateTest,
-       ReceiveBasePromptAction_WithoutDomConditionsAndWithoutResultKey) {
+       ReceiveBasePromptAction_FromDomCondition) {
+  base::MockOnceCallback<void(
+      const autofill_assistant::external::Result& result)>
+      result_callback;
+  base::MockOnceCallback<void(DomUpdateCallback)> start_dom_checks_callback;
+
+  std::vector<PasswordChangeRunDisplay::PromptChoice> choices;
+  EXPECT_CALL(*display(), ShowBasePrompt);
+
+  // Save the prompt result.
+  autofill_assistant::external::Result result;
+  EXPECT_CALL(result_callback, Run).WillOnce(SaveArg<0>(&result));
+
+  // DOM checks are started.
+  DomUpdateCallback dom_update_callback;
+  EXPECT_CALL(start_dom_checks_callback, Run)
+      .WillOnce(SaveArg<0>(&dom_update_callback));
+
+  autofill_assistant::password_change::BasePromptSpecification proto =
+      CreateBasePrompt();
+  action_delegate()->OnActionRequested(CreateAction(proto),
+                                       start_dom_checks_callback.Get(),
+                                       result_callback.Get());
+
+  // But no result is sent yet.
+  EXPECT_FALSE(result.has_success());
+
+  // After receiving a valid DOM condition ...
+  EXPECT_CALL(*display(), ClearPrompt);
+  dom_update_callback.Run(CreateDomUpdate({{1, true}, {0, true}}));
+
+  // ... there is now a result.
+  EXPECT_TRUE(result.has_success());
+  EXPECT_TRUE(result.success());
+  EXPECT_TRUE(result.has_result_info() &&
+              result.result_info().has_result_payload());
+  autofill_assistant::password_change::BasePromptSpecification::Result
+      prompt_result;
+  ASSERT_TRUE(
+      prompt_result.ParseFromString(result.result_info().result_payload()));
+
+  EXPECT_TRUE(prompt_result.has_selected_tag());
+  // The result with index 0 is selected even though the arguments of the
+  // DomUpdateCallback were not ordered.
+  EXPECT_EQ(prompt_result.selected_tag(), kPromptTag1);
+}
+
+TEST_F(ApcExternalActionDelegateTest,
+       ReceiveBasePromptAction_FailOnInvalidDomCondition) {
+  base::MockOnceCallback<void(
+      const autofill_assistant::external::Result& result)>
+      result_callback;
+  base::MockOnceCallback<void(DomUpdateCallback)> start_dom_checks_callback;
+
+  std::vector<PasswordChangeRunDisplay::PromptChoice> choices;
+  EXPECT_CALL(*display(), ShowBasePrompt);
+
+  // Save the prompt result.
+  autofill_assistant::external::Result result;
+  EXPECT_CALL(result_callback, Run).WillOnce(SaveArg<0>(&result));
+
+  // DOM checks are started.
+  DomUpdateCallback dom_update_callback;
+  EXPECT_CALL(start_dom_checks_callback, Run)
+      .WillOnce(SaveArg<0>(&dom_update_callback));
+
+  autofill_assistant::password_change::BasePromptSpecification proto =
+      CreateBasePrompt();
+  action_delegate()->OnActionRequested(CreateAction(proto),
+                                       start_dom_checks_callback.Get(),
+                                       result_callback.Get());
+
+  // But no result is sent yet.
+  EXPECT_FALSE(result.has_success());
+
+  // After receiving an invalid DOM condition ...
+  dom_update_callback.Run(CreateDomUpdate({{-1, true}, {0, true}}));
+
+  // ... the action fails.
+  EXPECT_TRUE(result.has_success());
+  EXPECT_FALSE(result.success());
+}
+
+TEST_F(ApcExternalActionDelegateTest,
+       ReceiveBasePromptAction_FromViewClickWithoutResultKey) {
   // TODO: Check that we do not send a result if the result key field is not
   // set.
   base::MockOnceCallback<void(
@@ -282,8 +379,8 @@
   autofill_assistant::external::Result result;
   EXPECT_CALL(result_callback, Run).WillOnce(SaveArg<0>(&result));
 
-  // DOM checks will never be started.
-  EXPECT_CALL(start_dom_checks_callback, Run).Times(0);
+  // DOM checks are started.
+  EXPECT_CALL(start_dom_checks_callback, Run);
 
   autofill_assistant::password_change::BasePromptSpecification proto =
       CreateBasePrompt();
diff --git a/chrome/browser/browser_commands_unittest.cc b/chrome/browser/browser_commands_unittest.cc
index 2e560d7..707bb909 100644
--- a/chrome/browser/browser_commands_unittest.cc
+++ b/chrome/browser/browser_commands_unittest.cc
@@ -219,7 +219,8 @@
 
   // Select the second tab and make it go forward in a new background tab.
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   // TODO(crbug.com/11055): It should not be necessary to commit the load here,
   // but because of this bug, it will assert later if we don't. When the bug is
   // fixed, one of the three commits here related to this bug should be removed
@@ -246,7 +247,8 @@
   // Now do back in a new foreground tab. Don't bother re-checking every sngle
   // thing above, just validate that it's opening properly.
   browser()->tab_strip_model()->ActivateTabAt(
-      2, {TabStripModel::GestureType::kOther});
+      2, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   // TODO(crbug.com/11055): see the comment above about why we need this.
   CommitPendingLoad(&second->GetController());
   chrome::GoBack(browser(), WindowOpenDisposition::NEW_FOREGROUND_TAB);
@@ -291,7 +293,8 @@
 
   // Select the second tab and make it go forward in a new background tab.
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   // TODO(crbug.com/11055): see the comment above about why we need this.
   CommitPendingLoad(
       &browser()->tab_strip_model()->GetActiveWebContents()->GetController());
@@ -404,7 +407,9 @@
   // Add Second tab.
   WebContents* second_tab = tab_strip_model->GetWebContentsAt(1);
 
-  tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(tab_strip_model->IsTabSelected(1));
   zoom::PageZoom::Zoom(second_tab, content::PAGE_ZOOM_OUT);
 
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index 9b7af74..9804d47 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -125,7 +125,6 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/browsing_data_filter_builder.h"
 #include "content/public/browser/host_zoom_map.h"
-#include "content/public/browser/plugin_data_remover.h"
 #include "content/public/browser/prefetch_service_delegate.h"
 #include "content/public/browser/ssl_host_state_delegate.h"
 #include "content/public/browser/storage_partition.h"
diff --git a/chrome/browser/captive_portal/captive_portal_browsertest.cc b/chrome/browser/captive_portal/captive_portal_browsertest.cc
index 4c54dd8d..0a7c205 100644
--- a/chrome/browser/captive_portal/captive_portal_browsertest.cc
+++ b/chrome/browser/captive_portal/captive_portal_browsertest.cc
@@ -1803,7 +1803,9 @@
   // it must happen before the captive_portal::CaptivePortalService sends out
   // its test request, so waiting for PortalObserver to see that request
   // prevents it from confusing the MultiNavigationObservers used later.
-  tab_strip_model->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   browser->OpenURL(content::OpenURLParams(timeout_url, content::Referrer(),
                                           WindowOpenDisposition::CURRENT_TAB,
                                           ui::PAGE_TRANSITION_TYPED, false));
@@ -1820,7 +1822,9 @@
   WaitForJobs(1);
 
   // Simulate logging in.
-  tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   SetSlowSSLLoadTime(tab_reloader, base::Days(1));
   Login(browser, 1 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
         1 /* expected_portal_checks */);
@@ -2423,12 +2427,16 @@
 
   // Activate the timed out tab and navigate it to a timeout again.
   TabStripModel* tab_strip_model = browser()->tab_strip_model();
-  tab_strip_model->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   FastTimeoutBehindCaptivePortal(browser(), false);
 
   // Activate and navigate the captive portal tab.  This should not trigger a
   // reload of the tab with the error.
-  tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   NavigateLoginTab(browser(), 0, 1);
 
   // Simulate logging in.
@@ -2488,7 +2496,9 @@
 
   SlowLoadBehindCaptivePortal(browser(), false);
 
-  tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   Login(browser(), 2 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
         1 /* expected_portal_checks */);
   FailLoadsAfterLogin(browser(), 2);
@@ -2506,7 +2516,9 @@
 
   // Switch back to the hung tab from the login tab, and abort the navigation.
   TabStripModel* tab_strip_model = browser()->tab_strip_model();
-  tab_strip_model->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   chrome::Stop(browser());
   navigation_observer.WaitForNavigations(1);
 
@@ -2516,7 +2528,9 @@
   EXPECT_EQ(captive_portal::CaptivePortalTabReloader::STATE_NONE,
             GetStateOfTabReloaderAt(browser(), 0));
 
-  tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   Login(browser(), 0 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
         1 /* expected_portal_checks */);
 }
@@ -2531,14 +2545,18 @@
 
   // Navigate the error tab to a non-error page.
   TabStripModel* tab_strip_model = browser()->tab_strip_model();
-  tab_strip_model->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_TRUE(ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL("/title2.html")));
   EXPECT_EQ(captive_portal::CaptivePortalTabReloader::STATE_NONE,
             GetStateOfTabReloaderAt(browser(), 0));
 
   // Simulate logging in.
-  tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   Login(browser(), 0 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
         1 /* expected_portal_checks */);
 }
@@ -2600,7 +2618,9 @@
 
   // Activate the error page tab again and go back.
   TabStripModel* tab_strip_model = browser()->tab_strip_model();
-  tab_strip_model->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
   navigation_observer.WaitForNavigations(1);
 
diff --git a/chrome/browser/chromeos/extensions/echo_private/echo_private_apitest.cc b/chrome/browser/chromeos/extensions/echo_private/echo_private_apitest.cc
index 121e3194..7b13fdf 100644
--- a/chrome/browser/chromeos/extensions/echo_private/echo_private_apitest.cc
+++ b/chrome/browser/chromeos/extensions/echo_private/echo_private_apitest.cc
@@ -104,7 +104,8 @@
     EXPECT_TRUE(
         AddTabAtIndex(0, GURL("about:blank"), ui::PAGE_TRANSITION_LINK));
     browser()->tab_strip_model()->ActivateTabAt(
-        0, {TabStripModel::GestureType::kOther});
+        0, TabStripUserGestureDetails(
+               TabStripUserGestureDetails::GestureType::kOther));
     return extensions::ExtensionTabUtil::GetTabId(
         browser()->tab_strip_model()->GetActiveWebContents());
   }
diff --git a/chrome/browser/devtools/devtools_window.cc b/chrome/browser/devtools/devtools_window.cc
index 497eab43..0b23420 100644
--- a/chrome/browser/devtools/devtools_window.cc
+++ b/chrome/browser/devtools/devtools_window.cc
@@ -36,6 +36,7 @@
 #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/webui/devtools_ui.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
@@ -909,8 +910,10 @@
     main_web_contents_->SetDelegate(this);
 
     TabStripModel* tab_strip_model = inspected_browser->tab_strip_model();
-    tab_strip_model->ActivateTabAt(inspected_tab_index,
-                                   {TabStripModel::GestureType::kOther});
+    tab_strip_model->ActivateTabAt(
+        inspected_tab_index,
+        TabStripUserGestureDetails(
+            TabStripUserGestureDetails::GestureType::kOther));
 
     inspected_window->UpdateDevTools();
     main_web_contents_->SetInitialFocus();
diff --git a/chrome/browser/download/download_browsertest.cc b/chrome/browser/download/download_browsertest.cc
index d8e3e81..52b2760 100644
--- a/chrome/browser/download/download_browsertest.cc
+++ b/chrome/browser/download/download_browsertest.cc
@@ -5585,7 +5585,8 @@
 
   // Go to the first tab.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(2, browser()->tab_strip_model()->count());
 
   // The shelf should now be closed.
diff --git a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc
index 68abba2..969f24f 100644
--- a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc
+++ b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc
@@ -322,25 +322,20 @@
                                frame->GetLastCommittedURL(), response_headers,
                                extension_misc::kCryptotokenDeprecationTrialName,
                                base::Time::Now()));
-  const bool u2f_api_enterprise_policy_enabled =
-      Profile::FromBrowserContext(browser_context())
-          ->GetPrefs()
-          ->GetBoolean(extensions::pref_names::kU2fSecurityKeyApiEnabled);
 
   DCHECK(
       base::FeatureList::IsEnabled(extensions_features::kU2FSecurityKeyAPI) ||
-      u2f_api_enterprise_policy_enabled || u2f_api_origin_trial_enabled);
+      u2f_api_origin_trial_enabled);
 
   // Don't show a permission prompt if its feature flag is disabled, or if the
   // site enrolled in the deprecation trial (since they're obviously aware of
-  // the deprecation), or if the enterprise policy to override U2F
-  // deprecation-related changes has been enabled.
+  // the deprecation).
   //
   // Also don't show the prompt in "non-regular" ChromeOS profiles, which
   // includes CrOS SAML sign-in context that doesn't support permission prompts
   // (crbug.com/1257293).
   if (!base::FeatureList::IsEnabled(device::kU2fPermissionPrompt) ||
-      u2f_api_enterprise_policy_enabled || u2f_api_origin_trial_enabled) {
+      u2f_api_origin_trial_enabled) {
     return RespondNow(OneArgument(base::Value(true)));
   }
 
diff --git a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
index 6a6c5a37..96ced8a 100644
--- a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
@@ -394,23 +394,4 @@
   EXPECT_TRUE(result);
 }
 
-TEST_F(CryptoTokenPermissionTest, EnterprisePolicyOverridesRequestPrompt) {
-  // Setting the deprecation override policy should cause the prompt to be
-  // suppressed. This should be true even when the API has been
-  // default-disabled, because the policy overrides that too.
-  for (bool api_enabled : {false, true}) {
-    SCOPED_TRACE(api_enabled);
-    base::test::ScopedFeatureList feature_list;
-    feature_list.InitWithFeatureState(extensions_features::kU2FSecurityKeyAPI,
-                                      api_enabled);
-    browser()->profile()->GetPrefs()->Set(
-        extensions::pref_names::kU2fSecurityKeyApiEnabled, base::Value(true));
-    bool result = false;
-    ASSERT_TRUE(CanMakeU2fApiRequest(
-        "https://test.com", permissions::PermissionRequestManager::NONE,
-        &result));
-    EXPECT_TRUE(result);
-  }
-}
-
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc
index 148f747..003d8a4 100644
--- a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc
+++ b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc
@@ -378,26 +378,6 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest, ConnectWithEnterprisePolicy) {
-  // Connection succeeds regardless of feature flag state with the enterprise
-  // policy overriding deprecation changes.
-  browser()->profile()->GetPrefs()->Set(
-      extensions::pref_names::kU2fSecurityKeyApiEnabled, base::Value(true));
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(
-      browser(), https_server_.GetURL(kNonOriginTrialDomain, "/empty.html")));
-  ExpectConnectSuccess();
-}
-
-IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest,
-                       SignWithEnterprisePolicyDoesNotShowPrompt) {
-  browser()->profile()->GetPrefs()->Set(
-      extensions::pref_names::kU2fSecurityKeyApiEnabled, base::Value(true));
-  GURL url = GURL(https_server_.GetURL(kNonOriginTrialDomain, "/empty.html"));
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
-  std::string app_id = url::Origin::Create(url).Serialize();
-  ExpectSignSuccess(app_id, PromptExpectation::kNoPrompt);
-}
-
 IN_PROC_BROWSER_TEST_P(CryptotokenBrowserTest, InsecureOriginCannotConnect) {
   // Connections from insecure origins always fail.
   ASSERT_TRUE(ui_test_utils::NavigateToURL(
diff --git a/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc b/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
index 0b7ae5b3..3890138 100644
--- a/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
+++ b/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
@@ -548,7 +548,8 @@
 
   // Go back to first tab, changed title should reappear.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ("Showing icon 2",
             GetBrowserActionsBar()->GetTooltip(extension->id()));
 
diff --git a/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc b/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
index 8e29c13e..3a43205 100644
--- a/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
+++ b/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
@@ -432,7 +432,8 @@
   ExtensionHostTestHelper host_helper(profile());
   // Change active tabs, the extension popup should close.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   host_helper.WaitForHostDestroyed();
 
   EXPECT_FALSE(ExtensionActionTestHelper::Create(browser())->HasPopup());
diff --git a/chrome/browser/extensions/api/extension_action/page_action_apitest.cc b/chrome/browser/extensions/api/extension_action/page_action_apitest.cc
index 7c8ef6f..c223d12 100644
--- a/chrome/browser/extensions/api/extension_action/page_action_apitest.cc
+++ b/chrome/browser/extensions/api/extension_action/page_action_apitest.cc
@@ -222,7 +222,8 @@
       browser(), embedded_test_server()->GetURL("/simple.html")));
   chrome::NewTab(browser());
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   // Give the extension time to show the page action on the tab.
   WaitForPageActionVisibilityChangeTo(1);
diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index 24b84b76..ff63a45 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -61,6 +61,7 @@
 #include "chrome/browser/ui/tabs/tab_group.h"
 #include "chrome/browser/ui/tabs/tab_group_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/tabs/tab_utils.h"
 #include "chrome/browser/ui/window_sizer/window_sizer.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
@@ -789,7 +790,10 @@
   if (!contents && urls.empty() && window_type == Browser::TYPE_NORMAL) {
     chrome::NewTab(new_window);
   }
-  chrome::SelectNumberedTab(new_window, 0, {TabStripModel::GestureType::kNone});
+  chrome::SelectNumberedTab(
+      new_window, 0,
+      TabStripUserGestureDetails(
+          TabStripUserGestureDetails::GestureType::kNone));
 
   if (focused) {
     new_window->window()->Show();
diff --git a/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc b/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc
index 7154cba7..c5c8395 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc
@@ -1117,8 +1117,9 @@
   ASSERT_EQ(2, tab_strip_model->count());
 
   // Activate first tab.
-  tab_strip_model->ActivateTabAt(tab1_index,
-                                 {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      tab1_index, TabStripUserGestureDetails(
+                      TabStripUserGestureDetails::GestureType::kOther));
 
   // Go back without tab_id. But first tab should be navigated since it's
   // activated.
@@ -1149,8 +1150,9 @@
               controller.GetLastCommittedEntry()->GetTransitionType());
 
   // Activate second tab.
-  tab_strip_model->ActivateTabAt(tab2_index,
-                                 {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      tab2_index, TabStripUserGestureDetails(
+                      TabStripUserGestureDetails::GestureType::kOther));
 
   auto goback_function2 = base::MakeRefCounted<TabsGoBackFunction>();
   goback_function2->set_extension(extension_with_tabs_permission.get());
diff --git a/chrome/browser/extensions/api/tabs/tabs_test.cc b/chrome/browser/extensions/api/tabs/tabs_test.cc
index c2204c8..29556e2 100644
--- a/chrome/browser/extensions/api/tabs/tabs_test.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_test.cc
@@ -2032,11 +2032,13 @@
   ASSERT_TRUE(navigation_manager.WaitForRequestStart());
 
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(first_web_contents,
             browser()->tab_strip_model()->GetActiveWebContents());
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(second_web_contents,
             browser()->tab_strip_model()->GetActiveWebContents());
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 7db7892..0370cc0 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3675,6 +3675,11 @@
     "expiry_milestone": -1
   },
   {
+    "name": "ignore-sync-encryption-keys-long-missing",
+    "owners": ["victorvianna", "chrome-sync-dev@google.com"],
+    "expiry_milestone": 116
+  },
+  {
     "name": "improve-reader-mode-prompt",
     "owners": ["lazzzis@google.com", "aishwaryarj@google.com",
                "//chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/OWNERS"],
@@ -5414,7 +5419,7 @@
   {
     "name": "scroll-unification",
     "owners": [ "skobes@chromium.org", "input-dev@chromium.org" ],
-    "expiry_milestone": 105
+    "expiry_milestone": 110
   },
   {
     "name": "scrollable-tabstrip",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index e3ba6fc..fc82837 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1547,6 +1547,14 @@
     "Overrides the built-in software rendering list and enables "
     "GPU-acceleration on unsupported system configurations.";
 
+const char kIgnoreSyncEncryptionKeysLongMissingName[] =
+    "Ignore Chrome Sync encryption keys long missing";
+const char kIgnoreSyncEncryptionKeysLongMissingDescription[] =
+    "Drops pending encrypted updates if their key has been missing for a "
+    "(configurable) number of consecutive GetUpdates. Restarting the browser "
+    "resets the counter. The threshold is configurable via the "
+    "MinGuResponsesToIgnoreKey feature parameter.";
+
 const char kImprovedDesksKeyboardShortcutsName[] =
     "Enable improved desks keyboard shortcuts";
 const char kImprovedDesksKeyboardShortcutsDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 042bdb9..952a2c07 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -866,6 +866,9 @@
 extern const char kIgnoreGpuBlocklistName[];
 extern const char kIgnoreGpuBlocklistDescription[];
 
+extern const char kIgnoreSyncEncryptionKeysLongMissingName[];
+extern const char kIgnoreSyncEncryptionKeysLongMissingDescription[];
+
 extern const char kImprovedDesksKeyboardShortcutsName[];
 extern const char kImprovedDesksKeyboardShortcutsDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 2c36c678..8130c4f 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -853,7 +853,7 @@
     base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kShowExtendedPreloadingSetting{
-    "ShowExtendedPreloadingSetting", base::FEATURE_DISABLED_BY_DEFAULT};
+    "ShowExtendedPreloadingSetting", base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kStartSurfaceAndroid{"StartSurfaceAndroid",
                                          base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/browser/lacros/web_contents_can_go_back_observer_browsertest.cc b/chrome/browser/lacros/web_contents_can_go_back_observer_browsertest.cc
index d98d51a..7653086 100644
--- a/chrome/browser/lacros/web_contents_can_go_back_observer_browsertest.cc
+++ b/chrome/browser/lacros/web_contents_can_go_back_observer_browsertest.cc
@@ -165,7 +165,8 @@
   // Switch to a different tab, and verify whether the `can go back` property
   // updates accordingly.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
 
   EXPECT_TRUE(chrome::CanGoBack(browser()));
diff --git a/chrome/browser/lookalikes/lookalike_url_navigation_throttle.cc b/chrome/browser/lookalikes/lookalike_url_navigation_throttle.cc
index b27e0625..dda9d41 100644
--- a/chrome/browser/lookalikes/lookalike_url_navigation_throttle.cc
+++ b/chrome/browser/lookalikes/lookalike_url_navigation_throttle.cc
@@ -476,12 +476,6 @@
     return false;
   }
 
-  // If the URL is in the component allowlist, don't show any warning.
-  if (reputation::IsUrlAllowlistedBySafetyTipsComponent(
-          proto, url.GetWithEmptyPath())) {
-    return false;
-  }
-
   // GetDomainInfo() is expensive, so do possible early-abort checks first.
   base::TimeTicks get_domain_info_start = base::TimeTicks::Now();
   const DomainInfo navigated_domain = GetDomainInfo(url);
@@ -538,13 +532,24 @@
     GURL::Replacements replace_host;
     replace_host.SetHostStr(suggested_domain);
     *suggested_url = url.ReplaceComponents(replace_host).GetWithEmptyPath();
-    return true;
+
+    // Only flag the URL if its not allowed to spoof the suggested URL.
+    if (!reputation::IsUrlAllowlistedBySafetyTipsComponent(
+            proto, url.GetWithEmptyPath(), *suggested_url)) {
+      return true;
+    }
   }
 
   if (ShouldBlockBySpoofCheckResult(navigated_domain)) {
     *match_type = LookalikeUrlMatchType::kFailedSpoofChecks;
     *suggested_url = GURL();
-    return true;
+
+    // Only flag the URL if its not allowed to spoof itself (which is how we
+    // indicate spoof-check-specific allowlisting).
+    if (!reputation::IsUrlAllowlistedBySafetyTipsComponent(
+            proto, url.GetWithEmptyPath(), url.GetWithEmptyPath())) {
+      return true;
+    }
   }
 
   return false;
diff --git a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
index ed5b5c2..9fba0f4 100644
--- a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
+++ b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
@@ -197,6 +197,30 @@
   EXPECT_EQ(nullptr, GetCurrentInterstitial(web_contents));
 }
 
+// Add an allowlist with entries that aren't allowlisted for all domains.
+void ConfigureAllowlistWithScopes() {
+  auto config_proto = reputation::GetOrCreateSafetyTipsConfig();
+  config_proto->clear_allowed_pattern();
+  config_proto->clear_canonical_pattern();
+  config_proto->clear_cohort();
+
+  // Note: allowed_pattern must be sorted, so "Allowed*" comes before "May*".
+
+  // may-spoof-anyone.com has no cohort.
+  auto* patternWildcard = config_proto->add_allowed_pattern();
+  patternWildcard->set_pattern("may-spoof-anyone.com/");
+
+  // may-spoof-google.com is only allowed to spoof google.com.
+  config_proto->add_canonical_pattern()->set_pattern("google.com/");
+  auto* pattern = config_proto->add_allowed_pattern();
+  pattern->set_pattern("may-spoof-google.com/");
+  auto* cohort = config_proto->add_cohort();
+  cohort->add_canonical_index(0);  // google.com
+  pattern->add_cohort_index(0);
+
+  reputation::SetSafetyTipsRemoteConfigProto(std::move(config_proto));
+}
+
 namespace test {
 #include "components/url_formatter/spoof_checks/top_domains/browsertest_domains-trie-inc.cc"
 }
@@ -545,6 +569,36 @@
   CheckUkm({kNavigatedUrl}, "TriggeredByInitialUrl", false);
 }
 
+// Embedding a top domain would normally show an interstitial, but shouldn't
+// here because it's narrowly allowlisted.
+IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest,
+                       TargetEmbedding_ScopedAllowlistMatch) {
+  ConfigureAllowlistWithScopes();
+  const GURL kNavigatedUrl = GetURL("google.com.may-spoof-google.com");
+  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
+
+  TestInterstitialNotShown(browser(), kNavigatedUrl);
+  CheckNoUkm();
+}
+
+// Same as TargetEmbedding_ScopedAllowlistMatch, but the attacker-controlled
+// domain is spoofing an unauthorized victim. This should show a warning.
+IN_PROC_BROWSER_TEST_P(LookalikeUrlNavigationThrottleBrowserTest,
+                       TargetEmbedding_ScopedAllowlistMatchWrongDomain) {
+  ConfigureAllowlistWithScopes();
+  const GURL kNavigatedUrl = GetURL("blogspot.com.may-spoof-google.com");
+  const GURL kExpectedSuggestedUrl = GetURLWithoutPath("blogspot.com");
+  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
+
+  base::HistogramTester histograms;
+  TestMetricsRecordedAndInterstitialShown(
+      browser(), histograms, kNavigatedUrl, kExpectedSuggestedUrl,
+      NavigationSuggestionEvent::kMatchTargetEmbedding);
+  CheckUkm({kNavigatedUrl}, "MatchType",
+           LookalikeUrlMatchType::kTargetEmbedding);
+  CheckUkm({kNavigatedUrl}, "TriggeredByInitialUrl", false);
+}
+
 // Same as TargetEmbedding_TopDomain_Match, but has a redirect where the first
 // and last URLs are both target embedding matches. Should only record
 // metrics for the first URL. Regression test for crbug.com/1136296.
diff --git a/chrome/browser/mac/bluetooth_utility.h b/chrome/browser/mac/bluetooth_utility.h
index 8f4f559..f057dec 100644
--- a/chrome/browser/mac/bluetooth_utility.h
+++ b/chrome/browser/mac/bluetooth_utility.h
@@ -18,7 +18,7 @@
   // there is no further indication of whether Low Energy is supported.
   BLUETOOTH_AVAILABLE_LE_UNKNOWN = 4,
   BLUETOOTH_NOT_SUPPORTED = 5,
-  BLUETOOTH_AVAILABILITY_COUNT,
+  kMaxValue = BLUETOOTH_NOT_SUPPORTED
 };
 
 // Returns the bluetooth availability of the system's hardware.
diff --git a/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc b/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
index 48e753b..9cebac0 100644
--- a/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
@@ -646,7 +646,8 @@
   int target_index =
       browser()->tab_strip_model()->GetIndexOfWebContents(target_tab);
   browser()->tab_strip_model()->ActivateTabAt(
-      target_index, {TabStripModel::GestureType::kOther});
+      target_index, TabStripUserGestureDetails(
+                        TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_EQ(target_tab, browser()->tab_strip_model()->GetActiveWebContents());
 
   // We navigate to a FileURL so that the origin will change, which should
@@ -688,7 +689,8 @@
   int target_index =
       browser()->tab_strip_model()->GetIndexOfWebContents(target_tab);
   browser()->tab_strip_model()->ActivateTabAt(
-      target_index, {TabStripModel::GestureType::kOther});
+      target_index, TabStripUserGestureDetails(
+                        TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_EQ(target_tab, browser()->tab_strip_model()->GetActiveWebContents());
 
   // We navigate using the test server so that the origin doesn't change.
diff --git a/chrome/browser/metrics/bluetooth_available_utility.cc b/chrome/browser/metrics/bluetooth_available_utility.cc
deleted file mode 100644
index 2163c1d..0000000
--- a/chrome/browser/metrics/bluetooth_available_utility.cc
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/metrics/bluetooth_available_utility.h"
-
-#include "base/bind.h"
-#include "base/metrics/histogram_macros.h"
-#include "build/build_config.h"
-#include "chrome/browser/mac/bluetooth_utility.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
-#include "device/bluetooth/bluetooth_adapter.h"
-#include "device/bluetooth/bluetooth_adapter_factory.h"
-
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
-#include "device/bluetooth/dbus/bluez_dbus_manager.h"
-#include "device/bluetooth/floss/floss_dbus_manager.h"
-#include "device/bluetooth/floss/floss_features.h"
-#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
-
-namespace bluetooth_utility {
-
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-enum class BluetoothStackName { kBlueZ = 0, kFloss = 1, kMaxValue = kFloss };
-
-void ReportAvailability(BluetoothAvailability availability) {
-  UMA_HISTOGRAM_ENUMERATION("Bluetooth.Availability.v2", availability,
-                            BLUETOOTH_AVAILABILITY_COUNT);
-}
-
-void ReportStackName(BluetoothStackName name) {
-  UMA_HISTOGRAM_ENUMERATION("Bluetooth.StackName", name);
-}
-
-void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter) {
-  if (!adapter->IsPresent()) {
-    ReportAvailability(BLUETOOTH_NOT_AVAILABLE);
-    return;
-  }
-
-  if (!device::BluetoothAdapterFactory::Get()->IsLowEnergySupported()) {
-    ReportAvailability(BLUETOOTH_AVAILABLE_WITHOUT_LE);
-    return;
-  }
-
-  ReportAvailability(BLUETOOTH_AVAILABLE_WITH_LE);
-}
-
-void ReportBluetoothAvailability() {
-  // This is only relevant for desktop platforms.
-#if BUILDFLAG(IS_MAC)
-  // TODO(kenrb): This is separate from other platforms because we get a
-  // little bit of extra information from the Mac-specific code. It might not
-  // be worth having the extra code path, and we should consider whether to
-  // combine them (https://crbug.com/907279).
-  ReportAvailability(bluetooth_utility::GetBluetoothAvailability());
-#elif BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
-  // GetAdapter must be called on the UI thread, because it creates a
-  // WeakPtr, which is checked from that thread on future calls.
-  if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
-    content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
-        ->PostTask(FROM_HERE, base::BindOnce(&ReportBluetoothAvailability));
-    return;
-  }
-
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
-  bool is_initialized;
-
-  if (base::FeatureList::IsEnabled(floss::features::kFlossEnabled)) {
-    ReportStackName(BluetoothStackName::kFloss);
-    is_initialized = floss::FlossDBusManager::IsInitialized();
-  } else {
-    ReportStackName(BluetoothStackName::kBlueZ);
-    is_initialized = bluez::BluezDBusManager::IsInitialized();
-  }
-
-  // This is for tests that have not initialized bluez/floss or dbus thread
-  // manager. Outside of tests these are initialized earlier during browser
-  // startup.
-  if (!is_initialized)
-    return;
-#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
-
-  if (!device::BluetoothAdapterFactory::Get()->IsBluetoothSupported()) {
-    ReportAvailability(BLUETOOTH_NOT_SUPPORTED);
-    return;
-  }
-
-  device::BluetoothAdapterFactory::Get()->GetAdapter(
-      base::BindOnce(&OnGetAdapter));
-#endif
-}
-
-}  // namespace bluetooth_utility
diff --git a/chrome/browser/metrics/bluetooth_available_utility.h b/chrome/browser/metrics/bluetooth_available_utility.h
deleted file mode 100644
index dfa79b4..0000000
--- a/chrome/browser/metrics/bluetooth_available_utility.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_METRICS_BLUETOOTH_AVAILABLE_UTILITY_H_
-#define CHROME_BROWSER_METRICS_BLUETOOTH_AVAILABLE_UTILITY_H_
-
-namespace bluetooth_utility {
-
-// Reports the bluetooth availability of the system's hardware.
-// This currently only works on ChromeOS and Windows. For Bluetooth
-// availability on OS X, see chrome/browser/mac/bluetooth_utility.h.
-void ReportBluetoothAvailability();
-
-}  // namespace bluetooth_utility
-
-#endif  // CHROME_BROWSER_METRICS_BLUETOOTH_AVAILABLE_UTILITY_H_
diff --git a/chrome/browser/metrics/bluetooth_metrics_provider.cc b/chrome/browser/metrics/bluetooth_metrics_provider.cc
new file mode 100644
index 0000000..273f4a6
--- /dev/null
+++ b/chrome/browser/metrics/bluetooth_metrics_provider.cc
@@ -0,0 +1,105 @@
+// Copyright 2022 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/bluetooth_metrics_provider.h"
+
+#include "base/bind.h"
+#include "base/metrics/histogram_functions.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#include "device/bluetooth/dbus/bluez_dbus_manager.h"
+#include "device/bluetooth/floss/floss_dbus_manager.h"
+#include "device/bluetooth/floss/floss_features.h"
+#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+
+namespace metrics {
+
+BluetoothMetricsProvider::BluetoothMetricsProvider() {
+  GetBluetoothAvailability();
+}
+
+BluetoothMetricsProvider::~BluetoothMetricsProvider() = default;
+
+void BluetoothMetricsProvider::ProvideCurrentSessionData(
+    metrics::ChromeUserMetricsExtension* uma_proto) {
+#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || \
+    BUILDFLAG(IS_WIN)
+  base::UmaHistogramEnumeration("Bluetooth.Availability.v2",
+                                bluetooth_availability_);
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  base::UmaHistogramEnumeration("Bluetooth.StackName", bluetooth_stack_name_);
+#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#endif  // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) ||
+        // BUILDFLAG(IS_WIN)
+}
+
+void BluetoothMetricsProvider::OnGetAdapter(
+    scoped_refptr<device::BluetoothAdapter> adapter) {
+  if (!adapter->IsPresent()) {
+    bluetooth_availability_ = BluetoothAvailability::BLUETOOTH_NOT_AVAILABLE;
+    return;
+  }
+
+  if (!device::BluetoothAdapterFactory::Get()->IsLowEnergySupported()) {
+    bluetooth_availability_ =
+        BluetoothAvailability::BLUETOOTH_AVAILABLE_WITHOUT_LE;
+    return;
+  }
+
+  bluetooth_availability_ = BluetoothAvailability::BLUETOOTH_AVAILABLE_WITH_LE;
+}
+
+void BluetoothMetricsProvider::GetBluetoothAvailability() {
+  // This is only relevant for desktop platforms.
+#if BUILDFLAG(IS_MAC)
+  // TODO(kenrb): This is separate from other platforms because we get a
+  // little bit of extra information from the Mac-specific code. It might not
+  // be worth having the extra code path, and we should consider whether to
+  // combine them (https://crbug.com/907279).
+  bluetooth_availability_ = bluetooth_utility::GetBluetoothAvailability();
+#elif BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
+  // GetAdapter must be called on the UI thread, because it creates a
+  // WeakPtr, which is checked from that thread on future calls.
+  if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
+    content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
+        ->PostTask(
+            FROM_HERE,
+            base::BindOnce(&BluetoothMetricsProvider::GetBluetoothAvailability,
+                           weak_ptr_factory_.GetWeakPtr()));
+    return;
+  }
+
+#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
+  bool is_initialized;
+
+  if (base::FeatureList::IsEnabled(floss::features::kFlossEnabled)) {
+    bluetooth_stack_name_ = BluetoothStackName::kFloss;
+    is_initialized = floss::FlossDBusManager::IsInitialized();
+  } else {
+    bluetooth_stack_name_ = BluetoothStackName::kBlueZ;
+    is_initialized = bluez::BluezDBusManager::IsInitialized();
+  }
+
+  // This is for tests that have not initialized bluez/floss or dbus thread
+  // manager. Outside of tests these are initialized earlier during browser
+  // startup.
+  if (!is_initialized)
+    return;
+#endif  // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
+
+  if (!device::BluetoothAdapterFactory::Get()->IsBluetoothSupported()) {
+    bluetooth_availability_ = BluetoothAvailability::BLUETOOTH_NOT_SUPPORTED;
+    return;
+  }
+
+  device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
+      &BluetoothMetricsProvider::OnGetAdapter, weak_ptr_factory_.GetWeakPtr()));
+#endif
+}
+
+}  // namespace metrics
diff --git a/chrome/browser/metrics/bluetooth_metrics_provider.h b/chrome/browser/metrics/bluetooth_metrics_provider.h
new file mode 100644
index 0000000..9c07e4e
--- /dev/null
+++ b/chrome/browser/metrics/bluetooth_metrics_provider.h
@@ -0,0 +1,60 @@
+// Copyright 2022 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_BLUETOOTH_METRICS_PROVIDER_H_
+#define CHROME_BROWSER_METRICS_BLUETOOTH_METRICS_PROVIDER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "build/build_config.h"
+#include "chrome/browser/mac/bluetooth_utility.h"
+#include "components/metrics/metrics_provider.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+
+using bluetooth_utility::BluetoothAvailability;
+
+namespace metrics {
+
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class BluetoothStackName {
+  kBlueZ = 0,
+  kFloss = 1,
+  kUnknown = 2,
+  kMaxValue = kUnknown
+};
+
+// BluetoothMetricsProvider reports the Bluetooth usage and stack identifiers.
+class BluetoothMetricsProvider : public metrics::MetricsProvider {
+ public:
+  BluetoothMetricsProvider();
+
+  BluetoothMetricsProvider(const BluetoothMetricsProvider&) = delete;
+  BluetoothMetricsProvider& operator=(const BluetoothMetricsProvider&) = delete;
+
+  ~BluetoothMetricsProvider() override;
+
+  // metrics::MetricsProvider:
+  void ProvideCurrentSessionData(
+      metrics::ChromeUserMetricsExtension* uma_proto) override;
+
+ private:
+  void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter);
+  void GetBluetoothAvailability();
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  BluetoothStackName bluetooth_stack_name_ = BluetoothStackName::kUnknown;
+#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  // bluetooth_availability_ is initialized to BLUETOOTH_AVAILABILITY_ERROR here
+  // as a precaution to the asynchronized fetch of Bluetooth adapter
+  // availability. This variable gets updated only once during the class
+  // construction time through GetBluetoothAvailability() and its callback
+  // OnGetAdapter().
+  BluetoothAvailability bluetooth_availability_ =
+      BluetoothAvailability::BLUETOOTH_AVAILABILITY_ERROR;
+  base::WeakPtrFactory<BluetoothMetricsProvider> weak_ptr_factory_{this};
+};
+
+}  // namespace metrics
+
+#endif  // CHROME_BROWSER_METRICS_BLUETOOTH_METRICS_PROVIDER_H_
diff --git a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
index bed300c..087a88d 100644
--- a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
+++ b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
@@ -30,7 +30,6 @@
 #include "chrome/browser/chrome_browser_main.h"
 #include "chrome/browser/enterprise/browser_management/management_service_factory.h"
 #include "chrome/browser/google/google_brand.h"
-#include "chrome/browser/metrics/bluetooth_available_utility.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "chrome/browser/metrics/power/battery_level_provider.h"
 #include "chrome/browser/metrics/power/power_metrics_reporter.h"
@@ -522,8 +521,6 @@
   crypto::MeasureTPMAvailabilityWin();
 #endif  // BUILDFLAG(IS_WIN)
 
-  bluetooth_utility::ReportBluetoothAvailability();
-
   // Record whether Chrome is the default browser or not.
   // Disabled on Linux due to hanging browser tests, see crbug.com/1216328.
 #if !BUILDFLAG(IS_LINUX)
diff --git a/chrome/browser/metrics/chrome_feature_list_creator.cc b/chrome/browser/metrics/chrome_feature_list_creator.cc
index 0d23b13..93893ba 100644
--- a/chrome/browser/metrics/chrome_feature_list_creator.cc
+++ b/chrome/browser/metrics/chrome_feature_list_creator.cc
@@ -47,6 +47,7 @@
 #include "components/variations/pref_names.h"
 #include "components/variations/service/variations_service.h"
 #include "components/variations/variations_crash_keys.h"
+#include "components/variations/variations_switches.h"
 #include "content/public/common/content_switch_dependent_feature_overrides.h"
 #include "ui/base/resource/resource_bundle.h"
 
@@ -61,11 +62,23 @@
 ChromeFeatureListCreator::~ChromeFeatureListCreator() = default;
 
 void ChromeFeatureListCreator::CreateFeatureList() {
+  // Get the variation IDs passed through the command line. This is done early
+  // on because ConvertFlagsToSwitches() will append to the command line
+  // the variation IDs from flags (so that they are visible in about://version).
+  // This will be passed on to `VariationsService::SetUpFieldTrials()`, which
+  // will manually fetch the variation IDs from flags (hence the reason we do
+  // not pass the mutated command line, otherwise the IDs will be duplicated).
+  // It also distinguishes between variation IDs coming from the command line
+  // and from flags, so we cannot rely on simply putting them all in the
+  // command line.
+  const std::string command_line_variation_ids =
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          variations::switches::kForceVariationIds);
   CreatePrefService();
   ConvertFlagsToSwitches();
   CreateMetricsServices();
   SetupInitialPrefs();
-  SetUpFieldTrials();
+  SetUpFieldTrials(command_line_variation_ids);
 }
 
 void ChromeFeatureListCreator::SetApplicationLocale(const std::string& locale) {
@@ -190,7 +203,8 @@
                                       flags_ui::kAddSentinels);
 }
 
-void ChromeFeatureListCreator::SetUpFieldTrials() {
+void ChromeFeatureListCreator::SetUpFieldTrials(
+    const std::string& command_line_variation_ids) {
   browser_field_trials_ =
       std::make_unique<ChromeBrowserFieldTrials>(local_state_.get());
 
@@ -212,7 +226,7 @@
   variations::VariationsService* variations_service =
       metrics_services_manager_->GetVariationsService();
   variations_service->SetUpFieldTrials(
-      variation_ids,
+      variation_ids, command_line_variation_ids,
       content::GetSwitchDependentFeatureOverrides(
           *base::CommandLine::ForCurrentProcess()),
       std::move(feature_list), browser_field_trials_.get());
diff --git a/chrome/browser/metrics/chrome_feature_list_creator.h b/chrome/browser/metrics/chrome_feature_list_creator.h
index 0904c96..4de41c9 100644
--- a/chrome/browser/metrics/chrome_feature_list_creator.h
+++ b/chrome/browser/metrics/chrome_feature_list_creator.h
@@ -91,7 +91,15 @@
  private:
   void CreatePrefService();
   void ConvertFlagsToSwitches();
-  void SetUpFieldTrials();
+
+  // Sets up the field trials and related initialization. Call only after
+  // about:flags have been converted to switches. However,
+  // |command_line_variation_ids| should be the value of the
+  // "--force-variation-ids" switch before it is mutated. See
+  // VariationsFieldTrialCreator::SetUpFieldTrials() for the format of
+  // |command_line_variation_ids|.
+  void SetUpFieldTrials(const std::string& command_line_variation_ids);
+
   void CreateMetricsServices();
 
   // Imports variations initial preference any preferences (to local state)
diff --git a/chrome/browser/metrics/chrome_metrics_service_client.cc b/chrome/browser/metrics/chrome_metrics_service_client.cc
index c02ed8f..57837d1 100644
--- a/chrome/browser/metrics/chrome_metrics_service_client.cc
+++ b/chrome/browser/metrics/chrome_metrics_service_client.cc
@@ -42,6 +42,7 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/google/google_brand.h"
 #include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/metrics/bluetooth_metrics_provider.h"
 #include "chrome/browser/metrics/cached_metrics_profile.h"
 #include "chrome/browser/metrics/chrome_metrics_extensions_helper.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
@@ -763,6 +764,9 @@
   metrics_service_->RegisterMetricsProvider(
       std::make_unique<safe_browsing::SafeBrowsingMetricsProvider>());
 
+  metrics_service_->RegisterMetricsProvider(
+      std::make_unique<metrics::BluetoothMetricsProvider>());
+
 #if BUILDFLAG(IS_ANDROID)
   metrics_service_->RegisterMetricsProvider(
       std::make_unique<metrics::AndroidMetricsProvider>());
diff --git a/chrome/browser/metrics/chrome_metrics_service_client_unittest.cc b/chrome/browser/metrics/chrome_metrics_service_client_unittest.cc
index 430bc576..0f664c0 100644
--- a/chrome/browser/metrics/chrome_metrics_service_client_unittest.cc
+++ b/chrome/browser/metrics/chrome_metrics_service_client_unittest.cc
@@ -155,7 +155,7 @@
   size_t expected_providers = 2;
 
   // This is the number of metrics providers that are outside any #if macros.
-  expected_providers += 22;
+  expected_providers += 23;
 
   int sample_rate;
   if (ChromeMetricsServicesManagerClient::GetSamplingRatePerMille(
diff --git a/chrome/browser/notifications/notification_interactive_uitest.cc b/chrome/browser/notifications/notification_interactive_uitest.cc
index 1ff6d36..8a46ebe 100644
--- a/chrome/browser/notifications/notification_interactive_uitest.cc
+++ b/chrome/browser/notifications/notification_interactive_uitest.cc
@@ -469,7 +469,8 @@
       browser(), GURL("about:blank"), WindowOpenDisposition::NEW_BACKGROUND_TAB,
       ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB);
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestPageURL()));
   CreateSimpleNotification(browser(), true);
   ASSERT_EQ(1, GetNotificationCount());
diff --git a/chrome/browser/page_load_metrics/integration_tests/layout_instability_browsertest.cc b/chrome/browser/page_load_metrics/integration_tests/layout_instability_browsertest.cc
index f4e35b8..2d50308 100644
--- a/chrome/browser/page_load_metrics/integration_tests/layout_instability_browsertest.cc
+++ b/chrome/browser/page_load_metrics/integration_tests/layout_instability_browsertest.cc
@@ -7,6 +7,7 @@
 #include "base/test/trace_event_analyzer.h"
 #include "build/build_config.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
 #include "components/page_load_metrics/browser/page_load_metrics_util.h"
 #include "content/public/test/browser_test.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -31,6 +32,10 @@
 
 void LayoutInstabilityTest::RunWPT(const std::string& test_file,
                                    bool trace_only) {
+  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
+      web_contents());
+  waiter->AddPageExpectation(
+      page_load_metrics::PageLoadMetricsTestWaiter::TimingField::kLayoutShift);
   Start();
   StartTracing({"loading", TRACE_DISABLED_BY_DEFAULT("layout_shift.debug")});
   Load("/layout-instability/" + test_file);
@@ -45,6 +50,8 @@
   if (trace_only)
     return;
 
+  waiter->Wait();
+
   // Finish session.
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
 
@@ -115,13 +122,7 @@
   }
 }
 
-// TODO(crbug.com/1336973): Re-enable this test.
-#if BUILDFLAG(IS_LINUX)
-#define MAYBE_SimpleBlockMovement DISABLED_SimpleBlockMovement
-#else
-#define MAYBE_SimpleBlockMovement SimpleBlockMovement
-#endif  //  BUILDFLAG(IS_LINUX)
-IN_PROC_BROWSER_TEST_F(LayoutInstabilityTest, MAYBE_SimpleBlockMovement) {
+IN_PROC_BROWSER_TEST_F(LayoutInstabilityTest, SimpleBlockMovement) {
   RunWPT("simple-block-movement.html");
 }
 
diff --git a/chrome/browser/page_load_metrics/observers/foreground_duration_ukm_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/foreground_duration_ukm_observer_browsertest.cc
index e338162..65215ca 100644
--- a/chrome/browser/page_load_metrics/observers/foreground_duration_ukm_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/foreground_duration_ukm_observer_browsertest.cc
@@ -104,10 +104,18 @@
   EXPECT_EQ(url1, tab_strip_model->GetWebContentsAt(0)->GetLastCommittedURL());
   EXPECT_EQ(url2, tab_strip_model->GetWebContentsAt(1)->GetLastCommittedURL());
 
-  tab_strip_model->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
-  tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
-  tab_strip_model->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
-  tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
+  tab_strip_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
+  tab_strip_model->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
+  tab_strip_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   tab_strip_model->CloseAllTabs();
   ExpectMetricCountForUrl(url1, "ForegroundDuration", 3);
   ExpectMetricCountForUrl(url1, "ForegroundNumInputEvents", 3);
diff --git a/chrome/browser/page_load_metrics/observers/live_tab_count_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/live_tab_count_page_load_metrics_observer_browsertest.cc
index e067c485..d35d138 100644
--- a/chrome/browser/page_load_metrics/observers/live_tab_count_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/live_tab_count_page_load_metrics_observer_browsertest.cc
@@ -95,7 +95,8 @@
 
   // Switch tabs so the paint events occur.
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   waiter->Wait();
 
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
index 309fab0b..dad4dec 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
@@ -2638,7 +2638,9 @@
   ASSERT_TRUE(tab_strip);
   ASSERT_EQ(2, tab_strip->count());
   ASSERT_EQ(0, tab_strip->active_index());
-  tab_strip->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_strip->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   session_restore_paint_waiter.WaitForForegroundTabs(1);
 
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 35817700..6e0ef4a 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -749,9 +749,6 @@
   { key::kCloudPrintProxyEnabled,
     prefs::kCloudPrintProxyEnabled,
     base::Value::Type::BOOLEAN },
-  { key::kCloudPrintSubmitEnabled,
-    prefs::kCloudPrintSubmitEnabled,
-    base::Value::Type::BOOLEAN },
   { key::kShowAppsShortcutInBookmarkBar,
     bookmarks::prefs::kShowAppsShortcutInBookmarkBar,
     base::Value::Type::BOOLEAN },
@@ -1591,9 +1588,6 @@
   { key::kSecurityKeyPermitAttestation,
     prefs::kSecurityKeyPermitAttestation,
     base::Value::Type::LIST },
-  { key::kU2fSecurityKeyApiEnabled,
-    extensions::pref_names::kU2fSecurityKeyApiEnabled,
-    base::Value::Type::BOOLEAN },
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
 #if !BUILDFLAG(IS_CHROMEOS) && BUILDFLAG(ENABLE_EXTENSIONS)
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
index 4c8cf99e..0acf1d81 100644
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -208,11 +208,20 @@
 
     public static final KeyPrefix CUSTOM_TABS_DEX_LAST_UPDATE_TIME_PREF_PREFIX =
             new KeyPrefix("pref_local_custom_tabs_module_dex_last_update_time_*");
-    public static final String CUSTOM_TABS_LAST_URL = "pref_last_custom_tab_url";
+
+    /** Package name of the client app that uses CCT service of the last launched CCT. */
+    public static final String CUSTOM_TABS_LAST_CLIENT_PACKAGE =
+            "Chrome.CustomTabs.LastClientPackage";
     public static final String CUSTOM_TABS_LAST_CLOSE_TIMESTAMP =
             "Chrome.CustomTabs.LastCloseTimestamp";
     public static final String CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION =
             "Chrome.CustomTabs.LastCloseTabInteraction";
+    /** The referrer URI string of the last launched CCT. */
+    public static final String CUSTOM_TABS_LAST_REFERRER = "Chrome.CustomTabs.LastReferrer";
+    /** {@link Activity#getTaskId()} of the last launched CCT. */
+    public static final String CUSTOM_TABS_LAST_TASK_ID = "Chrome.CustomTabs.LastTaskId";
+    /** Uri of the last launched CCT. */
+    public static final String CUSTOM_TABS_LAST_URL = "pref_last_custom_tab_url";
 
     /**
      * Keys used to save whether it is ready to promo.
@@ -969,9 +978,12 @@
                 CONTEXT_MENU_SEARCH_WITH_GOOGLE_LENS_CLICKED,
                 CONTEXT_MENU_SHOP_IMAGE_WITH_GOOGLE_LENS_CLICKED,
                 CONTINUOUS_SEARCH_DISMISSAL_COUNT,
-                CRYPTID_LAST_RENDER_TIMESTAMP,
-                CUSTOM_TABS_LAST_CLOSE_TIMESTAMP,
+                CUSTOM_TABS_LAST_CLIENT_PACKAGE,
                 CUSTOM_TABS_LAST_CLOSE_TAB_INTERACTION,
+                CUSTOM_TABS_LAST_CLOSE_TIMESTAMP,
+                CUSTOM_TABS_LAST_REFERRER,
+                CUSTOM_TABS_LAST_TASK_ID,
+                CRYPTID_LAST_RENDER_TIMESTAMP,
                 DEFAULT_BROWSER_PROMO_LAST_DEFAULT_STATE,
                 DEFAULT_BROWSER_PROMO_LAST_PROMO_TIME,
                 DEFAULT_BROWSER_PROMO_PROMOED_BY_SYSTEM_SETTINGS,
diff --git a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
index 445a7ba..b9d441d 100644
--- a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
+++ b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
@@ -509,9 +509,9 @@
   // features since order is tricky when doing different feature lists between
   // base and derived classes.
   virtual void SetFeatures() {
-    // Important: Features with parameters can't be used here, because it will
-    // cause a failed DCHECK in the SSL reporting test.
-    scoped_feature_list_.InitAndEnableFeature(features::kIsolatePrerenders);
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        features::kIsolatePrerenders,
+        {{"use_speculation_rules", "false"}, {"max_srp_prefetches", "1"}});
   }
 
   void SetUpOnMainThread() override {
@@ -2355,7 +2355,10 @@
 class PolicyTestPrefetchProxyBrowserTest : public policy::PolicyTest {
  public:
   void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(features::kIsolatePrerenders);
+    scoped_feature_list_.InitWithFeatures(
+        {features::kIsolatePrerenders,
+         blink::features::kSpeculationRulesPrefetchProxy},
+        {});
     policy::PolicyTest::SetUp();
   }
 
@@ -2368,14 +2371,34 @@
     return browser()->tab_strip_model()->GetActiveWebContents();
   }
 
-  void MakeNavigationPrediction(const GURL& doc_url,
-                                const std::vector<GURL>& predicted_urls) {
-    NavigationPredictorKeyedServiceFactory::GetForProfile(browser()->profile())
-        ->OnPredictionUpdated(
-            GetWebContents(), doc_url,
-            NavigationPredictorKeyedService::PredictionSource::
-                kAnchorElementsParsedFromWebPage,
-            predicted_urls);
+  void InsertSpeculation(bool use_prefetch_proxy,
+                         const std::vector<GURL>& prefetch_urls) {
+    std::string speculation_script = R"(
+      var script = document.createElement('script');
+      script.type = 'speculationrules';
+      script.text = `{
+        "prefetch": [{
+          "source": "list",
+          "urls": [)";
+
+    bool first = true;
+    for (const GURL& url : prefetch_urls) {
+      if (!first)
+        speculation_script.append(",");
+      first = false;
+      speculation_script.append("\"").append(url.spec()).append("\"");
+    }
+    speculation_script.append("]");
+
+    if (use_prefetch_proxy)
+      speculation_script.append(R"(,
+          "requires": ["anonymous-client-ip-when-cross-origin"])");
+    speculation_script.append(R"(
+        }]
+      }`;
+      document.head.appendChild(script);)");
+
+    EXPECT_TRUE(ExecuteScript(GetWebContents(), speculation_script));
   }
 
  private:
@@ -2398,7 +2421,8 @@
       PrefetchProxyTabHelper::FromWebContents(GetWebContents());
 
   GURL doc_url("https://www.google.com/search?q=test");
-  MakeNavigationPrediction(doc_url, {GURL("https://test.com/")});
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), doc_url));
+  InsertSpeculation(true, {GURL("https://test.com/")});
   base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(tab_helper->srp_metrics().predicted_urls_count_, 0U);
@@ -2412,7 +2436,8 @@
       PrefetchProxyTabHelper::FromWebContents(GetWebContents());
 
   GURL doc_url("https://www.google.com/search?q=test");
-  MakeNavigationPrediction(doc_url, {GURL("https://test.com/")});
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), doc_url));
+  InsertSpeculation(true, {GURL("https://test.com/")});
   base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(tab_helper->srp_metrics().predicted_urls_count_, 1U);
@@ -2426,6 +2451,15 @@
     CertReportHelper::SetFakeOfficialBuildForTesting();
   }
 
+  void SetFeatures() override {
+    // Important: Features with parameters can't be used here, because it will
+    // cause a failed DCHECK in the SSL reporting test.
+    scoped_feature_list_.InitWithFeatures(
+        {features::kIsolatePrerenders,
+         blink::features::kSpeculationRulesPrefetchProxy},
+        {});
+  }
+
   void SetUpCommandLine(base::CommandLine* cmd) override {
     PrefetchProxyBrowserTest::SetUpCommandLine(cmd);
     cmd->RemoveSwitch("ignore-certificate-errors");
@@ -2446,6 +2480,9 @@
       return nullptr;
     return helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting();
   }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(
@@ -2460,7 +2497,7 @@
   https_expired_server.ServeFilesFromSourceDirectory("chrome/test/data");
   ASSERT_TRUE(https_expired_server.Start());
 
-  GURL safe_page = GetOriginServerURL("/simple.html");
+  GURL safe_page = GURL("https://www.google.com/search?q=test");
 
   // Opt in to sending reports for invalid certificate chains.
   certificate_reporting_test_utils::SetCertReportingOptIn(
@@ -2482,8 +2519,7 @@
   tab_helper_observer.SetOnPrefetchErrorClosure(
       prefetch_run_loop.QuitClosure());
 
-  GURL doc_url("https://www.google.com/search?q=test");
-  MakeNavigationPrediction(doc_url, {eligible_link});
+  InsertSpeculation(false, true, {eligible_link});
 
   // This run loop stops when the prefetches completes with its error.
   prefetch_run_loop.Run();
@@ -2918,9 +2954,12 @@
   }
 
   void SetFeatures() override {
-    PrefetchProxyBrowserTest::SetFeatures();
-    scoped_feature_list_.InitAndEnableFeature(
-        blink::features::kLightweightNoStatePrefetch);
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{features::kIsolatePrerenders,
+          {{"use_speculation_rules", "false"},
+           {"max_subresource_count_per_prerender", "50"}}},
+         {blink::features::kLightweightNoStatePrefetch, {}}},
+        {});
   }
 
  private:
@@ -3575,19 +3614,21 @@
 
   void SetFeatures() override {
     PrefetchProxyBrowserTest::SetFeatures();
-    scoped_feature_list_.InitAndEnableFeature(
-        blink::features::kLightweightNoStatePrefetch);
-    probing_scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        features::kIsolatePrerendersMustProbeOrigin,
+    scoped_feature_list_.InitWithFeaturesAndParameters(
         {
-            {"do_canary", "false"},
-            {"ineligible_decoy_request_probability", "0"},
-        });
+            {features::kIsolatePrerenders,
+             {{"use_speculation_rules", "false"},
+              {"max_subresource_count_per_prerender", "50"}}},
+            {blink::features::kLightweightNoStatePrefetch, {}},
+            {features::kIsolatePrerendersMustProbeOrigin,
+             {{"do_canary", "false"},
+              {"ineligible_decoy_request_probability", "0"}}},
+        },
+        {});
   }
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
-  base::test::ScopedFeatureList probing_scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(ProbingAndNSPEnabledPrefetchProxyBrowserTest,
@@ -4058,7 +4099,9 @@
   void SetFeatures() override {
     scoped_feature_list_.InitWithFeaturesAndParameters(
         {{features::kIsolatePrerenders,
-          {{"use_speculation_rules", "true"}, {"max_srp_prefetches", "3"}}},
+          {{"use_speculation_rules", "true"},
+           {"max_srp_prefetches", "3"},
+           {"max_subresource_count_per_prerender", "50"}}},
          {blink::features::kLightweightNoStatePrefetch, {}},
          {blink::features::kSpeculationRulesPrefetchProxy, {}}},
         {{features::kLazyImageLoading}});
@@ -4518,6 +4561,7 @@
   void SetFeatures() override {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders, {
+                                          {"use_speculation_rules", "false"},
                                           {"cacheable_duration", "0"},
                                       });
   }
@@ -4581,7 +4625,8 @@
 
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders,
-        {{"use_individual_network_contexts", "true"}});
+        {{"use_speculation_rules", "false"},
+         {"use_individual_network_contexts", "true"}});
   }
 
  private:
diff --git a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_features.cc b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_features.cc
index 6e0d990..303f72c 100644
--- a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_features.cc
+++ b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_features.cc
@@ -8,11 +8,23 @@
 
 // Forces all eligible prerenders to be done in an isolated manner such that no
 // user-identifying information is used during the prefetch.
-const base::Feature kIsolatePrerenders{"IsolatePrerenders",
-                                       base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kIsolatePrerenders {
+  "IsolatePrerenders",
+#if BUILDFLAG(IS_ANDROID)
+      base::FEATURE_ENABLED_BY_DEFAULT
+#else
+      base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
 
 // Forces Chrome to probe the origin before reusing a cached response.
-const base::Feature kIsolatePrerendersMustProbeOrigin{
-    "IsolatePrerendersMustProbeOrigin", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kIsolatePrerendersMustProbeOrigin {
+  "IsolatePrerendersMustProbeOrigin",
+#if BUILDFLAG(IS_ANDROID)
+      base::FEATURE_ENABLED_BY_DEFAULT
+#else
+      base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+};
 
 }  // namespace features
diff --git a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_params.cc b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_params.cc
index 50005ec0..d9e99871 100644
--- a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_params.cc
+++ b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_params.cc
@@ -77,7 +77,7 @@
   }
 
   int max = base::GetFieldTrialParamByFeatureAsInt(features::kIsolatePrerenders,
-                                                   "max_srp_prefetches", 1);
+                                                   "max_srp_prefetches", 5);
   if (max < 0) {
     return absl::nullopt;
   }
@@ -166,7 +166,7 @@
   if (url.is_valid()) {
     return url;
   }
-  return GURL("http://tls.tunnel.check.googlezip.net/connect");
+  return GURL("http://tls-tunnel-check.googlezip.net/connect");
 }
 
 GURL PrefetchProxyDNSCanaryCheckURL() {
@@ -175,7 +175,7 @@
   if (url.is_valid()) {
     return url;
   }
-  return GURL("http://dns.tunnel.check.googlezip.net/connect");
+  return GURL("http://dns-tunnel-check.googlezip.net/connect");
 }
 
 base::TimeDelta PrefetchProxyCanaryCheckCacheLifetime() {
@@ -194,21 +194,21 @@
   }
 
   return base::GetFieldTrialParamByFeatureAsInt(
-      features::kIsolatePrerenders, "max_subresource_count_per_prerender", 50);
+      features::kIsolatePrerenders, "max_subresource_count_per_prerender", 0);
 }
 
 bool PrefetchProxyStartsSpareRenderer() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(
              "isolated-prerender-start-spare-renderer") ||
          base::GetFieldTrialParamByFeatureAsBool(features::kIsolatePrerenders,
-                                                 "start_spare_renderer", false);
+                                                 "start_spare_renderer", true);
 }
 
 bool PrefetchProxyUseSpeculationRules() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(
              "isolated-prerender-use-speculation-rules") ||
-         base::GetFieldTrialParamByFeatureAsBool(
-             features::kIsolatePrerenders, "use_speculation_rules", false);
+         base::GetFieldTrialParamByFeatureAsBool(features::kIsolatePrerenders,
+                                                 "use_speculation_rules", true);
 }
 
 bool PrefetchProxyShouldPrefetchPosition(size_t position) {
@@ -279,7 +279,7 @@
 bool PrefetchProxyAllowAllDomainsForExtendedPreloading() {
   return base::GetFieldTrialParamByFeatureAsBool(
       features::kIsolatePrerenders, "allow_all_domains_for_extended_preloading",
-      false);
+      true);
 }
 
 base::TimeDelta PrefetchProxyCacheableDuration() {
@@ -294,7 +294,7 @@
 
 bool PrefetchProxyUseIndividualNetworkContextsForEachPrefetch() {
   return base::GetFieldTrialParamByFeatureAsBool(
-      features::kIsolatePrerenders, "use_individual_network_contexts", false);
+      features::kIsolatePrerenders, "use_individual_network_contexts", true);
 }
 
 bool PrefetchProxySupportNonPrivatePrefetches() {
diff --git a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper_unittest.cc b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper_unittest.cc
index 6c0ce023..3c8448b 100644
--- a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper_unittest.cc
+++ b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper_unittest.cc
@@ -418,7 +418,8 @@
   PrefetchProxyTabHelperTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders,
-        {{"ineligible_decoy_request_probability", "0"}});
+        {{"use_speculation_rules", "false"},
+         {"ineligible_decoy_request_probability", "0"}});
   }
 };
 
@@ -938,7 +939,8 @@
  public:
   PrefetchProxyTabHelperWithHTMLOnly() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        features::kIsolatePrerenders, {{"html_only", "true"}});
+        features::kIsolatePrerenders,
+        {{"use_speculation_rules", "false"}, {"html_only", "true"}});
   }
 };
 
@@ -1354,7 +1356,8 @@
   PrefetchProxyTabHelperWithDecoyTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders,
-        {{"ineligible_decoy_request_probability", "1"},
+        {{"use_speculation_rules", "false"},
+         {"ineligible_decoy_request_probability", "1"},
          {"max_srp_prefetches", "2"}});
   }
 };
@@ -1497,7 +1500,8 @@
   PrefetchProxyTabHelperBodyLimitTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders,
-        {{"max_mainframe_body_length_kb", "0"},
+        {{"use_speculation_rules", "false"},
+         {"max_mainframe_body_length_kb", "0"},
          {"ineligible_decoy_request_probability", "0"}});
   }
 };
@@ -1548,7 +1552,8 @@
   PrefetchProxyTabHelperPredictionPositionsTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders,
-        {{"prefetch_positions", "0"},
+        {{"use_speculation_rules", "false"},
+         {"prefetch_positions", "0"},
          {"ineligible_decoy_request_probability", "0"}});
   }
 };
@@ -1584,7 +1589,8 @@
   PrefetchProxyTabHelperNoPrefetchesTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders,
-        {{"max_srp_prefetches", "0"},
+        {{"use_speculation_rules", "false"},
+         {"max_srp_prefetches", "0"},
          {"ineligible_decoy_request_probability", "0"}});
   }
 };
@@ -1630,7 +1636,8 @@
   PrefetchProxyTabHelperUnlimitedPrefetchesTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders,
-        {{"max_srp_prefetches", "-1"},
+        {{"use_speculation_rules", "false"},
+         {"max_srp_prefetches", "-1"},
          {"ineligible_decoy_request_probability", "0"}});
   }
 };
@@ -1699,7 +1706,8 @@
   PrefetchProxyTabHelperConcurrentPrefetchesTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders,
-        {{"max_concurrent_prefetches", "2"},
+        {{"use_speculation_rules", "false"},
+         {"max_concurrent_prefetches", "2"},
          {"max_srp_prefetches", "-1"},
          {"ineligible_decoy_request_probability", "0"}});
   }
@@ -1759,7 +1767,8 @@
   PrefetchProxyTabHelperLimitedPrefetchesTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders,
-        {{"max_srp_prefetches", "2"},
+        {{"use_speculation_rules", "false"},
+         {"max_srp_prefetches", "2"},
          {"ineligible_decoy_request_probability", "0"}});
   }
 };
@@ -1908,7 +1917,8 @@
   PrefetchProxyTabHelperRedirectWithDecoyTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders,
-        {{"ineligible_decoy_request_probability", "1"},
+        {{"use_speculation_rules", "false"},
+         {"ineligible_decoy_request_probability", "1"},
          {"max_srp_prefetches", "2"}});
   }
 };
@@ -2006,7 +2016,8 @@
   PrefetchProxyTabHelperRedirectTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders,
-        {{"ineligible_decoy_request_probability", "0"}});
+        {{"use_speculation_rules", "false"},
+         {"ineligible_decoy_request_probability", "0"}});
   }
 };
 
@@ -2109,7 +2120,8 @@
     // way.
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kIsolatePrerenders,
-        {{"max_srp_prefetches", "-1"},
+        {{"use_speculation_rules", "false"},
+         {"max_srp_prefetches", "-1"},
          {"ineligible_decoy_request_probability", "0"}});
   }
 };
diff --git a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_url_loader_interceptor_unittest.cc b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_url_loader_interceptor_unittest.cc
index 070525c..508f7a0 100644
--- a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_url_loader_interceptor_unittest.cc
+++ b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_url_loader_interceptor_unittest.cc
@@ -70,6 +70,13 @@
   PrefetchProxyURLLoaderInterceptorTest() = default;
   ~PrefetchProxyURLLoaderInterceptorTest() override = default;
 
+  void SetUp() override {
+    ChromeRenderViewHostTestHarness::SetUp();
+
+    scoped_feature_list_.InitAndDisableFeature(
+        features::kIsolatePrerendersMustProbeOrigin);
+  }
+
   void TearDown() override {
     prerender::NoStatePrefetchManager* no_state_prefetch_manager =
         prerender::NoStatePrefetchManagerFactory::GetForBrowserContext(
@@ -113,6 +120,7 @@
  private:
   absl::optional<bool> was_intercepted_;
   base::OnceClosure waiting_for_callback_closure_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 TEST_F(PrefetchProxyURLLoaderInterceptorTest, DISABLE_ASAN(WantIntercept)) {
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 4a6a777..4b07a0e 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -180,6 +180,7 @@
 #include "extensions/browser/api/runtime/runtime_api.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/permissions_manager.h"
+#include "extensions/browser/pref_names.h"
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ash/attestation/tpm_challenge_key.h"
 #include "chrome/browser/ash/crosapi/browser_data_migrator.h"
@@ -1217,9 +1218,6 @@
   DeviceOAuth2TokenStoreDesktop::RegisterPrefs(registry);
 #endif
 
-  registry->RegisterBooleanPref(
-      policy::policy_prefs::kSetTimeoutWithout1MsClampEnabled, false);
-
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
   screen_ai::RegisterLocalStatePrefs(registry);
 #endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
@@ -1947,6 +1945,12 @@
   profile_prefs->ClearPref(kImprovedShortcutsNotificationShownCount);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+  // Added 06/2022.
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+  profile_prefs->ClearPref(extensions::pref_names::kU2fSecurityKeyApiEnabled);
+#endif
+  profile_prefs->ClearPref(prefs::kCloudPrintSubmitEnabled);
+
   // Please don't delete the following line. It is used by PRESUBMIT.py.
   // END_MIGRATE_OBSOLETE_PROFILE_PREFS
 
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
index dc5829e..d9f81ce 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
@@ -99,6 +99,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/common/context_menu_data/context_menu_data.h"
+#include "third_party/blink/public/common/context_menu_data/edit_flags.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/common/switches.h"
 #include "third_party/blink/public/mojom/context_menu/context_menu.mojom.h"
@@ -368,11 +369,21 @@
   }
 
  protected:
+  struct PdfInfo {
+    // The selected text in the PDF when context menu is created.
+    std::u16string selection_text;
+
+    // Whether the PDF has copy permission.
+    bool can_copy;
+  };
+
   guest_view::TestGuestViewManager* test_guest_view_manager() const {
     return test_guest_view_manager_;
   }
 
-  std::unique_ptr<TestRenderViewContextMenu> SetupAndCreateMenu() {
+  // Creates a context menu with the given `info`:
+  std::unique_ptr<TestRenderViewContextMenu> SetupAndCreateMenuWithPdfInfo(
+      const PdfInfo& info) {
     // Load a pdf page.
     GURL page_url = ui_test_utils::GetTestUrl(
         base::FilePath(FILE_PATH_LITERAL("pdf")),
@@ -399,12 +410,22 @@
     params.frame_url = extension_frame_->GetLastCommittedURL();
     params.media_type = blink::mojom::ContextMenuDataMediaType::kPlugin;
     params.media_flags |= blink::ContextMenuData::kMediaCanRotate;
+    params.selection_text = info.selection_text;
+    // Mimic how `edit_flag` is set in ContextMenuController::ShowContextMenu().
+    if (info.can_copy)
+      params.edit_flags |= blink::ContextMenuDataEditFlags::kCanCopy;
+
     auto menu =
         std::make_unique<TestRenderViewContextMenu>(*extension_frame_, params);
     menu->Init();
     return menu;
   }
 
+  std::unique_ptr<TestRenderViewContextMenu> SetupAndCreateMenu() {
+    return SetupAndCreateMenuWithPdfInfo(
+        {/*selection_text=*/u"", /*can_copy=*/true});
+  }
+
   // Helper function for testing context menu of a pdf plugin inside a web page.
   void TestContextMenuOfPdfInsideWebPage(
       const base::FilePath::CharType* file_name) {
@@ -2142,6 +2163,34 @@
   ASSERT_FALSE(menu->IsCommandIdEnabled(IDC_CONTENT_CONTEXT_ROTATECCW));
 }
 
+IN_PROC_BROWSER_TEST_F(PdfPluginContextMenuBrowserTest, CopyWithoutText) {
+  std::unique_ptr<TestRenderViewContextMenu> menu = SetupAndCreateMenu();
+
+  // Test that 'Copy' doesn't exist.
+  ASSERT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_COPY));
+}
+
+IN_PROC_BROWSER_TEST_F(PdfPluginContextMenuBrowserTest, CopyText) {
+  std::unique_ptr<TestRenderViewContextMenu> menu =
+      SetupAndCreateMenuWithPdfInfo(
+          {/*selection_text=*/u"text", /*can_copy=*/true});
+
+  // Test that 'Copy' exists and it is enabled.
+  ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_COPY));
+  ASSERT_TRUE(menu->IsCommandIdEnabled(IDC_CONTENT_CONTEXT_COPY));
+}
+
+IN_PROC_BROWSER_TEST_F(PdfPluginContextMenuBrowserTest,
+                       CopyTextWithRestriction) {
+  std::unique_ptr<TestRenderViewContextMenu> menu =
+      SetupAndCreateMenuWithPdfInfo(
+          {/*selection_text=*/u"text", /*can_copy=*/false});
+
+  // Test that 'Copy' exists and it is disabled.
+  ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_COPY));
+  ASSERT_FALSE(menu->IsCommandIdEnabled(IDC_CONTENT_CONTEXT_COPY));
+}
+
 IN_PROC_BROWSER_TEST_F(PdfPluginContextMenuBrowserTest,
                        IframedPdfHasNoPageItems) {
   TestContextMenuOfPdfInsideWebPage(FILE_PATH_LITERAL("test-iframe-pdf.html"));
diff --git a/chrome/browser/reputation/reputation_service.cc b/chrome/browser/reputation/reputation_service.cc
index 6a9c9cc1..246acd1 100644
--- a/chrome/browser/reputation/reputation_service.cc
+++ b/chrome/browser/reputation/reputation_service.cc
@@ -70,12 +70,13 @@
   }
 };
 
-// Returns whether or not the Safety Tip should be suppressed for the given URL.
-// Checks SafeBrowsing-style permutations of |url| against the component updater
-// allowlist, as well as any enterprise-set allowlisting of the hostname, and
-// returns whether the URL is explicitly allowed. Fails closed, so that warnings
-// are suppressed if the component is unavailable.
-bool ShouldSuppressWarning(Profile* profile, const GURL& url) {
+// Returns whether or not the Safety Tip should be suppressed on the given URL,
+// if it's accused of spoofing |victim_url|. Checks both against the component
+// updater allowlist, as well as any enterprise-set allowlist.  Fails closed, so
+// that warnings are suppressed if the component is unavailable.
+bool ShouldSuppressWarning(Profile* profile,
+                           const GURL& url,
+                           const GURL& victim_url) {
   // Check any policy-set allowlist.
   if (IsAllowedByEnterprisePolicy(profile->GetPrefs(), url)) {
     return true;
@@ -90,7 +91,8 @@
     // flag on any known false positives until the client received the update.
     return true;
   }
-  return reputation::IsUrlAllowlistedBySafetyTipsComponent(proto, url);
+  return reputation::IsUrlAllowlistedBySafetyTipsComponent(
+      proto, url.GetWithEmptyPath(), victim_url.GetWithEmptyPath());
 }
 
 // Gets the eTLD+1 of the provided hostname, including private registries (e.g.
@@ -185,13 +187,6 @@
   // decision with other heuristics that may trigger later.
   bool done_checking_reputation_status = false;
 
-  // 0. Server-side warning suppression.
-  // If the URL is on the allowlist list, do nothing else. This is only used to
-  // mitigate false positives, so no further processing should be done.
-  if (ShouldSuppressWarning(profile_, url)) {
-    done_checking_reputation_status = true;
-  }
-
   // 1. Engagement check
   // Ensure that this URL is not already engaged. We can't use the synchronous
   // SiteEngagementService::IsEngagementAtLeast as it has side effects.  This
@@ -250,6 +245,16 @@
     done_checking_reputation_status = true;
   }
 
+  // If we found a SafetyTipStatus, possibly clear it if the URL is on the
+  // allowlist.
+  if (result.safety_tip_status != SafetyTipStatus::kUnknown &&
+      result.safety_tip_status != SafetyTipStatus::kNone &&
+      result.safety_tip_status != SafetyTipStatus::kBadKeyword &&
+      ShouldSuppressWarning(profile_, url, result.suggested_url)) {
+    result.safety_tip_status = SafetyTipStatus::kNone;
+    result.suggested_url = GURL();
+  }
+
   if (IsIgnored(url)) {
     if (result.safety_tip_status == SafetyTipStatus::kBadReputation) {
       result.safety_tip_status = SafetyTipStatus::kBadReputationIgnored;
diff --git a/chrome/browser/resource_coordinator/tab_activity_watcher_browsertest.cc b/chrome/browser/resource_coordinator/tab_activity_watcher_browsertest.cc
index bfa6433..987aa8a 100644
--- a/chrome/browser/resource_coordinator/tab_activity_watcher_browsertest.cc
+++ b/chrome/browser/resource_coordinator/tab_activity_watcher_browsertest.cc
@@ -211,7 +211,8 @@
   test_clock.Advance(base::Minutes(1));
 
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   test_clock.Advance(base::Minutes(1));
 
   // A background tab is scored successfully.
@@ -257,7 +258,8 @@
 
   // Switching to another tab logs the previously active tab.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   {
     SCOPED_TRACE("");
     ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, kTabUrls[2],
@@ -266,7 +268,8 @@
   }
 
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   {
     SCOPED_TRACE("");
     ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, kTabUrls[0],
@@ -339,7 +342,8 @@
 
   // Sanity check: the new tab doesn't have a beforeunload handler.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   {
     SCOPED_TRACE("");
     ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[0],
@@ -388,7 +392,8 @@
       1, std::move(owned_dragged_contents), TabStripModel::ADD_NONE);
   dragged_contents->WasShown();
   browser_2->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(0, ukm_entry_checker_->NumNewEntriesRecorded(kFOCEntryName));
 
   // The first tab in this window was backgrounded when the new one was
@@ -481,7 +486,8 @@
   // Switching to first tab logs a forgrounded event for test_urls_[0]
   // and a backgrounded event for test_urls_[1].
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   {
     SCOPED_TRACE("");
     ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[1],
@@ -572,7 +578,8 @@
   test_clock.Advance(base::Minutes(1));
   // Activate tab@0.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   test_clock.Advance(base::Minutes(1));
 
   // TabManager_TabMetrics should not have been logged yet.
@@ -609,7 +616,8 @@
   // Reactivate tab@1 should log a ForegroundedOrClosed event with LabelId as
   // label_id_1.
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   test_clock.Advance(base::Minutes(1));
   {
     SCOPED_TRACE("");
@@ -696,7 +704,8 @@
 
   // Switching to first tab logs a forgrounded event for test_urls_[0].
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   {
     SCOPED_TRACE("");
     UkmMetricMap expected_metrics = {
@@ -832,7 +841,8 @@
 
   // Switching to another tab logs the previously active tab.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   {
     SCOPED_TRACE("");
     ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[1],
@@ -851,7 +861,8 @@
 
   // Switching to another tab logs the previously active tab.
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   {
     SCOPED_TRACE("");
     UkmMetricMap expected_metrics = kBasicMetricValues;
diff --git a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source_unittest.cc b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source_unittest.cc
index 7737e1d..9d0d2f6 100644
--- a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source_unittest.cc
+++ b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source_unittest.cc
@@ -359,7 +359,9 @@
     // Focus the tab. Expect the state to be ACTIVE.
     EXPECT_CALL(tab_observer_,
                 OnDiscardedStateChange(::testing::_, reason, false));
-    tab_strip_model_->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+    tab_strip_model_->ActivateTabAt(
+        0, TabStripUserGestureDetails(
+               TabStripUserGestureDetails::GestureType::kOther));
     ::testing::Mock::VerifyAndClear(&tab_observer_);
     EXPECT_EQ(LifecycleUnitState::ACTIVE,
               background_lifecycle_unit->GetState());
@@ -446,7 +448,9 @@
   // Activate the first tab.
   task_environment()->FastForwardBy(kShortDelay);
   auto time_before_activate = NowTicks();
-  tab_strip_model_->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_strip_model_->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(IsFocused(first_lifecycle_unit));
   EXPECT_EQ(time_before_activate, second_lifecycle_unit->GetLastFocusedTime());
 
@@ -517,7 +521,7 @@
 // were destroyed from TabStripModelObserver::TabClosingAt(). If a tab was
 // detached (TabStripModel::DetachWebContentsAt) and its WebContents destroyed,
 // the TabLifecycleUnit was never destroyed. This was solved by giving ownership
-// of a TabLifecycleUnit to a WebContentsUserData.
+// of a tab lifecycleunit to a WebContentsUserData.
 TEST_F(TabLifecycleUnitSourceTest, DetachAndDeleteWebContents) {
   LifecycleUnit* first_lifecycle_unit = nullptr;
   LifecycleUnit* second_lifecycle_unit = nullptr;
diff --git a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
index a26df4a..241d12b3 100644
--- a/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_browsertest.cc
@@ -291,7 +291,8 @@
   EXPECT_FALSE(IsTabDiscarded(GetWebContentsAt(2)));
 
   // Kill the third tab after making second tab active.
-  tsm()->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tsm()->ActivateTabAt(1, TabStripUserGestureDetails(
+                              TabStripUserGestureDetails::GestureType::kOther));
 
   // Advance time so everything is urgent discardable again.
   test_clock_.Advance(kBackgroundUrgentProtectionTime);
@@ -443,7 +444,8 @@
   EXPECT_TRUE(tab_manager->DiscardTabImpl(LifecycleUnitDiscardReason::URGENT));
 
   // Activate the 2nd tab.
-  tsm->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tsm->ActivateTabAt(1, TabStripUserGestureDetails(
+                            TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(1, tsm->active_index());
 
   // Advance the clock for less than the protection time.
diff --git a/chrome/browser/resource_coordinator/tab_metrics_logger_interactive_uitest.cc b/chrome/browser/resource_coordinator/tab_metrics_logger_interactive_uitest.cc
index a5b5d625..165bd6a 100644
--- a/chrome/browser/resource_coordinator/tab_metrics_logger_interactive_uitest.cc
+++ b/chrome/browser/resource_coordinator/tab_metrics_logger_interactive_uitest.cc
@@ -120,13 +120,15 @@
   DiscardTabAt(0);
   EXPECT_EQ(CurrentTabFeatures(0).discard_count, 1);
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(CurrentTabFeatures(0).discard_count, 1);
 
   DiscardTabAt(1);
   EXPECT_EQ(CurrentTabFeatures(1).discard_count, 1);
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(CurrentTabFeatures(1).discard_count, 1);
 
   DiscardTabAt(0);
diff --git a/chrome/browser/resources/bluetooth_internals/BUILD.gn b/chrome/browser/resources/bluetooth_internals/BUILD.gn
index e51645a..8be2e22 100644
--- a/chrome/browser/resources/bluetooth_internals/BUILD.gn
+++ b/chrome/browser/resources/bluetooth_internals/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//tools/grit/grit_rule.gni")
+import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
 
 bluetooth_grd_prefix = "bluetooth_internals"
@@ -38,13 +39,36 @@
   deps = [
     ":build_internal_mojo_grdp",
     ":build_public_mojo_grdp",
+    ":build_ts",
   ]
   input_files = [
+    "bluetooth_internals.css",
+    "bluetooth_internals.html",
+    "menu.svg",
+  ]
+  input_files_base_dir = rebase_path(".", "//")
+
+  grdp_files = [
+    public_mojo_grdp_file,
+    internals_mojo_grdp_file,
+  ]
+
+  manifest_files = [ "$target_gen_dir/tsconfig.manifest" ]
+}
+
+# TODO(crbug.com/1337318): This page should be converted to TypeScript but this
+# will be a lot of work. Passing the JavaScript files through the TypeScript
+# compiler will provide basic static checks (e.g. syntax) without validating
+# types.
+ts_library("build_ts") {
+  root_dir = "."
+  out_dir = "$target_gen_dir/tsc"
+  tsconfig_base = "tsconfig_base.json"
+  in_files = [
     "adapter_broker.js",
     "adapter_page.js",
     "debug_log_page.js",
     "characteristic_list.js",
-    "bluetooth_internals.css",
     "descriptor_list.js",
     "device_broker.js",
     "device_collection.js",
@@ -53,9 +77,7 @@
     "device_utils.js",
     "devices_page.js",
     "expandable_list.js",
-    "bluetooth_internals.html",
     "bluetooth_internals.js",
-    "menu.svg",
     "main.js",
     "object_fieldset.js",
     "page_manager.js",
@@ -65,12 +87,7 @@
     "snackbar.js",
     "value_control.js",
   ]
-  input_files_base_dir = rebase_path("./", "//")
-
-  grdp_files = [
-    public_mojo_grdp_file,
-    internals_mojo_grdp_file,
-  ]
+  deps = [ "//ui/webui/resources:library" ]
 }
 
 grit("resources") {
diff --git a/chrome/browser/resources/bluetooth_internals/tsconfig_base.json b/chrome/browser/resources/bluetooth_internals/tsconfig_base.json
new file mode 100644
index 0000000..99a81eca
--- /dev/null
+++ b/chrome/browser/resources/bluetooth_internals/tsconfig_base.json
@@ -0,0 +1,6 @@
+{
+  "extends": "../../../../tools/typescript/tsconfig_base.json",
+  "compilerOptions": {
+    "allowJs": true
+  }
+}
diff --git a/chrome/browser/resources/chromeos/audio/input_page.ts b/chrome/browser/resources/chromeos/audio/input_page.ts
index 3bc89af..c64d349 100644
--- a/chrome/browser/resources/chromeos/audio/input_page.ts
+++ b/chrome/browser/resources/chromeos/audio/input_page.ts
@@ -141,7 +141,7 @@
   }
 
   record(source: MediaStream) {
-    let chunks = new Array<Blob>();
+    let chunks: Blob[] = [];
     const recordButton = $('record-btn');
     const clipSection = $('audio-file');
     this.mediaRecorder = new MediaRecorder(source);
@@ -164,7 +164,7 @@
 
         audio.controls = true;
         const blob = new Blob(chunks, {'type': 'audio/ogg; codecs=opus'});
-        chunks = new Array<Blob>();
+        chunks = [];
         const audioURL = window.URL.createObjectURL(blob);
         audio.src = audioURL;
         this.testInputFeedback.set('audioUrl', audioURL);
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding.js b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding.js
index 812f7f1..d222cbd 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding.js
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_port_forwarding.js
@@ -100,7 +100,7 @@
      * List of ports are currently being forwarded.
      * @private {!Array<?CrostiniPortActiveSetting>}
      */
-    this.activePorts_ = new Array();
+    this.activePorts_ = [];
 
     /**
      * Tracks the last port that was selected for removal.
diff --git a/chrome/browser/resources/settings/chromeos/device_page/display.js b/chrome/browser/resources/settings/chromeos/device_page/display.js
index 14efcb8..32171423 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/display.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/display.js
@@ -641,7 +641,7 @@
     // Clear the mappings before recalculating.
     this.modeToParentModeMap_ = new Map();
     this.parentModeToRefreshRateMap_ = new Map();
-    this.displayModeList_ = new Array();
+    this.displayModeList_ = [];
 
     // Build the modes into a nested map of width => height => refresh rate.
     const modes = this.createModeMap_(selectedDisplay);
@@ -715,7 +715,7 @@
     // Add an entry in the outer map for |parentModeIndex|. The inner
     // array (the value at |parentModeIndex|) will be populated with all
     // possible refresh rates for the given resolution.
-    this.parentModeToRefreshRateMap_.set(parentModeIndex, new Array());
+    this.parentModeToRefreshRateMap_.set(parentModeIndex, []);
 
     const resolutionOption =
         this.i18n('displayResolutionOnlyMenuItem', width, height);
diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
index 13710c8..c4c26eed 100644
--- a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
@@ -1311,7 +1311,8 @@
 
   // Interstitial still displays in the background tab.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
   EXPECT_EQ(interstitial_tab,
             browser()->tab_strip_model()->GetActiveWebContents());
@@ -2766,7 +2767,8 @@
 
   // Interstitial should still display in the background tab.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
   EXPECT_EQ(interstitial_tab,
             browser()->tab_strip_model()->GetActiveWebContents());
diff --git a/chrome/browser/sessions/session_restore.cc b/chrome/browser/sessions/session_restore.cc
index 2c68856..d463d8c2 100644
--- a/chrome/browser/sessions/session_restore.cc
+++ b/chrome/browser/sessions/session_restore.cc
@@ -69,6 +69,7 @@
 #include "chrome/browser/ui/tabs/tab_group.h"
 #include "chrome/browser/ui/tabs/tab_group_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/webui/whats_new/whats_new_util.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
@@ -293,7 +294,8 @@
 
     if (use_new_window) {
       browser->tab_strip_model()->ActivateTabAt(
-          0, {TabStripModel::GestureType::kOther});
+          0, TabStripUserGestureDetails(
+                 TabStripUserGestureDetails::GestureType::kOther));
       browser->window()->Show();
     }
     NotifySessionServiceOfRestoredTabs(browser,
@@ -871,7 +873,9 @@
     DCHECK(browser);
     DCHECK(browser->tab_strip_model()->count());
     browser->tab_strip_model()->ActivateTabAt(
-        selected_tab_index, {TabStripModel::GestureType::kOther});
+        selected_tab_index,
+        TabStripUserGestureDetails(
+            TabStripUserGestureDetails::GestureType::kOther));
 
     if (browser_ == browser)
       return;
diff --git a/chrome/browser/sessions/session_restore_browsertest.cc b/chrome/browser/sessions/session_restore_browsertest.cc
index 01392fd..e758061 100644
--- a/chrome/browser/sessions/session_restore_browsertest.cc
+++ b/chrome/browser/sessions/session_restore_browsertest.cc
@@ -1832,7 +1832,8 @@
   ASSERT_EQ(1, browser()->tab_strip_model()->active_index());
   // Select the pinned tab.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_EQ(0, browser()->tab_strip_model()->active_index());
   Profile* profile = browser()->profile();
 
@@ -2255,7 +2256,8 @@
   // Activate the tabs one by one following the specified activation order.
   for (int i : activation_order)
     browser()->tab_strip_model()->ActivateTabAt(
-        i, {TabStripModel::GestureType::kOther});
+        i, TabStripUserGestureDetails(
+               TabStripUserGestureDetails::GestureType::kOther));
 
   // Close the browser.
   auto keep_alive = std::make_unique<ScopedKeepAlive>(
@@ -2290,7 +2292,8 @@
   // Activate the 2nd tab before the browser closes. This should be persisted in
   // the following test.
   new_browser->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 }
 
 IN_PROC_BROWSER_TEST_F(SmartSessionRestoreTest, MAYBE_CorrectLoadingOrder) {
diff --git a/chrome/browser/signin/signin_ui_util_unittest.cc b/chrome/browser/signin/signin_ui_util_unittest.cc
index 6485ccf..ceb13a5 100644
--- a/chrome/browser/signin/signin_ui_util_unittest.cc
+++ b/chrome/browser/signin/signin_ui_util_unittest.cc
@@ -422,7 +422,9 @@
   ASSERT_EQ(0, tab_strip->active_index());
   GURL other_url = GURL("http://example.com");
   AddTab(browser(), other_url);
-  tab_strip->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_strip->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_EQ(other_url, tab_strip->GetActiveWebContents()->GetVisibleURL());
   ASSERT_EQ(0, tab_strip->active_index());
 
diff --git a/chrome/browser/site_isolation/site_details_browsertest.cc b/chrome/browser/site_isolation/site_details_browsertest.cc
index 48e9303..0386584 100644
--- a/chrome/browser/site_isolation/site_details_browsertest.cc
+++ b/chrome/browser/site_isolation/site_details_browsertest.cc
@@ -565,7 +565,8 @@
   // be three processes estimated by IsolateExtensions: one for extension3, one
   // for extension1's background page, and one for the web iframe in tab2.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_TRUE(ui_test_utils::NavigateToURL(
       browser(), extension3->GetResourceURL("blank_iframe.html")));
   details = new TestMemoryDetails();
diff --git a/chrome/browser/ssl/ssl_browsertest.cc b/chrome/browser/ssl/ssl_browsertest.cc
index d18eca4..cffe191 100644
--- a/chrome/browser/ssl/ssl_browsertest.cc
+++ b/chrome/browser/ssl/ssl_browsertest.cc
@@ -4239,7 +4239,8 @@
   EXPECT_FALSE(tab->GetRenderWidgetHostView()->IsShowing());
 
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(tab->GetRenderWidgetHostView()->IsShowing());
 }
 
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/CouponPersistedTabData.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/CouponPersistedTabData.java
index ee03c63a..6729f53 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/CouponPersistedTabData.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/CouponPersistedTabData.java
@@ -7,11 +7,16 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.google.protobuf.InvalidProtocolBufferException;
+
 import org.chromium.base.Callback;
+import org.chromium.base.Log;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.proto.CouponPersistedTabData.CouponPersistedTabDataProto;
 
 import java.nio.ByteBuffer;
+import java.util.Locale;
 
 /**
  * {@link PersistedTabData} for Shopping websites with coupons.
@@ -74,14 +79,42 @@
         return mCoupon;
     }
 
-    // TODO(crbug.com/1337470): Implement deserialize & serialize methods.
     @Override
     Supplier<ByteBuffer> getSerializeSupplier() {
-        return () -> null;
+        CouponPersistedTabDataProto.Builder builder = CouponPersistedTabDataProto.newBuilder();
+        if (mCoupon != null) {
+            if (mCoupon.promoCode != null) {
+                builder.setCode(mCoupon.promoCode);
+            }
+
+            if (mCoupon.couponName != null) {
+                builder.setName(mCoupon.couponName);
+            }
+        }
+        return () -> {
+            return builder.build().toByteString().asReadOnlyByteBuffer();
+        };
     }
 
     @Override
     boolean deserialize(@Nullable ByteBuffer bytes) {
+        // Do not attempt to deserialize if the bytes are null
+        if (bytes == null || !bytes.hasRemaining()) {
+            return false;
+        }
+        try {
+            CouponPersistedTabDataProto couponPersistedTabDataProto =
+                    CouponPersistedTabDataProto.parseFrom(bytes);
+            mCoupon = new Coupon(
+                    couponPersistedTabDataProto.getName(), couponPersistedTabDataProto.getCode());
+            return true;
+        } catch (InvalidProtocolBufferException e) {
+            Log.e(TAG,
+                    String.format(Locale.US,
+                            "There was a problem deserializing "
+                                    + "CouponPersistedTabData. Details: %s",
+                            e.getMessage()));
+        }
         return false;
     }
 
diff --git a/chrome/browser/tab_contents/view_source_browsertest.cc b/chrome/browser/tab_contents/view_source_browsertest.cc
index 480a20d..7398f17 100644
--- a/chrome/browser/tab_contents/view_source_browsertest.cc
+++ b/chrome/browser/tab_contents/view_source_browsertest.cc
@@ -215,7 +215,8 @@
 
   // Switch back to the first tab and navigate it cross-process.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
                                            GURL(chrome::kChromeUIVersionURL)));
   EXPECT_TRUE(chrome::CanViewSource(browser()));
diff --git a/chrome/browser/translate/translate_manager_browsertest.cc b/chrome/browser/translate/translate_manager_browsertest.cc
index b59edc5..237a8ae 100644
--- a/chrome/browser/translate/translate_manager_browsertest.cc
+++ b/chrome/browser/translate/translate_manager_browsertest.cc
@@ -1293,7 +1293,8 @@
   // Make restored tab active to (on some platforms) initiate language
   // detection.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   content::WebContents* restored_web_contents =
       browser()->tab_strip_model()->GetWebContentsAt(0);
@@ -2066,7 +2067,8 @@
   // Make restored tab active to (on some platforms) initiate language
   // detection.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   content::WebContents* restored_web_contents =
       browser()->tab_strip_model()->GetWebContentsAt(0);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 64f9d624..2291572 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1372,6 +1372,7 @@
       "tabs/tab_strip_model_observer.h",
       "tabs/tab_strip_model_stats_recorder.cc",
       "tabs/tab_strip_model_stats_recorder.h",
+      "tabs/tab_strip_user_gesture_details.h",
       "tabs/tab_style.cc",
       "tabs/tab_style.h",
       "tabs/tab_switch_event_latency_recorder.cc",
diff --git a/chrome/browser/ui/android/fast_checkout/BUILD.gn b/chrome/browser/ui/android/fast_checkout/BUILD.gn
index 7692b2d..7c4cda01 100644
--- a/chrome/browser/ui/android/fast_checkout/BUILD.gn
+++ b/chrome/browser/ui/android/fast_checkout/BUILD.gn
@@ -13,3 +13,7 @@
   ]
   resources_package = "org.chromium.chrome.browser.ui.fast_checkout"
 }
+
+generate_jni("jni_headers") {
+  sources = [ "internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutBridge.java" ]
+}
diff --git a/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn b/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
index 0e05feef..f93fc74 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
+++ b/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
@@ -5,11 +5,18 @@
 import("//build/config/android/rules.gni")
 
 android_library("java") {
+  sources = [
+    "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutBridge.java",
+    "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutCoordinator.java",
+  ]
   deps = [
     "//base:base_java",
+    "//base:jni_java",
     "//build/android:build_java",
     "//chrome/browser/ui/android/fast_checkout:java",
     "//components/browser_ui/bottomsheet/android:java",
+    "//third_party/androidx:androidx_annotation_annotation_java",
+    "//ui/android:ui_no_recycler_view_java",
   ]
-  sources = [ "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutCoordinator.java" ]
+  annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
 }
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutBridge.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutBridge.java
new file mode 100644
index 0000000..e8bd060
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutBridge.java
@@ -0,0 +1,84 @@
+// Copyright 2022 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.ui.fast_checkout;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.chrome.browser.ui.fast_checkout.data.FastCheckoutAutofillProfile;
+import org.chromium.chrome.browser.ui.fast_checkout.data.FastCheckoutCreditCard;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * This bridge creates and initializes a {@link FastCheckoutComponent} on construction and forwards
+ * native calls to it.
+ */
+class FastCheckoutBridge implements FastCheckoutComponent.Delegate {
+    private long mNativeFastCheckoutBridge;
+    private final FastCheckoutComponent mFastCheckoutComponent;
+
+    private FastCheckoutBridge(long nativeBridge, WindowAndroid windowAndroid,
+            BottomSheetController bottomSheetController) {
+        mNativeFastCheckoutBridge = nativeBridge;
+        mFastCheckoutComponent = new FastCheckoutCoordinator();
+        mFastCheckoutComponent.initialize(
+                windowAndroid.getContext().get(), bottomSheetController, this);
+    }
+
+    @CalledByNative
+    private static @Nullable FastCheckoutBridge create(
+            long nativeBridge, WindowAndroid windowAndroid) {
+        BottomSheetController bottomSheetController =
+                BottomSheetControllerProvider.from(windowAndroid);
+        if (bottomSheetController == null) return null;
+        return new FastCheckoutBridge(nativeBridge, windowAndroid, bottomSheetController);
+    }
+
+    @CalledByNative
+    private void showBottomSheet(
+            FastCheckoutAutofillProfile[] profiles, FastCheckoutCreditCard[] creditCards) {
+        mFastCheckoutComponent.showOptions(profiles, creditCards);
+    }
+
+    @CalledByNative
+    private static void setAutofillProfile(FastCheckoutAutofillProfile[] profiles, int index,
+            FastCheckoutAutofillProfile profile) {
+        profiles[index] = profile;
+    }
+
+    @CalledByNative
+    private static void setCreditCard(
+            FastCheckoutCreditCard[] creditCards, int index, FastCheckoutCreditCard creditCard) {
+        creditCards[index] = creditCard;
+    }
+
+    @CalledByNative
+    private static FastCheckoutAutofillProfile[] createAutofillProfilesArray(int size) {
+        return new FastCheckoutAutofillProfile[size];
+    }
+
+    @CalledByNative
+    private static FastCheckoutCreditCard[] createCreditCardsArray(int size) {
+        return new FastCheckoutCreditCard[size];
+    }
+
+    @CalledByNative
+    private void destroy() {
+        mNativeFastCheckoutBridge = 0;
+    }
+
+    @Override
+    public void onDismissed() {
+        // TODO(crbug.com/1334642): Call native side to continue dismissing.
+    }
+
+    @Override
+    public void onOptionsSelected(
+            FastCheckoutAutofillProfile profile, FastCheckoutCreditCard creditCard) {
+        // TODO(crbug.com/1334642): Call native side to continue filling.
+    }
+}
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeader.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeader.java
index 0adc1992..9f6a7f8 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeader.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeader.java
@@ -276,12 +276,14 @@
 
             if (!hasGeolocationPermission()) {
                 if (recordUma) recordHistogram(UMA_LOCATION_DISABLED_FOR_CHROME_APP);
+                Log.i(TAG, "[crbug/1338183] App permission is missing");
                 return HeaderState.LOCATION_PERMISSION_BLOCKED;
             }
 
             // Only send X-Geo header if the user hasn't disabled geolocation for url.
             if (isLocationDisabledForUrl(profile, uri)) {
                 if (recordUma) recordHistogram(UMA_LOCATION_DISABLED_FOR_GOOGLE_DOMAIN);
+                Log.i(TAG, "[crbug/1338183] Site permission is missing");
                 return HeaderState.LOCATION_PERMISSION_BLOCKED;
             }
 
@@ -305,11 +307,7 @@
     @Nullable
     public static String getGeoHeader(String url, Tab tab) {
         Profile profile = Profile.fromWebContents(tab.getWebContents());
-        Log.i(TAG, "[getGeoHeader] getGeoHeader for url " + url);
-        if (profile == null) {
-            Log.i(TAG, "[getGeoHeader] getGeoHeader failed because of a null profile");
-            return null;
-        }
+        if (profile == null) return null;
 
         return getGeoHeader(url, profile, tab);
     }
@@ -360,7 +358,7 @@
             long locationAge = Long.MAX_VALUE;
             @HeaderState
             int headerState = geoHeaderStateForUrl(profile, url, true);
-            Log.i(TAG, "[getGeoHeader] headerState: " + headerState);
+            Log.i(TAG, "[crbug/1338183] headerState: " + headerState);
             if (headerState == HeaderState.HEADER_ENABLED) {
                 locationToAttach = GeolocationTracker.getLastKnownLocation(
                         ContextUtils.getApplicationContext());
@@ -413,10 +411,6 @@
             String visibleNetworksProtoEncoding =
                     encodeProtoVisibleNetworks(visibleNetworksToAttach);
 
-            Log.i(TAG, "[getGeoHeader] locationProtoEncoding: " + locationProtoEncoding);
-            Log.i(TAG,
-                    "[getGeoHeader] visibleNetworksProtoEncoding: " + visibleNetworksProtoEncoding);
-
             if (locationProtoEncoding == null && visibleNetworksProtoEncoding == null) return null;
 
             StringBuilder header = new StringBuilder(XGEO_HEADER_PREFIX);
diff --git a/chrome/browser/ui/app_list/search/app_search_provider_unittest.cc b/chrome/browser/ui/app_list/search/app_search_provider_unittest.cc
index 71f6554..993235f 100644
--- a/chrome/browser/ui/app_list/search/app_search_provider_unittest.cc
+++ b/chrome/browser/ui/app_list/search/app_search_provider_unittest.cc
@@ -44,6 +44,7 @@
 #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "components/crx_file/id_util.h"
 #include "components/services/app_service/public/cpp/app_types.h"
@@ -643,6 +644,7 @@
  public:
   void SetUp() override {
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -661,6 +663,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 };
diff --git a/chrome/browser/ui/ash/shelf/shelf_context_menu_unittest.cc b/chrome/browser/ui/ash/shelf/shelf_context_menu_unittest.cc
index ac8fcea9..bad84465 100644
--- a/chrome/browser/ui/ash/shelf/shelf_context_menu_unittest.cc
+++ b/chrome/browser/ui/ash/shelf/shelf_context_menu_unittest.cc
@@ -53,6 +53,7 @@
 #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
+#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "components/exo/shell_surface_util.h"
 #include "components/prefs/pref_service.h"
@@ -105,6 +106,7 @@
 
   void SetUp() override {
     chromeos::DBusThreadManager::Initialize();
+    chromeos::ChunneldClient::InitializeFake();
     ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
@@ -210,6 +212,7 @@
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::CiceroneClient::Shutdown();
+    chromeos::ChunneldClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc b/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
index 299df9d..4347871 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
+++ b/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
@@ -47,7 +47,6 @@
 #include "chrome/browser/ash/system_web_apps/types/system_web_app_type.h"
 #include "chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/chromeos/extensions/wallpaper_private_api.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/sync_service_factory.h"
@@ -676,19 +675,6 @@
       profile, ash::SystemWebAppType::PERSONALIZATION, params);
 }
 
-void WallpaperControllerClientImpl::MaybeClosePreviewWallpaper() {
-  Profile* profile = ProfileManager::GetActiveUserProfile();
-  DCHECK(profile);
-
-  extensions::EventRouter* event_router = extensions::EventRouter::Get(profile);
-
-  auto event = std::make_unique<extensions::Event>(
-      extensions::events::WALLPAPER_PRIVATE_ON_CLOSE_PREVIEW_WALLPAPER,
-      extensions::api::wallpaper_private::OnClosePreviewWallpaper::kEventName,
-      base::Value::List());
-  event_router->DispatchEventToExtension(kWallpaperManagerId, std::move(event));
-}
-
 void WallpaperControllerClientImpl::SetDefaultWallpaper(
     const AccountId& account_id,
     bool show_wallpaper,
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_impl.h b/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
index 651738c..efd9a7c 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
+++ b/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
@@ -66,7 +66,6 @@
 
   // ash::WallpaperControllerClient:
   void OpenWallpaperPicker() override;
-  void MaybeClosePreviewWallpaper() override;
   void SetDefaultWallpaper(
       const AccountId& account_id,
       bool show_wallpaper,
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index c582ceb..4013ab3 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -42,6 +42,7 @@
 #include "chrome/browser/ui/page_info/page_info_dialog.h"
 #include "chrome/browser/ui/singleton_tabs.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/web_app_dialog_utils.h"
@@ -490,13 +491,17 @@
       break;
     case IDC_SELECT_NEXT_TAB:
       base::RecordAction(base::UserMetricsAction("Accel_SelectNextTab"));
-      SelectNextTab(browser_,
-                    {TabStripModel::GestureType::kKeyboard, time_stamp});
+      SelectNextTab(
+          browser_,
+          TabStripUserGestureDetails(
+              TabStripUserGestureDetails::GestureType::kKeyboard, time_stamp));
       break;
     case IDC_SELECT_PREVIOUS_TAB:
       base::RecordAction(base::UserMetricsAction("Accel_SelectPreviousTab"));
-      SelectPreviousTab(browser_,
-                        {TabStripModel::GestureType::kKeyboard, time_stamp});
+      SelectPreviousTab(
+          browser_,
+          TabStripUserGestureDetails(
+              TabStripUserGestureDetails::GestureType::kKeyboard, time_stamp));
       break;
     case IDC_MOVE_TAB_NEXT:
       MoveTabNext(browser_);
@@ -513,13 +518,17 @@
     case IDC_SELECT_TAB_6:
     case IDC_SELECT_TAB_7:
       base::RecordAction(base::UserMetricsAction("Accel_SelectNumberedTab"));
-      SelectNumberedTab(browser_, id - IDC_SELECT_TAB_0,
-                        {TabStripModel::GestureType::kKeyboard, time_stamp});
+      SelectNumberedTab(
+          browser_, id - IDC_SELECT_TAB_0,
+          TabStripUserGestureDetails(
+              TabStripUserGestureDetails::GestureType::kKeyboard, time_stamp));
       break;
     case IDC_SELECT_LAST_TAB:
       base::RecordAction(base::UserMetricsAction("Accel_SelectNumberedTab"));
-      SelectLastTab(browser_,
-                    {TabStripModel::GestureType::kKeyboard, time_stamp});
+      SelectLastTab(
+          browser_,
+          TabStripUserGestureDetails(
+              TabStripUserGestureDetails::GestureType::kKeyboard, time_stamp));
       break;
     case IDC_DUPLICATE_TAB:
       DuplicateTab(browser_);
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 1138415..1a1551c 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -80,6 +80,7 @@
 #include "chrome/browser/ui/tab_dialogs.h"
 #include "chrome/browser/ui/tabs/tab_group.h"
 #include "chrome/browser/ui/tabs/tab_group_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/translate/translate_bubble_ui_action_logger.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/user_education/reopen_tab_in_product_help.h"
@@ -787,13 +788,13 @@
 }
 
 void SelectNextTab(Browser* browser,
-                   TabStripModel::UserGestureDetails gesture_detail) {
+                   TabStripUserGestureDetails gesture_detail) {
   base::RecordAction(UserMetricsAction("SelectNextTab"));
   browser->tab_strip_model()->SelectNextTab(gesture_detail);
 }
 
 void SelectPreviousTab(Browser* browser,
-                       TabStripModel::UserGestureDetails gesture_detail) {
+                       TabStripUserGestureDetails gesture_detail) {
   base::RecordAction(UserMetricsAction("SelectPrevTab"));
   browser->tab_strip_model()->SelectPreviousTab(gesture_detail);
 }
@@ -810,7 +811,7 @@
 
 void SelectNumberedTab(Browser* browser,
                        int index,
-                       TabStripModel::UserGestureDetails gesture_detail) {
+                       TabStripUserGestureDetails gesture_detail) {
   int visible_count = 0;
   for (int i = 0; i < browser->tab_strip_model()->count(); i++) {
     if (browser->tab_strip_model()->IsTabCollapsed(i)) {
@@ -826,7 +827,7 @@
 }
 
 void SelectLastTab(Browser* browser,
-                   TabStripModel::UserGestureDetails gesture_detail) {
+                   TabStripUserGestureDetails gesture_detail) {
   for (int i = browser->tab_strip_model()->count() - 1; i >= 0; i--) {
     if (!browser->tab_strip_model()->IsTabCollapsed(i)) {
       base::RecordAction(UserMetricsAction("SelectLastTab"));
diff --git a/chrome/browser/ui/browser_commands.h b/chrome/browser/ui/browser_commands.h
index 65fc5a9..c16c8bee 100644
--- a/chrome/browser/ui/browser_commands.h
+++ b/chrome/browser/ui/browser_commands.h
@@ -13,8 +13,8 @@
 #include "chrome/browser/devtools/devtools_toggle_action.h"
 #include "chrome/browser/devtools/devtools_window.h"
 #include "chrome/browser/ui/chrome_pages.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "components/services/screen_ai/buildflags/buildflags.h"
 #include "content/public/common/page_zoom.h"
 #include "printing/buildflags/buildflags.h"
@@ -97,23 +97,23 @@
 void RestoreTab(Browser* browser);
 void SelectNextTab(
     Browser* browser,
-    TabStripModel::UserGestureDetails gesture_detail =
-        TabStripModel::UserGestureDetails(TabStripModel::GestureType::kOther));
+    TabStripUserGestureDetails gesture_detail = TabStripUserGestureDetails(
+        TabStripUserGestureDetails::GestureType::kOther));
 void SelectPreviousTab(
     Browser* browser,
-    TabStripModel::UserGestureDetails gesture_detail =
-        TabStripModel::UserGestureDetails(TabStripModel::GestureType::kOther));
+    TabStripUserGestureDetails gesture_detail = TabStripUserGestureDetails(
+        TabStripUserGestureDetails::GestureType::kOther));
 void MoveTabNext(Browser* browser);
 void MoveTabPrevious(Browser* browser);
 void SelectNumberedTab(
     Browser* browser,
     int index,
-    TabStripModel::UserGestureDetails gesture_detail =
-        TabStripModel::UserGestureDetails(TabStripModel::GestureType::kOther));
+    TabStripUserGestureDetails gesture_detail = TabStripUserGestureDetails(
+        TabStripUserGestureDetails::GestureType::kOther));
 void SelectLastTab(
     Browser* browser,
-    TabStripModel::UserGestureDetails gesture_detail =
-        TabStripModel::UserGestureDetails(TabStripModel::GestureType::kOther));
+    TabStripUserGestureDetails gesture_detail = TabStripUserGestureDetails(
+        TabStripUserGestureDetails::GestureType::kOther));
 void DuplicateTab(Browser* browser);
 bool CanDuplicateTab(const Browser* browser);
 bool CanDuplicateKeyboardFocusedTab(const Browser* browser);
diff --git a/chrome/browser/ui/browser_focus_uitest.cc b/chrome/browser/ui/browser_focus_uitest.cc
index 9e1dec2..e0e8843 100644
--- a/chrome/browser/ui/browser_focus_uitest.cc
+++ b/chrome/browser/ui/browser_focus_uitest.cc
@@ -260,7 +260,8 @@
     for (int j = 0; j < 5; j++) {
       // Activate the tab.
       browser()->tab_strip_model()->ActivateTabAt(
-          j, {TabStripModel::GestureType::kOther});
+          j, TabStripUserGestureDetails(
+                 TabStripUserGestureDetails::GestureType::kOther));
 
       // Activate the location bar or the page.
       if (kFocusPage[i][j]) {
@@ -274,14 +275,16 @@
     for (int j = 0; j < 5; j++) {
       // Activate the tab.
       browser()->tab_strip_model()->ActivateTabAt(
-          j, {TabStripModel::GestureType::kOther});
+          j, TabStripUserGestureDetails(
+                 TabStripUserGestureDetails::GestureType::kOther));
 
       ViewID vid = kFocusPage[i][j] ? VIEW_ID_TAB_CONTAINER : VIEW_ID_OMNIBOX;
       ASSERT_TRUE(IsViewFocused(vid));
     }
 
     browser()->tab_strip_model()->ActivateTabAt(
-        0, {TabStripModel::GestureType::kOther});
+        0, TabStripUserGestureDetails(
+               TabStripUserGestureDetails::GestureType::kOther));
     // Try the above, but with ctrl+tab. Since tab normally changes focus,
     // this has regressed in the past. Loop through several times to be sure.
     for (int j = 0; j < 15; j++) {
@@ -295,7 +298,8 @@
 
     // As above, but with ctrl+shift+tab.
     browser()->tab_strip_model()->ActivateTabAt(
-        4, {TabStripModel::GestureType::kOther});
+        4, TabStripUserGestureDetails(
+               TabStripUserGestureDetails::GestureType::kOther));
     for (int j = 14; j >= 0; --j) {
       ViewID vid =
           kFocusPage[i][j % 5] ? VIEW_ID_TAB_CONTAINER : VIEW_ID_OMNIBOX;
@@ -331,7 +335,8 @@
   // Select 1st tab, focus should still be on the location-bar.
   // (bug http://crbug.com/23296)
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_TRUE(IsViewFocused(VIEW_ID_OMNIBOX));
 
   // Now open the find box again, switch to another tab and come back, the focus
@@ -339,10 +344,12 @@
   chrome::Find(browser());
   ASSERT_TRUE(IsViewFocused(VIEW_ID_FIND_IN_PAGE_TEXT_FIELD));
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_TRUE(IsViewFocused(VIEW_ID_TAB_CONTAINER));
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_TRUE(IsViewFocused(VIEW_ID_FIND_IN_PAGE_TEXT_FIELD));
 }
 
diff --git a/chrome/browser/ui/browser_navigator.cc b/chrome/browser/ui/browser_navigator.cc
index e4e4ef2..c92d6e5 100644
--- a/chrome/browser/ui/browser_navigator.cc
+++ b/chrome/browser/ui/browser_navigator.cc
@@ -38,6 +38,7 @@
 #include "chrome/browser/ui/status_bubble.h"
 #include "chrome/browser/ui/tab_helpers.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
@@ -848,8 +849,9 @@
     if (params->source_contents != contents_to_navigate_or_insert) {
       // Use the index before the potential close below, because it could
       // make the index refer to a different tab.
-      auto gesture_type = user_initiated ? TabStripModel::GestureType::kOther
-                                         : TabStripModel::GestureType::kNone;
+      auto gesture_type = user_initiated
+                              ? TabStripUserGestureDetails::GestureType::kOther
+                              : TabStripUserGestureDetails::GestureType::kNone;
       bool should_close_this_tab = false;
       if (params->disposition == WindowOpenDisposition::SWITCH_TO_TAB) {
         // Close orphaned NTP (and the like) with no history when the user
@@ -867,8 +869,8 @@
           }
         }
       }
-      params->browser->tab_strip_model()->ActivateTabAt(singleton_index,
-                                                        {gesture_type});
+      params->browser->tab_strip_model()->ActivateTabAt(
+          singleton_index, TabStripUserGestureDetails(gesture_type));
       // Close tab after switch so index remains correct.
       if (should_close_this_tab)
         params->source_contents->Close();
diff --git a/chrome/browser/ui/browser_navigator_browsertest.cc b/chrome/browser/ui/browser_navigator_browsertest.cc
index 3f8162c..c82e29e 100644
--- a/chrome/browser/ui/browser_navigator_browsertest.cc
+++ b/chrome/browser/ui/browser_navigator_browsertest.cc
@@ -675,7 +675,8 @@
   WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
 
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   NavigateHelper(singleton_url, browser(), WindowOpenDisposition::SWITCH_TO_TAB,
                  false, new_tab);
@@ -748,7 +749,8 @@
                  false);
 
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   OmniboxView* omnibox_view = location_bar->GetOmniboxView();
   EXPECT_EQ(omnibox_view->model()->focus_state(),
@@ -1617,7 +1619,8 @@
   }
 
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   {
     content::LoadStopObserver observer(
diff --git a/chrome/browser/ui/browser_unittest.cc b/chrome/browser/ui/browser_unittest.cc
index b78565d..478ce10 100644
--- a/chrome/browser/ui/browser_unittest.cc
+++ b/chrome/browser/ui/browser_unittest.cc
@@ -84,7 +84,9 @@
   EXPECT_TRUE(raw_contents2->IsCrashed());
 
   // Selecting the second tab does not cause a load or clear the crash.
-  tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(tab_strip_model->IsTabSelected(1));
   EXPECT_FALSE(raw_contents2->IsLoading());
   EXPECT_TRUE(raw_contents2->IsCrashed());
@@ -116,7 +118,9 @@
   WebContentsTester::For(raw_contents2)->NavigateAndCommit(GURL("about:blank"));
   WebContentsTester::For(raw_contents2)->TestSetIsLoading(false);
 
-  tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_TRUE(
       raw_contents2->GetPrimaryMainFrame()->GetView()->GetBackgroundColor());
   EXPECT_EQ(
@@ -381,7 +385,8 @@
 
   // Activate the 2nd tab which is non-NTP.
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(BookmarkBar::HIDDEN, browser()->bookmark_bar_state());
   EXPECT_EQ(BookmarkBar::HIDDEN, window_bookmark_bar_state());
 
@@ -392,13 +397,15 @@
 
   // Activate the 1st tab which is NTP.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(BookmarkBar::SHOW, browser()->bookmark_bar_state());
   EXPECT_EQ(BookmarkBar::SHOW, window_bookmark_bar_state());
 
   // Activate the 2nd tab which is non-NTP.
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(BookmarkBar::SHOW, browser()->bookmark_bar_state());
   EXPECT_EQ(BookmarkBar::SHOW, window_bookmark_bar_state());
 }
diff --git a/chrome/browser/ui/cocoa/applescript/window_applescript.mm b/chrome/browser/ui/cocoa/applescript/window_applescript.mm
index 169ac04..f9bc3b0 100644
--- a/chrome/browser/ui/cocoa/applescript/window_applescript.mm
+++ b/chrome/browser/ui/cocoa/applescript/window_applescript.mm
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #import "chrome/browser/ui/cocoa/applescript/window_applescript.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 
 #include <memory>
 
@@ -29,6 +30,7 @@
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/common/url_constants.h"
 #include "content/public/browser/web_contents.h"
 
@@ -133,7 +135,8 @@
   int atIndex = [anActiveTabIndex intValue] - 1;
   if (atIndex >= 0 && atIndex < _browser->tab_strip_model()->count()) {
     _browser->tab_strip_model()->ActivateTabAt(
-        atIndex, {TabStripModel::GestureType::kOther});
+        atIndex, TabStripUserGestureDetails(
+                     TabStripUserGestureDetails::GestureType::kOther));
   } else
     AppleScript::SetError(AppleScript::errInvalidTabIndex);
 }
diff --git a/chrome/browser/ui/cocoa/tab_menu_bridge.mm b/chrome/browser/ui/cocoa/tab_menu_bridge.mm
index b347857..f011667b 100644
--- a/chrome/browser/ui/cocoa/tab_menu_bridge.mm
+++ b/chrome/browser/ui/cocoa/tab_menu_bridge.mm
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/cocoa/tab_menu_bridge.h"
 
 #import <Cocoa/Cocoa.h>
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 
 #include "base/callback.h"
 #include "base/mac/scoped_nsobject.h"
@@ -12,6 +13,7 @@
 #include "chrome/browser/ui/recently_audible_helper.h"
 #include "chrome/browser/ui/tab_ui_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/l10n/l10n_util_mac.h"
@@ -153,8 +155,9 @@
 
   DCHECK_EQ(item.target, menu_listener_.get());
   int index = [menu_item_.submenu indexOfItem:item] - dynamic_items_start_;
-  model_->ActivateTabAt(index, TabStripModel::UserGestureDetails(
-                                   TabStripModel::GestureType::kTabMenu));
+  model_->ActivateTabAt(index,
+                        TabStripUserGestureDetails(
+                            TabStripUserGestureDetails::GestureType::kTabMenu));
 }
 
 void TabMenuBridge::OnTabStripModelChanged(
diff --git a/chrome/browser/ui/exclusive_access/fullscreen_controller_state_unittest.cc b/chrome/browser/ui/exclusive_access/fullscreen_controller_state_unittest.cc
index c3d55af..5f204f76 100644
--- a/chrome/browser/ui/exclusive_access/fullscreen_controller_state_unittest.cc
+++ b/chrome/browser/ui/exclusive_access/fullscreen_controller_state_unittest.cc
@@ -529,7 +529,8 @@
 
   // Activate the first tab and tell its WebContents it is being captured.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   const gfx::Size kCaptureSize(1280, 720);
   auto capture_handle =
       first_tab->IncrementCapturerCount(kCaptureSize, /*stay_hidden=*/false,
@@ -550,7 +551,8 @@
   // Switch to the other tab.  Check that the first tab was resized to the
   // WebContents' preferred size.
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_FALSE(browser()->window()->IsFullscreen());
   EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
   EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
@@ -559,7 +561,8 @@
 
   // Switch back to the first tab and exit fullscreen.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_FALSE(browser()->window()->IsFullscreen());
   EXPECT_TRUE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
   EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
@@ -595,7 +598,8 @@
   // and fullscreen that.  The second tab will cause the browser window to
   // expand to fill the screen.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   const gfx::Size kCaptureSize(1280, 720);
   auto capture_handle =
       first_tab->IncrementCapturerCount(kCaptureSize, /*stay_hidden=*/false,
@@ -606,7 +610,8 @@
   EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
   EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
   ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
   EXPECT_TRUE(browser()->window()->IsFullscreen());
@@ -625,7 +630,8 @@
 
   // Finally, exit fullscreen on the captured tab.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_FALSE));
   EXPECT_FALSE(browser()->window()->IsFullscreen());
   EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(first_tab));
@@ -657,7 +663,8 @@
   // and fullscreen that.  The second tab will cause the browser window to
   // expand to fill the screen.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   const gfx::Size kCaptureSize(1280, 720);
   auto capture_handle =
       first_tab->IncrementCapturerCount(kCaptureSize, /*stay_hidden=*/false,
@@ -668,7 +675,8 @@
   EXPECT_FALSE(wc_delegate->IsFullscreenForTabOrPending(second_tab));
   EXPECT_FALSE(GetFullscreenController()->IsWindowFullscreenForTabOrPending());
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_TRUE(InvokeEvent(TAB_FULLSCREEN_TRUE));
   ASSERT_TRUE(InvokeEvent(WINDOW_CHANGE));
   EXPECT_TRUE(browser()->window()->IsFullscreen());
@@ -712,7 +720,8 @@
   // Start capturing the tab and fullscreen it.  The state of the browser window
   // should remain unchanged.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   const gfx::Size kCaptureSize(1280, 720);
   auto capture_handle =
       tab->IncrementCapturerCount(kCaptureSize, /*stay_hidden=*/false,
@@ -762,7 +771,8 @@
   // Start capturing the tab with stay_hidden==true, and fullscreen it.
   // The the browser window should enter fullscreen.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   auto capture_handle =
       tab->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/true,
                                   /*stay_awake=*/true);
@@ -847,7 +857,8 @@
 
   // Activate the first tab and tell its WebContents it is being captured.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   const gfx::Size kCaptureSize(1280, 720);
   auto capture_handle =
       tab->IncrementCapturerCount(kCaptureSize, /*stay_hidden=*/false,
diff --git a/chrome/browser/ui/extensions/application_launch.cc b/chrome/browser/ui/extensions/application_launch.cc
index 4d75ae3..6d03b6b 100644
--- a/chrome/browser/ui/extensions/application_launch.cc
+++ b/chrome/browser/ui/extensions/application_launch.cc
@@ -36,6 +36,7 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
 #include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
@@ -251,7 +252,9 @@
       tab_index = model->GetIndexOfWebContents(existing_tab);
     }
     if (params.tabstrip_add_types & TabStripModel::ADD_ACTIVE) {
-      model->ActivateTabAt(tab_index, {TabStripModel::GestureType::kOther});
+      model->ActivateTabAt(
+          tab_index, TabStripUserGestureDetails(
+                         TabStripUserGestureDetails::GestureType::kOther));
     }
 
     contents = existing_tab;
diff --git a/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc b/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
index 7341f30..c65e5c46 100644
--- a/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
+++ b/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
@@ -615,7 +615,8 @@
   // Switch back to the first tab.  The user text should be cleared, and the
   // omnibox should have the new URL.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(ASCIIToUTF16(url2.spec()), omnibox_view->GetText());
 }
 
@@ -1083,7 +1084,8 @@
 
   // Switch back to the first tab.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   // Make sure we're still in keyword mode.
   ASSERT_TRUE(omnibox_view->model()->is_keyword_selected());
@@ -1095,9 +1097,11 @@
 
   // Switch to the second tab and back to the first.
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   // Make sure we're still in keyword mode.
   ASSERT_TRUE(omnibox_view->model()->is_keyword_selected());
diff --git a/chrome/browser/ui/signin_view_controller.cc b/chrome/browser/ui/signin_view_controller.cc
index 6e0a7ee..5183ad8 100644
--- a/chrome/browser/ui/signin_view_controller.cc
+++ b/chrome/browser/ui/signin_view_controller.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/ui/signin_modal_dialog.h"
 #include "chrome/browser/ui/signin_modal_dialog_impl.h"
 #include "chrome/browser/ui/signin_view_controller_delegate.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/webui/signin/signin_utils.h"
 #include "components/signin/public/base/consent_level.h"
 #include "components/signin/public/base/signin_buildflags.h"
@@ -372,8 +373,10 @@
           signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS) {
         // Extensions do not activate the tab to prevent misbehaving
         // extensions to keep focusing the signin tab.
-        tab_strip->ActivateTabAt(dice_tab_index,
-                                 {TabStripModel::GestureType::kOther});
+        tab_strip->ActivateTabAt(
+            dice_tab_index,
+            TabStripUserGestureDetails(
+                TabStripUserGestureDetails::GestureType::kOther));
       }
       // Do not create a new signin tab, because there is already one.
       return;
diff --git a/chrome/browser/ui/tabs/hover_tab_selector.cc b/chrome/browser/ui/tabs/hover_tab_selector.cc
index b16d118d..1916294 100644
--- a/chrome/browser/ui/tabs/hover_tab_selector.cc
+++ b/chrome/browser/ui/tabs/hover_tab_selector.cc
@@ -10,6 +10,7 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 
 HoverTabSelector::HoverTabSelector(TabStripModel* tab_strip_model)
     : tab_strip_model_(tab_strip_model), tab_transition_tab_index_(-1) {
@@ -48,7 +49,9 @@
 void HoverTabSelector::PerformTabTransition() {
   DCHECK(tab_transition_tab_index_ >= 0 &&
          tab_transition_tab_index_ < tab_strip_model_->count());
-  tab_strip_model_->ActivateTabAt(tab_transition_tab_index_,
-                                  {TabStripModel::GestureType::kOther});
+  tab_strip_model_->ActivateTabAt(
+      tab_transition_tab_index_,
+      TabStripUserGestureDetails(
+          TabStripUserGestureDetails::GestureType::kOther));
 }
 
diff --git a/chrome/browser/ui/tabs/tab_activity_simulator.cc b/chrome/browser/ui/tabs/tab_activity_simulator.cc
index ef66053..c5f6a12 100644
--- a/chrome/browser/ui/tabs/tab_activity_simulator.cc
+++ b/chrome/browser/ui/tabs/tab_activity_simulator.cc
@@ -66,8 +66,9 @@
   // which is what actually triggers TabActivityWatcher to log the change. For
   // a TestWebContents, we must manually call WasHidden(), and do the reverse
   // for the newly activated tab.
-  tab_strip_model->ActivateTabAt(new_index,
-                                 {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      new_index, TabStripUserGestureDetails(
+                     TabStripUserGestureDetails::GestureType::kOther));
   active_contents->WasHidden();
   new_contents->WasShown();
 }
diff --git a/chrome/browser/ui/tabs/tab_menu_model_unittest.cc b/chrome/browser/ui/tabs/tab_menu_model_unittest.cc
index 4a47bd06..2a9df4d4 100644
--- a/chrome/browser/ui/tabs/tab_menu_model_unittest.cc
+++ b/chrome/browser/ui/tabs/tab_menu_model_unittest.cc
@@ -164,7 +164,9 @@
   feed::WebFeedTabHelper* web_feed_tab_helper2 =
       feed::WebFeedTabHelper::FromWebContents(web_contents2);
 
-  tab_strip->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_strip->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   tab_strip->ExtendSelectionTo(2);
 
   // Neither "Follow site" nor "Unfollow site" should be added when there is at
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index 01c5221d..2d1f6c5 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -588,7 +588,8 @@
   }
 }
 
-void TabStripModel::ActivateTabAt(int index, UserGestureDetails user_gesture) {
+void TabStripModel::ActivateTabAt(int index,
+                                  TabStripUserGestureDetails user_gesture) {
   ReentrancyCheck reentrancy_check(&reentrancy_guard_);
 
   CHECK(ContainsIndex(index));
@@ -596,16 +597,17 @@
 
   // Maybe increment count of tabs 'scrubbed' by mouse or key press for
   // histogram data.
-  if (user_gesture.type == GestureType::kMouse ||
-      user_gesture.type == GestureType::kKeyboard) {
+  if (user_gesture.type == TabStripUserGestureDetails::GestureType::kMouse ||
+      user_gesture.type == TabStripUserGestureDetails::GestureType::kKeyboard) {
     constexpr base::TimeDelta kMaxTimeConsideredScrubbing =
         base::Milliseconds(1500);
     base::TimeDelta elapsed_time_since_tab_switch =
         base::TimeTicks::Now() - last_tab_switch_timestamp_;
     if (elapsed_time_since_tab_switch <= kMaxTimeConsideredScrubbing) {
-      if (user_gesture.type == GestureType::kMouse)
+      if (user_gesture.type == TabStripUserGestureDetails::GestureType::kMouse)
         ++tabs_scrubbed_by_mouse_press_count_;
-      else if (user_gesture.type == GestureType::kKeyboard)
+      else if (user_gesture.type ==
+               TabStripUserGestureDetails::GestureType::kKeyboard)
         ++tabs_scrubbed_by_key_press_count_;
     }
   }
@@ -613,16 +615,16 @@
 
   TabSwitchEventLatencyRecorder::EventType event_type;
   switch (user_gesture.type) {
-    case GestureType::kMouse:
+    case TabStripUserGestureDetails::GestureType::kMouse:
       event_type = TabSwitchEventLatencyRecorder::EventType::kMouse;
       break;
-    case GestureType::kKeyboard:
+    case TabStripUserGestureDetails::GestureType::kKeyboard:
       event_type = TabSwitchEventLatencyRecorder::EventType::kKeyboard;
       break;
-    case GestureType::kTouch:
+    case TabStripUserGestureDetails::GestureType::kTouch:
       event_type = TabSwitchEventLatencyRecorder::EventType::kTouch;
       break;
-    case GestureType::kWheel:
+    case TabStripUserGestureDetails::GestureType::kWheel:
       event_type = TabSwitchEventLatencyRecorder::EventType::kWheel;
       break;
     default:
@@ -633,11 +635,12 @@
                                                         event_type);
   ui::ListSelectionModel new_model = selection_model_;
   new_model.SetSelectedIndex(index);
-  SetSelection(std::move(new_model),
-               user_gesture.type != GestureType::kNone
-                   ? TabStripModelObserver::CHANGE_REASON_USER_GESTURE
-                   : TabStripModelObserver::CHANGE_REASON_NONE,
-               /*triggered_by_other_operation=*/false);
+  SetSelection(
+      std::move(new_model),
+      user_gesture.type != TabStripUserGestureDetails::GestureType::kNone
+          ? TabStripModelObserver::CHANGE_REASON_USER_GESTURE
+          : TabStripModelObserver::CHANGE_REASON_NONE,
+      /*triggered_by_other_operation=*/false);
 }
 
 void TabStripModel::RecordTabScrubbingMetrics() {
@@ -1106,15 +1109,15 @@
             CLOSE_CREATE_HISTORICAL_TAB | CLOSE_USER_GESTURE);
 }
 
-void TabStripModel::SelectNextTab(UserGestureDetails detail) {
+void TabStripModel::SelectNextTab(TabStripUserGestureDetails detail) {
   SelectRelativeTab(TabRelativeDirection::kNext, detail);
 }
 
-void TabStripModel::SelectPreviousTab(UserGestureDetails detail) {
+void TabStripModel::SelectPreviousTab(TabStripUserGestureDetails detail) {
   SelectRelativeTab(TabRelativeDirection::kPrevious, detail);
 }
 
-void TabStripModel::SelectLastTab(UserGestureDetails detail) {
+void TabStripModel::SelectLastTab(TabStripUserGestureDetails detail) {
   ActivateTabAt(count() - 1, detail);
 }
 
@@ -2104,7 +2107,7 @@
 }
 
 void TabStripModel::SelectRelativeTab(TabRelativeDirection direction,
-                                      UserGestureDetails detail) {
+                                      TabStripUserGestureDetails detail) {
   // This may happen during automated testing or if a user somehow buffers
   // many key accelerators.
   if (contents_data_.empty())
diff --git a/chrome/browser/ui/tabs/tab_strip_model.h b/chrome/browser/ui/tabs/tab_strip_model.h
index 7f9077b..d6e9a27 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.h
+++ b/chrome/browser/ui/tabs/tab_strip_model.h
@@ -24,6 +24,7 @@
 #include "base/timer/timer.h"
 #include "build/build_config.h"
 #include "chrome/browser/ui/tabs/tab_group_controller.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/tabs/tab_switch_event_latency_recorder.h"
 #include "components/sessions/core/session_id.h"
 #include "components/tab_groups/tab_group_id.h"
@@ -302,35 +303,14 @@
   // Detaches the WebContents at the specified index and immediately deletes it.
   void DetachAndDeleteWebContentsAt(int index);
 
-  // User gesture type that triggers ActivateTabAt. kNone indicates that it was
-  // not triggered by a user gesture, but by a by-product of some other action.
-  enum class GestureType {
-    kMouse,
-    kTouch,
-    kWheel,
-    kKeyboard,
-    kOther,
-    kTabMenu,
-    kNone
-  };
-
-  // Encapsulates user gesture information for tab activation
-  struct UserGestureDetails {
-    UserGestureDetails(GestureType type,
-                       base::TimeTicks time_stamp = base::TimeTicks::Now())
-        : type(type), time_stamp(time_stamp) {}
-
-    GestureType type;
-    base::TimeTicks time_stamp;
-  };
-
   // Makes the tab at the specified index the active tab. |gesture_detail.type|
   // contains the gesture type that triggers the tab activation.
   // |gesture_detail.time_stamp| contains the timestamp of the user gesture, if
   // any.
-  void ActivateTabAt(int index,
-                     UserGestureDetails gesture_detail =
-                         UserGestureDetails(GestureType::kNone));
+  void ActivateTabAt(
+      int index,
+      TabStripUserGestureDetails gesture_detail = TabStripUserGestureDetails(
+          TabStripUserGestureDetails::GestureType::kNone));
 
   // Report histogram metrics for the number of tabs 'scrubbed' within a given
   // interval of time. Scrubbing is considered to be a tab activated for <= 1.5
@@ -493,13 +473,16 @@
 
   // Select adjacent tabs
   void SelectNextTab(
-      UserGestureDetails detail = UserGestureDetails(GestureType::kOther));
+      TabStripUserGestureDetails detail = TabStripUserGestureDetails(
+          TabStripUserGestureDetails::GestureType::kOther));
   void SelectPreviousTab(
-      UserGestureDetails detail = UserGestureDetails(GestureType::kOther));
+      TabStripUserGestureDetails detail = TabStripUserGestureDetails(
+          TabStripUserGestureDetails::GestureType::kOther));
 
   // Selects the last tab in the tab strip.
   void SelectLastTab(
-      UserGestureDetails detail = UserGestureDetails(GestureType::kOther));
+      TabStripUserGestureDetails detail = TabStripUserGestureDetails(
+          TabStripUserGestureDetails::GestureType::kOther));
 
   // Moves the active in the specified direction. Respects group boundaries.
   void MoveTabNext();
@@ -800,7 +783,7 @@
 
   // Selects either the next tab (kNext), or the previous tab (kPrevious).
   void SelectRelativeTab(TabRelativeDirection direction,
-                         UserGestureDetails detail);
+                         TabStripUserGestureDetails detail);
 
   // Moves the active tabs into the next slot (kNext), or the
   // previous slot (kPrevious). Respects group boundaries and creates
diff --git a/chrome/browser/ui/tabs/tab_strip_model_stats_recorder_unittest.cc b/chrome/browser/ui/tabs/tab_strip_model_stats_recorder_unittest.cc
index 29e1b08..f30819a1 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_stats_recorder_unittest.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model_stats_recorder_unittest.cc
@@ -50,7 +50,8 @@
 
   // Reactivate the first tab.
   tabstrip.ActivateTabAt(tabstrip.GetIndexOfWebContents(raw_contents1),
-                         {TabStripModel::GestureType::kOther});
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   tester.ExpectUniqueSample(
       "Tabs.StateTransfer.Target_Active",
@@ -133,7 +134,9 @@
       static_cast<int>(TabStripModelStatsRecorder::TabState::ACTIVE), 1);
 
   // Switch to the first tab in strip 2.
-  tabstrip2.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tabstrip2.ActivateTabAt(0,
+                          TabStripUserGestureDetails(
+                              TabStripUserGestureDetails::GestureType::kOther));
   tester.ExpectUniqueSample(
       "Tabs.StateTransfer.Target_Active",
       static_cast<int>(TabStripModelStatsRecorder::TabState::INACTIVE), 4);
@@ -178,7 +181,8 @@
 
   // Reactivate the first tab
   tabstrip.ActivateTabAt(tabstrip.GetIndexOfWebContents(raw_contents0),
-                         {TabStripModel::GestureType::kOther});
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   tester.ExpectUniqueSample(
       "Tabs.StateTransfer.NumberOfOtherTabsActivatedBeforeMadeActive", 9, 1);
@@ -213,13 +217,18 @@
 
   // Switch between tabs {0,1} for 5 times, then switch to tab 2
   for (int i = 0; i < 5; ++i) {
-    tabstrip.ActivateTabAt(tabstrip.GetIndexOfWebContents(raw_contents0),
-                           {TabStripModel::GestureType::kOther});
-    tabstrip.ActivateTabAt(tabstrip.GetIndexOfWebContents(raw_contents1),
-                           {TabStripModel::GestureType::kOther});
+    tabstrip.ActivateTabAt(
+        tabstrip.GetIndexOfWebContents(raw_contents0),
+        TabStripUserGestureDetails(
+            TabStripUserGestureDetails::GestureType::kOther));
+    tabstrip.ActivateTabAt(
+        tabstrip.GetIndexOfWebContents(raw_contents1),
+        TabStripUserGestureDetails(
+            TabStripUserGestureDetails::GestureType::kOther));
   }
   tabstrip.ActivateTabAt(tabstrip.GetIndexOfWebContents(raw_contents2),
-                         {TabStripModel::GestureType::kOther});
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   EXPECT_THAT(
       tester.GetAllSamples(
diff --git a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
index b4796ac6..5ff2b5a 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model_unittest.cc
@@ -27,6 +27,7 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/tabs/tab_group.h"
 #include "chrome/browser/ui/tabs/tab_group_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/tabs/tab_utils.h"
 #include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
@@ -539,7 +540,9 @@
 
   // Test ActivateTabAt
   {
-    tabstrip.ActivateTabAt(2, {TabStripModel::GestureType::kOther});
+    tabstrip.ActivateTabAt(
+        2, TabStripUserGestureDetails(
+               TabStripUserGestureDetails::GestureType::kOther));
     EXPECT_EQ(3, observer.GetStateCount());
     State s1(raw_contents2, 1, MockTabStripModelObserver::DEACTIVATE);
     observer.ExpectStateEquals(0, s1);
@@ -682,7 +685,9 @@
   // Test SelectNextTab, SelectPreviousTab, SelectLastTab
   {
     // Make sure the second of the two tabs is selected first...
-    tabstrip.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+    tabstrip.ActivateTabAt(
+        1, TabStripUserGestureDetails(
+               TabStripUserGestureDetails::GestureType::kOther));
     tabstrip.SelectPreviousTab();
     EXPECT_EQ(0, tabstrip.active_index());
     tabstrip.SelectLastTab();
@@ -925,7 +930,8 @@
   // Now select it, so that GestureType != kNone causes the opener
   // relationship to be forgotten...
   tabstrip.ActivateTabAt(tabstrip.count() - 1,
-                         {TabStripModel::GestureType::kOther});
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(tabstrip.count() - 1, tabstrip.active_index());
   EXPECT_EQ(raw_fg_nonlink_contents, tabstrip.GetActiveWebContents());
 
@@ -975,7 +981,9 @@
   EXPECT_EQ(raw_opener1, tabstrip.GetOpenerOfWebContentsAt(2));
 
   // Activate the parent tab again.
-  tabstrip.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(0,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(1, GetID(tabstrip.GetActiveWebContents()));
 
   // Open another link in a new background tab.
@@ -1017,7 +1025,9 @@
   EXPECT_EQ(1, tabstrip.GetIndexOfLastWebContentsOpenedBy(raw_opener1, 0));
 
   // Activate the child tab:
-  tabstrip.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(1,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(11, GetID(tabstrip.GetActiveWebContents()));
 
   // Open a link in a new background grandchild tab.
@@ -1032,7 +1042,9 @@
   EXPECT_EQ(2, tabstrip.GetIndexOfLastWebContentsOpenedBy(raw_child11, 1));
 
   // Activate the parent tab again:
-  tabstrip.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(0,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(1, GetID(tabstrip.GetActiveWebContents()));
 
   // Open another link in a new background child tab (a sibling of child11).
@@ -1099,7 +1111,9 @@
   ASSERT_EQ(0, tabstrip.active_index());
 
   tabstrip.ForgetAllOpeners();
-  tabstrip.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(1,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_EQ(1, tabstrip.active_index());
 
   tabstrip.CloseWebContentsAt(1, TabStripModel::CLOSE_NONE);
@@ -1130,7 +1144,9 @@
 
   tabstrip.ForgetAllOpeners();
   tabstrip.AddToNewGroup({0, 1, 2});
-  tabstrip.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(1,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_EQ(1, tabstrip.active_index());
 
   tabstrip.CloseWebContentsAt(1, TabStripModel::CLOSE_NONE);
@@ -1160,7 +1176,9 @@
 
   tabstrip.ForgetAllOpeners();
   tab_groups::TabGroupId group = tabstrip.AddToNewGroup({0, 1});
-  tabstrip.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(1,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_EQ(1, tabstrip.active_index());
 
   absl::optional<int> next_active =
@@ -1188,7 +1206,9 @@
 
   tabstrip.ForgetAllOpeners();
   tab_groups::TabGroupId group = tabstrip.AddToNewGroup({2, 3});
-  tabstrip.ActivateTabAt(2, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(2,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_EQ(2, tabstrip.active_index());
 
   absl::optional<int> next_active =
@@ -1216,7 +1236,9 @@
 
   tabstrip.ForgetAllOpeners();
   tab_groups::TabGroupId group = tabstrip.AddToNewGroup({0, 1, 2, 3});
-  tabstrip.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(1,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_EQ(1, tabstrip.active_index());
 
   absl::optional<int> next_active =
@@ -1242,7 +1264,9 @@
                       CreateWebContents());
   ASSERT_EQ(0, tabstrip.active_index());
 
-  tabstrip.ActivateTabAt(2, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(2,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_EQ(2, tabstrip.active_index());
 
   tabstrip.CloseWebContentsAt(2, TabStripModel::CLOSE_NONE);
@@ -1538,7 +1562,9 @@
   EXPECT_EQ(5, tabstrip.count());
 
   int dummy_index = tabstrip.count() - 1;
-  tabstrip.ActivateTabAt(dummy_index, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(dummy_index,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(raw_dummy, tabstrip.GetActiveWebContents());
 
   tabstrip.ExecuteContextMenuCommand(dummy_index,
@@ -1624,7 +1650,9 @@
   EXPECT_EQ(2, tabstrip.count());
 
   // Re-select the home page.
-  tabstrip.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(0,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   // Open a bunch of tabs by simulating middle clicking on links on the home
   // page.
@@ -1654,7 +1682,9 @@
   // is constructed to start at the middle WebContents in the tree to make sure
   // the cursor wraps around to the first WebContents in the tree before
   // closing the opener or any other WebContents.
-  tabstrip.ActivateTabAt(2, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(2,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   tabstrip.CloseSelectedTabs();
   EXPECT_EQ(raw_middle_click_contents3, tabstrip.GetActiveWebContents());
   tabstrip.CloseSelectedTabs();
@@ -1694,7 +1724,9 @@
   EXPECT_EQ(2, tabstrip.count());
 
   // Re-select the home page.
-  tabstrip.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(0,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   // Open a tab by simulating a left click on a link that opens in a new tab.
   std::unique_ptr<WebContents> left_click_contents = CreateWebContents();
@@ -1746,7 +1778,9 @@
   EXPECT_EQ(2, tabstrip.count());
 
   // Re-select the home page.
-  tabstrip.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(0,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   // Open a new blank tab in the foreground.
   std::unique_ptr<WebContents> new_blank_contents = CreateWebContents();
@@ -1806,7 +1840,9 @@
   EXPECT_EQ(2, tabstrip.count());
 
   // Re-select the first tab (home page).
-  tabstrip.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(0,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   // Open a bunch of tabs by simulating middle clicking on links on the home
   // page.
@@ -1829,7 +1865,9 @@
   EXPECT_EQ(raw_typed_page_contents, tabstrip.GetActiveWebContents());
 
   // Step back into the context by selecting a tab inside it.
-  tabstrip.ActivateTabAt(2, {TabStripModel::GestureType::kOther});
+  tabstrip.ActivateTabAt(2,
+                         TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(raw_middle_click_contents2, tabstrip.GetActiveWebContents());
 
   // Now test that closing tabs selects to the right until there are no more,
@@ -1970,7 +2008,8 @@
                        ui::PAGE_TRANSITION_LINK, TabStripModel::ADD_NONE);
 
   // Select page A.A
-  strip.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(1, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(raw_page_a_a_contents, strip.GetActiveWebContents());
 
   // Simulate a middle click to open page A.A.A
@@ -2019,7 +2058,8 @@
   }
 
   // Switch to page B's tab.
-  strip.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(1, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   // Open a New Tab at the end of the strip (simulate Ctrl+T)
   std::unique_ptr<WebContents> new_contents = CreateWebContents();
@@ -2106,7 +2146,8 @@
                        TabStripModel::ADD_NONE);
 
   // Tell the TabStripModel that we are navigating page D via a link click.
-  strip.ActivateTabAt(3, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(3, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   strip.TabNavigating(raw_page_d_contents, ui::PAGE_TRANSITION_LINK);
 
   // Close page D, page C should be selected. (part of same opener tree).
@@ -2148,7 +2189,8 @@
   strip.AddWebContents(std::move(page_d_contents), -1, ui::PAGE_TRANSITION_LINK,
                        TabStripModel::ADD_NONE);
 
-  strip.ActivateTabAt(2, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(2, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   // TEST 1: A tab in the middle of a bunch of tabs is active and the user opens
   // a new tab at the end of the strip. Closing that new tab will select the tab
@@ -2181,10 +2223,13 @@
                        TabStripModel::ADD_ACTIVE);
 
   // Now select the first tab.
-  strip.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(0, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   // Now select the last tab.
-  strip.ActivateTabAt(strip.count() - 1, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(strip.count() - 1,
+                      TabStripUserGestureDetails(
+                          TabStripUserGestureDetails::GestureType::kOther));
 
   // Now close the last tab. The next adjacent should be selected.
   strip.CloseWebContentsAt(strip.count() - 1, TabStripModel::CLOSE_NONE);
@@ -2192,7 +2237,8 @@
 
   // TEST 3: As above, but the user does multiple navigations and thus the tab's
   // opener relationship is forgotten.
-  strip.ActivateTabAt(2, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(2, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   // Open a new tab but navigate away from the new tab page.
   new_tab_contents = CreateWebContents();
@@ -2843,7 +2889,8 @@
   EXPECT_EQ(raw_page_b1_contents, strip.GetWebContentsAt(4));
 
   // Switch to A.
-  strip.ActivateTabAt(2, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(2, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(raw_page_a_contents, strip.GetActiveWebContents());
 
   // Open page A2 in the background from A. It should end up after A1.
@@ -2890,7 +2937,8 @@
   strip.AddObserver(&observer);
 
   // Selection and active tab change.
-  strip.ActivateTabAt(3, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(3, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_EQ(2, observer.GetStateCount());
   ASSERT_EQ(observer.GetStateAt(0).action, MockTabStripModelObserver::ACTIVATE);
   State s1(raw_contents3, 3, MockTabStripModelObserver::SELECT);
@@ -2988,7 +3036,8 @@
   strip.AddObserver(&observer);
   // This changes the selection (0 is no longer selected) but the selected_index
   // still remains at 1.
-  strip.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(1, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   ASSERT_EQ(1, observer.GetStateCount());
   State s(raw_contents2, 1, MockTabStripModelObserver::SELECT);
   s.src_index = 1;
@@ -3148,7 +3197,8 @@
                        TabStripModel::ADD_ACTIVE | TabStripModel::ADD_PINNED);
 
   // Activate the first tab (a).
-  strip.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(0, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   // Open two more tabs as link clicks. The first tab, c, should appear after
   // the pinned tabs followed by the second tab (d).
@@ -3180,7 +3230,8 @@
   strip.AppendWebContents(CreateWebContents(), false);
   strip.AddObserver(&observer);
 
-  strip.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(1, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(1, strip.active_index());
 
   strip.MoveWebContentsAt(2, 3, true);
@@ -3196,7 +3247,8 @@
 
   EXPECT_FALSE(strip.GetTabGroupForTab(0).has_value());
 
-  strip.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(0, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   strip.CloseAllTabs();
 }
 
@@ -3211,7 +3263,8 @@
 
   EXPECT_TRUE(strip.GetTabGroupForTab(0).has_value());
 
-  strip.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(0, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   strip.CloseAllTabs();
 }
 
@@ -4271,13 +4324,16 @@
   ASSERT_FALSE(has_tab_switch_start_time(1));
 
   // ActivateTabAt should only update the start time if the active tab changes.
-  strip.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(1, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_FALSE(has_tab_switch_start_time(0));
   EXPECT_FALSE(has_tab_switch_start_time(1));
-  strip.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(0, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(has_tab_switch_start_time(0));
   EXPECT_FALSE(has_tab_switch_start_time(1));
-  strip.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  strip.ActivateTabAt(1, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(has_tab_switch_start_time(0));
   EXPECT_TRUE(has_tab_switch_start_time(1));
 }
diff --git a/chrome/browser/ui/tabs/tab_strip_user_gesture_details.h b/chrome/browser/ui/tabs/tab_strip_user_gesture_details.h
new file mode 100644
index 0000000..5f5891e
--- /dev/null
+++ b/chrome/browser/ui/tabs/tab_strip_user_gesture_details.h
@@ -0,0 +1,33 @@
+// Copyright 2022 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_TABS_TAB_STRIP_USER_GESTURE_DETAILS_H_
+#define CHROME_BROWSER_UI_TABS_TAB_STRIP_USER_GESTURE_DETAILS_H_
+
+#include "base/time/time.h"
+
+// Encapsulates user gesture information for tab activation
+struct TabStripUserGestureDetails {
+  // User gesture type that triggers ActivateTabAt. kNone indicates that it was
+  // not triggered by a user gesture, but by a by-product of some other action.
+  enum class GestureType {
+    kMouse,
+    kTouch,
+    kWheel,
+    kKeyboard,
+    kOther,
+    kTabMenu,
+    kNone
+  };
+
+  explicit TabStripUserGestureDetails(
+      GestureType type,
+      base::TimeTicks time_stamp = base::TimeTicks::Now())
+      : type(type), time_stamp(time_stamp) {}
+
+  GestureType type;
+  base::TimeTicks time_stamp;
+};
+
+#endif  // CHROME_BROWSER_UI_TABS_TAB_STRIP_USER_GESTURE_DETAILS_H_
diff --git a/chrome/browser/ui/user_education/active_tab_tracker_unittest.cc b/chrome/browser/ui/user_education/active_tab_tracker_unittest.cc
index e9cd96b..77b0e6f22 100644
--- a/chrome/browser/ui/user_education/active_tab_tracker_unittest.cc
+++ b/chrome/browser/ui/user_education/active_tab_tracker_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/test/mock_callback.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/browser/web_contents.h"
@@ -80,7 +81,8 @@
   AddTab(&model);
   clock()->Advance(kTimeStep);
 
-  model.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  model.ActivateTabAt(0, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   clock()->Advance(kTimeStep);
 
@@ -102,12 +104,15 @@
 
   AddTab(&model);
   AddTab(&model);
-  model.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  model.ActivateTabAt(0, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   clock()->Advance(kTimeStep);
 
-  model.ActivateTabAt(1, {TabStripModel::GestureType::kOther});
-  model.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  model.ActivateTabAt(1, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
+  model.ActivateTabAt(0, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   EXPECT_CALL(cb, Run(&model, base::TimeDelta())).Times(1);
   CloseTabAt(&model, 0);
@@ -127,7 +132,8 @@
 
   AddTab(&model);
   AddTab(&model);
-  model.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  model.ActivateTabAt(0, TabStripUserGestureDetails(
+                             TabStripUserGestureDetails::GestureType::kOther));
 
   EXPECT_CALL(cb, Run(_, _)).Times(0);
   CloseTabAt(&model, 1);
@@ -153,10 +159,14 @@
   AddTab(&model_2);
 
   clock()->Advance(kTimeStep);
-  model_1.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  model_1.ActivateTabAt(0,
+                        TabStripUserGestureDetails(
+                            TabStripUserGestureDetails::GestureType::kOther));
 
   clock()->Advance(kTimeStep);
-  model_2.ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  model_2.ActivateTabAt(0,
+                        TabStripUserGestureDetails(
+                            TabStripUserGestureDetails::GestureType::kOther));
 
   {
     InSequence seq;
diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
index ff0fe4b..36f72b7 100644
--- a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
+++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura_ash.cc
@@ -536,7 +536,9 @@
   // is no need for immersive mode.
   // TODO(crbug.com/801619): This adds a little extra animation
   // when minimizing or unminimizing window.
-  return ash::TabletMode::IsInTabletMode() && CanResize() && !IsMinimized();
+  return ash::TabletMode::IsInTabletMode() && CanResize() && !IsMinimized() &&
+         GetNativeWindow()->GetProperty(chromeos::kWindowStateTypeKey) !=
+             chromeos::WindowStateType::kFloated;
 }
 
 void ChromeNativeAppWindowViewsAuraAsh::UpdateImmersiveMode() {
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_browsertest.cc b/chrome/browser/ui/views/autofill/payments/local_card_migration_browsertest.cc
index 0b00cdf..e381bff 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_browsertest.cc
@@ -1115,14 +1115,18 @@
                                      ui::PAGE_TRANSITION_TYPED,
                                      /*check_navigation_success=*/true));
   TabStripModel* tab_model = GetBrowser(0)->tab_strip_model();
-  tab_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   WaitForAnimationToComplete();
 
   // Ensures bubble and icon go away if user navigates to another tab.
   EXPECT_FALSE(GetLocalCardMigrationIconView()->GetVisible());
   EXPECT_FALSE(GetLocalCardMigrationOfferBubbleViews());
 
-  tab_model->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_model->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   WaitForAnimationToComplete();
 
   // If the user navigates back, shows only the icon not the bubble.
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
index ed15b4e..88a8896 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
@@ -2149,14 +2149,18 @@
                                      ui::PAGE_TRANSITION_TYPED,
                                      /*check_navigation_success=*/true));
   TabStripModel* tab_model = GetBrowser(0)->tab_strip_model();
-  tab_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   WaitForAnimationToEnd();
 
   // Ensures bubble and icon go away if user navigates to another tab.
   EXPECT_FALSE(GetSaveCardIconView()->GetVisible());
   EXPECT_FALSE(GetSaveCardBubbleViews());
 
-  tab_model->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_model->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   WaitForAnimationToEnd();
 
   // If the user navigates back, shows only the icon not the bubble.
diff --git a/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view_browsertest.cc b/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view_browsertest.cc
index 66ef11f..e9013d7 100644
--- a/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_request_access_dialog_view_browsertest.cc
@@ -38,6 +38,9 @@
       extensions_features::kExtensionsMenuAccessControl};
 };
 
-IN_PROC_BROWSER_TEST_F(ExtensionsRequestAccessDialogViewBrowserTest, InvokeUi) {
+// TODO(crbug.com/1339738): Flaky on win-clang and win/win64 trunk builds. 
+// ExtensionsRequestAccessDialog may not longer be used, wait to see if the class is
+// deleted before fixing this.
+IN_PROC_BROWSER_TEST_F(ExtensionsRequestAccessDialogViewBrowserTest, DISABLED_InvokeUi) {
   ShowAndVerifyUi();
 }
diff --git a/chrome/browser/ui/views/find_bar_views_interactive_uitest.cc b/chrome/browser/ui/views/find_bar_views_interactive_uitest.cc
index bddfa4f..72da2b4 100644
--- a/chrome/browser/ui/views/find_bar_views_interactive_uitest.cc
+++ b/chrome/browser/ui/views/find_bar_views_interactive_uitest.cc
@@ -127,7 +127,8 @@
 
   // Select tab A.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   // Close tab B.
   browser()->tab_strip_model()->CloseWebContentsAt(1,
@@ -368,13 +369,15 @@
 
   // Select tab A. Find bar should select "bc".
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(IsViewFocused(browser(), VIEW_ID_FIND_IN_PAGE_TEXT_FIELD));
   EXPECT_EQ(u"bc", GetFindBarSelectedText());
 
   // Select tab B. Find bar should select "de".
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(IsViewFocused(browser(), VIEW_ID_FIND_IN_PAGE_TEXT_FIELD));
   EXPECT_EQ(u"de", GetFindBarSelectedText());
 }
@@ -419,13 +422,15 @@
 
   // Select tab A. Find bar should get focus.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(IsViewFocused(browser(), VIEW_ID_FIND_IN_PAGE_TEXT_FIELD));
   EXPECT_EQ(u"a", GetFindBarSelectedText());
 
   // Select tab B. Location bar should get focus.
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(IsViewFocused(browser(), VIEW_ID_OMNIBOX));
 }
 
@@ -451,7 +456,8 @@
 
   // Select tab A. Find bar should get focus.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(IsViewFocused(browser(), VIEW_ID_FIND_IN_PAGE_TEXT_FIELD));
 
   // Dismiss the Find box. Focus should go to the content view.
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc
index a641263..37d0add 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc
@@ -9,7 +9,6 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/scoped_disable_client_side_decorations_for_test.h"
-#include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/page_action/page_action_icon_type.h"
@@ -31,7 +30,6 @@
 #include "content/public/test/theme_change_waiter.h"
 #include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
-#include "ui/base/theme_provider.h"
 #include "ui/color/color_id.h"
 #include "ui/color/color_provider.h"
 
@@ -179,10 +177,9 @@
   // color to the system color (not the app theme color); otherwise the title
   // and border would clash horribly with the GTK title bar.
   // (https://crbug.com/878636)
-  const ui::ThemeProvider* theme_provider =
-      GetAppFrameView()->GetThemeProvider();
-  const SkColor frame_color =
-      theme_provider->GetColor(ThemeProperties::COLOR_FRAME_ACTIVE);
+  const ui::ColorProvider* color_provider =
+      GetAppFrameView()->GetColorProvider();
+  const SkColor frame_color = color_provider->GetColor(ui::kColorFrameActive);
   EXPECT_EQ(frame_color,
             GetAppFrameView()->GetFrameColor(BrowserFrameActiveState::kActive));
 #else
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc
index 89dccc5..ab8871d 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc
@@ -632,25 +632,14 @@
   if (web_app_frame_toolbar())
     web_app_frame_toolbar()->SetVisible(should_show_caption_buttons);
 
-  if (enabled) {
-    // Enter immersive mode if the feature is enabled and the widget is not
-    // already in fullscreen mode. Popups that are not activated but not
-    // minimized are still put in immersive mode, since they may still be
-    // visible but not activated due to something transparent and/or not
-    // fullscreen (ie. fullscreen launcher).
-    if (!frame()->IsFullscreen() && !browser_view()->GetSupportsTabStrip() &&
-        !frame()->IsMinimized()) {
-      browser_view()->immersive_mode_controller()->SetEnabled(true);
-      return;
-    }
-  } else {
-    // Exit immersive mode if the feature is enabled and the widget is not in
-    // fullscreen mode.
-    if (!frame()->IsFullscreen() && !browser_view()->GetSupportsTabStrip()) {
-      browser_view()->immersive_mode_controller()->SetEnabled(false);
-      return;
-    }
-  }
+  ImmersiveModeController* immersive_mode_controller =
+      browser_view()->immersive_mode_controller();
+  const bool was_enabled = immersive_mode_controller->IsEnabled();
+  immersive_mode_controller->SetEnabled(ShouldEnableImmersiveModeController());
+
+  // Do not relayout if immersive mode has not changed.
+  if (was_enabled == immersive_mode_controller->IsEnabled())
+    return;
 
   InvalidateLayout();
   // Can be null in tests.
@@ -699,6 +688,27 @@
       ResetWindowControls();
   }
 
+  if (key == chromeos::kWindowStateTypeKey) {
+    if (!chromeos::TabletState::Get()->InTabletMode())
+      return;
+
+    // Update the window controls if we are entering or exiting float state.
+    const bool enter_floated = IsFloated();
+    const bool exit_floated = static_cast<chromeos::WindowStateType>(old) ==
+                              chromeos::WindowStateType::kFloated;
+    if (!enter_floated && !exit_floated)
+      return;
+
+    ResetWindowControls();
+
+    // Additionally updates immersive mode for PWA/SWA so that we show the title
+    // bar when floated, and hide the title bar otherwise.
+    browser_view()->immersive_mode_controller()->SetEnabled(
+        ShouldEnableImmersiveModeController());
+
+    return;
+  }
+
   if (key == chromeos::kIsShowingInOverviewKey) {
     OnAddedToOrRemovedFromOverview();
     return;
@@ -794,8 +804,11 @@
 
 bool BrowserNonClientFrameViewChromeOS::GetShowCaptionButtonsWhenNotInOverview()
     const {
-  return UsePackagedAppHeaderStyle(browser_view()->browser()) ||
-         !chromeos::TabletState::Get()->InTabletMode();
+  if (UsePackagedAppHeaderStyle(browser_view()->browser()))
+    return true;
+  if (!chromeos::TabletState::Get()->InTabletMode())
+    return true;
+  return IsFloated();
 }
 
 int BrowserNonClientFrameViewChromeOS::GetToolbarLeftInset() const {
@@ -823,6 +836,10 @@
 }
 
 bool BrowserNonClientFrameViewChromeOS::GetShouldPaint() const {
+  // Floated windows show their frame as they need to be dragged or hidden.
+  if (IsFloated())
+    return true;
+
 #if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
   // Normal windows that have a WebUI-based tab strip do not need a browser
   // frame as no tab strip is drawn on top of the browser frame.
@@ -1049,6 +1066,32 @@
       theme_changed_animation_callback_.callback());
 }
 
+bool BrowserNonClientFrameViewChromeOS::IsFloated() const {
+  return GetFrameWindow()->GetProperty(chromeos::kWindowStateTypeKey) ==
+         chromeos::WindowStateType::kFloated;
+}
+
+bool BrowserNonClientFrameViewChromeOS::ShouldEnableImmersiveModeController()
+    const {
+  if (chromeos::TabletState::Get()->InTabletMode()) {
+    // Tabbed browsers do not support immersive mode in tablet mode. We use the
+    // web ui touchable tabstrip, which has its own sliding mechanism to view
+    // the tabs.
+    if (browser_view()->GetSupportsTabStrip())
+      return false;
+
+    // No immersive mode for minimized windows as they aren't visible, and
+    // floated windows need a permanent header to drag.
+    if (frame()->IsMinimized() || IsFloated())
+      return false;
+
+    return true;
+  }
+
+  // In clamshell mode, we want immersive mode if fullscreen.
+  return frame()->IsFullscreen();
+}
+
 const aura::Window* BrowserNonClientFrameViewChromeOS::GetFrameWindow() const {
   return const_cast<BrowserNonClientFrameViewChromeOS*>(this)->GetFrameWindow();
 }
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h
index 9433a9e..48e0e2d 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h
@@ -143,8 +143,6 @@
   FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewChromeOSTest,
                            BrowserHeaderVisibilityInTabletModeTest);
   FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewChromeOSTest,
-                           AppHeaderVisibilityInTabletModeTest);
-  FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewChromeOSTest,
                            ImmersiveModeTopViewInset);
   FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewChromeOSTest,
                            ToggleTabletModeOnMinimizedWindow);
@@ -152,6 +150,8 @@
                            ActiveStateOfButtonMatchesWidget);
   FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewChromeOSTest,
                            RestoreMinimizedBrowserUpdatesCaption);
+  FRIEND_TEST_ALL_PREFIXES(FloatBrowserNonClientFrameViewChromeOSTest,
+                           AppHeaderVisibilityInTabletModeTest);
   FRIEND_TEST_ALL_PREFIXES(ImmersiveModeControllerChromeosWebAppBrowserTest,
                            FrameLayoutToggleTabletMode);
   FRIEND_TEST_ALL_PREFIXES(HomeLauncherBrowserNonClientFrameViewChromeOSTest,
@@ -234,6 +234,12 @@
   // Called any time the theme has changed and may need to be animated.
   void MaybeAnimateThemeChanged();
 
+  // Returns whether the associated window is currently floated or not.
+  bool IsFloated() const;
+
+  // Helper to check whether we should enable immersive mode.
+  bool ShouldEnableImmersiveModeController() const;
+
   // Returns the top level aura::Window for this browser window.
   const aura::Window* GetFrameWindow() const;
   aura::Window* GetFrameWindow();
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc
index 69275cc..0b4831e 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc
@@ -1157,43 +1157,6 @@
                                      ash::SplitViewTestApi::SnapPosition::LEFT);
   EXPECT_FALSE(frame_view->caption_button_container_->GetVisible());
 }
-
-// Test that for a browser app window, its caption buttons may or may not hide
-// in tablet mode.
-IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewChromeOSTest,
-                       AppHeaderVisibilityInTabletModeTest) {
-  // Create a browser app window.
-  Browser::CreateParams params = Browser::CreateParams::CreateForApp(
-      "test_browser_app", true /* trusted_source */, gfx::Rect(),
-      browser()->profile(), true);
-  params.initial_show_state = ui::SHOW_STATE_DEFAULT;
-  Browser* browser2 = Browser::Create(params);
-  AddBlankTabAndShow(browser2);
-  BrowserView* browser_view2 = BrowserView::GetBrowserViewForBrowser(browser2);
-  Widget* widget2 = browser_view2->GetWidget();
-  BrowserNonClientFrameViewChromeOS* frame_view2 =
-      GetFrameViewChromeOS(browser_view2);
-  widget2->GetNativeWindow()->SetProperty(
-      aura::client::kResizeBehaviorKey,
-      aura::client::kResizeBehaviorCanMaximize |
-          aura::client::kResizeBehaviorCanResize);
-  StartOverview();
-  EXPECT_FALSE(frame_view2->caption_button_container_->GetVisible());
-  EndOverview();
-  EXPECT_TRUE(frame_view2->caption_button_container_->GetVisible());
-
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
-  StartOverview();
-  EXPECT_FALSE(frame_view2->caption_button_container_->GetVisible());
-
-  EndOverview();
-  EXPECT_TRUE(frame_view2->caption_button_container_->GetVisible());
-
-  ash::SplitViewTestApi().SnapWindow(
-      widget2->GetNativeWindow(), ash::SplitViewTestApi::SnapPosition::RIGHT);
-  EXPECT_TRUE(frame_view2->caption_button_container_->GetVisible());
-}
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Regression test for https://crbug.com/879851.
@@ -1280,6 +1243,71 @@
   EXPECT_EQ(inset_normal, inset_in_overview_mode);
 }
 
+class FloatBrowserNonClientFrameViewChromeOSTest
+    : public TopChromeMdParamTest<InProcessBrowserTest> {
+ public:
+  FloatBrowserNonClientFrameViewChromeOSTest() = default;
+  FloatBrowserNonClientFrameViewChromeOSTest(
+      const FloatBrowserNonClientFrameViewChromeOSTest&) = delete;
+  FloatBrowserNonClientFrameViewChromeOSTest& operator=(
+      const FloatBrowserNonClientFrameViewChromeOSTest&) = delete;
+  ~FloatBrowserNonClientFrameViewChromeOSTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_{
+      chromeos::wm::features::kFloatWindow};
+};
+
+// Test that for a browser app window, its caption buttons may or may not hide
+// in tablet mode.
+IN_PROC_BROWSER_TEST_P(FloatBrowserNonClientFrameViewChromeOSTest,
+                       AppHeaderVisibilityInTabletModeTest) {
+  // Create a browser app window.
+  Browser::CreateParams params = Browser::CreateParams::CreateForApp(
+      "test_browser_app", /*trusted_source=*/true, gfx::Rect(),
+      browser()->profile(), true);
+  params.initial_show_state = ui::SHOW_STATE_DEFAULT;
+  Browser* browser2 = Browser::Create(params);
+  AddBlankTabAndShow(browser2);
+  BrowserView* browser_view2 = BrowserView::GetBrowserViewForBrowser(browser2);
+  Widget* widget2 = browser_view2->GetWidget();
+  BrowserNonClientFrameViewChromeOS* frame_view2 =
+      GetFrameViewChromeOS(browser_view2);
+  widget2->GetNativeWindow()->SetProperty(
+      aura::client::kResizeBehaviorKey,
+      aura::client::kResizeBehaviorCanMaximize |
+          aura::client::kResizeBehaviorCanResize);
+  StartOverview();
+  EXPECT_FALSE(frame_view2->caption_button_container_->GetVisible());
+  EndOverview();
+  EXPECT_TRUE(frame_view2->caption_button_container_->GetVisible());
+
+  ASSERT_NO_FATAL_FAILURE(
+      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
+  StartOverview();
+  EXPECT_FALSE(frame_view2->caption_button_container_->GetVisible());
+
+  EndOverview();
+  EXPECT_TRUE(frame_view2->caption_button_container_->GetVisible());
+
+  auto* immersive_controller = chromeos::ImmersiveFullscreenController::Get(
+      views::Widget::GetWidgetForNativeView(widget2->GetNativeWindow()));
+
+  // Snap a window. Immersive mode is enabled so its title bar is not visible.
+  ash::SplitViewTestApi().SnapWindow(
+      widget2->GetNativeWindow(), ash::SplitViewTestApi::SnapPosition::RIGHT);
+  EXPECT_TRUE(frame_view2->caption_button_container_->GetVisible());
+  EXPECT_TRUE(immersive_controller->IsEnabled());
+
+  // Float a window. Immersive mode is disabled so its title bar is visible.
+  ui::test::EventGenerator event_generator(
+      widget2->GetNativeWindow()->GetRootWindow());
+  event_generator.PressAndReleaseKey(ui::VKEY_F,
+                                     ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
+  EXPECT_TRUE(frame_view2->caption_button_container_->GetVisible());
+  EXPECT_FALSE(immersive_controller->IsEnabled());
+}
+
 namespace {
 
 class HomeLauncherBrowserNonClientFrameViewChromeOSTest
@@ -1420,6 +1448,7 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 INSTANTIATE_TEST_SUITE(BrowserNonClientFrameViewChromeOSTestWithWebUiTabStrip);
 INSTANTIATE_TEST_SUITE(WebAppNonClientFrameViewAshTest);
+INSTANTIATE_TEST_SUITE(FloatBrowserNonClientFrameViewChromeOSTest);
 INSTANTIATE_TEST_SUITE(HomeLauncherBrowserNonClientFrameViewChromeOSTest);
 INSTANTIATE_TEST_SUITE(TabSearchFrameCaptionButtonTest);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/views/frame/browser_root_view.cc b/chrome/browser/ui/views/frame/browser_root_view.cc
index c0c8124..2fe5d24 100644
--- a/chrome/browser/ui/views/frame/browser_root_view.cc
+++ b/chrome/browser/ui/views/frame/browser_root_view.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_frame.h"
 #include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
@@ -280,7 +281,9 @@
       if (whole_scroll_offset < 0 &&
           model->active_index() + 1 < model->count()) {
         chrome::SelectNextTab(
-            browser, {TabStripModel::GestureType::kWheel, event.time_stamp()});
+            browser, TabStripUserGestureDetails(
+                         TabStripUserGestureDetails::GestureType::kWheel,
+                         event.time_stamp()));
         return true;
       }
 
@@ -288,7 +291,9 @@
       // tab-strip.
       if (whole_scroll_offset > 0 && model->active_index() > 0) {
         chrome::SelectPreviousTab(
-            browser, {TabStripModel::GestureType::kWheel, event.time_stamp()});
+            browser, TabStripUserGestureDetails(
+                         TabStripUserGestureDetails::GestureType::kWheel,
+                         event.time_stamp()));
         return true;
       }
     }
diff --git a/chrome/browser/ui/views/frame/browser_view_browsertest.cc b/chrome/browser/ui/views/frame/browser_view_browsertest.cc
index 8138edc..7a19c32 100644
--- a/chrome/browser/ui/views/frame/browser_view_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_view_browsertest.cc
@@ -141,7 +141,8 @@
   chrome::AddTabAt(browser2, GURL(), -1, true);
   chrome::AddTabAt(browser2, GURL(), -1, true);
   browser2->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   TestWebContentsObserver observer(
       browser2->tab_strip_model()->GetWebContentsAt(0),
       browser2->tab_strip_model()->GetWebContentsAt(1));
@@ -249,14 +250,16 @@
 
   // Go to empty tab. Bookmark bar should hide.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_FALSE(bookmark_bar->GetVisible());
   EXPECT_EQ(0, observer.change_count());
   observer.clear_change_count();
 
   // Go to ntp tab. Bookmark bar should not show.
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_FALSE(bookmark_bar->GetVisible());
   EXPECT_EQ(0, observer.change_count());
   observer.clear_change_count();
@@ -265,13 +268,15 @@
   browser()->profile()->GetPrefs()->SetBoolean(
       bookmarks::prefs::kShowBookmarkBar, true);
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(bookmark_bar->GetVisible());
   EXPECT_EQ(1, observer.change_count());
   observer.clear_change_count();
 
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_TRUE(bookmark_bar->GetVisible());
   EXPECT_EQ(0, observer.change_count());
   observer.clear_change_count();
diff --git a/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.cc b/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.cc
index 453a316..e664b47 100644
--- a/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.cc
+++ b/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chromeos/ui/base/tablet_state.h"
 #include "chromeos/ui/base/window_properties.h"
+#include "chromeos/ui/base/window_state_type.h"
 #include "chromeos/ui/frame/immersive/immersive_revealed_lock.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/aura/client/aura_constants.h"
@@ -145,10 +146,19 @@
   if (platform_util::IsBrowserLockedFullscreen(browser_view_->browser()))
     return;
 
+  // TODO(sammiequon): Investigate if we can move immersive mode logic to the
+  // browser non client frame view.
+  DCHECK_EQ(browser_view_->frame(), widget);
+  if (widget->GetNativeWindow()->GetProperty(chromeos::kWindowStateTypeKey) ==
+      chromeos::WindowStateType::kFloated) {
+    chromeos::ImmersiveFullscreenController::EnableForWidget(widget, false);
+    return;
+  }
+
   // Enable immersive mode if the widget is activated. Do not disable immersive
   // mode if the widget deactivates, but is not minimized.
   chromeos::ImmersiveFullscreenController::EnableForWidget(
-      browser_view_->frame(), active || !widget->IsMinimized());
+      widget, active && !widget->IsMinimized());
 }
 
 void ImmersiveModeControllerChromeos::LayoutBrowserRootView() {
diff --git a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
index 9ab5d87..c2ead422 100644
--- a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
@@ -229,6 +229,34 @@
   }
 }
 
+// Add an allowlist with entries that aren't allowlisted for all domains.
+void ConfigureAllowlistWithScopes() {
+  auto config_proto = reputation::GetOrCreateSafetyTipsConfig();
+  config_proto->clear_allowed_pattern();
+  config_proto->clear_canonical_pattern();
+  config_proto->clear_cohort();
+
+  // Note: allowed_pattern must be sorted, so "googlee" comes before "gooogle".
+
+  // googlee.com may spoof google.com
+  config_proto->add_canonical_pattern()->set_pattern("google.com/");
+  auto* pattern = config_proto->add_allowed_pattern();
+  pattern->set_pattern("googlee.com/");
+  auto* cohort = config_proto->add_cohort();
+  cohort->add_canonical_index(0);  // google.com
+  pattern->add_cohort_index(0);
+
+  // gooogle.com may spoof blogspot, but not google.
+  config_proto->add_canonical_pattern()->set_pattern("blogspot.com/");
+  pattern = config_proto->add_allowed_pattern();
+  pattern->set_pattern("gooogle.com/");
+  cohort = config_proto->add_cohort();
+  cohort->add_canonical_index(1);  // blogspot.com
+  pattern->add_cohort_index(1);
+
+  reputation::SetSafetyTipsRemoteConfigProto(std::move(config_proto));
+}
+
 }  // namespace
 
 class SafetyTipPageInfoBubbleViewBrowserTest : public InProcessBrowserTest {
@@ -806,11 +834,10 @@
   CheckRecordedHeuristicsUkmCount(0);
 }
 
-// Tests that Safety Tips trigger (or not) on lookalike domains with edit
-// distance when enabled, and not otherwise.
+// Tests that Safety Tips trigger on lookalike domains with edit distance.
 IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewBrowserTest,
                        TriggersOnEditDistance) {
-  // This domain is an edit distance from google.com.
+  // This domain is one edit from google.com.
   const GURL kNavigatedUrl = GetURL("goooglé.com");
   const GURL kTargetUrl = GetURL("google.com");
   SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
@@ -819,6 +846,34 @@
   EXPECT_TRUE(IsUIShowing());
 }
 
+// Tests that Safety Tips don't trigger when using a scoped allowlist.
+IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewBrowserTest,
+                       DoesntTriggersOnScopedAllowlist) {
+  // This domain is one edit from google.com, but is allowed to spoof google.
+  const GURL kNavigatedUrl = GetURL("googlee.com");
+  const GURL kTargetUrl = GetURL("google.com");
+  ConfigureAllowlistWithScopes();
+  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
+  SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
+  NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
+  EXPECT_FALSE(IsUIShowing());
+  ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
+}
+
+// Tests that Safety Tips trigger when the URL is on the allowlist, but is
+// scoped to a different domain.
+IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewBrowserTest,
+                       TriggersOnWrongScopedAllowlist) {
+  // This domain is one edit from google.com, and may spoof blogspot.com...
+  const GURL kNavigatedUrl = GetURL("gooogle.com");
+  const GURL kTargetUrl = GetURL("google.com");
+  ConfigureAllowlistWithScopes();
+  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
+  SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
+  NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
+  EXPECT_TRUE(IsUIShowing());
+}
+
 // Tests that Character Swap for engaged sites is disabled by default.
 IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewBrowserTest,
                        TriggersOnCharacterSwap_SiteEngagement_NotLaunched) {
diff --git a/chrome/browser/ui/views/passwords/password_bubble_interactive_uitest.cc b/chrome/browser/ui/views/passwords/password_bubble_interactive_uitest.cc
index 3e0e43e..4bcffb27 100644
--- a/chrome/browser/ui/views/passwords/password_bubble_interactive_uitest.cc
+++ b/chrome/browser/ui/views/passwords/password_bubble_interactive_uitest.cc
@@ -288,13 +288,17 @@
   ASSERT_TRUE(AddTabAtIndex(1, embedded_test_server()->GetURL("/empty.html"),
                             ui::PAGE_TRANSITION_TYPED));
   TabStripModel* tab_model = browser()->tab_strip_model();
-  tab_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_FALSE(IsBubbleShowing());
   EXPECT_EQ(1, tab_model->active_index());
   SetupPendingPassword();
   EXPECT_TRUE(IsBubbleShowing());
   // Back to the first tab.
-  tab_model->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_model->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_FALSE(IsBubbleShowing());
 }
 
@@ -304,13 +308,17 @@
   ASSERT_TRUE(AddTabAtIndex(1, embedded_test_server()->GetURL("/empty.html"),
                             ui::PAGE_TRANSITION_TYPED));
   TabStripModel* tab_model = browser()->tab_strip_model();
-  tab_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_FALSE(IsBubbleShowing());
   EXPECT_EQ(1, tab_model->active_index());
   SetupPendingPassword();
   EXPECT_TRUE(IsBubbleShowing());
   // Back to the first tab. Set up the bubble.
-  tab_model->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_model->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   // Drain message pump to ensure the bubble view is cleared so that it can be
   // created again (it is checked on Mac to prevent re-opening the bubble when
   // clicking the location bar button repeatedly).
diff --git a/chrome/browser/ui/views/sad_tab_view_interactive_uitest.cc b/chrome/browser/ui/views/sad_tab_view_interactive_uitest.cc
index ac22d80..21b7aa9 100644
--- a/chrome/browser/ui/views/sad_tab_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/sad_tab_view_interactive_uitest.cc
@@ -203,7 +203,9 @@
   // Switch back to the first tab.
   TabStripModel* tab_strip_model = browser()->tab_strip_model();
   EXPECT_EQ(1, tab_strip_model->active_index());
-  tab_strip_model->ActivateTabAt(0, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(0, tab_strip_model->active_index());
   content::WebContents* web_contents = tab_strip_model->GetActiveWebContents();
   EXPECT_TRUE(web_contents->IsCrashed());
@@ -215,7 +217,9 @@
   EXPECT_FALSE(web_contents->IsCrashed());
 
   // Switch to the second tab, reload it too.
-  tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
+  tab_strip_model->ActivateTabAt(
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   web_contents = tab_strip_model->GetActiveWebContents();
   EXPECT_TRUE(web_contents->IsCrashed());
   ClickOnActionButtonInSadTab();
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
index aaaaf35..b5f942f 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator.cc
@@ -8,8 +8,6 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/callback_forward.h"
-#include "base/metrics/user_metrics.h"
-#include "base/metrics/user_metrics_action.h"
 #include "chrome/browser/feature_engagement/tracker_factory.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
@@ -19,7 +17,6 @@
 #include "chrome/browser/ui/views/side_panel/side_panel_combobox_model.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_content_proxy.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
-#include "chrome/browser/ui/views/side_panel/side_panel_util.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/common/webui_url_constants.h"
@@ -167,7 +164,9 @@
   browser_view_->browser()->tab_strip_model()->RemoveObserver(this);
 }
 
-void SidePanelCoordinator::Show(absl::optional<SidePanelEntry::Id> entry_id) {
+void SidePanelCoordinator::Show(
+    absl::optional<SidePanelEntry::Id> entry_id,
+    absl::optional<SidePanelUtil::SidePanelOpenTrigger> open_trigger) {
   if (!entry_id.has_value())
     entry_id = GetLastActiveEntryId().value_or(kDefaultEntry);
 
@@ -177,7 +176,8 @@
 
   if (GetContentView() == nullptr) {
     InitializeSidePanel();
-    base::RecordAction(base::UserMetricsAction("SidePanel.Show"));
+    opened_timestamp_ = base::TimeTicks::Now();
+    SidePanelUtil::RecordSidePanelOpen(open_trigger);
     // Record usage for side panel promo.
     feature_engagement::TrackerFactory::GetForBrowserContext(
         browser_view_->GetProfile())
@@ -241,14 +241,14 @@
   if (views::View* content_view = GetContentView())
     browser_view_->right_aligned_side_panel()->RemoveChildViewT(content_view);
   header_combobox_ = nullptr;
-  base::RecordAction(base::UserMetricsAction("SidePanel.Hide"));
+  SidePanelUtil::RecordSidePanelClosed(opened_timestamp_);
 }
 
 void SidePanelCoordinator::Toggle() {
   if (IsSidePanelShowing()) {
     Close();
   } else {
-    Show();
+    Show(absl::nullopt, SidePanelUtil::SidePanelOpenTrigger::kToolbarButton);
   }
 }
 
diff --git a/chrome/browser/ui/views/side_panel/side_panel_coordinator.h b/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
index 7a919a9..759e27d 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_coordinator.h
@@ -6,10 +6,12 @@
 #define CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_COORDINATOR_H_
 
 #include "base/memory/raw_ptr.h"
+#include "base/time/time.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_registry_observer.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_util.h"
 
 class BrowserView;
 class SidePanelComboboxModel;
@@ -38,7 +40,9 @@
   SidePanelCoordinator& operator=(const SidePanelCoordinator&) = delete;
   ~SidePanelCoordinator() override;
 
-  void Show(absl::optional<SidePanelEntry::Id> entry_id = absl::nullopt);
+  void Show(absl::optional<SidePanelEntry::Id> entry_id = absl::nullopt,
+            absl::optional<SidePanelUtil::SidePanelOpenTrigger> open_trigger =
+                absl::nullopt);
   void Close();
   void Toggle();
 
@@ -115,6 +119,11 @@
   // entries.
   bool no_delays_for_testing_ = false;
 
+  // Timestamp of when the side panel was opened. Updated when the side panel is
+  // triggered to be opened, not when visibility changes. These can differ due
+  // to delays for loading content. This is used for metrics.
+  base::TimeTicks opened_timestamp_;
+
   const raw_ptr<BrowserView> browser_view_;
   raw_ptr<SidePanelRegistry> global_registry_;
   absl::optional<SidePanelEntry::Id> last_active_global_entry_id_;
diff --git a/chrome/browser/ui/views/side_panel/side_panel_util.cc b/chrome/browser/ui/views/side_panel/side_panel_util.cc
index 9668893..7eaebae 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_util.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_util.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/browser/ui/views/side_panel/side_panel_util.h"
 
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/user_metrics.h"
+#include "base/metrics/user_metrics_action.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/ui_features.h"
@@ -66,3 +69,18 @@
         std::make_unique<SidePanelContentProxy>(true).release());
   return content_view->GetProperty(kSidePanelContentProxyKey);
 }
+
+void SidePanelUtil::RecordSidePanelOpen(
+    absl::optional<SidePanelUtil::SidePanelOpenTrigger> trigger) {
+  base::RecordAction(base::UserMetricsAction("SidePanel.Show"));
+
+  if (trigger.has_value())
+    base::UmaHistogramEnumeration("SidePanel.OpenTrigger", trigger.value());
+}
+
+void SidePanelUtil::RecordSidePanelClosed(base::TimeTicks opened_timestamp) {
+  base::RecordAction(base::UserMetricsAction("SidePanel.Hide"));
+
+  base::UmaHistogramLongTimes("SidePanel.OpenDuration",
+                              base::TimeTicks::Now() - opened_timestamp);
+}
diff --git a/chrome/browser/ui/views/side_panel/side_panel_util.h b/chrome/browser/ui/views/side_panel/side_panel_util.h
index 535ff651..ed8b3b8f 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_util.h
+++ b/chrome/browser/ui/views/side_panel/side_panel_util.h
@@ -5,6 +5,9 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_UTIL_H_
 #define CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_UTIL_H_
 
+#include "base/time/time.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
 class Browser;
 class SidePanelRegistry;
 class SidePanelContentProxy;
@@ -15,6 +18,16 @@
 
 class SidePanelUtil {
  public:
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  enum class SidePanelOpenTrigger {
+    kToolbarButton = 0,
+    kLensContextMenu = 1,
+    kSideSearchPageAction = 2,
+    kNotesInPageContextMenu = 3,
+    kMaxValue = kNotesInPageContextMenu,
+  };
+
   static void PopulateGlobalEntries(Browser* browser,
                                     SidePanelRegistry* global_registry);
 
@@ -22,6 +35,9 @@
   // exist, this creates one indicating the view is available.
   static SidePanelContentProxy* GetSidePanelContentProxy(
       views::View* content_view);
+
+  static void RecordSidePanelOpen(absl::optional<SidePanelOpenTrigger> trigger);
+  static void RecordSidePanelClosed(base::TimeTicks opened_timestamp);
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_SIDE_PANEL_UTIL_H_
diff --git a/chrome/browser/ui/views/side_panel/user_note/user_note_ui_coordinator.cc b/chrome/browser/ui/views/side_panel/user_note/user_note_ui_coordinator.cc
index 15bf0c97..81fa0162 100644
--- a/chrome/browser/ui/views/side_panel/user_note/user_note_ui_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/user_note/user_note_ui_coordinator.cc
@@ -241,7 +241,9 @@
 void UserNoteUICoordinator::Show() {
   auto* side_panel_coordinator =
       BrowserView::GetBrowserViewForBrowser(browser_)->side_panel_coordinator();
-  side_panel_coordinator->Show(SidePanelEntry::Id::kUserNote);
+  side_panel_coordinator->Show(
+      SidePanelEntry::Id::kUserNote,
+      SidePanelUtil::SidePanelOpenTrigger::kNotesInPageContextMenu);
 }
 
 void UserNoteUICoordinator::OnTabStripModelChanged(
diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views_browsertest.cc b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views_browsertest.cc
index edd8dd2d..0781aac 100644
--- a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views_browsertest.cc
+++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views_browsertest.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
@@ -123,7 +124,8 @@
 
 void ActivateTab(Browser* browser, int tab) {
   browser->tab_strip_model()->ActivateTabAt(
-      tab, {TabStripModel::GestureType::kMouse});
+      tab, TabStripUserGestureDetails(
+               TabStripUserGestureDetails::GestureType::kMouse));
 }
 
 constexpr int kNullTabIndex = -1;
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index 288ca84..4fc2e72 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -37,6 +37,7 @@
 #include "chrome/browser/ui/tabs/tab_renderer_data.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/tabs/tab_utils.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/user_education/reopen_tab_in_product_help.h"
@@ -270,13 +271,14 @@
       content::PeakGpuMemoryTracker::Create(
           content::PeakGpuMemoryTracker::Usage::CHANGE_TAB);
 
-  TabStripModel::UserGestureDetails gesture_detail(
-      TabStripModel::GestureType::kOther, event.time_stamp());
-  TabStripModel::GestureType type = TabStripModel::GestureType::kOther;
+  TabStripUserGestureDetails gesture_detail(
+      TabStripUserGestureDetails::GestureType::kOther, event.time_stamp());
+  TabStripUserGestureDetails::GestureType type =
+      TabStripUserGestureDetails::GestureType::kOther;
   if (event.type() == ui::ET_MOUSE_PRESSED)
-    type = TabStripModel::GestureType::kMouse;
+    type = TabStripUserGestureDetails::GestureType::kMouse;
   else if (event.type() == ui::ET_GESTURE_TAP_DOWN)
-    type = TabStripModel::GestureType::kTouch;
+    type = TabStripUserGestureDetails::GestureType::kTouch;
   gesture_detail.type = type;
   model_->ActivateTabAt(model_index, gesture_detail);
 
@@ -390,14 +392,18 @@
         base::RecordAction(base::UserMetricsAction("TabGroups_CannotCollapse"));
         return false;
       }
-      model_->ActivateTabAt(next_active.value(),
-                            {TabStripModel::GestureType::kOther});
+      model_->ActivateTabAt(
+          next_active.value(),
+          TabStripUserGestureDetails(
+              TabStripUserGestureDetails::GestureType::kOther));
     } else {
       // If the active tab is not in the group that is toggling to collapse,
       // reactive the active tab to deselect any other potentially selected
       // tabs.
-      model_->ActivateTabAt(GetActiveIndex(),
-                            {TabStripModel::GestureType::kOther});
+      model_->ActivateTabAt(
+          GetActiveIndex(),
+          TabStripUserGestureDetails(
+              TabStripUserGestureDetails::GestureType::kOther));
     }
     if (origin != ToggleTabGroupCollapsedStateOrigin::kImplicitAction) {
       base::RecordAction(
diff --git a/chrome/browser/ui/views/tabs/tab_scrubber_chromeos.cc b/chrome/browser/ui/views/tabs/tab_scrubber_chromeos.cc
index dc11c37..6fa988a8 100644
--- a/chrome/browser/ui/views/tabs/tab_scrubber_chromeos.cc
+++ b/chrome/browser/ui/views/tabs/tab_scrubber_chromeos.cc
@@ -274,7 +274,9 @@
       UMA_HISTOGRAM_TIMES("Tabs.ScrubDuration",
                           base::TimeTicks::Now() - scrubbing_start_time_);
       browser_->tab_strip_model()->ActivateTabAt(
-          highlighted_tab_, {TabStripModel::GestureType::kOther});
+          highlighted_tab_,
+          TabStripUserGestureDetails(
+              TabStripUserGestureDetails::GestureType::kOther));
     }
     tab_strip->RemoveObserver(this);
   }
diff --git a/chrome/browser/ui/views/task_manager_view_browsertest.cc b/chrome/browser/ui/views/task_manager_view_browsertest.cc
index 12d1647..2fa5731 100644
--- a/chrome/browser/ui/views/task_manager_view_browsertest.cc
+++ b/chrome/browser/ui/views/task_manager_view_browsertest.cc
@@ -237,7 +237,8 @@
 
   // Activate tab 0. The selection should not change.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
   EXPECT_EQ(1UL, GetTable()->selection_model().size());
   EXPECT_EQ(GetTable()->GetFirstSelectedRow(),
             FindRowForTab(browser()->tab_strip_model()->GetWebContentsAt(1)));
diff --git a/chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.cc b/chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.cc
index 97f1d19..263356a 100644
--- a/chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.cc
+++ b/chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.cc
@@ -172,13 +172,15 @@
       views::LayoutProvider::Get()->GetDistanceMetric(
           views::DISTANCE_RELATED_CONTROL_VERTICAL)));
 
-  auto title_label = std::make_unique<views::Label>(
-      model()->GetStepTitle(), views::style::CONTEXT_DIALOG_TITLE,
-      views::style::STYLE_PRIMARY);
-  title_label->SetMultiLine(true);
-  title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  title_label->SetAllowCharacterBreak(true);
-  label_container->AddChildView(title_label.release());
+  const std::u16string title = model()->GetStepTitle();
+  if (!title.empty()) {
+    auto title_label = std::make_unique<views::Label>(
+        title, views::style::CONTEXT_DIALOG_TITLE, views::style::STYLE_PRIMARY);
+    title_label->SetMultiLine(true);
+    title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+    title_label->SetAllowCharacterBreak(true);
+    label_container->AddChildView(title_label.release());
+  }
 
   std::u16string description = model()->GetStepDescription();
   if (!description.empty()) {
diff --git a/chrome/browser/ui/views/webauthn/sheet_view_factory.cc b/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
index 769865e..b0c2db7 100644
--- a/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
+++ b/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/check.h"
+#include "build/build_config.h"
 #include "chrome/browser/ui/autofill/payments/webauthn_dialog_model.h"
 #include "chrome/browser/ui/views/webauthn/authenticator_bio_enrollment_sheet_view.h"
 #include "chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.h"
@@ -127,6 +128,13 @@
           std::make_unique<AuthenticatorBlePowerOnManualSheetModel>(
               dialog_model));
       break;
+#if BUILDFLAG(IS_MAC)
+    case Step::kBlePermissionMac:
+      sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
+          std::make_unique<AuthenticatorBlePermissionMacSheetModel>(
+              dialog_model));
+      break;
+#endif
     case Step::kOffTheRecordInterstitial:
       sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
           std::make_unique<AuthenticatorOffTheRecordInterstitialSheetModel>(
diff --git a/chrome/browser/ui/web_applications/web_app_launch_process.cc b/chrome/browser/ui/web_applications/web_app_launch_process.cc
index 4846dc0..6088fa9 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_process.cc
+++ b/chrome/browser/ui/web_applications/web_app_launch_process.cc
@@ -17,7 +17,7 @@
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/share_target_utils.h"
 #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
@@ -345,7 +345,9 @@
       /*is_renderer_initiated=*/false));
 
   content::WebContents* web_contents = tab_strip->GetActiveWebContents();
-  tab_strip->ActivateTabAt(tab_index, {TabStripModel::GestureType::kOther});
+  tab_strip->ActivateTabAt(
+      tab_index, TabStripUserGestureDetails(
+                     TabStripUserGestureDetails::GestureType::kOther));
   SetWebContentsActingAsApp(web_contents, params_.app_id);
   return {.web_contents = web_contents, .did_navigate = true};
 }
diff --git a/chrome/browser/ui/webauthn/authenticator_dialog_browsertest.cc b/chrome/browser/ui/webauthn/authenticator_dialog_browsertest.cc
index b74b6ff..44840e65 100644
--- a/chrome/browser/ui/webauthn/authenticator_dialog_browsertest.cc
+++ b/chrome/browser/ui/webauthn/authenticator_dialog_browsertest.cc
@@ -268,6 +268,12 @@
       model_->SetCurrentStepForTesting(
           AuthenticatorRequestDialogModel::Step::kCableActivate);
     }
+#if BUILDFLAG(IS_MAC)
+    else if (name == "ble_permission_mac") {
+      model_->SetCurrentStepForTesting(
+          AuthenticatorRequestDialogModel::Step::kBlePermissionMac);
+    }
+#endif
 
 #define EXP_SHEET(x)                                                    \
   else if (name == "server_link_sheet_" #x) {                           \
@@ -480,3 +486,9 @@
 EXP_SHEET(ARM_5)
 EXP_SHEET(ARM_6)
 #undef EXP_SHEET
+
+#if BUILDFLAG(IS_MAC)
+IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_ble_permission_mac) {
+  ShowAndVerifyUi();
+}
+#endif
diff --git a/chrome/browser/ui/webauthn/sheet_models.cc b/chrome/browser/ui/webauthn/sheet_models.cc
index a3ef562..01c11a8cf 100644
--- a/chrome/browser/ui/webauthn/sheet_models.cc
+++ b/chrome/browser/ui/webauthn/sheet_models.cc
@@ -495,6 +495,52 @@
   dialog_model()->PowerOnBleAdapter();
 }
 
+#if BUILDFLAG(IS_MAC)
+
+// AuthenticatorBlePermissionMacSheetModel
+// ------------------------------------
+
+const gfx::VectorIcon&
+AuthenticatorBlePermissionMacSheetModel::GetStepIllustration(
+    ImageColorScheme color_scheme) const {
+  return color_scheme == ImageColorScheme::kDark
+             ? kWebauthnErrorBluetoothDarkIcon
+             : kWebauthnErrorBluetoothIcon;
+}
+
+std::u16string AuthenticatorBlePermissionMacSheetModel::GetStepTitle() const {
+  // An empty title causes the title View to be omitted.
+  return u"";
+}
+
+std::u16string AuthenticatorBlePermissionMacSheetModel::GetStepDescription()
+    const {
+  return l10n_util::GetStringUTF16(IDS_WEBAUTHN_BLUETOOTH_PERMISSION);
+}
+
+bool AuthenticatorBlePermissionMacSheetModel::IsAcceptButtonVisible() const {
+  return true;
+}
+
+bool AuthenticatorBlePermissionMacSheetModel::IsAcceptButtonEnabled() const {
+  return true;
+}
+
+bool AuthenticatorBlePermissionMacSheetModel::IsCancelButtonVisible() const {
+  return false;
+}
+
+std::u16string AuthenticatorBlePermissionMacSheetModel::GetAcceptButtonLabel()
+    const {
+  return l10n_util::GetStringUTF16(IDS_OPEN_PREFERENCES_LINK);
+}
+
+void AuthenticatorBlePermissionMacSheetModel::OnAccept() {
+  dialog_model()->OpenBlePreferences();
+}
+
+#endif  // IS_MAC
+
 // AuthenticatorOffTheRecordInterstitialSheetModel
 // -----------------------------------------
 
diff --git a/chrome/browser/ui/webauthn/sheet_models.h b/chrome/browser/ui/webauthn/sheet_models.h
index 32f2981..212ed23 100644
--- a/chrome/browser/ui/webauthn/sheet_models.h
+++ b/chrome/browser/ui/webauthn/sheet_models.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/memory/raw_ptr.h"
+#include "build/build_config.h"
 #include "chrome/browser/ui/webauthn/authenticator_request_sheet_model.h"
 #include "chrome/browser/ui/webauthn/transport_hover_list_model.h"
 #include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
@@ -232,6 +233,28 @@
   bool busy_powering_on_ble_ = false;
 };
 
+#if BUILDFLAG(IS_MAC)
+
+class AuthenticatorBlePermissionMacSheetModel
+    : public AuthenticatorSheetModelBase {
+ public:
+  using AuthenticatorSheetModelBase::AuthenticatorSheetModelBase;
+
+ private:
+  // AuthenticatorSheetModelBase:
+  const gfx::VectorIcon& GetStepIllustration(
+      ImageColorScheme color_scheme) const override;
+  std::u16string GetStepTitle() const override;
+  std::u16string GetStepDescription() const override;
+  bool IsAcceptButtonVisible() const override;
+  bool IsAcceptButtonEnabled() const override;
+  bool IsCancelButtonVisible() const override;
+  std::u16string GetAcceptButtonLabel() const override;
+  void OnAccept() override;
+};
+
+#endif  // IS_MAC
+
 class AuthenticatorOffTheRecordInterstitialSheetModel
     : public AuthenticatorSheetModelBase {
  public:
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui_browsertest.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui_browsertest.cc
index 2ff2872..a18c6f3 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_ui_browsertest.cc
@@ -140,13 +140,15 @@
       ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
 
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   // Navigate main tab to hide print preview.
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
 
   browser()->tab_strip_model()->ActivateTabAt(
-      1, {TabStripModel::GestureType::kOther});
+      1, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 }
 #endif  // BUILDFLAG(IS_WIN)
 
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index 25f3e6e..2b3df8d 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -12,6 +12,7 @@
 #include "base/containers/contains.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/observer_list.h"
+#include "base/process/launch.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "build/build_config.h"
@@ -27,6 +28,10 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/text_elider.h"
 
+#if BUILDFLAG(IS_MAC)
+#include "base/mac/mac_util.h"
+#endif
+
 namespace {
 
 // BleEvent enumerates user-visible BLE events.
@@ -262,6 +267,21 @@
          current_step() == Step::kOffTheRecordInterstitial ||
          current_step() == Step::kNotStarted);
 
+#if BUILDFLAG(IS_MAC)
+  // The BLE permission screen is only shown on macOS <= 12 because:
+  //    * System Preferences has been renamed to System Settings, so the
+  //      string on the button would need to be changed.
+  //    * Opening Preferences/Settings at the BLE permissions page is broken.
+  if (transport_availability()->ble_access_denied &&
+      base::mac::IsAtMostOS12()) {
+    // |step| is not saved because macOS asks the user to restart Chrome
+    // after permission has been granted. So the user will end up retrying
+    // the whole WebAuthn request in the new process.
+    SetCurrentStep(Step::kBlePermissionMac);
+    return;
+  }
+#endif
+
   if (ble_adapter_is_powered()) {
     base::UmaHistogramEnumeration("WebAuthentication.BLEUserEvents",
                                   BleEvent::kAlreadyPowered);
@@ -303,6 +323,19 @@
   bluetooth_adapter_power_on_callback_.Run();
 }
 
+#if BUILDFLAG(IS_MAC)
+void AuthenticatorRequestDialogModel::OpenBlePreferences() {
+  DCHECK_EQ(current_step(), Step::kBlePermissionMac);
+
+  base::LaunchOptions opts;
+  opts.disclaim_responsibility = true;
+  base::LaunchProcess({"open",
+                       "x-apple.systempreferences:com.apple.preference."
+                       "security?Privacy_Bluetooth"},
+                      opts);
+}
+#endif  // IS_MAC
+
 void AuthenticatorRequestDialogModel::TryUsbDevice() {
   DCHECK_EQ(current_step(), Step::kUsbInsertAndActivate);
 }
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.h b/chrome/browser/webauthn/authenticator_request_dialog_model.h
index e62df6d..3ac3c6e 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.h
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.h
@@ -86,6 +86,9 @@
     // Bluetooth Low Energy (BLE).
     kBlePowerOnAutomatic,
     kBlePowerOnManual,
+#if BUILDFLAG(IS_MAC)
+    kBlePermissionMac,
+#endif
 
     // Let the user confirm that they want to create a credential in an
     // off-the-record browsing context. Used for platform and caBLE credentials,
@@ -345,6 +348,11 @@
   // Valid action when at step: kBlePowerOnAutomatic.
   void PowerOnBleAdapter();
 
+  // Open the system dialog to grant BLE permission to Chrome.
+  //
+  // Valid action when at step: kBlePermissionMac.
+  void OpenBlePreferences();
+
   // Tries if a USB device is present -- the user claims they plugged it in.
   //
   // Valid action when at step: kUsbInsert.
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 0f75b455..688dcf96 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1656331133-e188c75043fb172bb25e56585a65bfb4d555a028.profdata
+chrome-linux-main-1656352780-29b864beabc43da931f35ed5e96ec6730b553ff3.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 0eae1728..90cc493 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1656331133-a87a490e3491ee6b22dae312d1d5347435280cba.profdata
+chrome-win32-main-1656352780-5daf3e33415b6f908ce296574bcb01e54bb545c7.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index e9c6e43..4585cb4 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1656331133-4becbbd6a2c3171b7cf4baeaf684180260bd6617.profdata
+chrome-win64-main-1656352780-6a26ecd34ca03ea103fa8e994bbbe865a5597d76.profdata
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index cb667d6..2f8eb3cc 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -6857,6 +6857,7 @@
       "//chrome/browser/ui/webui/new_tab_page:mojo_bindings",
       "//chrome/browser/web_applications:web_applications_test_support",
       "//components/app_constants",
+      "//components/autofill_assistant/browser/public:proto",
       "//components/autofill_assistant/browser/public:unit_test_support",
       "//components/chrome_cleaner/test:test_name_helper",
       "//components/endpoint_fetcher:endpoint_fetcher",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
index eff689e..b2f6643 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
@@ -200,7 +200,11 @@
     public void startActivityCompletely(Intent intent) {
         launchActivity(intent);
         waitForActivityNativeInitializationComplete();
+        waitForActivityCompletelyLoaded();
+    }
 
+    /** Wait until the activity is completely loaded, and a tab is shown. */
+    public void waitForActivityCompletelyLoaded() {
         CriteriaHelper.pollUiThread(
                 () -> getActivity().getActivityTab() != null, "Tab never selected/initialized.");
         Tab tab = getActivity().getActivityTab();
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 137d188..106139f 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -18376,7 +18376,7 @@
         },
         "prefs": {
           "policy.set_timeout_without_1ms_clamp": {
-            "location": "local_state"
+            "location": "user_profile"
           }
         }
       }
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 9137841..e4ab3d2 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -561,6 +561,8 @@
   in_files = [
     "chrome_timeticks_test.ts",
     "color_provider_css_colors_test.ts",
+    "cr_focus_row_behavior_test.ts",
+    "resources/list_property_update_behavior_tests.ts",
     "resources/list_property_update_mixin_tests.ts",
     "text_defaults_test.ts",
   ]
diff --git a/chrome/test/data/webui/cr_focus_row_behavior_interactive_test.js b/chrome/test/data/webui/cr_focus_row_behavior_interactive_test.js
index 064729c8..40e8c66 100644
--- a/chrome/test/data/webui/cr_focus_row_behavior_interactive_test.js
+++ b/chrome/test/data/webui/cr_focus_row_behavior_interactive_test.js
@@ -15,7 +15,7 @@
 var CrFocusRowBehaviorTest = class extends PolymerInteractiveUITest {
   /** @override */
   get browsePreload() {
-    return 'chrome://test/test_loader.html?module=cr_focus_row_behavior_test.js&host=test';
+    return 'chrome://test/test_loader.html?module=cr_focus_row_behavior_test.js';
   }
 
   /** @override */
diff --git a/chrome/test/data/webui/cr_focus_row_behavior_test.js b/chrome/test/data/webui/cr_focus_row_behavior_test.js
deleted file mode 100644
index 82179c71..0000000
--- a/chrome/test/data/webui/cr_focus_row_behavior_test.js
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// clang-format off
-import {FocusRowBehavior} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js';
-import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
-import {down, pressAndReleaseKeyOn, up} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
-import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {eventToPromise, waitAfterNextRender} from 'chrome://test/test_util.js';
-
-// clang-format on
-
-suite('cr-focus-row-behavior-test', function() {
-  /** @type {FocusableIronListItemElement} */ let testElement;
-
-  suiteSetup(function() {
-    Polymer({
-      is: 'button-three',
-
-      _template: html`
-        <button>
-          fake button three
-        </button>
-      `,
-
-      /** @return {!Element} */
-      getFocusableElement: function() {
-        return this.$$('button');
-      },
-    });
-
-    Polymer({
-      is: 'focus-row-element',
-
-      _template: html`
-        <div id="container" focus-row-container>
-          <span>fake text</span>
-          <button id="control" focus-row-control focus-type='fake-btn'>
-            fake button
-          </button>
-          <button id="controlTwo" focus-row-control focus-type='fake-btn-two'>
-            fake button two
-          </button>
-          <button-three focus-row-control focus-type='fake-btn-three'>
-          </button-three>
-        </div>
-      `,
-
-      behaviors: [FocusRowBehavior],
-      focusCallCount: 0,
-
-      focus: function() {
-        this.focusCallCount++;
-      },
-    });
-  });
-
-  setup(async function() {
-    PolymerTest.clearBody();
-
-    testElement = document.createElement('focus-row-element');
-    document.body.appendChild(testElement);
-
-    // Block so that FocusRowBehavior.attached can run.
-    await waitAfterNextRender(testElement);
-    // Wait one more time to ensure that async setup in FocusRowBehavior has
-    // executed.
-    await waitAfterNextRender(testElement);
-  });
-
-  test('ID is not overriden when index is set', function() {
-    assertFalse(testElement.hasAttribute('id'));
-    assertFalse(testElement.hasAttribute('aria-rowindex'));
-    testElement.id = 'test-id';
-    assertTrue(testElement.hasAttribute('id'));
-    assertEquals('test-id', testElement.id);
-    assertFalse(testElement.hasAttribute('aria-rowindex'));
-    testElement.focusRowIndex = 5;  // Arbitrary index.
-    assertTrue(testElement.hasAttribute('id'));
-    assertEquals('test-id', testElement.id);
-    assertTrue(testElement.hasAttribute('aria-rowindex'));
-  });
-
-  test('ID and aria-rowindex are only set when index is set', function() {
-    assertFalse(testElement.hasAttribute('id'));
-    assertFalse(testElement.hasAttribute('aria-rowindex'));
-    testElement.focusRowIndex = 5;  // Arbitrary index.
-    assertTrue(testElement.hasAttribute('id'));
-    assertTrue(testElement.hasAttribute('aria-rowindex'));
-  });
-
-  test('item passes focus to first focusable child', function() {
-    let focused = false;
-    testElement.$.control.addEventListener('focus', function() {
-      focused = true;
-    });
-    testElement.fire('focus');
-    assertTrue(focused);
-  });
-
-  test('will focus a similar item that was last focused', function() {
-    const lastButton = document.createElement('button');
-    lastButton.setAttribute('focus-type', 'fake-btn-two');
-    testElement.lastFocused = lastButton;
-
-    let focused = false;
-    testElement.$.controlTwo.addEventListener('focus', function() {
-      focused = true;
-    });
-    testElement.fire('focus');
-    assertTrue(focused);
-  });
-
-  test('mouse clicks on the row does not focus the controls', function() {
-    let focused = false;
-    testElement.$.control.addEventListener('focus', function() {
-      focused = true;
-    });
-    down(testElement);
-    up(testElement);
-    testElement.click();
-    // iron-list is responsible for firing 'focus' after taps, but is not used
-    // in the test, so its necessary to manually fire 'focus' after tap.
-    testElement.fire('focus');
-    assertFalse(focused);
-  });
-
-  test('when focus-override is defined, returned element gains focus', () => {
-    const lastButton = document.createElement('button');
-    lastButton.setAttribute('focus-type', 'fake-btn-three');
-    testElement.lastFocused = lastButton;
-
-    const wait = eventToPromise('focus', testElement);
-    testElement.fire('focus');
-    return wait.then(() => {
-      const button = getDeepActiveElement();
-      assertEquals('fake button three', button.textContent.trim());
-    });
-  });
-
-  test('when shift+tab pressed on first control, focus on container', () => {
-    const first = testElement.$.control;
-    const second = testElement.$.controlTwo;
-    pressAndReleaseKeyOn(first, '', 'shift', 'Tab');
-    assertEquals(1, testElement.focusCallCount);
-    pressAndReleaseKeyOn(second, '', 'shift', 'Tab');
-    assertEquals(1, testElement.focusCallCount);
-
-    // Simulate updating a row with same first control.
-    testElement.fire('dom-change');
-    pressAndReleaseKeyOn(first, '', 'shift', 'Tab');
-    assertEquals(2, testElement.focusCallCount);
-    pressAndReleaseKeyOn(second, '', 'shift', 'Tab');
-    assertEquals(2, testElement.focusCallCount);
-
-    // Simulate updating row with different first control.
-    first.remove();
-    testElement.fire('dom-change');
-    pressAndReleaseKeyOn(second, '', 'shift', 'Tab');
-    assertEquals(3, testElement.focusCallCount);
-  });
-});
diff --git a/chrome/test/data/webui/cr_focus_row_behavior_test.ts b/chrome/test/data/webui/cr_focus_row_behavior_test.ts
new file mode 100644
index 0000000..a593917
--- /dev/null
+++ b/chrome/test/data/webui/cr_focus_row_behavior_test.ts
@@ -0,0 +1,189 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// clang-format off
+import {FocusRowBehavior} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js';
+import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
+import {down, pressAndReleaseKeyOn, up} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
+import {html, PolymerElement, mixinBehaviors} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {eventToPromise, waitAfterNextRender} from 'chrome://webui-test/test_util.js';
+import {assertFalse, assertTrue, assertEquals} from 'chrome://webui-test/chai_assert.js';
+
+// clang-format on
+
+class ButtonThreeElement extends PolymerElement {
+  static get is() {
+    return 'button-three';
+  }
+
+  static get template() {
+    return html`
+      <button>
+        fake button three
+      </button>
+    `;
+  }
+
+  getFocusableElement() {
+    return this.shadowRoot!.querySelector('button');
+  }
+}
+customElements.define(ButtonThreeElement.is, ButtonThreeElement);
+
+const TestElementBase = mixinBehaviors([FocusRowBehavior], PolymerElement) as
+    {new (): PolymerElement & FocusRowBehavior};
+
+interface TestFocusRowBehaviorElement {
+  $: {
+    control: HTMLElement,
+    controlTwo: HTMLElement,
+  };
+}
+
+class TestFocusRowBehaviorElement extends TestElementBase {
+  static get is() {
+    return 'test-focus-row-behavior-element';
+  }
+
+  static get template() {
+    return html`
+      <div id="container" focus-row-container>
+        <span>fake text</span>
+        <button id="control" focus-row-control focus-type='fake-btn'>
+          fake button
+        </button>
+        <button id="controlTwo" focus-row-control focus-type='fake-btn-two'>
+          fake button two
+        </button>
+        <button-three focus-row-control focus-type='fake-btn-three'>
+        </button-three>
+      </div>
+    `;
+  }
+
+  focusCallCount: number = 0;
+
+  override focus() {
+    this.focusCallCount++;
+  }
+}
+customElements.define(
+    TestFocusRowBehaviorElement.is, TestFocusRowBehaviorElement);
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'test-focus-row-behavior-element': TestFocusRowBehaviorElement;
+  }
+}
+
+
+suite('cr-focus-row-behavior-test', function() {
+  let testElement: TestFocusRowBehaviorElement;
+
+  setup(async function() {
+    document.body.innerHTML = '';
+
+    testElement = document.createElement('test-focus-row-behavior-element');
+    document.body.appendChild(testElement);
+
+    // Block so that FocusRowBehavior.attached can run.
+    await waitAfterNextRender(testElement);
+    // Wait one more time to ensure that async setup in FocusRowBehavior has
+    // executed.
+    await waitAfterNextRender(testElement);
+  });
+
+  test('ID is not overriden when index is set', function() {
+    assertFalse(testElement.hasAttribute('id'));
+    assertFalse(testElement.hasAttribute('aria-rowindex'));
+    testElement.id = 'test-id';
+    assertTrue(testElement.hasAttribute('id'));
+    assertEquals('test-id', testElement.id);
+    assertFalse(testElement.hasAttribute('aria-rowindex'));
+    testElement.focusRowIndex = 5;  // Arbitrary index.
+    assertTrue(testElement.hasAttribute('id'));
+    assertEquals('test-id', testElement.id);
+    assertTrue(testElement.hasAttribute('aria-rowindex'));
+  });
+
+  test('ID and aria-rowindex are only set when index is set', function() {
+    assertFalse(testElement.hasAttribute('id'));
+    assertFalse(testElement.hasAttribute('aria-rowindex'));
+    testElement.focusRowIndex = 5;  // Arbitrary index.
+    assertTrue(testElement.hasAttribute('id'));
+    assertTrue(testElement.hasAttribute('aria-rowindex'));
+  });
+
+  test('item passes focus to first focusable child', function() {
+    let focused = false;
+    testElement.$.control.addEventListener('focus', function() {
+      focused = true;
+    });
+    testElement.dispatchEvent(new CustomEvent('focus'));
+    assertTrue(focused);
+  });
+
+  test('will focus a similar item that was last focused', function() {
+    const lastButton = document.createElement('button');
+    lastButton.setAttribute('focus-type', 'fake-btn-two');
+    testElement.lastFocused = lastButton;
+
+    let focused = false;
+    testElement.$.controlTwo.addEventListener('focus', function() {
+      focused = true;
+    });
+    testElement.dispatchEvent(new CustomEvent('focus'));
+    assertTrue(focused);
+  });
+
+  test('mouse clicks on the row does not focus the controls', function() {
+    let focused = false;
+    testElement.$.control.addEventListener('focus', function() {
+      focused = true;
+    });
+    down(testElement);
+    up(testElement);
+    testElement.click();
+    // iron-list is responsible for firing 'focus' after taps, but is not used
+    // in the test, so its necessary to manually fire 'focus' after tap.
+    testElement.dispatchEvent(new CustomEvent('focus'));
+    assertFalse(focused);
+  });
+
+  test('when focus-override is defined, returned element gains focus', () => {
+    const lastButton = document.createElement('button');
+    lastButton.setAttribute('focus-type', 'fake-btn-three');
+    testElement.lastFocused = lastButton;
+
+    const wait = eventToPromise('focus', testElement);
+    testElement.dispatchEvent(new CustomEvent('focus'));
+    return wait.then(() => {
+      const button = getDeepActiveElement();
+      assertTrue(!!button);
+      assertEquals('fake button three', button.textContent!.trim());
+    });
+  });
+
+  test('when shift+tab pressed on first control, focus on container', () => {
+    const first = testElement.$.control;
+    const second = testElement.$.controlTwo;
+    pressAndReleaseKeyOn(first, 0, 'shift', 'Tab');
+    assertEquals(1, testElement.focusCallCount);
+    pressAndReleaseKeyOn(second, 0, 'shift', 'Tab');
+    assertEquals(1, testElement.focusCallCount);
+
+    // Simulate updating a row with same first control.
+    testElement.dispatchEvent(new CustomEvent('dom-change'));
+    pressAndReleaseKeyOn(first, 0, 'shift', 'Tab');
+    assertEquals(2, testElement.focusCallCount);
+    pressAndReleaseKeyOn(second, 0, 'shift', 'Tab');
+    assertEquals(2, testElement.focusCallCount);
+
+    // Simulate updating row with different first control.
+    first.remove();
+    testElement.dispatchEvent(new CustomEvent('dom-change'));
+    pressAndReleaseKeyOn(second, 0, 'shift', 'Tab');
+    assertEquals(3, testElement.focusCallCount);
+  });
+});
diff --git a/chrome/test/data/webui/js/BUILD.gn b/chrome/test/data/webui/js/BUILD.gn
index b546bbb1..507ad1c 100644
--- a/chrome/test/data/webui/js/BUILD.gn
+++ b/chrome/test/data/webui/js/BUILD.gn
@@ -32,11 +32,7 @@
     "parse_html_subset_test.ts",
     "parse_html_subset_trusted_types_test.ts",
     "promise_resolver_test.ts",
-
-    # TODO(1189595): Need to add TypeScript definitions for TrustedHTML before
-    # passing this file to the TS Compiler.
-    #"static_types_test.js",
-
+    "static_types_test.ts",
     "util_test.ts",
     "web_ui_listener_mixin_test.ts",
 
diff --git a/chrome/test/data/webui/js/static_types_test.js b/chrome/test/data/webui/js/static_types_test.ts
similarity index 87%
rename from chrome/test/data/webui/js/static_types_test.js
rename to chrome/test/data/webui/js/static_types_test.ts
index 4557488..e2789d4 100644
--- a/chrome/test/data/webui/js/static_types_test.js
+++ b/chrome/test/data/webui/js/static_types_test.ts
@@ -3,7 +3,8 @@
 // found in the LICENSE file.
 
 import {getTrustedHTML, getTrustedScript, getTrustedScriptURL} from 'chrome://resources/js/static_types.js';
-import {assertEquals, assertNotReached} from '../chai_assert.js';
+
+import {assertEquals, assertNotReached, assertThrows} from '../chai_assert.js';
 
 suite('StaticTypesTest', function() {
   test('compatible with Trusted Types', () => {
@@ -42,7 +43,7 @@
   });
 
   test('throws when invalid', () => {
-    const ensureThrows = function(arg) {
+    function ensureThrows(arg: any) {
       assertThrows(() => {
         getTrustedHTML(arg);
       });
@@ -52,7 +53,7 @@
       assertThrows(() => {
         getTrustedScriptURL(arg);
       });
-    };
+    }
 
     const a = 'test';
     ensureThrows(a);
@@ -60,12 +61,8 @@
     const b = [a];
     ensureThrows(b);
 
-    const c = b;
-    c.raw = b;
+    // c holds stringified value of `test`, which isn't a template literal.
+    const c = `test`;
     ensureThrows(c);
-
-    // d holds stringified value of `test`, which isn't a template literal.
-    const d = `test`;
-    ensureThrows(d);
   });
 });
diff --git a/chrome/test/data/webui/js/webui_resource_module_async_browsertest.js b/chrome/test/data/webui/js/webui_resource_module_async_browsertest.js
index 4436f9e9..146edc9 100644
--- a/chrome/test/data/webui/js/webui_resource_module_async_browsertest.js
+++ b/chrome/test/data/webui/js/webui_resource_module_async_browsertest.js
@@ -172,7 +172,7 @@
 var StaticTypesTest = class extends WebUIResourceModuleAsyncTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://test/test_loader.html?module=js/static_types_test.js&host=test';
+    return 'chrome://test/test_loader.html?module=js/static_types_test.js';
   }
 };
 
diff --git a/chrome/test/data/webui/resources/list_property_update_behavior_tests.js b/chrome/test/data/webui/resources/list_property_update_behavior_tests.ts
similarity index 66%
rename from chrome/test/data/webui/resources/list_property_update_behavior_tests.js
rename to chrome/test/data/webui/resources/list_property_update_behavior_tests.ts
index fde99c8..e1bc960 100644
--- a/chrome/test/data/webui/resources/list_property_update_behavior_tests.js
+++ b/chrome/test/data/webui/resources/list_property_update_behavior_tests.ts
@@ -5,138 +5,161 @@
 /** @fileoverview Suite of tests for the ListPropertyUpdateBehavior.  */
 
 import {ListPropertyUpdateBehavior} from 'chrome://resources/js/list_property_update_behavior.m.js';
-import {Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {assertDeepEquals, assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+
+type SimpleArrayEntry = {
+  id: number,
+};
+
+type ComplexArrayEntry = {
+  letter: string,
+  words: string[],
+};
+
+/** A test element that implements the ListPropertyUpdateBehavior. */
+const ListPropertyUpdateBehaviorTestElementBase =
+    mixinBehaviors([ListPropertyUpdateBehavior], PolymerElement) as
+    {new (): PolymerElement & ListPropertyUpdateBehavior};
+
+class ListPropertyUpdateBehaviorTestElement extends
+    ListPropertyUpdateBehaviorTestElementBase {
+  static get is() {
+    return 'list-property-update-behavior-test-element';
+  }
+
+  static get properties() {
+    return {
+      /**
+       * A test array containing objects with Array properties. The elements
+       * in the array represent an object that maps a list of |words| to the
+       * |letter| that they begin with.
+       */
+      complexArray: Array,
+
+      /**
+       * A test array containing objects with numerical |id|s.
+       */
+      simpleArray: Array,
+    };
+  }
+
+  complexArray: ComplexArrayEntry[] = [];
+  simpleArray: SimpleArrayEntry[] = [];
+
+  constructor() {
+    super();
+
+    this.resetSimpleArray();
+    this.resetComplexArray();
+  }
+
+  resetComplexArray() {
+    this.complexArray = [
+      {letter: 'a', words: ['adventure', 'apple']},
+      {letter: 'b', words: ['banana', 'bee', 'bottle']},
+      {letter: 'c', words: ['car']},
+    ];
+  }
+
+  resetSimpleArray() {
+    this.simpleArray = [{id: 1}, {id: 2}, {id: 3}];
+  }
+
+  /**
+   * Updates the |complexArray| with |newArray| using the
+   * ListPropertyUpdateBehavior.updateList() method. This method will
+   * iterate through the elements of |complexArray| to check if their
+   * |words| property array need to be updated if |complexArray| did not
+   * have any changes.
+   * @param newArray The array update |complexArray| with.
+   * @return An object that has a |topArrayChanged| property set to true if
+   *     notifySplices() was called for the 'complexArray' property path and
+   *     a |wordsArrayChanged| property set to true if notifySplices() was
+   *     called for the |words| property on an item of |complexArray|.
+   */
+  updateComplexArray(newArray: ComplexArrayEntry[]):
+      {topArrayChanged: boolean, wordsArrayChanged: boolean} {
+    if (this.updateList(
+            'complexArray', x => x.letter, newArray,
+            true /* identityBasedUpdate */)) {
+      return {topArrayChanged: true, wordsArrayChanged: false};
+    }
+
+    // At this point, |complexArray| and |newArray| should have the same
+    // elements.
+    let wordsSplicesNotified = false;
+    assertEquals(this.complexArray.length, newArray.length);
+    this.complexArray.forEach((item, i) => {
+      assertEquals(item.letter, newArray[i]!.letter);
+      const propertyPath = 'complexArray.' + i + '.words';
+      const newWordsArray = newArray[i]!.words;
+
+      if (this.updateList(propertyPath, x => x, newWordsArray)) {
+        wordsSplicesNotified = true;
+      }
+    });
+
+    return {
+      topArrayChanged: false,
+      wordsArrayChanged: wordsSplicesNotified,
+    };
+  }
+
+  /**
+   * Updates the |simpleArray| with |newArray| using the
+   * ListPropertyUpdateBehavior.updateList() method.
+   * @param newArray The array to update |simpleArray| with.
+   * @returns True if the update called notifySplices() for
+   *     |simpleArray|.
+   */
+  updateSimpleArray(newArray: SimpleArrayEntry[]): boolean {
+    return this.updateList('simpleArray', x => String(x.id), newArray);
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'list-property-update-behavior-test-element':
+        ListPropertyUpdateBehaviorTestElement;
+  }
+}
+
+customElements.define(
+    ListPropertyUpdateBehaviorTestElement.is,
+    ListPropertyUpdateBehaviorTestElement);
 
 suite('ListPropertyUpdateBehavior', function() {
   /**
-   * A list property update behavior test element created before each test.
-   * @type {ListPropertyUpdateBehaviorTestElement}
+   * A list-property-update-behavior test element created before each test.
    */
-  let testElement;
-
-  suiteSetup(function() {
-    /** A test element that implements the ListPropertyUpdateBehavior. */
-    Polymer({
-      is: 'list-property-update-behavior-test-element',
-
-      behaviors: [ListPropertyUpdateBehavior],
-
-      properties: {
-        /**
-         * A test array containing objects with Array properties. The elements
-         * in the array represent an object that maps a list of |words| to the
-         * |letter| that they begin with.
-         * @type {!Array<{letter: !string, words: !Array<string>}>}
-         */
-        complexArray: {
-          type: Array,
-        },
-
-        /**
-         * A test array containing objects with numerical |id|s.
-         * @type {!Array<{id: !number}>}
-         */
-        simpleArray: {
-          type: Array,
-        },
-      },
-
-      /** @override */
-      created: function() {
-        this.resetSimpleArray();
-        this.resetComplexArray();
-      },
-
-      resetComplexArray() {
-        this.complexArray = [
-          {letter: 'a', words: ['adventure', 'apple']},
-          {letter: 'b', words: ['banana', 'bee', 'bottle']},
-          {letter: 'c', words: ['car']},
-        ];
-      },
-
-      resetSimpleArray() {
-        this.simpleArray = [{id: 1}, {id: 2}, {id: 3}];
-      },
-
-      /**
-       * Updates the |complexArray| with |newArray| using the
-       * ListPropertyUpdateBehavior.updateList() method. This method will
-       * iterate through the elements of |complexArray| to check if their
-       * |words| property array need to be updated if |complexArray| did not
-       * have any changes.
-       * @param {!Array{letter: !string, words: !Array<string>}>} newArray The
-       *     array update |complexArray| with.
-       * @returns {{topArrayChanged: boolean, wordsArrayChanged: boolean}} An
-       *     object that has a |topArrayChanged| property set to true if
-       *     notifySplices() was called for the 'complexArray' property path and
-       *     a |wordsArrayChanged| property set to true if notifySplices() was
-       *     called for the |words| property on an item of |complexArray|.
-       */
-      updateComplexArray(newArray) {
-        if (this.updateList(
-                'complexArray', x => x.letter, newArray,
-                true /* identityBasedUpdate */)) {
-          return {topArrayChanged: true, wordsArrayChanged: false};
-        }
-
-        // At this point, |complexArray| and |newArray| should have the same
-        // elements.
-        let wordsSplicesNotified = false;
-        assertEquals(this.complexArray.length, newArray.length);
-        this.complexArray.forEach((item, i) => {
-          assertEquals(item.letter, newArray[i].letter);
-          const propertyPath = 'complexArray.' + i + '.words';
-          const newWordsArray = newArray[i].words;
-
-          if (this.updateList(propertyPath, x => x, newWordsArray)) {
-            wordsSplicesNotified = true;
-          }
-        });
-
-        return {
-          topArrayChanged: false,
-          wordsArrayChanged: wordsSplicesNotified,
-        };
-      },
-
-      /**
-       * Updates the |simpleArray| with |newArray| using the
-       * ListPropertyUpdateBehavior.updateList() method.
-       * @param {!Array{id: !number}>} newArray The array to update
-       *     |simpleArray| with.
-       * @returns {boolean} True if the update called notifySplices() for
-       *     |simpleArray|.
-       */
-      updateSimpleArray(newArray) {
-        return this.updateList('simpleArray', x => x.id, newArray);
-      },
-    });
-  });
+  let testElement: ListPropertyUpdateBehaviorTestElement;
 
   // Initialize a list-property-update-behavior-test-element before each test.
   setup(function() {
-    PolymerTest.clearBody();
+    document.body.innerHTML = '';
     testElement =
         document.createElement('list-property-update-behavior-test-element');
     document.body.appendChild(testElement);
   });
 
-  function assertSimpleArrayEquals(array, expectedArray) {
+  function assertSimpleArrayEquals(
+      array: SimpleArrayEntry[], expectedArray: SimpleArrayEntry[]) {
     assertEquals(array.length, expectedArray.length);
     array.forEach((item, i) => {
-      assertEquals(item.id, expectedArray[i].id);
+      assertEquals(item.id, expectedArray[i]!.id);
     });
   }
 
-  function assertComplexArrayEquals(array, expectedArray) {
+  function assertComplexArrayEquals(
+      array: ComplexArrayEntry[], expectedArray: ComplexArrayEntry[]) {
     assertEquals(array.length, expectedArray.length);
     array.forEach((item, i) => {
-      assertEquals(item.letter, expectedArray[i].letter);
-      assertEquals(item.words.length, expectedArray[i].words.length);
+      assertEquals(item.letter, expectedArray[i]!.letter);
+      assertEquals(item.words.length, expectedArray[i]!.words.length);
 
       item.words.forEach((word, j) => {
-        assertEquals(word, expectedArray[i].words[j]);
+        assertEquals(word, expectedArray[i]!.words[j]);
       });
     });
   }
@@ -291,8 +314,9 @@
     assertTrue(newArray[0].words.length > 0);
     assertNotEquals('apricot', newArray[0].words[0]);
     newArray[0].words = ['apricot'];
-    assertTrue(testElement.updateList('complexArray', x => x.letter, newArray));
-    assertDeepEquals(['apricot'], testElement.complexArray[0].words);
+    assertTrue(testElement.updateList(
+        'complexArray', (x: ComplexArrayEntry) => x.letter, newArray));
+    assertDeepEquals(['apricot'], testElement.complexArray[0]!.words);
   });
 
   test('first item modified with same uid and last item removed', () => {
@@ -302,8 +326,9 @@
     newArray[0].words = ['apricot'];
     assertTrue(newArray.length > 1);
     newArray.pop();
-    assertTrue(testElement.updateList('complexArray', x => x.letter, newArray));
-    assertDeepEquals(['apricot'], testElement.complexArray[0].words);
+    assertTrue(testElement.updateList(
+        'complexArray', (x: ComplexArrayEntry) => x.letter, newArray));
+    assertDeepEquals(['apricot'], testElement.complexArray[0]!.words);
   });
 
   test('updateList() function triggers notifySplices()', () => {
diff --git a/chrome/test/data/webui/resources/webui_resources_browsertest.js b/chrome/test/data/webui/resources/webui_resources_browsertest.js
index bece9ee..39dd41c 100644
--- a/chrome/test/data/webui/resources/webui_resources_browsertest.js
+++ b/chrome/test/data/webui/resources/webui_resources_browsertest.js
@@ -27,7 +27,7 @@
     class extends WebUIResourcesBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://test/test_loader.html?module=resources/list_property_update_behavior_tests.js&host=test';
+    return 'chrome://test/test_loader.html?module=resources/list_property_update_behavior_tests.js';
   }
 };
 
diff --git a/chrome/test/data/webui/settings/chromeos/test_crostini_browser_proxy.js b/chrome/test/data/webui/settings/chromeos/test_crostini_browser_proxy.js
index 7edef58..e244f75 100644
--- a/chrome/test/data/webui/settings/chromeos/test_crostini_browser_proxy.js
+++ b/chrome/test/data/webui/settings/chromeos/test_crostini_browser_proxy.js
@@ -167,7 +167,7 @@
   /** @override */
   getCrostiniActivePorts() {
     this.methodCalled('getCrostiniActivePorts');
-    return Promise.resolve(new Array());
+    return Promise.resolve([]);
   }
 
   /** @override */
diff --git a/chrome/test/ppapi/ppapi_browsertest.cc b/chrome/test/ppapi/ppapi_browsertest.cc
index ad5cc27..8c68d18 100644
--- a/chrome/test/ppapi/ppapi_browsertest.cc
+++ b/chrome/test/ppapi/ppapi_browsertest.cc
@@ -1937,7 +1937,8 @@
 
   // Switch back to the test tab.
   browser()->tab_strip_model()->ActivateTabAt(
-      0, {TabStripModel::GestureType::kOther});
+      0, TabStripUserGestureDetails(
+             TabStripUserGestureDetails::GestureType::kOther));
 
   ASSERT_TRUE(observer.Run()) << handler.error_message();
   EXPECT_STREQ("PASS", handler.message().c_str());
diff --git a/chromecast/media/audio/cma_audio_output.cc b/chromecast/media/audio/cma_audio_output.cc
index b7949aa..732faa9 100644
--- a/chromecast/media/audio/cma_audio_output.cc
+++ b/chromecast/media/audio/cma_audio_output.cc
@@ -91,9 +91,17 @@
       // If AUDIO_PREFETCH is enabled, we're able to push audio ahead of
       // realtime. Set the sync mode to kModeSyncPts to allow cma backend to
       // buffer the early pushed data, instead of dropping them.
+      // If the output is created with a valid audio track session id, it means
+      // the output stream is owned by other native applications on Android.
+      // In that case, other native applications relay on the reported playback
+      // position to do av sync or use hardware av sync mode. Set the sync mode
+      // to kModeApkSyncPts to avoid setting timestamp of silence buffers pushed
+      // by us to allow the backend decoder distinguishes real audio data vs
+      // silence.
       audio_params_.effects() & ::media::AudioParameters::AUDIO_PREFETCH
-          ? (use_hw_av_sync_ ? MediaPipelineDeviceParams::kModeHwAvSyncPts
-                             : MediaPipelineDeviceParams::kModeSyncPts)
+          ? (audio_track_session_id > 0
+                 ? MediaPipelineDeviceParams::kModeApkSyncPts
+                 : MediaPipelineDeviceParams::kModeSyncPts)
           : MediaPipelineDeviceParams::kModeIgnorePts,
       MediaPipelineDeviceParams::kAudioStreamNormal,
       cma_backend_task_runner.get(), GetContentType(device_id), device_id);
diff --git a/chromecast/media/cma/backend/android/audio_decoder_android.cc b/chromecast/media/cma/backend/android/audio_decoder_android.cc
index 87fc4d1..b3b5f53 100644
--- a/chromecast/media/cma/backend/android/audio_decoder_android.cc
+++ b/chromecast/media/cma/backend/android/audio_decoder_android.cc
@@ -14,7 +14,6 @@
 #include "base/trace_event/trace_event.h"
 #include "chromecast/base/task_runner_impl.h"
 #include "chromecast/media/api/decoder_buffer_base.h"
-#include "chromecast/media/cma/backend/android/audio_sink_manager.h"
 #include "chromecast/media/cma/backend/android/media_pipeline_backend_android.h"
 #include "chromecast/media/cma/base/decoder_buffer_adapter.h"
 #include "chromecast/media/cma/base/decoder_config_adapter.h"
@@ -67,8 +66,7 @@
 // static
 int64_t MediaPipelineBackend::AudioDecoder::GetMinimumBufferedTime(
     const AudioConfig& config) {
-  return AudioSinkAndroid::GetMinimumBufferedTime(
-      AudioSinkManager::GetDefaultSinkType(), config);
+  return AudioSinkAndroid::GetMinimumBufferedTime(config);
 }
 
 AudioDecoderAndroid::AudioDecoderAndroid(MediaPipelineBackendAndroid* backend)
@@ -80,7 +78,6 @@
       pushed_eos_(false),
       sink_error_(false),
       current_pts_(kInvalidTimestamp),
-      sink_(AudioSinkManager::GetDefaultSinkType()),
       pending_output_frames_(kNoPendingOutput),
       volume_multiplier_(1.0f),
       pool_(new ::media::AudioBufferMemoryPool()),
@@ -115,9 +112,6 @@
   pushed_eos_ = false;
   current_pts_ = kInvalidTimestamp;
   pending_output_frames_ = kNoPendingOutput;
-
-  last_sink_delay_.timestamp_microseconds = kInvalidTimestamp;
-  last_sink_delay_.delay_microseconds = 0;
 }
 
 bool AudioDecoderAndroid::Start(int64_t start_pts) {
@@ -166,7 +160,6 @@
   TRACE_FUNCTION_ENTRY0();
   DCHECK(sink_);
   sink_->SetPaused(false);
-  last_sink_delay_ = AudioDecoderAndroid::RenderingDelay();
   return true;
 }
 
@@ -297,7 +290,6 @@
               backend_->ContentType());
   sink_->SetStreamVolumeMultiplier(volume_multiplier_);
   pending_output_frames_ = kNoPendingOutput;
-  last_sink_delay_ = AudioDecoderAndroid::RenderingDelay();
 }
 
 void AudioDecoderAndroid::CreateDecoder() {
@@ -352,7 +344,10 @@
 
 AudioDecoderAndroid::RenderingDelay AudioDecoderAndroid::GetRenderingDelay() {
   TRACE_FUNCTION_ENTRY0();
-  AudioDecoderAndroid::RenderingDelay delay = last_sink_delay_;
+  if (!sink_) {
+    return AudioDecoderAndroid::RenderingDelay();
+  }
+  AudioDecoderAndroid::RenderingDelay delay = sink_->GetRenderingDelay();
   if (delay.timestamp_microseconds != kInvalidTimestamp) {
     double usec_per_sample = 1000000.0 / config_.samples_per_second;
 
@@ -638,15 +633,13 @@
           config_.sample_format == kSampleFormatPlanarF32);
 }
 
-void AudioDecoderAndroid::OnWritePcmCompletion(BufferStatus status,
-                                               const RenderingDelay& delay) {
+void AudioDecoderAndroid::OnWritePcmCompletion(BufferStatus status) {
   DVLOG(3) << __func__ << ": status=" << status;
 
   TRACE_FUNCTION_ENTRY0();
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK_EQ(MediaPipelineBackendAndroid::kBufferSuccess, status);
   pending_output_frames_ = kNoPendingOutput;
-  last_sink_delay_ = delay;
 
   task_runner_->PostTask(FROM_HERE,
                          base::BindOnce(&AudioDecoderAndroid::PushMorePcm,
diff --git a/chromecast/media/cma/backend/android/audio_decoder_android.h b/chromecast/media/cma/backend/android/audio_decoder_android.h
index 3047b32..300ad18 100644
--- a/chromecast/media/cma/backend/android/audio_decoder_android.h
+++ b/chromecast/media/cma/backend/android/audio_decoder_android.h
@@ -74,8 +74,7 @@
   };
 
   // AudioSinkAndroid::Delegate implementation:
-  void OnWritePcmCompletion(BufferStatus status,
-                            const RenderingDelay& delay) override;
+  void OnWritePcmCompletion(BufferStatus status) override;
   void OnSinkError(SinkError error) override;
 
   void CleanUpPcm();
@@ -116,7 +115,6 @@
   int64_t current_pts_;
 
   ManagedAudioSink sink_;
-  RenderingDelay last_sink_delay_;
   int64_t pending_output_frames_;
   float volume_multiplier_;
 
diff --git a/chromecast/media/cma/backend/android/audio_sink_android.cc b/chromecast/media/cma/backend/android/audio_sink_android.cc
index ad6d40b..424f1e3 100644
--- a/chromecast/media/cma/backend/android/audio_sink_android.cc
+++ b/chromecast/media/cma/backend/android/audio_sink_android.cc
@@ -12,23 +12,12 @@
 namespace media {
 
 // static
-int64_t AudioSinkAndroid::GetMinimumBufferedTime(SinkType sink_type,
-                                                 const AudioConfig& config) {
-  const int64_t kDefaultMinBufferTimeUs = 50000;
-  switch (sink_type) {
-    case AudioSinkAndroid::kSinkTypeNativeBased:
-      // TODO(ckuiper): implement a sink using native code.
-      NOTREACHED() << "Native-based audio sink is not implemented yet!";
-      break;
-    case AudioSinkAndroid::kSinkTypeJavaBased:
-      return AudioSinkAndroidAudioTrackImpl::GetMinimumBufferedTime(
-          config.channel_number, config.samples_per_second);
-  }
-  return kDefaultMinBufferTimeUs;
+int64_t AudioSinkAndroid::GetMinimumBufferedTime(const AudioConfig& config) {
+  return AudioSinkAndroidAudioTrackImpl::GetMinimumBufferedTime(
+      config.channel_number, config.samples_per_second);
 }
 
-ManagedAudioSink::ManagedAudioSink(SinkType sink_type)
-    : sink_type_(sink_type), sink_(nullptr) {}
+ManagedAudioSink::ManagedAudioSink() : sink_(nullptr) {}
 
 ManagedAudioSink::~ManagedAudioSink() {
   Remove();
@@ -47,18 +36,9 @@
                              const std::string& device_id,
                              AudioContentType content_type) {
   Remove();
-
-  LOG(INFO) << __func__ << ": Creating new sink of type=" << sink_type_;
-  switch (sink_type_) {
-    case AudioSinkAndroid::kSinkTypeNativeBased:
-      // TODO(ckuiper): implement a sink using native code.
-      NOTREACHED() << "Native-based audio sink is not implemented yet!";
-      break;
-    case AudioSinkAndroid::kSinkTypeJavaBased:
-      sink_ = new AudioSinkAndroidAudioTrackImpl(
-          delegate, num_channels, samples_per_second, audio_track_session_id,
-          primary, use_hw_av_sync, device_id, content_type);
-  }
+  sink_ = new AudioSinkAndroidAudioTrackImpl(
+      delegate, num_channels, samples_per_second, audio_track_session_id,
+      primary, use_hw_av_sync, device_id, content_type);
   AudioSinkManager::Get()->Add(sink_);
 }
 
diff --git a/chromecast/media/cma/backend/android/audio_sink_android.h b/chromecast/media/cma/backend/android/audio_sink_android.h
index 2a625617..56924bd 100644
--- a/chromecast/media/cma/backend/android/audio_sink_android.h
+++ b/chromecast/media/cma/backend/android/audio_sink_android.h
@@ -32,11 +32,6 @@
     kInternalError,
   };
 
-  enum SinkType {
-    kSinkTypeJavaBased,   // Java-based (using AudioTrack)
-    kSinkTypeNativeBased  // Native-based (not implemented yet)
-  };
-
   class Delegate {
    public:
     using SinkError = AudioSinkAndroid::SinkError;
@@ -44,8 +39,7 @@
     // Called when the last data passed to WritePcm() has been successfully
     // added to the queue.
     virtual void OnWritePcmCompletion(
-        MediaPipelineBackendAndroid::BufferStatus status,
-        const MediaPipelineBackendAndroid::RenderingDelay& delay) = 0;
+        MediaPipelineBackendAndroid::BufferStatus status) = 0;
 
     // Called when a sink error occurs. No further data should be written.
     virtual void OnSinkError(SinkError error) = 0;
@@ -54,8 +48,7 @@
     virtual ~Delegate() {}
   };
 
-  static int64_t GetMinimumBufferedTime(SinkType sink_type,
-                                        const AudioConfig& config);
+  static int64_t GetMinimumBufferedTime(const AudioConfig& config);
 
   AudioSinkAndroid() {}
   virtual ~AudioSinkAndroid() {}
@@ -85,6 +78,9 @@
   // stream multiplier and limiter multiplier.
   virtual float EffectiveVolume() const = 0;
 
+  // Returns the current audio rendering delay.
+  virtual MediaPipelineBackendAndroid::RenderingDelay GetRenderingDelay() = 0;
+
   // Getters
   virtual int input_samples_per_second() const = 0;
   virtual bool primary() const = 0;
@@ -97,10 +93,9 @@
 // destroyed. Inspired by std::unique_ptr<>.
 class ManagedAudioSink {
  public:
-  using SinkType = AudioSinkAndroid::SinkType;
   using Delegate = AudioSinkAndroid::Delegate;
 
-  explicit ManagedAudioSink(SinkType sink_type);
+  ManagedAudioSink();
 
   ManagedAudioSink(const ManagedAudioSink&) = delete;
   ManagedAudioSink& operator=(const ManagedAudioSink&) = delete;
@@ -128,7 +123,6 @@
  private:
   void Remove();
 
-  SinkType sink_type_;
   AudioSinkAndroid* sink_;
 };
 
diff --git a/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.cc b/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.cc
index 2bec137e..2926802 100644
--- a/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.cc
+++ b/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.cc
@@ -133,6 +133,22 @@
   return content_type_;
 }
 
+MediaPipelineBackendAndroid::RenderingDelay
+AudioSinkAndroidAudioTrackImpl::GetRenderingDelay() {
+  DVLOG(3) << __func__ << "(" << this << "): "
+           << " delay=" << sink_rendering_delay_.delay_microseconds
+           << " ts=" << sink_rendering_delay_.timestamp_microseconds;
+  // TODO(ziyangch): Add a rate limiter to avoid calling AudioTrack.getTimestamp
+  // too frequent.
+  Java_AudioSinkAudioTrackImpl_getAudioTrackTimestamp(
+      base::android::AttachCurrentThread(), j_audio_sink_audiotrack_impl_);
+  sink_rendering_delay_.audio_track_frame_position =
+      direct_audio_track_timestamp_address_[0];
+  sink_rendering_delay_.audio_track_nano_time =
+      direct_audio_track_timestamp_address_[1];
+  return sink_rendering_delay_;
+}
+
 void AudioSinkAndroidAudioTrackImpl::FinalizeOnFeederThread() {
   RUN_ON_FEEDER_THREAD(FinalizeOnFeederThread);
   wait_for_eos_task_.Cancel();
@@ -150,11 +166,14 @@
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
     const JavaParamRef<jobject>& pcm_byte_buffer,
-    const JavaParamRef<jobject>& timestamp_byte_buffer) {
+    const JavaParamRef<jobject>& rendering_delay_byte_buffer,
+    const JavaParamRef<jobject>& audio_track_timestamp_byte_buffer) {
   direct_pcm_buffer_address_ =
       static_cast<uint8_t*>(env->GetDirectBufferAddress(pcm_byte_buffer));
   direct_rendering_delay_address_ = static_cast<uint64_t*>(
-      env->GetDirectBufferAddress(timestamp_byte_buffer));
+      env->GetDirectBufferAddress(rendering_delay_byte_buffer));
+  direct_audio_track_timestamp_address_ = static_cast<uint64_t*>(
+      env->GetDirectBufferAddress(audio_track_timestamp_byte_buffer));
 }
 
 void AudioSinkAndroidAudioTrackImpl::WritePcm(
@@ -178,7 +197,7 @@
 
   if (pending_data_->data_size() == 0) {
     LOG(INFO) << __func__ << "(" << this << "): empty data buffer!";
-    PostPcmCallback(sink_rendering_delay_);
+    PostPcmCallback();
     return;
   }
 
@@ -221,7 +240,7 @@
 
   TrackRawMonotonicClockDeviation();
 
-  PostPcmCallback(sink_rendering_delay_);
+  PostPcmCallback();
 }
 
 void AudioSinkAndroidAudioTrackImpl::ScheduleWaitForEosTask() {
@@ -244,7 +263,7 @@
 void AudioSinkAndroidAudioTrackImpl::OnPlayoutDone() {
   DCHECK(feeder_task_runner_->BelongsToCurrentThread());
   DCHECK(state_ == kStateGotEos);
-  PostPcmCallback(sink_rendering_delay_);
+  PostPcmCallback();
 }
 
 int AudioSinkAndroidAudioTrackImpl::ReformatData() {
@@ -334,20 +353,15 @@
 
   TrackRawMonotonicClockDeviation();
 
-  PostPcmCallback(sink_rendering_delay_);
+  PostPcmCallback();
 }
 
-void AudioSinkAndroidAudioTrackImpl::PostPcmCallback(
-    const MediaPipelineBackendAndroid::RenderingDelay& delay) {
-  RUN_ON_CALLER_THREAD(PostPcmCallback, delay);
+void AudioSinkAndroidAudioTrackImpl::PostPcmCallback() {
+  RUN_ON_CALLER_THREAD(PostPcmCallback);
   DCHECK(pending_data_);
-  DVLOG(3) << __func__ << "(" << this << "): "
-           << " delay=" << delay.delay_microseconds
-           << " ts=" << delay.timestamp_microseconds;
   pending_data_ = nullptr;
   pending_data_bytes_already_fed_ = 0;
-  delegate_->OnWritePcmCompletion(MediaPipelineBackendAndroid::kBufferSuccess,
-                                  delay);
+  delegate_->OnWritePcmCompletion(MediaPipelineBackendAndroid::kBufferSuccess);
 }
 
 void AudioSinkAndroidAudioTrackImpl::SignalError(
@@ -373,6 +387,7 @@
                                        j_audio_sink_audiotrack_impl_);
   } else {
     LOG(INFO) << __func__ << "(" << this << "): Unpausing";
+    sink_rendering_delay_ = MediaPipelineBackendAndroid::RenderingDelay();
     state_ = kStateNormalPlayback;
     Java_AudioSinkAudioTrackImpl_play(base::android::AttachCurrentThread(),
                                       j_audio_sink_audiotrack_impl_);
diff --git a/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h b/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h
index ec70d591..0fcbd63 100644
--- a/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h
+++ b/chromecast/media/cma/backend/android/audio_sink_android_audiotrack_impl.h
@@ -55,14 +55,17 @@
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj,
       const base::android::JavaParamRef<jobject>& pcm_byte_buffer,
-      const base::android::JavaParamRef<jobject>& timestamp_byte_buffer);
+      const base::android::JavaParamRef<jobject>& rendering_delay_byte_buffer,
+      const base::android::JavaParamRef<jobject>&
+          audio_track_timestamp_byte_buffer);
 
-  // AudioSinkAndroid implementation
+  // AudioSinkAndroid implementation:
   void WritePcm(scoped_refptr<DecoderBufferBase> data) override;
   void SetPaused(bool paused) override;
   void SetStreamVolumeMultiplier(float multiplier) override;
   void SetLimiterVolumeMultiplier(float multiplier) override;
   float EffectiveVolume() const override;
+  MediaPipelineBackendAndroid::RenderingDelay GetRenderingDelay() override;
 
   // Getters
   int input_samples_per_second() const override;
@@ -106,8 +109,7 @@
 
   void TrackRawMonotonicClockDeviation();
 
-  void PostPcmCallback(
-      const MediaPipelineBackendAndroid::RenderingDelay& delay);
+  void PostPcmCallback();
 
   void SignalError(AudioSinkAndroid::SinkError error);
   void PostError(AudioSinkAndroid::SinkError error);
@@ -135,6 +137,8 @@
   uint8_t* direct_pcm_buffer_address_;  // PCM audio data native->java
   // rendering delay+timestamp return value, java->native
   uint64_t* direct_rendering_delay_address_;
+  // AudioTrack.getTimestamp return value, java->native
+  uint64_t* direct_audio_track_timestamp_address_;
 
   // Java AudioSinkAudioTrackImpl instance.
   const base::android::ScopedJavaGlobalRef<jobject>
diff --git a/chromecast/media/cma/backend/android/audio_sink_manager.cc b/chromecast/media/cma/backend/android/audio_sink_manager.cc
index 434681a..5f1ab60 100644
--- a/chromecast/media/cma/backend/android/audio_sink_manager.cc
+++ b/chromecast/media/cma/backend/android/audio_sink_manager.cc
@@ -33,11 +33,6 @@
   return sink_manager_instance.get();
 }
 
-// static
-AudioSinkAndroid::SinkType AudioSinkManager::GetDefaultSinkType() {
-  return AudioSinkAndroid::kSinkTypeJavaBased;
-}
-
 AudioSinkManager::AudioSinkManager() {}
 AudioSinkManager::~AudioSinkManager() {}
 
diff --git a/chromecast/media/cma/backend/android/audio_sink_manager.h b/chromecast/media/cma/backend/android/audio_sink_manager.h
index b4e260f4..982a38c 100644
--- a/chromecast/media/cma/backend/android/audio_sink_manager.h
+++ b/chromecast/media/cma/backend/android/audio_sink_manager.h
@@ -23,14 +23,6 @@
   AudioSinkManager(const AudioSinkManager&) = delete;
   AudioSinkManager& operator=(const AudioSinkManager&) = delete;
 
-  static AudioSinkAndroid::SinkType GetDefaultSinkType();
-
-  // Gets the Android audio session ids used for media and communication (TTS)
-  // tracks.
-  // Set a return value pointer to null if that id is not needed.
-  // Returns true if the ids populated are valid.
-  static bool GetSessionIds(int* media_id, int* communication_id);
-
   // Adds the given sink instance to the vector.
   void Add(AudioSinkAndroid* sink);
 
diff --git a/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java b/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java
index c5eb08ab..d719ad2 100644
--- a/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java
+++ b/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java
@@ -10,6 +10,7 @@
 import android.media.AudioTrack;
 import android.os.Build;
 import android.os.SystemClock;
+import android.util.Pair;
 import android.util.SparseIntArray;
 
 import androidx.annotation.IntDef;
@@ -25,6 +26,8 @@
 import java.lang.annotation.RetentionPolicy;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.LinkedList;
+import java.util.Queue;
 
 /**
  * Implements an audio sink object using Android's AudioTrack module to
@@ -97,11 +100,7 @@
     private static final long TIMESTAMP_UPDATE_PERIOD = 250 * MSEC_IN_NSEC;
     private static final long UNDERRUN_LOG_THROTTLE_PERIOD = SEC_IN_NSEC;
 
-    // Internally Android fetches data from AudioTrack buffer in periods of 20ms.
-    private static final long ANDROID_AUDIO_PERIOD_SIZE_US = 20000;
-
-    // Threshold at which we start logging low buffer warnings.
-    private static final long VERY_LOW_BUFFER_LEVEL = ANDROID_AUDIO_PERIOD_SIZE_US;
+    private static final int START_THRESHOLD_MS = 50;
 
     private static long sInstanceCounter;
 
@@ -180,12 +179,13 @@
     private long mTimestampStabilityCounter; // Counts consecutive stable timestamps at startup.
     private long mTimestampStabilityStartTimeNsec; // Time when we started being stable.
 
-    private long mLastRenderingDelayUsecs;
-
     private int mLastUnderrunCount;
 
     // Statistics
     private long mTotalFramesWritten;
+    // Store intervals of audio buffers without timestamp, [startPosition, endPosition).
+    private Queue<Pair<Long, Long>> mPendingFramesWithoutTimestamp;
+    private long mTotalPlayedFramesWithoutTimestamp;
 
     // Sample Rate calculator
     private long mSRWindowStartTimeNsec;
@@ -199,6 +199,8 @@
     private ByteBuffer mPcmBuffer; // PCM audio data (native->java)
     private ByteBuffer mRenderingDelayBuffer; // RenderingDelay return value
                                               // (java->native)
+    private ByteBuffer
+            mAudioTrackTimestampBuffer; // AudioTrack.getTimestamp return value (java->native)
 
     /**
      * Converts the given nanoseconds value into microseconds with proper rounding. It is assumed
@@ -264,13 +266,16 @@
             int bytesPerBuffer, int sessionId, boolean useHwAvSync) {
         mNativeAudioSinkAudioTrackImpl = nativeAudioSinkAudioTrackImpl;
         mLastTimestampUpdateNsec = NO_TIMESTAMP;
-        mLastRenderingDelayUsecs = NO_TIMESTAMP;
         mTriggerTimestampUpdateNow = false;
         mTimestampStabilityCounter = 0;
         mReferenceTimestampState = ReferenceTimestampState.STARTING_UP;
         mOriginalFramePosOfLastTimestamp = NO_FRAME_POSITION;
         mLastUnderrunCount = 0;
         mTotalFramesWritten = 0;
+        if (isValidSessionId(sessionId) && !useHwAvSync) {
+            mPendingFramesWithoutTimestamp = new LinkedList<>();
+        }
+        mTotalPlayedFramesWithoutTimestamp = 0;
         init(castContentType, channelCount, sampleRateInHz, bytesPerBuffer, sessionId, useHwAvSync);
     }
 
@@ -345,6 +350,15 @@
                                             .build());
             if (isValidSessionId(sessionId)) builder.setSessionId(sessionId);
             mAudioTrack = builder.build();
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && isValidSessionId(sessionId)) {
+                // The playback will not be started until Android AudioTrack has more data than
+                // the start threshold. Reduce the start threshold to 50ms in order to start
+                // playback as soon as possible after starting or resuming. Sometimes other
+                // native applications like Youtube will not push audio data until we play all
+                // pushed data before pausing. See b/237011415.
+                int startThresholdInFrames = START_THRESHOLD_MS * mSampleRateInHz / 1000;
+                mAudioTrack.setStartThresholdInFrames(startThresholdInFrames);
+            }
         } while (mAudioTrack == null && retries++ < MAX_RETRIES_FOR_AUDIO_TRACKS);
 
         // Allocated shared buffers.
@@ -353,9 +367,16 @@
 
         mRenderingDelayBuffer = ByteBuffer.allocateDirect(2 * 8); // 2 long
         mRenderingDelayBuffer.order(ByteOrder.nativeOrder());
+        // Initialize with a invalid rendering delay.
+        mRenderingDelayBuffer.putLong(0, 0);
+        mRenderingDelayBuffer.putLong(8, NO_TIMESTAMP);
+
+        mAudioTrackTimestampBuffer = ByteBuffer.allocateDirect(2 * 8); // 2 long
+        mAudioTrackTimestampBuffer.order(ByteOrder.nativeOrder());
 
         AudioSinkAudioTrackImplJni.get().cacheDirectBufferAddress(mNativeAudioSinkAudioTrackImpl,
-                AudioSinkAudioTrackImpl.this, mPcmBuffer, mRenderingDelayBuffer);
+                AudioSinkAudioTrackImpl.this, mPcmBuffer, mRenderingDelayBuffer,
+                mAudioTrackTimestampBuffer);
     }
 
     @CalledByNative
@@ -394,8 +415,10 @@
         return mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED;
     }
 
-    /** Stops the AudioTrack and returns an estimate of the time it takes for the remaining data
-     * left in the internal queue to be played out (in usecs). */
+    /**
+     * Stops the AudioTrack and returns an estimate of the time it takes for the remaining data
+     * left in the internal queue to be played out (in usecs).
+     */
     @CalledByNative
     private long prepareForShutdown() {
         long playtimeLeftNsecs;
@@ -451,7 +474,8 @@
         return 0;
     }
 
-    /** Writes the PCM data of the given size into the AudioTrack object. The
+    /**
+     * Writes the PCM data of the given size into the AudioTrack object. The
      * PCM data is provided through the memory-mapped ByteBuffer.
      *
      * Returns the number of bytes written into the AudioTrack object, -1 for
@@ -466,9 +490,6 @@
                             + " underruns=" + mLastUnderrunCount);
         }
 
-        // Check buffer level before feeding in new data.
-        if (haveValidRefPoint()) checkBufferLevel();
-
         // Setup the PCM ByteBuffer correctly.
         mPcmBuffer.limit(sizeInBytes);
         mPcmBuffer.position(0);
@@ -525,6 +546,10 @@
         }
 
         int framesWritten = bytesWritten / (mSampleSize * mChannelCount);
+        if (mPendingFramesWithoutTimestamp != null && timestampNs == NO_TIMESTAMP) {
+            mPendingFramesWithoutTimestamp.add(
+                    Pair.create(mTotalFramesWritten, mTotalFramesWritten + framesWritten));
+        }
         mTotalFramesWritten += framesWritten;
 
         if (DEBUG_LEVEL >= 3) {
@@ -549,24 +574,46 @@
         return bytesWritten;
     }
 
+    @CalledByNative
+    public void getAudioTrackTimestamp() {
+        AudioTimestamp timestamp = new AudioTimestamp();
+        if (!mAudioTrack.getTimestamp(timestamp)) {
+            mAudioTrackTimestampBuffer.putLong(0, 0);
+            mAudioTrackTimestampBuffer.putLong(8, NO_TIMESTAMP);
+            return;
+        }
+        if (mPendingFramesWithoutTimestamp == null) {
+            mAudioTrackTimestampBuffer.putLong(0, timestamp.framePosition);
+            mAudioTrackTimestampBuffer.putLong(8, timestamp.nanoTime);
+            return;
+        }
+        while (!mPendingFramesWithoutTimestamp.isEmpty()
+                && timestamp.framePosition >= mPendingFramesWithoutTimestamp.peek().second) {
+            // Calculate the total frames without timestamp before current reported position.
+            mTotalPlayedFramesWithoutTimestamp += (mPendingFramesWithoutTimestamp.peek().second
+                    - mPendingFramesWithoutTimestamp.peek().first);
+            mPendingFramesWithoutTimestamp.remove();
+        }
+        assert timestamp.framePosition >= mTotalPlayedFramesWithoutTimestamp;
+        if (!mPendingFramesWithoutTimestamp.isEmpty()
+                && timestamp.framePosition >= mPendingFramesWithoutTimestamp.peek().first) {
+            // The reported position is in the middle of an audio buffer without timestamp. Use
+            // the start position to calculate the accurate position.
+            mAudioTrackTimestampBuffer.putLong(0,
+                    mPendingFramesWithoutTimestamp.peek().first
+                            - mTotalPlayedFramesWithoutTimestamp);
+        } else {
+            mAudioTrackTimestampBuffer.putLong(
+                    0, timestamp.framePosition - mTotalPlayedFramesWithoutTimestamp);
+        }
+        mAudioTrackTimestampBuffer.putLong(8, timestamp.nanoTime);
+    }
+
     /** Returns the elapsed time from the given start_time until now, in nsec. */
     private long elapsedNsec(long startTimeNsec) {
         return System.nanoTime() - startTimeNsec;
     }
 
-    private void checkBufferLevel() {
-        long bufferLevel = mTotalFramesWritten - mAudioTrack.getPlaybackHeadPosition();
-        long bufferLevelUsec = convertNsecsToUsecs(convertFramesToNanoTime(bufferLevel));
-        if (bufferLevelUsec <= VERY_LOW_BUFFER_LEVEL) {
-            long lastRenderingDelayUsec =
-                    (mLastRenderingDelayUsecs == NO_TIMESTAMP) ? -1 : mLastRenderingDelayUsecs;
-            boolean hitUnderrun = (getUnderrunCount() != mLastUnderrunCount);
-            mBufferLevelWarningLog.log(mTag,
-                    "Low buffer level=" + bufferLevelUsec + "us "
-                            + " RD=" + lastRenderingDelayUsec + (hitUnderrun ? "us *" : "us"));
-        }
-    }
-
     private void updateSampleRateMeasure(long framesWritten) {
         if (mSRWindowFramesWritten == 0) {
             // Start new window.
@@ -587,29 +634,51 @@
     private void updateRenderingDelay() {
         checkForUnderruns();
         updateRefPointTimestamp();
+        long playoutTimeUsecs;
+        long delayUsecs;
+        long nowUsecs = convertNsecsToUsecs(System.nanoTime());
         if (!haveValidRefPoint()) {
+            // Timestamp is resynced because of resuming, reuse the last valid stable rendering
+            // delay before pausing.
+            if (mRenderingDelayBuffer.getLong(8) != NO_TIMESTAMP) {
+                mRenderingDelayBuffer.putLong(8, nowUsecs);
+                return;
+            }
+            if (mUseHwAvSync) {
+                // Hw av sync stream uses the timestamp in the audio buffer instead
+                // of the reported rendering delay to do synchronization. Therefore
+                // it is safe to report zero rendering delay when it is not
+                // available.
+                mRenderingDelayBuffer.putLong(0, 0);
+                mRenderingDelayBuffer.putLong(8, nowUsecs);
+                return;
+            }
+            AudioTimestamp timestamp = new AudioTimestamp();
+            if (mPendingFramesWithoutTimestamp != null && mAudioTrack.getTimestamp(timestamp)) {
+                // mPendingFramesWithoutTimestamp is not null indicates we are using the playback
+                // position instead of the rendering delay to do av sync. Therefore, it is not
+                // necessary to wait for a stable timestamp reference point. Immediately return a
+                // valid rendering delay when it is available could reduce the silence buffer
+                // pushed by cma backend.
+                playoutTimeUsecs = convertNsecsToUsecs(timestamp.nanoTime
+                        + convertFramesToNanoTime(mTotalFramesWritten - timestamp.framePosition));
+                delayUsecs = playoutTimeUsecs - nowUsecs;
+                mRenderingDelayBuffer.putLong(0, delayUsecs);
+                mRenderingDelayBuffer.putLong(8, nowUsecs);
+                return;
+            }
             // No timestamp available yet, just put dummy values and return.
             mRenderingDelayBuffer.putLong(0, 0);
-            // Hw av sync stream uses the timestamp in the audio buffer instead
-            // of the reported rendering delay to do synchronization. Therefore
-            // it is safe to report zero rendering delay when it is not
-            // available.
-            mRenderingDelayBuffer.putLong(
-                    8, mUseHwAvSync ? convertNsecsToUsecs(System.nanoTime()) : NO_TIMESTAMP);
-            mLastRenderingDelayUsecs = NO_TIMESTAMP;
+            mRenderingDelayBuffer.putLong(8, NO_TIMESTAMP);
             return;
         }
 
         // Interpolate to get proper Rendering delay.
-        long playoutTimeNsecs = getInterpolatedTStampNsecs(mTotalFramesWritten);
-        long playoutTimeUsecs = convertNsecsToUsecs(playoutTimeNsecs);
-        long nowUsecs = convertNsecsToUsecs(System.nanoTime());
-        long delayUsecs = playoutTimeUsecs - nowUsecs;
-
+        playoutTimeUsecs = convertNsecsToUsecs(getInterpolatedTStampNsecs(mTotalFramesWritten));
+        delayUsecs = playoutTimeUsecs - nowUsecs;
         // Populate RenderingDelay return value for native land.
         mRenderingDelayBuffer.putLong(0, delayUsecs);
         mRenderingDelayBuffer.putLong(8, nowUsecs);
-        mLastRenderingDelayUsecs = delayUsecs;
 
         if (DEBUG_LEVEL >= 3) {
             Log.i(mTag, "RenderingDelay: delay=" + delayUsecs + " play=" + nowUsecs);
@@ -655,6 +724,8 @@
                             + ")! Resetting rendering delay logic.");
             // Invalidate timestamp (resets RenderingDelay).
             mLastUnderrunCount = underruns;
+            mRenderingDelayBuffer.putLong(0, 0);
+            mRenderingDelayBuffer.putLong(8, NO_TIMESTAMP);
             resyncTimestamp(ReferenceTimestampState.RESYNCING_AFTER_UNDERRUN);
         }
     }
@@ -804,6 +875,6 @@
     interface Natives {
         void cacheDirectBufferAddress(long nativeAudioSinkAndroidAudioTrackImpl,
                 AudioSinkAudioTrackImpl caller, ByteBuffer mPcmBuffer,
-                ByteBuffer mRenderingDelayBuffer);
+                ByteBuffer mRenderingDelayBuffer, ByteBuffer mAudioTrackTimestampBuffer);
     }
 }
diff --git a/chromecast/public/media/media_pipeline_backend.h b/chromecast/public/media/media_pipeline_backend.h
index 92af40c7..11f63308 100644
--- a/chromecast/public/media/media_pipeline_backend.h
+++ b/chromecast/public/media/media_pipeline_backend.h
@@ -109,13 +109,33 @@
     // delay measurement was taken. Both times in microseconds.
     struct RenderingDelay {
       RenderingDelay()
-          : delay_microseconds(0), timestamp_microseconds(INT64_MIN) {}
+          : delay_microseconds(0),
+            timestamp_microseconds(INT64_MIN),
+            audio_track_frame_position(0),
+            audio_track_nano_time(INT64_MIN) {}
       RenderingDelay(int64_t delay_microseconds_in,
                      int64_t timestamp_microseconds_in)
           : delay_microseconds(delay_microseconds_in),
-            timestamp_microseconds(timestamp_microseconds_in) {}
+            timestamp_microseconds(timestamp_microseconds_in),
+            audio_track_frame_position(0),
+            audio_track_nano_time(INT64_MIN) {}
+      RenderingDelay(int64_t delay_microseconds_in,
+                     int64_t timestamp_microseconds_in,
+                     int64_t audio_track_frame_position_in,
+                     int64_t audio_track_nano_time_in)
+          : delay_microseconds(delay_microseconds_in),
+            timestamp_microseconds(timestamp_microseconds_in),
+            audio_track_frame_position(audio_track_frame_position_in),
+            audio_track_nano_time(audio_track_nano_time_in) {}
       int64_t delay_microseconds;
       int64_t timestamp_microseconds;
+      // TODO(ziyangch): Create a new struct and add a new getter function for
+      // audio track timestamp.
+      // Position in frames relative to start of an assumed audio stream in the
+      // Android AudioTrack.
+      int64_t audio_track_frame_position;
+      // Time associated with the frame in the Android audio pipeline.
+      int64_t audio_track_nano_time;
     };
 
     // Statistics (computed since last call to backend Start).
diff --git a/chromecast/public/media/media_pipeline_device_params.h b/chromecast/public/media/media_pipeline_device_params.h
index 6d70886..a6e5622 100644
--- a/chromecast/public/media/media_pipeline_device_params.h
+++ b/chromecast/public/media/media_pipeline_device_params.h
@@ -44,9 +44,13 @@
     kModeIgnorePtsAndVSync = 2,
     // Almost same as kModeSyncPts except two things:
     // 1. When pushing silence to the backend decoder, set an invalid timestamp
-    // to the silence buffer.
-    // 2. When pushing non-silence buffers, do not adjust the timestamp.
-    kModeHwAvSyncPts = 3,
+    // to the silence buffer. If the stream uses hardware av sync mode, it will
+    // drop the silence buffer. Otherwise we will play the silence.
+    // 2. When pushing non-silence buffers, do not adjust the timestamp. When
+    // calculating the rendering delay, the silence buffer will be counted. But
+    // when calculating the current playback position of the real audio data,
+    // buffers without timestamp, like silence buffer, will not be counted.
+    kModeApkSyncPts = 3,
   };
 
   enum AudioStreamType {
diff --git a/chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser.cc b/chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser.cc
index 221701a..0e73cd2 100644
--- a/chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser.cc
+++ b/chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser.cc
@@ -17,10 +17,6 @@
 constexpr uint8_t kFastPairModelId[] = {0x41, 0xc0, 0xd9};
 constexpr uint16_t kCompanyId = 0x00e0;
 
-// TODO(b/207087915): This value comes from Android, but we may need to
-// find a more appropriate power setting for Chrome OS devices.
-const int8_t kAdjustedTxPower = -66;
-
 }  // namespace
 
 // static
@@ -90,9 +86,6 @@
       std::make_unique<device::BluetoothAdvertisement::ServiceData>();
   auto payload = std::vector<uint8_t>(std::begin(kFastPairModelId),
                                       std::end(kFastPairModelId));
-  auto service_metadata = GenerateServiceMetadata();
-  payload.insert(std::end(payload), std::begin(service_metadata),
-                 std::end(service_metadata));
   service_data->insert(std::pair<std::string, std::vector<uint8_t>>(
       kFastPairServiceUuid, payload));
   advertisement_data->set_service_data(std::move(service_data));
@@ -104,8 +97,6 @@
       kCompanyId, manufacturer_metadata));
   advertisement_data->set_manufacturer_data(std::move(manufacturer_data));
 
-  advertisement_data->set_include_tx_power(true);
-
   adapter_->RegisterAdvertisement(
       std::move(advertisement_data),
       base::BindOnce(&FastPairAdvertiser::OnRegisterAdvertisement,
@@ -155,16 +146,6 @@
   // |this| might be destroyed here, do not access local fields.
 }
 
-std::vector<uint8_t> FastPairAdvertiser::GenerateServiceMetadata() {
-  std::vector<uint8_t> metadata;
-
-  // Note: We convert this to a positive value before transport to align with
-  // Android's behavior.
-  int8_t powerConverted = -kAdjustedTxPower;
-  metadata.push_back(powerConverted);
-  return metadata;
-}
-
 std::vector<uint8_t> FastPairAdvertiser::GenerateManufacturerMetadata() {
   // TODO(b/235403498): This code may need to be updated later to be derived
   // from the device BT address. It is not required in order for the
diff --git a/chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser.h b/chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser.h
index 0201b6a..85b7284 100644
--- a/chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser.h
+++ b/chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser.h
@@ -68,9 +68,6 @@
   void OnUnregisterAdvertisementError(
       device::BluetoothAdvertisement::ErrorCode error_code);
 
-  // Returns metadata in format [ adjusted_tx_power (1 byte) ].
-  std::vector<uint8_t> GenerateServiceMetadata();
-
   // Returns metadata in format [ fast_pair_code (2 bytes) ].
   std::vector<uint8_t> GenerateManufacturerMetadata();
 
diff --git a/chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser_unittest.cc b/chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser_unittest.cc
index bdac6ec..7b4e643 100644
--- a/chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser_unittest.cc
+++ b/chromeos/ash/components/oobe_quick_start/connectivity/fast_pair_advertiser_unittest.cc
@@ -24,7 +24,6 @@
 constexpr const char kFastPairServiceUuid[] =
     "0000fe2c-0000-1000-8000-00805f9b34fb";
 const uint8_t kFastPairModelId[] = {0x41, 0xc0, 0xd9};
-const int8_t kAdjustedTxPower = -66;
 
 }  // namespace
 
@@ -154,12 +153,6 @@
 
     auto expected_payload = std::vector<uint8_t>(std::begin(kFastPairModelId),
                                                  std::end(kFastPairModelId));
-    std::vector<uint8_t> metadata;
-    int8_t power_converted = -kAdjustedTxPower;
-    metadata.push_back(power_converted);
-    expected_payload.insert(std::end(expected_payload), std::begin(metadata),
-                            std::end(metadata));
-
     EXPECT_EQ(expected_payload,
               register_args_->service_data[kFastPairServiceUuid]);
   }
diff --git a/chromeos/components/quick_answers/BUILD.gn b/chromeos/components/quick_answers/BUILD.gn
index f4c358f..f8680da 100644
--- a/chromeos/components/quick_answers/BUILD.gn
+++ b/chromeos/components/quick_answers/BUILD.gn
@@ -38,6 +38,8 @@
     "utils/quick_answers_metrics.h",
     "utils/quick_answers_utils.cc",
     "utils/quick_answers_utils.h",
+    "utils/spell_check_language.cc",
+    "utils/spell_check_language.h",
     "utils/spell_checker.cc",
     "utils/spell_checker.h",
     "utils/unit_conversion_constants.cc",
diff --git a/chromeos/components/quick_answers/public/cpp/quick_answers_state.cc b/chromeos/components/quick_answers/public/cpp/quick_answers_state.cc
index 21f9216a..64e0d48 100644
--- a/chromeos/components/quick_answers/public/cpp/quick_answers_state.cc
+++ b/chromeos/components/quick_answers/public/cpp/quick_answers_state.cc
@@ -91,9 +91,13 @@
   if (resolved_application_locale_.empty())
     return;
 
-  is_eligible_ = IsQuickAnswersAllowedForLocale(
+  bool is_eligible = IsQuickAnswersAllowedForLocale(
       resolved_application_locale_, icu::Locale::getDefault().getName());
 
+  if (is_eligible_ == is_eligible)
+    return;
+  is_eligible_ = is_eligible;
+
   for (auto& observer : observers_) {
     observer.OnEligibilityChanged(is_eligible_);
   }
diff --git a/chromeos/components/quick_answers/utils/spell_check_language.cc b/chromeos/components/quick_answers/utils/spell_check_language.cc
new file mode 100644
index 0000000..935f268
--- /dev/null
+++ b/chromeos/components/quick_answers/utils/spell_check_language.cc
@@ -0,0 +1,258 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/components/quick_answers/utils/spell_check_language.h"
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/task/task_runner_util.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "chrome/common/chrome_paths.h"
+#include "components/spellcheck/common/spellcheck_common.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/service_process_host.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
+
+namespace quick_answers {
+namespace {
+
+constexpr char kDownloadServerUrl[] =
+    "https://redirector.gvt1.com/edgedl/chrome/dict/";
+
+constexpr net::NetworkTrafficAnnotationTag kNetworkTrafficAnnotationTag =
+    net::DefineNetworkTrafficAnnotation("quick_answers_spellchecker", R"(
+          semantics {
+            sender: "Quick answers Spellchecker"
+            description:
+              "Download dictionary for Quick answers feature if necessary."
+            trigger: "Quick answers feature enabled."
+            data:
+              "The spell checking language identifier. No user identifier is "
+              "sent."
+            destination: GOOGLE_OWNED_SERVICE
+          }
+          policy {
+            cookies_allowed: NO
+            setting:
+              "Quick Answers can be enabled/disabled in ChromeOS Settings and"
+              "is subject to eligibility requirements."
+            chrome_policy {
+              QuickAnswersEnabled {
+                QuickAnswersEnabled: false
+              }
+            }
+          })");
+
+constexpr int kMaxRetries = 3;
+
+base::FilePath GetDictionaryFilePath(const std::string& language) {
+  base::FilePath dict_dir;
+  base::PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir);
+  base::FilePath dict_path =
+      spellcheck::GetVersionedFileName(language, dict_dir);
+  return dict_path;
+}
+
+GURL GetDictionaryURL(const std::string& file_name) {
+  return GURL(std::string(kDownloadServerUrl) + base::ToLowerASCII(file_name));
+}
+
+bool SaveDictionaryData(std::unique_ptr<std::string> data,
+                        const base::FilePath& file_path) {
+  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+                                                base::BlockingType::MAY_BLOCK);
+
+  // Create a temporary file.
+  base::FilePath tmp_path;
+  if (!base::CreateTemporaryFileInDir(file_path.DirName(), &tmp_path)) {
+    LOG(ERROR) << "Failed to create a temporary file.";
+    return false;
+  }
+
+  // Write to the temporary file.
+  size_t bytes_written =
+      base::WriteFile(tmp_path, data->data(), data->length());
+  if (bytes_written != data->length()) {
+    base::DeleteFile(tmp_path);
+    LOG(ERROR) << "Failed to write dictionary data to the temporary file";
+    return false;
+  }
+
+  // Atomically rename the temporary file to become the real one.
+  return base::ReplaceFile(tmp_path, file_path, nullptr);
+}
+
+base::File OpenDictionaryFile(const base::FilePath& file_path) {
+  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+                                                base::BlockingType::MAY_BLOCK);
+
+  base::File file(file_path, base::File::FLAG_READ | base::File::FLAG_OPEN);
+  return file;
+}
+
+void CloseDictionaryFile(base::File file) {
+  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+                                                base::BlockingType::MAY_BLOCK);
+  file.Close();
+}
+
+void RemoveDictionaryFile(const base::FilePath& file_path) {
+  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+                                                base::BlockingType::MAY_BLOCK);
+
+  base::DeleteFile(file_path);
+}
+
+}  // namespace
+
+SpellCheckLanguage::SpellCheckLanguage(
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
+    : task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
+          {base::MayBlock(), base::TaskPriority::USER_VISIBLE})),
+      url_loader_factory_(url_loader_factory) {}
+
+SpellCheckLanguage::~SpellCheckLanguage() = default;
+
+void SpellCheckLanguage::Initialize(const std::string& locale) {
+  locale_ = locale;
+  dictionary_file_path_ = GetDictionaryFilePath(locale);
+
+  base::PostTaskAndReplyWithResult(
+      task_runner_.get(), FROM_HERE,
+      base::BindOnce(&base::PathExists, dictionary_file_path_),
+      base::BindOnce(&SpellCheckLanguage::OnPathExistsComplete,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void SpellCheckLanguage::CheckSpelling(const std::string& word,
+                                       CheckSpellingCallback callback) {
+  if (!dictionary_initialized_) {
+    std::move(callback).Run(false);
+    return;
+  }
+
+  dictionary_->CheckSpelling(word, std::move(callback));
+}
+
+void SpellCheckLanguage::InitializeSpellCheckService() {
+  if (!service_) {
+    service_ = content::ServiceProcessHost::Launch<mojom::SpellCheckService>(
+        content::ServiceProcessHost::Options()
+            .WithDisplayName("Quick answers spell check service")
+            .Pass());
+  }
+
+  base::PostTaskAndReplyWithResult(
+      task_runner_.get(), FROM_HERE,
+      base::BindOnce(&OpenDictionaryFile, dictionary_file_path_),
+      base::BindOnce(&SpellCheckLanguage::OnOpenDictionaryFileComplete,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void SpellCheckLanguage::OnSimpleURLLoaderComplete(
+    std::unique_ptr<std::string> data) {
+  int response_code = -1;
+  if (loader_->ResponseInfo() && loader_->ResponseInfo()->headers)
+    response_code = loader_->ResponseInfo()->headers->response_code();
+
+  if (loader_->NetError() != net::OK || ((response_code / 100) != 2)) {
+    LOG(ERROR) << "Failed to download the dictionary.";
+    MaybeRetryInitialize();
+    return;
+  }
+
+  // Basic sanity check on the dictionary data.
+  if (!data || data->size() < 4 || data->compare(0, 4, "BDic") != 0) {
+    LOG(ERROR) << "Downloaded dictionary data is empty or broken.";
+    MaybeRetryInitialize();
+    return;
+  }
+
+  base::PostTaskAndReplyWithResult(
+      task_runner_.get(), FROM_HERE,
+      base::BindOnce(&SaveDictionaryData, std::move(data),
+                     dictionary_file_path_),
+      base::BindOnce(&SpellCheckLanguage::OnSaveDictionaryDataComplete,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void SpellCheckLanguage::OnDictionaryCreated(
+    mojo::PendingRemote<mojom::SpellCheckDictionary> dictionary) {
+  dictionary_.reset();
+
+  if (dictionary.is_valid()) {
+    dictionary_.Bind(std::move(dictionary));
+    dictionary_initialized_ = true;
+    return;
+  }
+
+  MaybeRetryInitialize();
+}
+
+void SpellCheckLanguage::MaybeRetryInitialize() {
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&RemoveDictionaryFile, dictionary_file_path_));
+
+  if (num_retries_ >= kMaxRetries) {
+    LOG(ERROR) << "Service initialize failed after max retries";
+    service_.reset();
+    return;
+  }
+
+  ++num_retries_;
+  Initialize(locale_);
+}
+
+void SpellCheckLanguage::OnPathExistsComplete(bool path_exists) {
+  // If the dictionary is not available, try to download it from the server.
+  if (!path_exists) {
+    auto url =
+        GetDictionaryURL(dictionary_file_path_.BaseName().MaybeAsASCII());
+
+    auto resource_request = std::make_unique<network::ResourceRequest>();
+    resource_request->url = url;
+    resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+    loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
+                                               kNetworkTrafficAnnotationTag);
+    loader_->SetRetryOptions(
+        /*max_retries=*/5,
+        network::SimpleURLLoader::RetryMode::RETRY_ON_5XX |
+            network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
+
+    // TODO(b/226221138): Probably use |DownloadToTempFile| instead.
+    loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+        url_loader_factory_.get(),
+        base::BindOnce(&SpellCheckLanguage::OnSimpleURLLoaderComplete,
+                       base::Unretained(this)));
+    return;
+  }
+
+  InitializeSpellCheckService();
+}
+
+void SpellCheckLanguage::OnSaveDictionaryDataComplete(bool dictionary_saved) {
+  if (!dictionary_saved) {
+    MaybeRetryInitialize();
+    return;
+  }
+
+  InitializeSpellCheckService();
+}
+
+void SpellCheckLanguage::OnOpenDictionaryFileComplete(base::File file) {
+  service_->CreateDictionary(
+      file.Duplicate(), base::BindOnce(&SpellCheckLanguage::OnDictionaryCreated,
+                                       base::Unretained(this)));
+
+  task_runner_->PostTask(FROM_HERE,
+                         base::BindOnce(&CloseDictionaryFile, std::move(file)));
+}
+
+}  // namespace quick_answers
diff --git a/chromeos/components/quick_answers/utils/spell_check_language.h b/chromeos/components/quick_answers/utils/spell_check_language.h
new file mode 100644
index 0000000..a9d9e83
--- /dev/null
+++ b/chromeos/components/quick_answers/utils/spell_check_language.h
@@ -0,0 +1,87 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_COMPONENTS_QUICK_ANSWERS_UTILS_SPELL_CHECK_LANGUAGE_H_
+#define CHROMEOS_COMPONENTS_QUICK_ANSWERS_UTILS_SPELL_CHECK_LANGUAGE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
+#include "base/task/sequenced_task_runner.h"
+#include "chromeos/components/quick_answers/public/cpp/quick_answers_state.h"
+#include "chromeos/components/quick_answers/public/mojom/spell_check.mojom.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace network {
+class SharedURLLoaderFactory;
+class SimpleURLLoader;
+}  // namespace network
+
+namespace quick_answers {
+
+// Utility class for spell check.
+class SpellCheckLanguage : public QuickAnswersStateObserver {
+ public:
+  using CheckSpellingCallback = base::OnceCallback<void(bool)>;
+
+  explicit SpellCheckLanguage(
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
+
+  SpellCheckLanguage(const SpellCheckLanguage&) = delete;
+  SpellCheckLanguage& operator=(const SpellCheckLanguage&) = delete;
+
+  ~SpellCheckLanguage() override;
+
+  void Initialize(const std::string& locale);
+
+  // Check spelling of the given word, run |callback| with true if the word is
+  // spelled correctly. Virtual for testing.
+  virtual void CheckSpelling(const std::string& word,
+                             CheckSpellingCallback callback);
+
+  base::WeakPtr<SpellCheckLanguage> GetWeakPtr() {
+    return weak_factory_.GetWeakPtr();
+  }
+
+ private:
+  void InitializeSpellCheckService();
+
+  void OnSimpleURLLoaderComplete(std::unique_ptr<std::string> response_body);
+
+  void OnDictionaryCreated(
+      mojo::PendingRemote<mojom::SpellCheckDictionary> dictionary);
+
+  void MaybeRetryInitialize();
+
+  // The reply points for PostTaskAndReplyWithResult.
+  void OnPathExistsComplete(bool path_exists);
+  void OnSaveDictionaryDataComplete(bool dictionary_saved);
+  void OnOpenDictionaryFileComplete(base::File file);
+
+  // Task runner where the file operations takes place.
+  scoped_refptr<base::SequencedTaskRunner> const task_runner_;
+
+  std::string locale_;
+
+  // Whether the spell check dictionary has been successfully initialized.
+  bool dictionary_initialized_ = false;
+
+  // Number of retries used for initializing the spell check service.
+  int num_retries_ = 0;
+
+  base::FilePath dictionary_file_path_;
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+  std::unique_ptr<network::SimpleURLLoader> loader_;
+  mojo::Remote<mojom::SpellCheckService> service_;
+  mojo::Remote<mojom::SpellCheckDictionary> dictionary_;
+
+  base::WeakPtrFactory<SpellCheckLanguage> weak_factory_{this};
+};
+
+}  // namespace quick_answers
+
+#endif  // CHROMEOS_COMPONENTS_QUICK_ANSWERS_UTILS_SPELL_CHECK_LANGUAGE_H_
diff --git a/chromeos/components/quick_answers/utils/spell_checker.cc b/chromeos/components/quick_answers/utils/spell_checker.cc
index 3e2d3e5..2d1f936 100644
--- a/chromeos/components/quick_answers/utils/spell_checker.cc
+++ b/chromeos/components/quick_answers/utils/spell_checker.cc
@@ -4,277 +4,127 @@
 
 #include "chromeos/components/quick_answers/utils/spell_checker.h"
 
-#include "base/files/file_util.h"
 #include "base/logging.h"
-#include "base/path_service.h"
-#include "base/task/task_runner_util.h"
-#include "base/task/task_traits.h"
-#include "base/task/thread_pool.h"
-#include "base/threading/scoped_blocking_call.h"
-#include "chrome/common/chrome_paths.h"
-#include "components/spellcheck/common/spellcheck_common.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/service_process_host.h"
-#include "services/network/public/cpp/resource_request.h"
+#include "base/strings/string_split.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
-#include "services/network/public/cpp/simple_url_loader.h"
-#include "services/network/public/mojom/url_response_head.mojom.h"
+#include "ui/base/l10n/l10n_util.h"
 
 namespace quick_answers {
 namespace {
 
-constexpr char kDownloadServerUrl[] =
-    "https://redirector.gvt1.com/edgedl/chrome/dict/";
-
-constexpr net::NetworkTrafficAnnotationTag kNetworkTrafficAnnotationTag =
-    net::DefineNetworkTrafficAnnotation("quick_answers_spellchecker", R"(
-          semantics {
-            sender: "Quick answers Spellchecker"
-            description:
-              "Download dictionary for Quick answers feature if necessary."
-            trigger: "Quick answers feature enabled."
-            data:
-              "The spell checking language identifier. No user identifier is "
-              "sent."
-            destination: GOOGLE_OWNED_SERVICE
-          }
-          policy {
-            cookies_allowed: NO
-            setting:
-              "Quick Answers can be enabled/disabled in ChromeOS Settings and"
-              "is subject to eligibility requirements."
-            chrome_policy {
-              QuickAnswersEnabled {
-                QuickAnswersEnabled: false
-              }
-            }
-          })");
-
-constexpr int kMaxRetries = 3;
-
-base::FilePath GetDictionaryFilePath(const std::string& language) {
-  base::FilePath dict_dir;
-  base::PathService::Get(chrome::DIR_APP_DICTIONARIES, &dict_dir);
-  base::FilePath dict_path =
-      spellcheck::GetVersionedFileName(language, dict_dir);
-  return dict_path;
-}
-
-GURL GetDictionaryURL(const std::string& file_name) {
-  return GURL(std::string(kDownloadServerUrl) + base::ToLowerASCII(file_name));
-}
-
-bool SaveDictionaryData(std::unique_ptr<std::string> data,
-                        const base::FilePath& file_path) {
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
-
-  // Create a temporary file.
-  base::FilePath tmp_path;
-  if (!base::CreateTemporaryFileInDir(file_path.DirName(), &tmp_path)) {
-    LOG(ERROR) << "Failed to create a temporary file.";
-    return false;
-  }
-
-  // Write to the temporary file.
-  size_t bytes_written =
-      base::WriteFile(tmp_path, data->data(), data->length());
-  if (bytes_written != data->length()) {
-    base::DeleteFile(tmp_path);
-    LOG(ERROR) << "Failed to write dictionary data to the temporary file";
-    return false;
-  }
-
-  // Atomically rename the temporary file to become the real one.
-  return base::ReplaceFile(tmp_path, file_path, nullptr);
-}
-
-base::File OpenDictionaryFile(const base::FilePath& file_path) {
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
-
-  base::File file(file_path, base::File::FLAG_READ | base::File::FLAG_OPEN);
-  return file;
-}
-
-void CloseDictionaryFile(base::File file) {
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
-  file.Close();
-}
-
-void RemoveDictionaryFile(const base::FilePath& file_path) {
-  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
-                                                base::BlockingType::MAY_BLOCK);
-
-  base::DeleteFile(file_path);
-}
-
 }  // namespace
 
 SpellChecker::SpellChecker(
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
-    : task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
-          {base::MayBlock(), base::TaskPriority::USER_VISIBLE})),
-      url_loader_factory_(url_loader_factory) {
+    : url_loader_factory_(url_loader_factory) {
   quick_answers_state_observation_.Observe(QuickAnswersState::Get());
 }
 
-SpellChecker::~SpellChecker() = default;
+SpellChecker::~SpellChecker() {
+  spellcheck_languages_.clear();
+}
 
 void SpellChecker::CheckSpelling(const std::string& word,
                                  CheckSpellingCallback callback) {
-  if (!dictionary_initialized_) {
+  auto iterator = spellcheck_languages_.begin();
+  if (iterator == spellcheck_languages_.end()) {
     std::move(callback).Run(false);
     return;
   }
 
-  dictionary_->CheckSpelling(word, std::move(callback));
+  iterator->get()->CheckSpelling(
+      word, base::BindOnce(&SpellChecker::CollectResults,
+                           base::Unretained(this), word, std::move(callback),
+                           iterator, languages_list_version_));
 }
 
 void SpellChecker::OnSettingsEnabled(bool enabled) {
   feature_enabled_ = enabled;
 
-  // Reset spell check service if the feature is disabled.
-  if (!enabled) {
-    dictionary_.reset();
-    service_.reset();
-    return;
-  }
-
-  if (!dictionary_file_path_.empty())
-    InitializeDictionary();
+  OnStateUpdated();
 }
 
 void SpellChecker::OnApplicationLocaleReady(const std::string& locale) {
-  dictionary_file_path_ = GetDictionaryFilePath(locale);
+  application_locale_ = locale;
 
-  if (feature_enabled_)
-    InitializeDictionary();
+  OnStateUpdated();
 }
 
-void SpellChecker::InitializeDictionary() {
-  DCHECK(!dictionary_file_path_.empty());
+void SpellChecker::OnPreferredLanguagesChanged(
+    const std::string& preferred_languages) {
+  preferred_languages_ = preferred_languages;
 
-  base::PostTaskAndReplyWithResult(
-      task_runner_.get(), FROM_HERE,
-      base::BindOnce(&base::PathExists, dictionary_file_path_),
-      base::BindOnce(&SpellChecker::OnPathExistsComplete,
-                     weak_factory_.GetWeakPtr()));
+  OnStateUpdated();
 }
 
-void SpellChecker::InitializeSpellCheckService() {
-  if (!service_) {
-    service_ = content::ServiceProcessHost::Launch<mojom::SpellCheckService>(
-        content::ServiceProcessHost::Options()
-            .WithDisplayName("Quick answers spell check service")
-            .Pass());
-  }
+void SpellChecker::OnEligibilityChanged(bool eligible) {
+  feature_eligible_ = eligible;
 
-  base::PostTaskAndReplyWithResult(
-      task_runner_.get(), FROM_HERE,
-      base::BindOnce(&OpenDictionaryFile, dictionary_file_path_),
-      base::BindOnce(&SpellChecker::OnOpenDictionaryFileComplete,
-                     weak_factory_.GetWeakPtr()));
+  OnStateUpdated();
 }
 
-void SpellChecker::OnSimpleURLLoaderComplete(
-    std::unique_ptr<std::string> data) {
-  int response_code = -1;
-  if (loader_->ResponseInfo() && loader_->ResponseInfo()->headers)
-    response_code = loader_->ResponseInfo()->headers->response_code();
-
-  if (loader_->NetError() != net::OK || ((response_code / 100) != 2)) {
-    LOG(ERROR) << "Failed to download the dictionary.";
-    MaybeRetryInitialize();
+void SpellChecker::OnStateUpdated() {
+  if (!feature_eligible_.has_value() || !feature_enabled_.has_value() ||
+      !application_locale_.has_value() || !preferred_languages_.has_value()) {
+    // Still waiting for all of the states to be ready.
     return;
   }
 
-  // Basic sanity check on the dictionary data.
-  if (!data || data->size() < 4 || data->compare(0, 4, "BDic") != 0) {
-    LOG(ERROR) << "Downloaded dictionary data is empty or broken.";
-    MaybeRetryInitialize();
+  if (!feature_eligible_.value() || !feature_enabled_.value()) {
+    spellcheck_languages_.clear();
+    languages_list_version_++;
     return;
   }
 
-  base::PostTaskAndReplyWithResult(
-      task_runner_.get(), FROM_HERE,
-      base::BindOnce(&SaveDictionaryData, std::move(data),
-                     dictionary_file_path_),
-      base::BindOnce(&SpellChecker::OnSaveDictionaryDataComplete,
-                     weak_factory_.GetWeakPtr()));
+  std::set<std::string> languages;
+  languages.insert(l10n_util::GetLanguage(application_locale_.value()));
+
+  // Add preferred languages.
+  if (chromeos::features::IsQuickAnswersForMoreLocalesEnabled()) {
+    auto preferred_languages_list =
+        base::SplitString(preferred_languages_.value(), ",",
+                          base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+    for (const std::string& locale : preferred_languages_list) {
+      languages.insert(l10n_util::GetLanguage(locale));
+    }
+  }
+
+  spellcheck_languages_.clear();
+  languages_list_version_++;
+  for (auto language : languages) {
+    spellcheck_languages_.push_back(
+        std::make_unique<SpellCheckLanguage>(url_loader_factory_));
+    spellcheck_languages_.back()->Initialize(language);
+  }
 }
 
-void SpellChecker::OnDictionaryCreated(
-    mojo::PendingRemote<mojom::SpellCheckDictionary> dictionary) {
-  dictionary_.reset();
-
-  if (dictionary.is_valid()) {
-    dictionary_.Bind(std::move(dictionary));
-    dictionary_initialized_ = true;
+void SpellChecker::CollectResults(const std::string& word,
+                                  CheckSpellingCallback callback,
+                                  SpellCheckLanguageIterator iterator,
+                                  int languages_list_version,
+                                  bool is_correct) {
+  if (is_correct) {
+    std::move(callback).Run(true);
     return;
   }
 
-  MaybeRetryInitialize();
-}
-
-void SpellChecker::MaybeRetryInitialize() {
-  task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&RemoveDictionaryFile, dictionary_file_path_));
-
-  if (num_retries_ >= kMaxRetries) {
-    LOG(ERROR) << "Service initialize failed after max retries";
-    service_.reset();
+  // The languages list has been updated, return false for the current call.
+  if (languages_list_version != languages_list_version_) {
+    std::move(callback).Run(true);
     return;
   }
 
-  ++num_retries_;
-  InitializeDictionary();
-}
-
-void SpellChecker::OnPathExistsComplete(bool path_exists) {
-  // If the dictionary is not available, try to download it from the server.
-  if (!path_exists) {
-    auto url =
-        GetDictionaryURL(dictionary_file_path_.BaseName().MaybeAsASCII());
-
-    auto resource_request = std::make_unique<network::ResourceRequest>();
-    resource_request->url = url;
-    resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
-    loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
-                                               kNetworkTrafficAnnotationTag);
-    loader_->SetRetryOptions(
-        /*max_retries=*/5,
-        network::SimpleURLLoader::RetryMode::RETRY_ON_5XX |
-            network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
-
-    // TODO(b/226221138): Probably use |DownloadToTempFile| instead.
-    loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
-        url_loader_factory_.get(),
-        base::BindOnce(&SpellChecker::OnSimpleURLLoaderComplete,
-                       base::Unretained(this)));
+  iterator++;
+  if (iterator == spellcheck_languages_.end()) {
+    std::move(callback).Run(false);
     return;
   }
 
-  InitializeSpellCheckService();
-}
-
-void SpellChecker::OnSaveDictionaryDataComplete(bool dictionary_saved) {
-  if (!dictionary_saved) {
-    MaybeRetryInitialize();
-    return;
-  }
-
-  InitializeSpellCheckService();
-}
-
-void SpellChecker::OnOpenDictionaryFileComplete(base::File file) {
-  service_->CreateDictionary(file.Duplicate(),
-                             base::BindOnce(&SpellChecker::OnDictionaryCreated,
-                                            base::Unretained(this)));
-
-  task_runner_->PostTask(FROM_HERE,
-                         base::BindOnce(&CloseDictionaryFile, std::move(file)));
+  iterator->get()->CheckSpelling(
+      word, base::BindOnce(&SpellChecker::CollectResults,
+                           base::Unretained(this), word, std::move(callback),
+                           iterator, languages_list_version));
 }
 
 }  // namespace quick_answers
diff --git a/chromeos/components/quick_answers/utils/spell_checker.h b/chromeos/components/quick_answers/utils/spell_checker.h
index f78dbbe..cead569 100644
--- a/chromeos/components/quick_answers/utils/spell_checker.h
+++ b/chromeos/components/quick_answers/utils/spell_checker.h
@@ -8,17 +8,14 @@
 #include <memory>
 #include <string>
 
-#include "base/files/file_path.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
-#include "base/task/sequenced_task_runner.h"
 #include "chromeos/components/quick_answers/public/cpp/quick_answers_state.h"
-#include "chromeos/components/quick_answers/public/mojom/spell_check.mojom.h"
+#include "chromeos/components/quick_answers/utils/spell_check_language.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
 namespace network {
 class SharedURLLoaderFactory;
-class SimpleURLLoader;
 }  // namespace network
 
 namespace quick_answers {
@@ -27,6 +24,8 @@
 class SpellChecker : public QuickAnswersStateObserver {
  public:
   using CheckSpellingCallback = base::OnceCallback<void(bool)>;
+  using SpellCheckLanguageIterator =
+      std::vector<std::unique_ptr<SpellCheckLanguage>>::iterator;
 
   explicit SpellChecker(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
@@ -44,44 +43,41 @@
   // QuickAnswersStateObserver:
   void OnSettingsEnabled(bool enabled) override;
   void OnApplicationLocaleReady(const std::string& locale) override;
+  void OnPreferredLanguagesChanged(
+      const std::string& preferred_languages) override;
+  void OnEligibilityChanged(bool eligible) override;
 
   base::WeakPtr<SpellChecker> GetWeakPtr() {
     return weak_factory_.GetWeakPtr();
   }
 
  private:
-  void InitializeDictionary();
-  void InitializeSpellCheckService();
+  // Called when the Quick answers states are updated.
+  void OnStateUpdated();
 
-  void OnSimpleURLLoaderComplete(std::unique_ptr<std::string> response_body);
+  // Collect spell check results from the language indicated by |iterator|. Run
+  // |callback| with true if the word is found in the current language,
+  // otherwise continue to check the next language.
+  void CollectResults(const std::string& word,
+                      CheckSpellingCallback callback,
+                      SpellCheckLanguageIterator iterator,
+                      int languages_list_version,
+                      bool is_correct);
 
-  void OnDictionaryCreated(
-      mojo::PendingRemote<mojom::SpellCheckDictionary> dictionary);
-
-  void MaybeRetryInitialize();
-
-  // The reply points for PostTaskAndReplyWithResult.
-  void OnPathExistsComplete(bool path_exists);
-  void OnSaveDictionaryDataComplete(bool dictionary_saved);
-  void OnOpenDictionaryFileComplete(base::File file);
-
-  // Task runner where the file operations takes place.
-  scoped_refptr<base::SequencedTaskRunner> const task_runner_;
-
-  // Whether the Quick answers feature is enabled in settings.
-  bool feature_enabled_ = false;
-
-  // Whether the spell check dictionary has been successfully initialized.
-  bool dictionary_initialized_ = false;
-
-  // Number of retries used for initializing the spell check service.
-  int num_retries_ = 0;
-
-  base::FilePath dictionary_file_path_;
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
-  std::unique_ptr<network::SimpleURLLoader> loader_;
-  mojo::Remote<mojom::SpellCheckService> service_;
-  mojo::Remote<mojom::SpellCheckDictionary> dictionary_;
+
+  // States of the Quick answers feature.
+  absl::optional<bool> feature_eligible_;
+  absl::optional<bool> feature_enabled_;
+  absl::optional<std::string> application_locale_;
+  absl::optional<std::string> preferred_languages_;
+
+  // List of spell check languages.
+  std::vector<std::unique_ptr<SpellCheckLanguage>> spellcheck_languages_;
+
+  // Version of the languages list to invalidate pending calls if the languages
+  // has ben updated.
+  int languages_list_version_ = 0;
 
   base::ScopedObservation<QuickAnswersState, QuickAnswersStateObserver>
       quick_answers_state_observation_{this};
diff --git a/chromeos/dbus/chunneld/chunneld_client.cc b/chromeos/dbus/chunneld/chunneld_client.cc
index 5af12e4..ed4050ba 100644
--- a/chromeos/dbus/chunneld/chunneld_client.cc
+++ b/chromeos/dbus/chunneld/chunneld_client.cc
@@ -12,15 +12,19 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "chromeos/dbus/chunneld/fake_chunneld_client.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
 #include "third_party/cros_system_api/dbus/chunneld/dbus-constants.h"
 
 namespace chromeos {
+namespace {
+
+ChunneldClient* g_instance = nullptr;
 
 class ChunneldClientImpl : public ChunneldClient {
  public:
-  ChunneldClientImpl() {}
+  ChunneldClientImpl() = default;
 
   ~ChunneldClientImpl() override = default;
 
@@ -41,7 +45,6 @@
     chunneld_proxy_->WaitForServiceToBeAvailable(std::move(callback));
   }
 
- protected:
   void Init(dbus::Bus* bus) override {
     chunneld_proxy_ = bus->GetObjectProxy(
         vm_tools::chunneld::kChunneldServiceName,
@@ -92,12 +95,38 @@
   base::WeakPtrFactory<ChunneldClientImpl> weak_ptr_factory_{this};
 };
 
-ChunneldClient::ChunneldClient() = default;
+}  // namespace
 
-ChunneldClient::~ChunneldClient() = default;
+// static
+ChunneldClient* ChunneldClient::Get() {
+  return g_instance;
+}
 
-std::unique_ptr<ChunneldClient> ChunneldClient::Create() {
-  return std::make_unique<ChunneldClientImpl>();
+// static
+void ChunneldClient::Initialize(dbus::Bus* bus) {
+  CHECK(bus);
+  (new ChunneldClientImpl())->Init(bus);
+}
+
+// static
+void ChunneldClient::InitializeFake() {
+  (new FakeChunneldClient())->Init(nullptr);
+}
+
+// static
+void ChunneldClient::Shutdown() {
+  CHECK(g_instance);
+  delete g_instance;
+}
+
+ChunneldClient::ChunneldClient() {
+  CHECK(!g_instance);
+  g_instance = this;
+}
+
+ChunneldClient::~ChunneldClient() {
+  CHECK_EQ(g_instance, this);
+  g_instance = nullptr;
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/chunneld/chunneld_client.h b/chromeos/dbus/chunneld/chunneld_client.h
index f547a5e..e6f7af8 100644
--- a/chromeos/dbus/chunneld/chunneld_client.h
+++ b/chromeos/dbus/chunneld/chunneld_client.h
@@ -26,21 +26,28 @@
     virtual void ChunneldServiceStarted() = 0;
   };
 
+  // Returns the global instance if initialized. May return null.
+  static ChunneldClient* Get();
+
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
+
+  // Creates and initializes a fake global instance used on Linux desktop, if
+  // no instance already exists.
+  static void InitializeFake();
+
+  // Destroys the global instance if it has been initialized.
+  static void Shutdown();
+
+  ChunneldClient(const ChunneldClient&) = delete;
+  ChunneldClient& operator=(const ChunneldClient&) = delete;
+
   // Adds an observer.
   virtual void AddObserver(Observer* observer) = 0;
 
   // Removes an observer if added.
   virtual void RemoveObserver(Observer* observer) = 0;
 
-  ~ChunneldClient() override;
-
-  ChunneldClient(const ChunneldClient&) = delete;
-  ChunneldClient& operator=(const ChunneldClient&) = delete;
-
-  // Factory function, creates a new instance and returns ownership.
-  // For normal usage, access the singleton via DBusThreadManager::Get().
-  static std::unique_ptr<ChunneldClient> Create();
-
   // Registers |callback| to run when the Concierge service becomes available.
   // If the service is already available, or if connecting to the name-owner-
   // changed signal fails, |callback| will be run once asynchronously.
@@ -50,8 +57,9 @@
       dbus::ObjectProxy::WaitForServiceToBeAvailableCallback callback) = 0;
 
  protected:
-  // Create() should be used instead.
+  // Initialize() should be used instead.
   ChunneldClient();
+  ~ChunneldClient() override;
 };
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/chunneld/fake_chunneld_client.h b/chromeos/dbus/chunneld/fake_chunneld_client.h
index babee6c6..ea4e20d 100644
--- a/chromeos/dbus/chunneld/fake_chunneld_client.h
+++ b/chromeos/dbus/chunneld/fake_chunneld_client.h
@@ -22,6 +22,7 @@
   FakeChunneldClient& operator=(const FakeChunneldClient&) = delete;
 
   // ChunneldClient:
+  void Init(dbus::Bus* bus) override {}
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
   void WaitForServiceToBeAvailable(
@@ -30,9 +31,6 @@
   void NotifyChunneldStopped();
   void NotifyChunneldStarted();
 
- protected:
-  void Init(dbus::Bus* bus) override {}
-
  private:
   void InitializeProtoResponses();
 
diff --git a/chromeos/dbus/dbus_clients_browser.cc b/chromeos/dbus/dbus_clients_browser.cc
index 8fd5ad5..21d5baa 100644
--- a/chromeos/dbus/dbus_clients_browser.cc
+++ b/chromeos/dbus/dbus_clients_browser.cc
@@ -17,8 +17,6 @@
 #include "chromeos/dbus/arc/fake_arc_obb_mounter_client.h"
 #include "chromeos/dbus/cec_service/cec_service_client.h"
 #include "chromeos/dbus/cec_service/fake_cec_service_client.h"
-#include "chromeos/dbus/chunneld/chunneld_client.h"
-#include "chromeos/dbus/chunneld/fake_chunneld_client.h"
 #include "chromeos/dbus/cros_disks/cros_disks_client.h"
 #include "chromeos/dbus/cros_disks/fake_cros_disks_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
@@ -38,10 +36,6 @@
 #include "chromeos/dbus/oobe_config/oobe_configuration_client.h"
 #include "chromeos/dbus/runtime_probe/fake_runtime_probe_client.h"
 #include "chromeos/dbus/runtime_probe/runtime_probe_client.h"
-#include "chromeos/dbus/smbprovider/fake_smb_provider_client.h"
-#include "chromeos/dbus/smbprovider/smb_provider_client.h"
-#include "chromeos/dbus/virtual_file_provider/fake_virtual_file_provider_client.h"
-#include "chromeos/dbus/virtual_file_provider/virtual_file_provider_client.h"
 
 namespace chromeos {
 
@@ -67,7 +61,6 @@
       CREATE_DBUS_CLIENT(ArcObbMounterClient, use_real_clients);
   cec_service_client_ = CREATE_DBUS_CLIENT(CecServiceClient, use_real_clients);
   cros_disks_client_ = CREATE_DBUS_CLIENT(CrosDisksClient, use_real_clients);
-  chunneld_client_ = CREATE_DBUS_CLIENT(ChunneldClient, use_real_clients);
   debug_daemon_client_ =
       CREATE_DBUS_CLIENT(DebugDaemonClient, use_real_clients);
   easy_unlock_client_ = CREATE_DBUS_CLIENT(EasyUnlockClient, use_real_clients);
@@ -81,10 +74,6 @@
       CREATE_DBUS_CLIENT(OobeConfigurationClient, use_real_clients);
   runtime_probe_client_ =
       CREATE_DBUS_CLIENT(RuntimeProbeClient, use_real_clients);
-  smb_provider_client_ =
-      CREATE_DBUS_CLIENT(SmbProviderClient, use_real_clients);
-  virtual_file_provider_client_ =
-      CREATE_DBUS_CLIENT(VirtualFileProviderClient, use_real_clients);
 }
 
 DBusClientsBrowser::~DBusClientsBrowser() = default;
@@ -97,7 +86,6 @@
   arc_midis_client_->Init(system_bus);
   arc_obb_mounter_client_->Init(system_bus);
   cec_service_client_->Init(system_bus);
-  chunneld_client_->Init(system_bus);
   cros_disks_client_->Init(system_bus);
   debug_daemon_client_->Init(system_bus);
   easy_unlock_client_->Init(system_bus);
@@ -107,8 +95,6 @@
   image_loader_client_->Init(system_bus);
   oobe_configuration_client_->Init(system_bus);
   runtime_probe_client_->Init(system_bus);
-  smb_provider_client_->Init(system_bus);
-  virtual_file_provider_client_->Init(system_bus);
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/dbus_clients_browser.h b/chromeos/dbus/dbus_clients_browser.h
index 23703a3..33f0cd2 100644
--- a/chromeos/dbus/dbus_clients_browser.h
+++ b/chromeos/dbus/dbus_clients_browser.h
@@ -20,7 +20,6 @@
 class ArcMidisClient;
 class ArcObbMounterClient;
 class CecServiceClient;
-class ChunneldClient;
 class CrosDisksClient;
 class DebugDaemonClient;
 class EasyUnlockClient;
@@ -30,8 +29,6 @@
 class ImageLoaderClient;
 class OobeConfigurationClient;
 class RuntimeProbeClient;
-class SmbProviderClient;
-class VirtualFileProviderClient;
 
 // Owns D-Bus clients.
 // TODO(jamescook): Rename this class. "Browser" refers to the browser process
@@ -59,7 +56,6 @@
   std::unique_ptr<ArcMidisClient> arc_midis_client_;
   std::unique_ptr<ArcObbMounterClient> arc_obb_mounter_client_;
   std::unique_ptr<CecServiceClient> cec_service_client_;
-  std::unique_ptr<ChunneldClient> chunneld_client_;
   std::unique_ptr<CrosDisksClient> cros_disks_client_;
   std::unique_ptr<DebugDaemonClient> debug_daemon_client_;
   std::unique_ptr<EasyUnlockClient> easy_unlock_client_;
@@ -69,8 +65,6 @@
   std::unique_ptr<ImageLoaderClient> image_loader_client_;
   std::unique_ptr<OobeConfigurationClient> oobe_configuration_client_;
   std::unique_ptr<RuntimeProbeClient> runtime_probe_client_;
-  std::unique_ptr<SmbProviderClient> smb_provider_client_;
-  std::unique_ptr<VirtualFileProviderClient> virtual_file_provider_client_;
 };
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/dbus_thread_manager.cc b/chromeos/dbus/dbus_thread_manager.cc
index 181baf7..bbc15f4f 100644
--- a/chromeos/dbus/dbus_thread_manager.cc
+++ b/chromeos/dbus/dbus_thread_manager.cc
@@ -15,7 +15,6 @@
 #include "chromeos/dbus/arc/arc_midis_client.h"
 #include "chromeos/dbus/arc/arc_obb_mounter_client.h"
 #include "chromeos/dbus/cec_service/cec_service_client.h"
-#include "chromeos/dbus/chunneld/chunneld_client.h"
 #include "chromeos/dbus/common/dbus_client.h"
 #include "chromeos/dbus/cros_disks/cros_disks_client.h"
 #include "chromeos/dbus/dbus_clients_browser.h"
@@ -27,7 +26,6 @@
 #include "chromeos/dbus/oobe_config/oobe_configuration_client.h"
 #include "chromeos/dbus/runtime_probe/runtime_probe_client.h"
 #include "chromeos/dbus/shill/shill_clients.h"
-#include "chromeos/dbus/smbprovider/smb_provider_client.h"
 
 namespace chromeos {
 
@@ -73,10 +71,6 @@
                           : nullptr;
 }
 
-ChunneldClient* DBusThreadManager::GetChunneldClient() {
-  return clients_browser_ ? clients_browser_->chunneld_client_.get() : nullptr;
-}
-
 CrosDisksClient* DBusThreadManager::GetCrosDisksClient() {
   RETURN_DBUS_CLIENT(cros_disks_client_);
 }
@@ -111,16 +105,6 @@
                           : nullptr;
 }
 
-SmbProviderClient* DBusThreadManager::GetSmbProviderClient() {
-  RETURN_DBUS_CLIENT(smb_provider_client_);
-}
-
-VirtualFileProviderClient* DBusThreadManager::GetVirtualFileProviderClient() {
-  return clients_browser_
-             ? clients_browser_->virtual_file_provider_client_.get()
-             : nullptr;
-}
-
 #undef RETURN_DBUS_CLIENT
 
 void DBusThreadManager::InitializeClients() {
@@ -213,9 +197,4 @@
   image_loader_client_ = std::move(client);
 }
 
-void DBusThreadManagerSetter::SetSmbProviderClient(
-    std::unique_ptr<SmbProviderClient> client) {
-  smb_provider_client_ = std::move(client);
-}
-
 }  // namespace chromeos
diff --git a/chromeos/dbus/dbus_thread_manager.h b/chromeos/dbus/dbus_thread_manager.h
index ff6575b..b2c7c21 100644
--- a/chromeos/dbus/dbus_thread_manager.h
+++ b/chromeos/dbus/dbus_thread_manager.h
@@ -20,7 +20,6 @@
 class ArcMidisClient;
 class ArcObbMounterClient;
 class CecServiceClient;
-class ChunneldClient;
 class CrosDisksClient;
 class DBusClientsBrowser;
 class DBusThreadManagerSetter;
@@ -31,8 +30,6 @@
 class ImageLoaderClient;
 class OobeConfigurationClient;
 class RuntimeProbeClient;
-class SmbProviderClient;
-class VirtualFileProviderClient;
 
 // THIS CLASS IS BEING DEPRECATED. See README.md for guidelines and
 // https://crbug.com/647367 for details.
@@ -74,7 +71,6 @@
   ArcMidisClient* GetArcMidisClient();
   ArcObbMounterClient* GetArcObbMounterClient();
   CecServiceClient* GetCecServiceClient();
-  ChunneldClient* GetChunneldClient();
   CrosDisksClient* GetCrosDisksClient();
   DebugDaemonClient* GetDebugDaemonClient();
   EasyUnlockClient* GetEasyUnlockClient();
@@ -83,8 +79,6 @@
   ImageLoaderClient* GetImageLoaderClient();
   OobeConfigurationClient* GetOobeConfigurationClient();
   RuntimeProbeClient* GetRuntimeProbeClient();
-  SmbProviderClient* GetSmbProviderClient();
-  VirtualFileProviderClient* GetVirtualFileProviderClient();
 
  private:
   DBusThreadManager();
@@ -108,7 +102,6 @@
   void SetGnubbyClient(std::unique_ptr<GnubbyClient> client);
   void SetImageBurnerClient(std::unique_ptr<ImageBurnerClient> client);
   void SetImageLoaderClient(std::unique_ptr<ImageLoaderClient> client);
-  void SetSmbProviderClient(std::unique_ptr<SmbProviderClient> client);
 
  private:
   friend class DBusThreadManager;
@@ -124,7 +117,6 @@
   std::unique_ptr<GnubbyClient> gnubby_client_;
   std::unique_ptr<ImageBurnerClient> image_burner_client_;
   std::unique_ptr<ImageLoaderClient> image_loader_client_;
-  std::unique_ptr<SmbProviderClient> smb_provider_client_;
 };
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/smbprovider/smb_provider_client.cc b/chromeos/dbus/smbprovider/smb_provider_client.cc
index b1a27f53..3f4655e 100644
--- a/chromeos/dbus/smbprovider/smb_provider_client.cc
+++ b/chromeos/dbus/smbprovider/smb_provider_client.cc
@@ -11,6 +11,7 @@
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/memory/weak_ptr.h"
+#include "chromeos/dbus/smbprovider/fake_smb_provider_client.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
 #include "dbus/object_proxy.h"
@@ -19,6 +20,8 @@
 
 namespace {
 
+SmbProviderClient* g_instance = nullptr;
+
 smbprovider::ErrorType GetErrorFromReader(dbus::MessageReader* reader) {
   int32_t int_error;
   if (!reader->PopInt32(&int_error) ||
@@ -91,7 +94,6 @@
                &callback);
   }
 
- protected:
   // DBusClient override.
   void Init(dbus::Bus* bus) override {
     proxy_ = bus->GetObjectProxy(
@@ -225,13 +227,36 @@
 
 }  // namespace
 
-SmbProviderClient::SmbProviderClient() = default;
-
-SmbProviderClient::~SmbProviderClient() = default;
+// static
+SmbProviderClient* SmbProviderClient::Get() {
+  return g_instance;
+}
 
 // static
-std::unique_ptr<SmbProviderClient> SmbProviderClient::Create() {
-  return std::make_unique<SmbProviderClientImpl>();
+void SmbProviderClient::Initialize(dbus::Bus* bus) {
+  CHECK(bus);
+  (new SmbProviderClientImpl())->Init(bus);
+}
+
+// static
+void SmbProviderClient::InitializeFake() {
+  (new FakeSmbProviderClient())->Init(nullptr);
+}
+
+// static
+void SmbProviderClient::Shutdown() {
+  CHECK(g_instance);
+  delete g_instance;
+}
+
+SmbProviderClient::SmbProviderClient() {
+  CHECK(!g_instance);
+  g_instance = this;
+}
+
+SmbProviderClient::~SmbProviderClient() {
+  CHECK_EQ(g_instance, this);
+  g_instance = nullptr;
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/smbprovider/smb_provider_client.h b/chromeos/dbus/smbprovider/smb_provider_client.h
index cb97c0f1..77c1b41 100644
--- a/chromeos/dbus/smbprovider/smb_provider_client.h
+++ b/chromeos/dbus/smbprovider/smb_provider_client.h
@@ -38,11 +38,18 @@
   SmbProviderClient(const SmbProviderClient&) = delete;
   SmbProviderClient& operator=(const SmbProviderClient&) = delete;
 
-  ~SmbProviderClient() override;
+  // Returns the global instance if initialized. May return null.
+  static SmbProviderClient* Get();
 
-  // Factory function, creates a new instance and returns ownership.
-  // For normal usage, access the singleton via DBusThreadManager::Get().
-  static std::unique_ptr<SmbProviderClient> Create();
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
+
+  // Creates and initializes a fake global instance used on Linux desktop, if
+  // no instance already exists.
+  static void InitializeFake();
+
+  // Destroys the global instance if it has been initialized.
+  static void Shutdown();
 
   // Calls GetShares. This gets the shares from |server_url| and calls
   // |callback| when shares are found. The DirectoryEntryListProto will contain
@@ -65,8 +72,9 @@
                                   ParseNetBiosPacketCallback callback) = 0;
 
  protected:
-  // Create() should be used instead.
+  // Initialize() should be used instead.
   SmbProviderClient();
+  ~SmbProviderClient() override;
 };
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/virtual_file_provider/virtual_file_provider_client.cc b/chromeos/dbus/virtual_file_provider/virtual_file_provider_client.cc
index ccf58fa..2abb038 100644
--- a/chromeos/dbus/virtual_file_provider/virtual_file_provider_client.cc
+++ b/chromeos/dbus/virtual_file_provider/virtual_file_provider_client.cc
@@ -10,6 +10,7 @@
 #include "base/callback.h"
 #include "base/callback_helpers.h"
 #include "base/logging.h"
+#include "chromeos/dbus/virtual_file_provider/fake_virtual_file_provider_client.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
 #include "dbus/object_proxy.h"
@@ -19,6 +20,8 @@
 
 namespace {
 
+VirtualFileProviderClient* g_instance = nullptr;
+
 class VirtualFileProviderClientImpl : public VirtualFileProviderClient {
  public:
   VirtualFileProviderClientImpl() {}
@@ -55,7 +58,6 @@
                        weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
   }
 
- protected:
   // DBusClient override.
   void Init(dbus::Bus* bus) override {
     proxy_ = bus->GetObjectProxy(
@@ -105,13 +107,36 @@
 
 }  // namespace
 
-VirtualFileProviderClient::VirtualFileProviderClient() = default;
-
-VirtualFileProviderClient::~VirtualFileProviderClient() = default;
+// static
+VirtualFileProviderClient* VirtualFileProviderClient::Get() {
+  return g_instance;
+}
 
 // static
-std::unique_ptr<VirtualFileProviderClient> VirtualFileProviderClient::Create() {
-  return std::make_unique<VirtualFileProviderClientImpl>();
+void VirtualFileProviderClient::Initialize(dbus::Bus* bus) {
+  CHECK(bus);
+  (new VirtualFileProviderClientImpl())->Init(bus);
+}
+
+// static
+void VirtualFileProviderClient::InitializeFake() {
+  (new FakeVirtualFileProviderClient())->Init(nullptr);
+}
+
+// static
+void VirtualFileProviderClient::Shutdown() {
+  CHECK(g_instance);
+  delete g_instance;
+}
+
+VirtualFileProviderClient::VirtualFileProviderClient() {
+  CHECK(!g_instance);
+  g_instance = this;
+}
+
+VirtualFileProviderClient::~VirtualFileProviderClient() {
+  CHECK_EQ(g_instance, this);
+  g_instance = nullptr;
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/virtual_file_provider/virtual_file_provider_client.h b/chromeos/dbus/virtual_file_provider/virtual_file_provider_client.h
index 9637edfa..a8c4e6e8 100644
--- a/chromeos/dbus/virtual_file_provider/virtual_file_provider_client.h
+++ b/chromeos/dbus/virtual_file_provider/virtual_file_provider_client.h
@@ -30,12 +30,18 @@
       base::OnceCallback<void(const absl::optional<std::string>& id)>;
   using OpenFileByIdCallback = base::OnceCallback<void(base::ScopedFD fd)>;
 
-  VirtualFileProviderClient();
-  ~VirtualFileProviderClient() override;
+  // Returns the global instance if initialized. May return null.
+  static VirtualFileProviderClient* Get();
 
-  // Factory function, creates a new instance and returns ownership.
-  // For normal usage, access the singleton via DBusThreadManager::Get().
-  static std::unique_ptr<VirtualFileProviderClient> Create();
+  // Creates and initializes the global instance. |bus| must not be null.
+  static void Initialize(dbus::Bus* bus);
+
+  // Creates and initializes a fake global instance used on Linux desktop, if
+  // no instance already exists.
+  static void InitializeFake();
+
+  // Destroys the global instance if it has been initialized.
+  static void Shutdown();
 
   // Generates and returns a unique ID, to be used by OpenFileById() for FD
   // creation. |size| will be used to perform boundary check when FD is seeked.
@@ -47,6 +53,10 @@
   // the read request is forwarded to the request handler.
   virtual void OpenFileById(const std::string& id,
                             OpenFileByIdCallback callback) = 0;
+
+ protected:
+  VirtualFileProviderClient();
+  ~VirtualFileProviderClient() override;
 };
 
 }  // namespace chromeos
diff --git a/components/android_autofill/browser/android_autofill_manager.cc b/components/android_autofill/browser/android_autofill_manager.cc
index 4330a12..db8575e 100644
--- a/components/android_autofill/browser/android_autofill_manager.cc
+++ b/components/android_autofill/browser/android_autofill_manager.cc
@@ -64,9 +64,12 @@
   NOTREACHED();
 }
 
-void AndroidAutofillManager::SetFillViaAutofillAssistantIntent(
-    const FormData& form,
-    const FormFieldData& field,
+void AndroidAutofillManager::SetProfileFillViaAutofillAssistantIntent(
+    const autofill_assistant::AutofillAssistantIntent intent) {
+  NOTREACHED();
+}
+
+void AndroidAutofillManager::SetCreditCardFillViaAutofillAssistantIntent(
     const autofill_assistant::AutofillAssistantIntent intent) {
   NOTREACHED();
 }
diff --git a/components/android_autofill/browser/android_autofill_manager.h b/components/android_autofill/browser/android_autofill_manager.h
index 67d8e63..5ec843d8 100644
--- a/components/android_autofill/browser/android_autofill_manager.h
+++ b/components/android_autofill/browser/android_autofill_manager.h
@@ -75,9 +75,10 @@
                          mojom::RendererFormDataAction action,
                          const FormData& form);
 
-  void SetFillViaAutofillAssistantIntent(
-      const FormData& form,
-      const FormFieldData& field,
+  void SetProfileFillViaAutofillAssistantIntent(
+      const autofill_assistant::AutofillAssistantIntent intent) override;
+
+  void SetCreditCardFillViaAutofillAssistantIntent(
       const autofill_assistant::AutofillAssistantIntent intent) override;
 
  protected:
diff --git a/components/autofill/content/browser/content_autofill_driver.cc b/components/autofill/content/browser/content_autofill_driver.cc
index 8f159765..6e57a48 100644
--- a/components/autofill/content/browser/content_autofill_driver.cc
+++ b/components/autofill/content/browser/content_autofill_driver.cc
@@ -391,10 +391,10 @@
     const autofill_assistant::AutofillAssistantIntent intent) {
   DCHECK(autofill_manager_);
   if (fill_data.is_profile()) {
-    autofill_manager_->SetFillViaAutofillAssistantIntent(form, field, intent);
+    autofill_manager_->SetProfileFillViaAutofillAssistantIntent(intent);
     autofill_manager_->FillProfileForm(fill_data.profile(), form, field);
   } else if (fill_data.is_credit_card()) {
-    autofill_manager_->SetFillViaAutofillAssistantIntent(form, field, intent);
+    autofill_manager_->SetCreditCardFillViaAutofillAssistantIntent(intent);
     autofill_manager_->FillCreditCardForm(
         /*query_id=*/kNoQueryId, form, field, fill_data.credit_card(),
         fill_data.cvc());
diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h
index 64bb3ce..43d4d67d 100644
--- a/components/autofill/core/browser/autofill_manager.h
+++ b/components/autofill/core/browser/autofill_manager.h
@@ -168,6 +168,16 @@
                                const FormData& form,
                                const FormFieldData& field) = 0;
 
+  // Profile Autofill was triggered by assistant's |intent|. This only affects
+  // metrics logging.
+  virtual void SetProfileFillViaAutofillAssistantIntent(
+      const autofill_assistant::AutofillAssistantIntent intent) = 0;
+
+  // Credit Card Autofill was triggered by assistant's |intent|. This only
+  // affects metrics logging.
+  virtual void SetCreditCardFillViaAutofillAssistantIntent(
+      const autofill_assistant::AutofillAssistantIntent intent) = 0;
+
   // Invoked when changes of the forms have been detected: the forms in
   // |updated_forms| are either new or have changed, and the forms in
   // |removed_forms| have been removed from the DOM (but may be re-added to the
@@ -281,12 +291,6 @@
     OnServerRequestError(form_signature, request_type, http_error);
   }
 
-  // Autofill was triggered by assistant's |intent|.
-  virtual void SetFillViaAutofillAssistantIntent(
-      const FormData& form,
-      const FormFieldData& field,
-      const autofill_assistant::AutofillAssistantIntent intent) = 0;
-
 #ifdef UNIT_TEST
   // A public wrapper that calls |mutable_form_structures| for testing purposes
   // only.
diff --git a/components/autofill/core/browser/autofill_manager_unittest.cc b/components/autofill/core/browser/autofill_manager_unittest.cc
index 2a2b5a4..671cc38 100644
--- a/components/autofill/core/browser/autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/autofill_manager_unittest.cc
@@ -76,10 +76,12 @@
                const FormFieldData& field),
               (override));
   MOCK_METHOD(void,
-              SetFillViaAutofillAssistantIntent,
-              (const FormData& form,
-               const FormFieldData& field,
-               const autofill_assistant::AutofillAssistantIntent intent),
+              SetProfileFillViaAutofillAssistantIntent,
+              (const autofill_assistant::AutofillAssistantIntent intent),
+              (override));
+  MOCK_METHOD(void,
+              SetCreditCardFillViaAutofillAssistantIntent,
+              (const autofill_assistant::AutofillAssistantIntent intent),
               (override));
   MOCK_METHOD(void,
               OnFocusNoLongerOnForm,
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc
index b5cf986..af75561 100644
--- a/components/autofill/core/browser/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -1188,18 +1188,14 @@
                            /*query_id=*/kNoQueryId, form, field, profile);
 }
 
-void BrowserAutofillManager::SetFillViaAutofillAssistantIntent(
-    const FormData& form,
-    const FormFieldData& field,
+void BrowserAutofillManager::SetProfileFillViaAutofillAssistantIntent(
     const autofill_assistant::AutofillAssistantIntent intent) {
-  FormStructure* form_structure = nullptr;
-  AutofillField* autofill_field = nullptr;
-  if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field))
-    return;
+  address_form_event_logger_->SetAutofillAssistantIntentForFilling(intent);
+}
 
-  auto* logger = GetEventFormLogger(autofill_field->Type().group());
-  if (logger)
-    logger->SetAutofillAssistantIntentForFilling(intent);
+void BrowserAutofillManager::SetCreditCardFillViaAutofillAssistantIntent(
+    const autofill_assistant::AutofillAssistantIntent intent) {
+  credit_card_form_event_logger_->SetAutofillAssistantIntentForFilling(intent);
 }
 
 void BrowserAutofillManager::FillOrPreviewVirtualCardInformation(
diff --git a/components/autofill/core/browser/browser_autofill_manager.h b/components/autofill/core/browser/browser_autofill_manager.h
index c6ea4a1..2291194 100644
--- a/components/autofill/core/browser/browser_autofill_manager.h
+++ b/components/autofill/core/browser/browser_autofill_manager.h
@@ -281,9 +281,10 @@
   // to be uploadable. Exposed for testing.
   bool ShouldUploadForm(const FormStructure& form);
 
-  void SetFillViaAutofillAssistantIntent(
-      const FormData& form,
-      const FormFieldData& field,
+  void SetProfileFillViaAutofillAssistantIntent(
+      const autofill_assistant::AutofillAssistantIntent intent) override;
+
+  void SetCreditCardFillViaAutofillAssistantIntent(
       const autofill_assistant::AutofillAssistantIntent intent) override;
 
   // Returns the last form the autofill manager considered in this frame.
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index 3227296..239f842 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -347,7 +347,7 @@
 // shown. This is to prevent double clicks accidentally accepting suggestions.
 // TODO(crbug/1279268): Remove once launched.
 const base::Feature kAutofillIgnoreEarlyClicksOnPopup{
-    "AutofillIgnoreEarlyClicksOnPopup", base::FEATURE_ENABLED_BY_DEFAULT};
+    "AutofillIgnoreEarlyClicksOnPopup", base::FEATURE_DISABLED_BY_DEFAULT};
 
 // The duration for which clicks on the just-shown Autofill popup should be
 // ignored if AutofillIgnoreEarlyClicksOnPopup is enabled.
@@ -356,7 +356,7 @@
 const base::FeatureParam<base::TimeDelta>
     kAutofillIgnoreEarlyClicksOnPopupDuration{
         &kAutofillIgnoreEarlyClicksOnPopup, "duration",
-        base::Milliseconds(250)};
+        base::Milliseconds(500)};
 
 // When enabled, HTML autocomplete values that do not map to any known type, but
 // look reasonable (e.g. contain "address") are simply ignored. Without the
diff --git a/components/commerce/core/BUILD.gn b/components/commerce/core/BUILD.gn
index 065afa3..13fd9aa 100644
--- a/components/commerce/core/BUILD.gn
+++ b/components/commerce/core/BUILD.gn
@@ -145,6 +145,7 @@
     ":feature_list",
     ":metrics",
     ":proto",
+    ":subscriptions",
     "//base",
     "//components/bookmarks/browser",
     "//components/keyed_service/core",
@@ -198,3 +199,17 @@
     "//url:url",
   ]
 }
+
+source_set("subscriptions") {
+  sources = [
+    "subscriptions/commerce_subscription.cc",
+    "subscriptions/commerce_subscription.h",
+    "subscriptions/subscriptions_manager.cc",
+    "subscriptions/subscriptions_manager.h",
+  ]
+
+  deps = [
+    ":feature_list",
+    "//base",
+  ]
+}
diff --git a/components/commerce/core/shopping_service.cc b/components/commerce/core/shopping_service.cc
index 8379727..4e6b29bf 100644
--- a/components/commerce/core/shopping_service.cc
+++ b/components/commerce/core/shopping_service.cc
@@ -16,6 +16,8 @@
 #include "components/commerce/core/proto/merchant_trust.pb.h"
 #include "components/commerce/core/proto/price_tracking.pb.h"
 #include "components/commerce/core/shopping_bookmark_model_observer.h"
+#include "components/commerce/core/subscriptions/commerce_subscription.h"
+#include "components/commerce/core/subscriptions/subscriptions_manager.h"
 #include "components/commerce/core/web_wrapper.h"
 #include "components/optimization_guide/core/new_optimization_guide_decider.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
@@ -60,6 +62,8 @@
     shopping_bookmark_observer_ =
         std::make_unique<ShoppingBookmarkModelObserver>(bookmark_model);
   }
+
+  subscriptions_manager_ = std::make_unique<SubscriptionsManager>();
 }
 
 void ShoppingService::RegisterPrefs(PrefRegistrySimple* registry) {
@@ -341,6 +345,20 @@
   std::move(callback).Run(url, std::move(info));
 }
 
+void ShoppingService::Subscribe(
+    std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+    base::OnceCallback<void(bool)> callback) {
+  subscriptions_manager_->Subscribe(std::move(subscriptions),
+                                    std::move(callback));
+}
+
+void ShoppingService::Unsubscribe(
+    std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+    base::OnceCallback<void(bool)> callback) {
+  subscriptions_manager_->Unsubscribe(std::move(subscriptions),
+                                      std::move(callback));
+}
+
 void ShoppingService::Shutdown() {}
 
 ShoppingService::~ShoppingService() = default;
diff --git a/components/commerce/core/shopping_service.h b/components/commerce/core/shopping_service.h
index 6e911bbc..8289687 100644
--- a/components/commerce/core/shopping_service.h
+++ b/components/commerce/core/shopping_service.h
@@ -35,7 +35,9 @@
 namespace commerce {
 
 class ShoppingBookmarkModelObserver;
+class SubscriptionsManager;
 class WebWrapper;
+struct CommerceSubscription;
 
 // Information returned by the product info APIs.
 struct ProductInfo {
@@ -94,6 +96,18 @@
 
   void GetMerchantInfoForUrl(const GURL& url, MerchantInfoCallback callback);
 
+  // Create new subscriptions in batch if needed, and will notify |callback| if
+  // the operation completes successfully.
+  void Subscribe(
+      std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+      base::OnceCallback<void(bool)> callback);
+
+  // Delete existing subscriptions in batch if needed, and will notify
+  // |callback| if the operation completes successfully.
+  void Unsubscribe(
+      std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+      base::OnceCallback<void(bool)> callback);
+
   void Shutdown() override;
 
  private:
@@ -190,6 +204,8 @@
                      std::tuple<uint32_t, bool, std::unique_ptr<ProductInfo>>>
       product_info_cache_;
 
+  std::unique_ptr<SubscriptionsManager> subscriptions_manager_;
+
   base::WeakPtrFactory<ShoppingService> weak_ptr_factory_;
 };
 
diff --git a/components/commerce/core/subscriptions/commerce_subscription.cc b/components/commerce/core/subscriptions/commerce_subscription.cc
new file mode 100644
index 0000000..42a2570
--- /dev/null
+++ b/components/commerce/core/subscriptions/commerce_subscription.cc
@@ -0,0 +1,44 @@
+// Copyright 2022 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 "components/commerce/core/subscriptions/commerce_subscription.h"
+
+namespace commerce {
+
+UserSeenOffer::UserSeenOffer(uint64_t offer_id,
+                             long user_seen_price,
+                             const std::string& country_code)
+    : offer_id(offer_id),
+      user_seen_price(user_seen_price),
+      country_code(country_code) {}
+UserSeenOffer::~UserSeenOffer() = default;
+
+CommerceSubscription::CommerceSubscription(SubscriptionType type,
+                                           IdentifierType id_type,
+                                           uint64_t id,
+                                           ManagementType management_type)
+    : type(type),
+      id_type(id_type),
+      id(id),
+      management_type(management_type),
+      user_seen_offer(absl::nullopt),
+      timestamp(0) {}
+
+CommerceSubscription::CommerceSubscription(
+    SubscriptionType type,
+    IdentifierType id_type,
+    uint64_t id,
+    ManagementType management_type,
+    absl::optional<UserSeenOffer> user_seen_offer)
+    : type(type),
+      id_type(id_type),
+      id(id),
+      management_type(management_type),
+      user_seen_offer(std::move(user_seen_offer)),
+      timestamp(0) {}
+CommerceSubscription::~CommerceSubscription() = default;
+
+}  // namespace commerce
diff --git a/components/commerce/core/subscriptions/commerce_subscription.h b/components/commerce/core/subscriptions/commerce_subscription.h
new file mode 100644
index 0000000..9aeb718
--- /dev/null
+++ b/components/commerce/core/subscriptions/commerce_subscription.h
@@ -0,0 +1,75 @@
+// Copyright 2022 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 COMPONENTS_COMMERCE_CORE_SUBSCRIPTIONS_COMMERCE_SUBSCRIPTION_H_
+#define COMPONENTS_COMMERCE_CORE_SUBSCRIPTIONS_COMMERCE_SUBSCRIPTION_H_
+
+#include <string>
+
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace commerce {
+
+// The type of subscription.
+enum class SubscriptionType {
+  // Unspecified type.
+  kTypeUnspecified = 0,
+  // Subscription for price tracking.
+  kPriceTrack = 1,
+};
+
+// The type of subscription identifier.
+enum class IdentifierType {
+  // Unspecified identifier type.
+  kIdentifierTypeUnspecified = 0,
+  // Offer id identifier, used in chrome-managed price tracking.
+  kOfferId = 1,
+  // Product cluster id identifier, used in user-managed price tracking.
+  kProductClusterId = 2,
+};
+
+// The type of subscription management.
+enum class ManagementType {
+  // Unspecified management type.
+  kTypeUnspecified = 0,
+  // Automatic chrome-managed subscription.
+  kChromeManaged = 1,
+  // Explicit user-managed subscription.
+  kUserManaged = 2,
+};
+
+struct UserSeenOffer {
+  UserSeenOffer(uint64_t offer_id,
+                long user_seen_price,
+                const std::string& country_code);
+  ~UserSeenOffer();
+
+  const uint64_t offer_id;
+  const long user_seen_price;
+  const std::string country_code;
+};
+
+struct CommerceSubscription {
+  CommerceSubscription(SubscriptionType type,
+                       IdentifierType id_type,
+                       uint64_t id,
+                       ManagementType management_type);
+  CommerceSubscription(SubscriptionType type,
+                       IdentifierType id_type,
+                       uint64_t id,
+                       ManagementType management_type,
+                       absl::optional<UserSeenOffer> user_seen_offer);
+  ~CommerceSubscription();
+
+  const SubscriptionType type;
+  const IdentifierType id_type;
+  const uint64_t id;
+  const ManagementType management_type;
+  const absl::optional<UserSeenOffer> user_seen_offer;
+  const uint64_t timestamp;
+};
+
+}  // namespace commerce
+
+#endif  // COMPONENTS_COMMERCE_CORE_SUBSCRIPTIONS_COMMERCE_SUBSCRIPTION_H_
diff --git a/components/commerce/core/subscriptions/subscriptions_manager.cc b/components/commerce/core/subscriptions/subscriptions_manager.cc
new file mode 100644
index 0000000..c0453d9
--- /dev/null
+++ b/components/commerce/core/subscriptions/subscriptions_manager.cc
@@ -0,0 +1,137 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/commerce/core/subscriptions/subscriptions_manager.h"
+#include "components/commerce/core/commerce_feature_list.h"
+#include "components/commerce/core/subscriptions/commerce_subscription.h"
+
+#include <queue>
+#include <string>
+
+namespace commerce {
+
+SubscriptionsManager::SubscriptionsManager() : weak_ptr_factory_(this) {
+  InitSubscriptions();
+}
+SubscriptionsManager::~SubscriptionsManager() = default;
+
+SubscriptionsManager::Request::Request(SubscriptionType type,
+                                       AsyncOperation operation,
+                                       base::OnceCallback<void(bool)> callback)
+    : type(type), operation(operation), callback(std::move(callback)) {
+  CHECK(operation == AsyncOperation::kInit);
+}
+SubscriptionsManager::Request::Request(
+    SubscriptionType type,
+    AsyncOperation operation,
+    std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+    base::OnceCallback<void(bool)> callback)
+    : type(type),
+      operation(operation),
+      subscriptions(std::move(subscriptions)),
+      callback(std::move(callback)) {
+  CHECK(operation == AsyncOperation::kSubscribe ||
+        operation == AsyncOperation::kUnsubscribe);
+}
+SubscriptionsManager::Request::~Request() = default;
+
+void SubscriptionsManager::Subscribe(
+    std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+    base::OnceCallback<void(bool)> callback) {
+  SubscriptionType type = (*subscriptions)[0].type;
+  pending_requests_.push(std::make_unique<Request>(
+      type, AsyncOperation::kSubscribe, std::move(subscriptions),
+      base::BindOnce(
+          [](base::WeakPtr<SubscriptionsManager> manager,
+             base::OnceCallback<void(bool)> callback, bool result) {
+            std::move(callback).Run(result);
+            manager->OnRequestCompletion();
+          },
+          weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
+  CheckAndProcessRequest();
+}
+
+void SubscriptionsManager::Unsubscribe(
+    std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+    base::OnceCallback<void(bool)> callback) {
+  SubscriptionType type = (*subscriptions)[0].type;
+  pending_requests_.push(std::make_unique<Request>(
+      type, AsyncOperation::kUnsubscribe, std::move(subscriptions),
+      base::BindOnce(
+          [](base::WeakPtr<SubscriptionsManager> manager,
+             base::OnceCallback<void(bool)> callback, bool result) {
+            std::move(callback).Run(result);
+            manager->OnRequestCompletion();
+          },
+          weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
+  CheckAndProcessRequest();
+}
+
+void SubscriptionsManager::InitSubscriptions() {
+  init_succeeded_ = false;
+  if (base::FeatureList::IsEnabled(commerce::kShoppingList)) {
+    pending_requests_.push(std::make_unique<Request>(
+        SubscriptionType::kPriceTrack, AsyncOperation::kInit,
+        base::BindOnce(
+            [](base::WeakPtr<SubscriptionsManager> manager, bool result) {
+              manager->init_succeeded_ = result;
+              manager->OnRequestCompletion();
+            },
+            weak_ptr_factory_.GetWeakPtr())));
+  }
+  CheckAndProcessRequest();
+}
+
+void SubscriptionsManager::CheckAndProcessRequest() {
+  if (has_request_running_ || pending_requests_.empty())
+    return;
+
+  // If there is no request running, we can start processing next request in the
+  // queue.
+  has_request_running_ = true;
+  std::unique_ptr<Request> request = std::move(pending_requests_.front());
+  pending_requests_.pop();
+  CHECK(request->type != SubscriptionType::kTypeUnspecified);
+
+  switch (request->operation) {
+    case AsyncOperation::kInit:
+      ProcessInitRequest(std::move(request));
+      break;
+    case AsyncOperation::kSubscribe:
+      ProcessSubscribeRequest(std::move(request));
+      break;
+    case AsyncOperation::kUnsubscribe:
+      ProcessUnsubscribeRequest(std::move(request));
+      break;
+  }
+}
+
+void SubscriptionsManager::OnRequestCompletion() {
+  has_request_running_ = false;
+  CheckAndProcessRequest();
+}
+
+void SubscriptionsManager::ProcessInitRequest(std::unique_ptr<Request> request){
+    // TODO: Get all subscriptions from server and sync with local db.
+};
+
+void SubscriptionsManager::ProcessSubscribeRequest(
+    std::unique_ptr<Request> request) {
+  if (!init_succeeded_) {
+    std::move(request->callback).Run(false);
+    return;
+  }
+  // TODO: Check local db and send request to server.
+}
+
+void SubscriptionsManager::ProcessUnsubscribeRequest(
+    std::unique_ptr<Request> request) {
+  if (!init_succeeded_) {
+    std::move(request->callback).Run(false);
+    return;
+  }
+  // TODO: Check local db and send request to server.
+}
+
+}  // namespace commerce
diff --git a/components/commerce/core/subscriptions/subscriptions_manager.h b/components/commerce/core/subscriptions/subscriptions_manager.h
new file mode 100644
index 0000000..be43290
--- /dev/null
+++ b/components/commerce/core/subscriptions/subscriptions_manager.h
@@ -0,0 +1,92 @@
+// Copyright 2022 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 COMPONENTS_COMMERCE_CORE_SUBSCRIPTIONS_SUBSCRIPTIONS_MANAGER_H_
+#define COMPONENTS_COMMERCE_CORE_SUBSCRIPTIONS_SUBSCRIPTIONS_MANAGER_H_
+
+#include <queue>
+#include <string>
+#include <unordered_map>
+
+#include "base/callback.h"
+#include "base/check.h"
+
+namespace commerce {
+
+enum class SubscriptionType;
+struct CommerceSubscription;
+
+class SubscriptionsManager {
+ public:
+  SubscriptionsManager();
+  SubscriptionsManager(const SubscriptionsManager&) = delete;
+  SubscriptionsManager& operator=(const SubscriptionsManager&) = delete;
+  ~SubscriptionsManager();
+
+  void Subscribe(
+      std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+      base::OnceCallback<void(bool)> callback);
+
+  void Unsubscribe(
+      std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+      base::OnceCallback<void(bool)> callback);
+
+ private:
+  enum class AsyncOperation {
+    kInit = 0,
+    kSubscribe = 1,
+    kUnsubscribe = 2,
+  };
+
+  struct Request {
+    Request(SubscriptionType type,
+            AsyncOperation operation,
+            base::OnceCallback<void(bool)> callback);
+    Request(SubscriptionType type,
+            AsyncOperation operation,
+            std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
+            base::OnceCallback<void(bool)> callback);
+    ~Request();
+
+    SubscriptionType type;
+    AsyncOperation operation;
+    std::unique_ptr<std::vector<CommerceSubscription>> subscriptions;
+    base::OnceCallback<void(bool)> callback;
+  };
+
+  // Fetch all backend subscriptions and sync with local storage. This should
+  // only be called on manager instantiation and user primary account changed.
+  void InitSubscriptions();
+
+  // Check if there is any request running. If not, process the next request in
+  // the queue.
+  void CheckAndProcessRequest();
+
+  // On request completion, mark that no request is running and then check next
+  // request. This is chained to the main callback when Request object is built.
+  void OnRequestCompletion();
+
+  void ProcessSubscribeRequest(std::unique_ptr<Request> request);
+
+  void ProcessUnsubscribeRequest(std::unique_ptr<Request> request);
+
+  void ProcessInitRequest(std::unique_ptr<Request> request);
+
+  // Hold coming requests until previous ones have finished to avoid race
+  // conditions.
+  std::queue<std::unique_ptr<Request>> pending_requests_;
+
+  // Whether the initialization is successful. If not, all (un)subscribe
+  // operations will fail immediately.
+  bool init_succeeded_ = false;
+
+  // Whether there is any request running.
+  bool has_request_running_ = false;
+
+  base::WeakPtrFactory<SubscriptionsManager> weak_ptr_factory_;
+};
+
+}  // namespace commerce
+
+#endif  // COMPONENTS_COMMERCE_CORE_SUBSCRIPTIONS_SUBSCRIPTIONS_MANAGER_H_
diff --git a/components/exo/wayland/weston_test.cc b/components/exo/wayland/weston_test.cc
index d97a1b72..6cbc250 100644
--- a/components/exo/wayland/weston_test.cc
+++ b/components/exo/wayland/weston_test.cc
@@ -23,6 +23,7 @@
 #include "ui/events/keycodes/dom/dom_code.h"
 #include "ui/events/keycodes/dom/keycode_converter.h"
 #include "ui/events/keycodes/keyboard_code_conversion.h"
+#include "ui/wm/core/coordinate_conversion.h"
 #include "ui/wm/core/window_util.h"
 
 namespace exo {
@@ -99,15 +100,14 @@
   auto* weston_test = GetUserDataAs<WestonTestState>(resource);
 
   // Convert cursor point from window space to root space
-  gfx::Point point_in_root(x, y);
+  gfx::Point point_in_screen(x, y);
   if (surface_resource) {
     aura::Window* window = GetUserDataAs<Surface>(surface_resource)->window();
-    aura::Window::ConvertPointToTarget(window, window->GetRootWindow(),
-                                       &point_in_root);
+    wm::ConvertPointToScreen(window, &point_in_screen);
   }
   base::RunLoop run_loop;
-  ui_controls::SendMouseMoveNotifyWhenDone(point_in_root.x(), point_in_root.y(),
-                                           run_loop.QuitClosure());
+  ui_controls::SendMouseMoveNotifyWhenDone(
+      point_in_screen.x(), point_in_screen.y(), run_loop.QuitClosure());
   {
     // Do not process incoming wayland events which may destroy resources.
     ScopedEventDispatchDisabler disable(weston_test->server);
diff --git a/components/flags_ui/flags_state.cc b/components/flags_ui/flags_state.cc
index 7124851..da41851 100644
--- a/components/flags_ui/flags_state.cc
+++ b/components/flags_ui/flags_state.cc
@@ -16,6 +16,7 @@
 #include "base/logging.h"
 #include "base/metrics/field_trial.h"
 #include "base/stl_util.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_tokenizer.h"
 #include "base/strings/string_util.h"
@@ -751,8 +752,7 @@
                                   false, command_line);
   }
   if (!variation_ids.empty()) {
-    command_line->AppendSwitchASCII(variations::switches::kForceVariationIds,
-                                    base::JoinString(variation_ids, ","));
+    MergeVariationIdsCommandLineSwitch(variation_ids, command_line);
   }
 
   if (sentinels == kAddSentinels) {
@@ -788,6 +788,25 @@
     command_line->AppendSwitchASCII(switch_name, switch_value);
 }
 
+void FlagsState::MergeVariationIdsCommandLineSwitch(
+    const std::vector<std::string>& variation_ids,
+    base::CommandLine* command_line) {
+  DCHECK(!variation_ids.empty());
+  std::string variation_ids_switch = command_line->GetSwitchValueASCII(
+      variations::switches::kForceVariationIds);
+
+  // At this point, the switch value is guaranteed to change since
+  // |variation_ids| is not empty. Hence, we do not conditionally update the
+  // switch value, as is done in FlagsState::MergeFeatureCommandLineSwitch().
+  // Note that it is an error to try to set the same variation id in multiple
+  // ways.
+  command_line->AppendSwitchASCII(
+      variations::switches::kForceVariationIds,
+      base::StrCat({variation_ids_switch,
+                    variation_ids_switch.empty() ? "" : ",",
+                    base::JoinString(variation_ids, ",")}));
+}
+
 std::set<std::string> FlagsState::SanitizeList(
     const FlagsStorage* storage,
     const std::set<std::string>& enabled_entries,
diff --git a/components/flags_ui/flags_state.h b/components/flags_ui/flags_state.h
index 06fef2a2..e70c09b62 100644
--- a/components/flags_ui/flags_state.h
+++ b/components/flags_ui/flags_state.h
@@ -225,6 +225,12 @@
       bool feature_state,
       base::CommandLine* command_line);
 
+  // Updates |command_line| by merging the value of the --force-variation-ids
+  // list with corresponding entries in |variation_ids|.
+  void MergeVariationIdsCommandLineSwitch(
+      const std::vector<std::string>& variation_ids,
+      base::CommandLine* command_line);
+
   // Sanitizes |enabled_entries| to only contain entries that are defined in the
   // |feature_entries_| and whose |supported_platforms| matches |platform_mask|.
   // Pass -1 to |platform_mask| to not do platform filtering.
diff --git a/components/lens/lens_features.cc b/components/lens/lens_features.cc
index 2c39849..3a391d4e 100644
--- a/components/lens/lens_features.cc
+++ b/components/lens/lens_features.cc
@@ -13,21 +13,18 @@
 const base::Feature kLensStandalone{"LensStandalone",
                                     base::FEATURE_ENABLED_BY_DEFAULT};
 
-const base::Feature kLensFullscreenSearch{"LensFullscreenSearch",
-                                          base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kLensImageCompression{"LensImageCompression",
+                                          base::FEATURE_ENABLED_BY_DEFAULT};
 
-const base::FeatureParam<bool> kUseGoogleAsVisualSearchProvider{
-    &kLensStandalone, "use-google-as-visual-search-provider", false};
+const base::Feature kLensSearchOptimizations{"LensSearchOptimizations",
+                                             base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::Feature kLensTransparentImagesFix{
+    "LensTransparentImagesFix", base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::FeatureParam<bool> kRegionSearchMacCursorFix{
     &kLensStandalone, "region-search-mac-cursor-fix", true};
 
-const base::FeatureParam<bool> kRegionSearchUseMenuItemAltText1{
-    &kLensStandalone, "use-menu-item-alt-text-1", false};
-
-const base::FeatureParam<bool> kRegionSearchUseMenuItemAltText2{
-    &kLensStandalone, "use-menu-item-alt-text-2", false};
-
 const base::FeatureParam<bool> kEnableUKMLoggingForRegionSearch{
     &kLensStandalone, "region-search-enable-ukm-logging", true};
 
@@ -37,20 +34,32 @@
 const base::FeatureParam<bool> kEnableSidePanelForLens{
     &kLensStandalone, "enable-side-panel", true};
 
-constexpr base::FeatureParam<int> kMaxPixelsForRegionSearch{
-    &kLensStandalone, "region-search-dimensions-max-pixels", 1000};
-
-constexpr base::FeatureParam<int> kMaxAreaForRegionSearch{
-    &kLensStandalone, "region-search-dimensions-max-area", 1000000};
-
-constexpr base::FeatureParam<int> kMaxPixelsForImageSearch{
-    &kLensStandalone, "dimensions-max-pixels", 1000};
-
 constexpr base::FeatureParam<std::string> kHomepageURLForLens{
     &kLensStandalone, "lens-homepage-url", "https://lens.google.com/"};
 
-const base::FeatureParam<bool> kSendImagesAsPng{&kLensStandalone,
-                                                "send-png-images", false};
+constexpr base::FeatureParam<int> kMaxPixelsForRegionSearch{
+    &kLensImageCompression, "region-search-dimensions-max-pixels", 1000};
+
+constexpr base::FeatureParam<int> kMaxAreaForRegionSearch{
+    &kLensImageCompression, "region-search-dimensions-max-area", 1000000};
+
+constexpr base::FeatureParam<int> kMaxPixelsForImageSearch{
+    &kLensImageCompression, "dimensions-max-pixels", 1000};
+
+const base::FeatureParam<bool> kUseGoogleAsVisualSearchProvider{
+    &kLensSearchOptimizations, "use-google-as-visual-search-provider", false};
+
+const base::FeatureParam<bool> kRegionSearchUseMenuItemAltText1{
+    &kLensSearchOptimizations, "use-menu-item-alt-text-1", false};
+
+const base::FeatureParam<bool> kRegionSearchUseMenuItemAltText2{
+    &kLensSearchOptimizations, "use-menu-item-alt-text-2", false};
+
+// Default is set to true but it is only enabled if kLensSearchOptimizations is
+// enabled. This setup allows us to have fullscreen search as a toggleable
+// experience in chrome://flags
+const base::FeatureParam<bool> kEnableLensFullscreenSearch{
+    &kLensSearchOptimizations, "enable-lens-fullscreen-search", true};
 
 bool GetEnableUKMLoggingForRegionSearch() {
   return kEnableUKMLoggingForRegionSearch.Get();
@@ -78,21 +87,26 @@
 
 bool UseRegionSearchMenuItemAltText1() {
   return base::FeatureList::IsEnabled(kLensStandalone) &&
+         base::FeatureList::IsEnabled(kLensSearchOptimizations) &&
          kRegionSearchUseMenuItemAltText1.Get();
 }
 
 bool UseRegionSearchMenuItemAltText2() {
   return base::FeatureList::IsEnabled(kLensStandalone) &&
+         base::FeatureList::IsEnabled(kLensSearchOptimizations) &&
          kRegionSearchUseMenuItemAltText2.Get();
 }
 
 bool UseGoogleAsVisualSearchProvider() {
   return base::FeatureList::IsEnabled(kLensStandalone) &&
+         base::FeatureList::IsEnabled(kLensSearchOptimizations) &&
          kUseGoogleAsVisualSearchProvider.Get();
 }
 
 bool IsLensFullscreenSearchEnabled() {
-  return base::FeatureList::IsEnabled(kLensFullscreenSearch);
+  return base::FeatureList::IsEnabled(kLensStandalone) &&
+         base::FeatureList::IsEnabled(kLensSearchOptimizations) &&
+         kEnableLensFullscreenSearch.Get();
 }
 
 bool IsLensSidePanelEnabled() {
@@ -102,7 +116,7 @@
 
 bool GetSendImagesAsPng() {
   return base::FeatureList::IsEnabled(kLensStandalone) &&
-         kSendImagesAsPng.Get();
+         base::FeatureList::IsEnabled(kLensTransparentImagesFix);
 }
 
 }  // namespace features
diff --git a/components/lens/lens_features.h b/components/lens/lens_features.h
index 3ac3dd8f..f228e96 100644
--- a/components/lens/lens_features.h
+++ b/components/lens/lens_features.h
@@ -16,8 +16,15 @@
 // Enables context menu search by image sending to the Lens homepage.
 extern const base::Feature kLensStandalone;
 
-// Enables Lens fullscreen search on Desktop platforms.
-extern const base::Feature kLensFullscreenSearch;
+// Feature that controls the compression of images before they are sent to Lens.
+extern const base::Feature kLensImageCompression;
+
+// Enables a variety of changes aimed to improve user's engagement with current
+// Lens features.
+extern const base::Feature kLensSearchOptimizations;
+
+// Enables a fix to properly handle transparent images in Lens Image Search
+extern const base::Feature kLensTransparentImagesFix;
 
 // Enables a fix for cursor pointer/crosshair state over overlay on Mac.
 // TODO(crbug/1266514): make default and remove feature once launched.
@@ -42,8 +49,8 @@
 // Enables the side panel for Lens features on Chrome where supported.
 extern const base::FeatureParam<bool> kEnableSidePanelForLens;
 
-// Sends images as PNG to Lens Standalone to fix issues with transparency.
-extern const base::FeatureParam<bool> kSendImagesAsPng;
+// Enables Lens fullscreen search on Desktop platforms.
+extern const base::FeatureParam<bool> kEnableFullscreenSearch;
 
 // Returns whether to enable UKM logging for Lens Region Search feature.
 extern bool GetEnableUKMLoggingForRegionSearch();
diff --git a/components/omnibox/browser/history_cluster_provider.cc b/components/omnibox/browser/history_cluster_provider.cc
index 204be41c..c6ac7de 100644
--- a/components/omnibox/browser/history_cluster_provider.cc
+++ b/components/omnibox/browser/history_cluster_provider.cc
@@ -22,9 +22,9 @@
     SearchProvider* search_provider)
     : AutocompleteProvider(AutocompleteProvider::TYPE_HISTORY_CLUSTER_PROVIDER),
       client_(client),
-      listener_(listener),
       search_provider_(search_provider) {
   DCHECK(search_provider_);
+  AddListener(listener);
   search_provider_->AddListener(this);
 }
 
@@ -59,7 +59,7 @@
 void HistoryClusterProvider::OnProviderUpdate(bool updated_matches) {
   if (done_ || !search_provider_->done())
     return;
-  listener_->OnProviderUpdate(CreateMatches());
+  NotifyListeners(CreateMatches());
 }
 
 bool HistoryClusterProvider::CreateMatches() {
diff --git a/components/omnibox/browser/history_cluster_provider.h b/components/omnibox/browser/history_cluster_provider.h
index b886c0d..f08c4fd 100644
--- a/components/omnibox/browser/history_cluster_provider.h
+++ b/components/omnibox/browser/history_cluster_provider.h
@@ -49,7 +49,6 @@
 
   // These are never null.
   const raw_ptr<AutocompleteProviderClient> client_;
-  const raw_ptr<AutocompleteProviderListener> listener_;
   const raw_ptr<SearchProvider> search_provider_;
 };
 
diff --git a/components/omnibox/browser/search_suggestion_parser.cc b/components/omnibox/browser/search_suggestion_parser.cc
index c65805c..541339f2 100644
--- a/components/omnibox/browser/search_suggestion_parser.cc
+++ b/components/omnibox/browser/search_suggestion_parser.cc
@@ -676,7 +676,8 @@
       absl::optional<int> suggestion_group_id;
 
       if (suggestion_details &&
-          suggestion_details->GetListDeprecated()[index].is_dict()) {
+          suggestion_details->GetListDeprecated()[index].is_dict() &&
+          !suggestion_details->GetListDeprecated()[index].DictEmpty()) {
         const base::Value& suggestion_detail =
             suggestion_details->GetListDeprecated()[index];
         match_contents =
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index e097d0f..8097d01 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -30926,7 +30926,7 @@
       'owners': ['file://third_party/blink/renderer/core/frame/OWNERS', 'shaseley@chromium.org', 'bokan@chromium.org', 'dcheng@chromium.org', 'japhet@chromium.org'],
       'type': 'main',
       'schema': { 'type': 'boolean' },
-      'supported_on': ['chrome_os:101-104', 'chrome.*:101-104', 'android:101-104'],
+      'supported_on': ['chrome_os:101-107', 'chrome.*:101-107', 'android:101-107'],
       'features': {
         'per_profile': True,
         'dynamic_refresh': True,
diff --git a/components/policy/tools/generate_policy_source.py b/components/policy/tools/generate_policy_source.py
index 390360c7..974e573 100755
--- a/components/policy/tools/generate_policy_source.py
+++ b/components/policy/tools/generate_policy_source.py
@@ -90,7 +90,8 @@
       raise RuntimeError('Platform "%s" is not supported' % platform)
     return PLATFORM_STRINGS[platform]
 
-  def __init__(self, policy, chrome_major_version, target_platform, valid_tags):
+  def __init__(self, policy, chrome_major_version, deprecation_milestone_buffer,
+               target_platform, valid_tags):
     self.id = policy['id']
     self.name = policy['name']
     self.tags = policy.get('tags', None)
@@ -133,8 +134,9 @@
       # Skip if filtering by Chromium version and the current Chromium version
       # does not support the policy.
       if chrome_major_version:
-        if (int(version_min) > chrome_major_version or
-            version_max != '' and int(version_max) < chrome_major_version):
+        if (int(version_min) > chrome_major_version
+            or version_max != '' and int(version_max) <
+            chrome_major_version - deprecation_milestone_buffer):
           continue
       self.platforms.update(self._ConvertPlatform(platform))
 
@@ -331,6 +333,13 @@
       dest='policy_templates_file',
       help='path to the policy_templates.json input file',
       metavar='FILE')
+  parser.add_argument(
+      '--deprecation-milestone-buffer',
+      dest='deprecation_milestone_buffer',
+      type=int,
+      help='Number of major versions before a code for a policy stops being '
+      'generated',
+      default=2)
   args = parser.parse_args()
 
   has_arg_error = False
@@ -358,6 +367,7 @@
   version_path = args.chrome_version_file
   target_platform = args.target_platform
   template_file_name = args.policy_templates_file
+  deprecation_milestone_buffer = int(args.deprecation_milestone_buffer)
 
   # --target-platform accepts "chromeos" as its input because that's what is
   # used within GN. Within policy templates, "chrome_os" is used instead.
@@ -372,8 +382,8 @@
   template_file_contents = _LoadJSONFile(template_file_name)
   risk_tags = RiskTags(template_file_contents)
   policy_details = [
-      PolicyDetails(policy, chrome_major_version, target_platform,
-                    risk_tags.GetValidTags())
+      PolicyDetails(policy, chrome_major_version, deprecation_milestone_buffer,
+                    target_platform, risk_tags.GetValidTags())
       for policy in template_file_contents['policy_definitions']
       if policy['type'] != 'group'
   ]
@@ -490,19 +500,26 @@
   ]
 
 
+# Returns the policies supported by at least one platform.
+def _GetSupportedPolicies(policies):
+  return [
+      policy for policy in policies
+      if len(policy.platforms) + len(policy.future_on) > 0
+  ]
+
 #------------------ policy constants header ------------------------#
 
 
 # Return a list of all policies of type |metapolicy_type|.
 def _GetMetapoliciesOfType(policies, metapolicy_type):
   return [
-      policy.name for policy in policies
-      if policy.metapolicy_type == metapolicy_type
+      policy for policy in policies if policy.metapolicy_type == metapolicy_type
   ]
 
 
-def _WritePolicyConstantHeader(policies, policy_atomic_groups, target_platform,
-                               f, risk_tags):
+def _WritePolicyConstantHeader(all_policies, policy_atomic_groups,
+                               target_platform, f, risk_tags):
+  policies = _GetSupportedPolicies(all_policies)
   f.write('''#ifndef COMPONENTS_POLICY_POLICY_CONSTANTS_H_
 #define COMPONENTS_POLICY_POLICY_CONSTANTS_H_
 
@@ -1090,8 +1107,10 @@
   return [], None
 
 
-def _WritePolicyConstantSource(policies, policy_atomic_groups, target_platform,
-                               f, risk_tags):
+def _WritePolicyConstantSource(all_policies, policy_atomic_groups,
+                               target_platform, f, risk_tags):
+  policies = _GetSupportedPolicies(all_policies)
+  policy_names = [policy.name for policy in policies]
   f.write('''#include "components/policy/policy_constants.h"
 
 #include <algorithm>
@@ -1296,7 +1315,8 @@
   for group in policy_atomic_groups:
     f.write('const char* const %s[] = {' % (group.name))
     for policy in group.policies:
-      f.write('key::k%s, ' % (policy))
+      if policy in policy_names:
+        f.write('key::k%s, ' % (policy))
     f.write('nullptr};\n')
   f.write('\n}  // namespace\n')
   f.write('\n}  // namespace group\n\n')
@@ -1319,7 +1339,7 @@
                                               METAPOLICY_TYPE['merge'])
   f.write('const char* const kMerge[%s] = {\n' % len(merge_metapolicies))
   for metapolicy in merge_metapolicies:
-    f.write('  key::k%s,\n' % metapolicy)
+    f.write('  key::k%s,\n' % metapolicy.name)
   f.write('};\n\n')
 
   # Populate precedence metapolicy array.
@@ -1328,7 +1348,7 @@
   f.write('const char* const kPrecedence[%s] = {\n' %
           len(precedence_metapolicies))
   for metapolicy in precedence_metapolicies:
-    f.write('  key::k%s,\n' % metapolicy)
+    f.write('  key::k%s,\n' % metapolicy.name)
   f.write('};\n\n')
   f.write('}  // namespace metapolicy\n\n')
 
diff --git a/components/policy/tools/generate_policy_source_test.py b/components/policy/tools/generate_policy_source_test.py
index 2628272..79da976 100755
--- a/components/policy/tools/generate_policy_source_test.py
+++ b/components/policy/tools/generate_policy_source_test.py
@@ -56,7 +56,8 @@
           "schema": {
               "type": "boolean"
           },
-          "supported_on": ["chrome_os:1-"],
+          "supported_on":
+          ["chrome_os:1-", "chrome.*:1-", "android:1-", "ios:1-"],
           "features": {
               "metapolicy_type": "merge",
           },
@@ -70,7 +71,8 @@
           "schema": {
               "type": "boolean"
           },
-          "supported_on": ["chrome_os:1-"],
+          "supported_on":
+          ["chrome_os:1-", "chrome.*:1-", "android:1-", "ios:1-"],
           "features": {
               "metapolicy_type": "precedence",
           },
@@ -104,6 +106,39 @@
           "caption": "CloudManagementEnrollmentToken caption",
           "desc": "CloudManagementEnrollmentToken desc"
       }, {
+          "name": "DeprecatedButGenerated",
+          "type": "string",
+          "schema": {
+              "type": "string"
+          },
+          "supported_on": ["chrome_os:1-93"],
+          "id": 7,
+          "tags": [],
+          "caption": "DeprecatedButGenerated caption",
+          "desc": "DeprecatedButGenerated desc"
+      },  {
+          "name": "DeprecatedNotGenerated",
+          "type": "string",
+          "schema": {
+              "type": "string"
+          },
+          "supported_on": ["chrome_os:1-92"],
+          "id": 8,
+          "tags": [],
+          "caption": "DeprecatedNotGenerated caption",
+          "desc": "DeprecatedNotGenerated desc"
+      }, {
+          "name": "UnsupportedPolicy",
+          "type": "string",
+          "schema": {
+              "type": "string"
+          },
+          "supported_on": [],
+          "id": 9,
+          "tags": [],
+          "caption": "UnsupportedPolicy caption",
+          "desc": "UnsupportedPolicy desc"
+      }, {
           "name": "ChunkZeroLastFieldBooleanPolicy",
           "type": "main",
           "schema": {
@@ -163,13 +198,14 @@
   }
 
   def setUp(self):
-    self.maxDiff = 10000
     self.chrome_major_version = 94
     self.target_platform = 'chrome_os'
+    self.deprecation_milestone_buffer = 1
     self.all_target_platforms = ['win', 'mac', 'linux', 'chromeos', 'fuchsia']
     self.risk_tags = generate_policy_source.RiskTags(self.TEMPLATES_JSON)
     self.policies = [
         generate_policy_source.PolicyDetails(policy, self.chrome_major_version,
+                                             self.deprecation_milestone_buffer,
                                              self.target_platform,
                                              self.risk_tags.GetValidTags())
         for policy in self.TEMPLATES_JSON['policy_definitions']
@@ -314,18 +350,17 @@
   def testGetMetapoliciesOfType(self):
     merge_metapolicies = generate_policy_source._GetMetapoliciesOfType(
         self.policies, "merge")
-    self.assertListEqual(["ExampleBoolMergeMetapolicy"], merge_metapolicies)
     self.assertEqual(1, len(merge_metapolicies))
+    self.assertEqual("ExampleBoolMergeMetapolicy", merge_metapolicies[0].name)
 
     precedence_metapolicies = generate_policy_source._GetMetapoliciesOfType(
         self.policies, "precedence")
-    self.assertListEqual(["ExampleBoolPrecedenceMetapolicy"],
-                         precedence_metapolicies)
     self.assertEqual(1, len(precedence_metapolicies))
+    self.assertEqual("ExampleBoolPrecedenceMetapolicy",
+                     precedence_metapolicies[0].name)
 
     invalid_metapolicies = generate_policy_source._GetMetapoliciesOfType(
         self.policies, "invalid")
-    self.assertListEqual([], invalid_metapolicies)
     self.assertEqual(0, len(invalid_metapolicies))
 
   def testWritePolicyConstantHeader(self):
diff --git a/components/policy/tools/generate_policy_source_test_data.py b/components/policy/tools/generate_policy_source_test_data.py
index 2bc4db62c..f358abd 100644
--- a/components/policy/tools/generate_policy_source_test_data.py
+++ b/components/policy/tools/generate_policy_source_test_data.py
@@ -35,6 +35,7 @@
   optional BooleanPolicyProto ExampleBoolPrecedenceMetapolicy = 6;
   optional BooleanPolicyProto CloudOnlyPolicy = 7;
   optional StringPolicyProto CloudManagementEnrollmentToken = 8;
+  optional StringPolicyProto DeprecatedButGenerated = 9;
   optional BooleanPolicyProto ChunkZeroLastFieldBooleanPolicy = 1017;
   optional CloudPolicySubProto1 subProto1 = 1018;
   optional CloudPolicySubProto2 subProto2 = 1019;
@@ -79,7 +80,7 @@
 //
 // ExampleBoolMergeMetapolicy desc
 //
-// Supported on: chrome_os
+// Supported on: android, chrome_os, fuchsia, ios, linux, mac, win
 message ExampleBoolMergeMetapolicyProto {
   optional PolicyOptions policy_options = 1;
   optional bool ExampleBoolMergeMetapolicy = 2;
@@ -89,7 +90,7 @@
 //
 // ExampleBoolPrecedenceMetapolicy desc
 //
-// Supported on: chrome_os
+// Supported on: android, chrome_os, fuchsia, ios, linux, mac, win
 message ExampleBoolPrecedenceMetapolicyProto {
   optional PolicyOptions policy_options = 1;
   optional bool ExampleBoolPrecedenceMetapolicy = 2;
@@ -115,6 +116,36 @@
   optional string CloudManagementEnrollmentToken = 2;
 }
 
+// DeprecatedButGenerated caption
+//
+// DeprecatedButGenerated desc
+//
+// Supported on: chrome_os
+message DeprecatedButGeneratedProto {
+  optional PolicyOptions policy_options = 1;
+  optional string DeprecatedButGenerated = 2;
+}
+
+// DeprecatedNotGenerated caption
+//
+// DeprecatedNotGenerated desc
+//
+// Supported on:
+message DeprecatedNotGeneratedProto {
+  optional PolicyOptions policy_options = 1;
+  optional string DeprecatedNotGenerated = 2;
+}
+
+// UnsupportedPolicy caption
+//
+// UnsupportedPolicy desc
+//
+// Supported on:
+message UnsupportedPolicyProto {
+  optional PolicyOptions policy_options = 1;
+  optional string UnsupportedPolicy = 2;
+}
+
 // ChunkZeroLastFieldBooleanPolicy caption
 //
 // ChunkZeroLastFieldBooleanPolicy desc.
@@ -188,6 +219,9 @@
   optional ExampleBoolPrecedenceMetapolicyProto ExampleBoolPrecedenceMetapolicy = 6;
   optional CloudOnlyPolicyProto CloudOnlyPolicy = 7;
   optional CloudManagementEnrollmentTokenProto CloudManagementEnrollmentToken = 8;
+  optional DeprecatedButGeneratedProto DeprecatedButGenerated = 9;
+  optional DeprecatedNotGeneratedProto DeprecatedNotGenerated = 10;
+  optional UnsupportedPolicyProto UnsupportedPolicy = 11;
   optional ChunkZeroLastFieldBooleanPolicyProto ChunkZeroLastFieldBooleanPolicy = 1017;
   optional ChromeSettingsSubProto1 subProto1 = 1018;
   optional ChromeSettingsSubProto2 subProto2 = 1019;
@@ -258,6 +292,7 @@
 extern const char kExampleBoolPrecedenceMetapolicy[];
 extern const char kCloudOnlyPolicy[];
 extern const char kCloudManagementEnrollmentToken[];
+extern const char kDeprecatedButGenerated[];
 extern const char kChunkZeroLastFieldBooleanPolicy[];
 extern const char kChunkOneFirstFieldBooleanPolicy[];
 extern const char kChunkOneLastFieldBooleanPolicy[];
@@ -325,7 +360,7 @@
       const em::CloudPolicySettings& policy);
   const StringPolicyType type;
 };
-extern const std::array<StringPolicyAccess, 4> kStringPolicyAccess;
+extern const std::array<StringPolicyAccess, 5> kStringPolicyAccess;
 
 // Read access to the protobufs of all supported stringlist user policies.
 struct StringListPolicyAccess {
@@ -380,6 +415,8 @@
   { false,        false,    false,              5,                     0, {  } },
   // CloudManagementEnrollmentToken
   { false,        false,    false,              6,                     0, {  } },
+  // DeprecatedButGenerated
+  { false,        false,    false,              7,                     0, {  } },
   // ChunkZeroLastFieldBooleanPolicy
   { false,        false,    false,           1015,                     0, {  } },
   // ChunkOneFirstFieldBooleanPolicy
@@ -408,6 +445,7 @@
   { key::kChunkZeroLastFieldBooleanPolicy,                                1 },
   { key::kCloudManagementEnrollmentToken,                                 2 },
   { key::kCloudOnlyPolicy,                                                1 },
+  { key::kDeprecatedButGenerated,                                         2 },
   { key::kExampleBoolMergeMetapolicy,                                     1 },
   { key::kExampleBoolPolicy,                                              1 },
   { key::kExampleBoolPrecedenceMetapolicy,                                1 },
@@ -416,7 +454,7 @@
 
 const internal::PropertiesNode kProperties[] = {
 //  Begin    End  PatternEnd  RequiredBegin  RequiredEnd  Additional Properties
-  {     0,    11,    11,     0,          0,    -1 },  // root node
+  {     0,    12,    12,     0,          0,    -1 },  // root node
 };
 
 const internal::SchemaData* GetChromeSchemaData() {
@@ -462,7 +500,7 @@
   // First index in kPropertyNodes of the Chrome policies.
   static const int begin_index = 0;
   // One-past-the-end of the Chrome policies in kPropertyNodes.
-  static const int end_index = 11;
+  static const int end_index = 12;
   const internal::PropertyNode* begin =
      kPropertyNodes + begin_index;
   const internal::PropertyNode* end = kPropertyNodes + end_index;
@@ -493,6 +531,7 @@
 const char kExampleBoolPrecedenceMetapolicy[] = "ExampleBoolPrecedenceMetapolicy";
 const char kCloudOnlyPolicy[] = "CloudOnlyPolicy";
 const char kCloudManagementEnrollmentToken[] = "CloudManagementEnrollmentToken";
+const char kDeprecatedButGenerated[] = "DeprecatedButGenerated";
 const char kChunkZeroLastFieldBooleanPolicy[] = "ChunkZeroLastFieldBooleanPolicy";
 const char kChunkOneFirstFieldBooleanPolicy[] = "ChunkOneFirstFieldBooleanPolicy";
 const char kChunkOneLastFieldBooleanPolicy[] = "ChunkOneLastFieldBooleanPolicy";
@@ -606,7 +645,7 @@
 const std::array<IntegerPolicyAccess, 0> kIntegerPolicyAccess {{
 }};
 
-const std::array<StringPolicyAccess, 4> kStringPolicyAccess {{
+const std::array<StringPolicyAccess, 5> kStringPolicyAccess {{
   {key::kExampleStringPolicy,
    false,
    [](const em::CloudPolicySettings& policy) {
@@ -629,6 +668,17 @@
    },
    StringPolicyType::STRING
   },
+  {key::kDeprecatedButGenerated,
+   false,
+   [](const em::CloudPolicySettings& policy) {
+     return policy.has_deprecatedbutgenerated();
+   },
+   [](const em::CloudPolicySettings& policy)
+       -> const em::StringPolicyProto& {
+     return policy.deprecatedbutgenerated();
+   },
+   StringPolicyType::STRING
+  },
   {key::kChunkTwoFirstFieldStringPolicy,
    false,
    [](const em::CloudPolicySettings& policy) {
@@ -695,6 +745,7 @@
 extern const char kExampleBoolPrecedenceMetapolicy[];
 extern const char kCloudOnlyPolicy[];
 extern const char kCloudManagementEnrollmentToken[];
+extern const char kDeprecatedButGenerated[];
 extern const char kChunkZeroLastFieldBooleanPolicy[];
 extern const char kChunkOneFirstFieldBooleanPolicy[];
 extern const char kChunkOneLastFieldBooleanPolicy[];
@@ -734,7 +785,7 @@
   enterprise_management::StringPolicyProto* (*mutable_proto_ptr)(
       enterprise_management::CloudPolicySettings* policy);
 };
-extern const std::array<StringPolicyAccess, 4> kStringPolicyAccess;
+extern const std::array<StringPolicyAccess, 5> kStringPolicyAccess;
 
 // Access to the mutable protobuf function of all supported stringlist user
 // policies.
@@ -767,6 +818,7 @@
 const char kExampleBoolPrecedenceMetapolicy[] = "ExampleBoolPrecedenceMetapolicy";
 const char kCloudOnlyPolicy[] = "CloudOnlyPolicy";
 const char kCloudManagementEnrollmentToken[] = "CloudManagementEnrollmentToken";
+const char kDeprecatedButGenerated[] = "DeprecatedButGenerated";
 const char kChunkZeroLastFieldBooleanPolicy[] = "ChunkZeroLastFieldBooleanPolicy";
 const char kChunkOneFirstFieldBooleanPolicy[] = "ChunkOneFirstFieldBooleanPolicy";
 const char kChunkOneLastFieldBooleanPolicy[] = "ChunkOneLastFieldBooleanPolicy";
@@ -834,7 +886,7 @@
 const std::array<IntegerPolicyAccess, 0> kIntegerPolicyAccess {{
 }};
 
-const std::array<StringPolicyAccess, 4> kStringPolicyAccess {{
+const std::array<StringPolicyAccess, 5> kStringPolicyAccess {{
   {key::kExampleStringPolicy,
    false,
    [](em::CloudPolicySettings* policy)
@@ -849,6 +901,13 @@
      return policy->mutable_cloudmanagementenrollmenttoken();
    }
   },
+  {key::kDeprecatedButGenerated,
+   false,
+   [](em::CloudPolicySettings* policy)
+       -> em::StringPolicyProto* {
+     return policy->mutable_deprecatedbutgenerated();
+   }
+  },
   {key::kChunkTwoFirstFieldStringPolicy,
    false,
    [](em::CloudPolicySettings* policy)
@@ -911,6 +970,12 @@
         android:restrictionType="bool"/>
 
     <restriction
+        android:key="DeprecatedButGenerated"
+        android:title="@string/DeprecatedButGeneratedTitle"
+        android:description="@string/DeprecatedButGeneratedDesc"
+        android:restrictionType="string"/>
+
+    <restriction
         android:key="ExampleBoolMergeMetapolicy"
         android:title="@string/ExampleBoolMergeMetapolicyTitle"
         android:description="@string/ExampleBoolMergeMetapolicyDesc"
diff --git a/components/policy/tools/template_writers/writers/ios_app_config_writer.py b/components/policy/tools/template_writers/writers/ios_app_config_writer.py
index a5a61f9..74d09067 100755
--- a/components/policy/tools/template_writers/writers/ios_app_config_writer.py
+++ b/components/policy/tools/template_writers/writers/ios_app_config_writer.py
@@ -138,7 +138,9 @@
     constraint = self.AddElement(parent, 'constraint', attrs)
     if 'enum' in policy['type']:
       values_element = self.AddElement(constraint, 'values', {})
-      for v in policy['schema']['enum']:
+      enum = policy['schema']['enum'] if 'enum' in policy['schema'] else policy[
+          'schema']['items']['enum']
+      for v in enum:
         value = self.AddElement(values_element, 'value', {})
         self.AddText(value,
                      _ParseSchemaTypeValueToString(v, policy['schema']['type']))
diff --git a/components/policy/tools/template_writers/writers/ios_app_config_writer_unittest.py b/components/policy/tools/template_writers/writers/ios_app_config_writer_unittest.py
index 8774f2e..89cd7f4 100755
--- a/components/policy/tools/template_writers/writers/ios_app_config_writer_unittest.py
+++ b/components/policy/tools/template_writers/writers/ios_app_config_writer_unittest.py
@@ -229,8 +229,11 @@
         'desc':
         'string-enum-list description',
         'schema': {
-          'type': 'string',
-          'enum': ['0', '1'],
+            'type': 'array',
+            'items': {
+                'type': 'string',
+                'enum': ['0', '1'],
+            },
         },
         'items': [{
             'name': 'item0',
diff --git a/components/reputation/core/BUILD.gn b/components/reputation/core/BUILD.gn
index 53ff4b0..16d13aa 100644
--- a/components/reputation/core/BUILD.gn
+++ b/components/reputation/core/BUILD.gn
@@ -29,6 +29,7 @@
   sources = [ "safety_tips_config_unittest.cc" ]
   deps = [
     ":core",
+    ":proto",
     "//testing/gtest",
     "//url",
   ]
diff --git a/components/reputation/core/safety_tip_test_utils.cc b/components/reputation/core/safety_tip_test_utils.cc
index bf2983d..5cbb2130 100644
--- a/components/reputation/core/safety_tip_test_utils.cc
+++ b/components/reputation/core/safety_tip_test_utils.cc
@@ -12,10 +12,7 @@
 
 namespace reputation {
 
-namespace {
-
-// Retrieve existing config proto if set, or create a new one otherwise.
-std::unique_ptr<SafetyTipsConfig> GetConfig() {
+std::unique_ptr<SafetyTipsConfig> GetOrCreateSafetyTipsConfig() {
   auto* old = GetSafetyTipsRemoteConfigProto();
   if (old) {
     return std::make_unique<SafetyTipsConfig>(*old);
@@ -27,15 +24,13 @@
   return conf;
 }
 
-}  // namespace
-
 void InitializeSafetyTipConfig() {
-  SetSafetyTipsRemoteConfigProto(GetConfig());
+  SetSafetyTipsRemoteConfigProto(GetOrCreateSafetyTipsConfig());
 }
 
 void SetSafetyTipPatternsWithFlagType(std::vector<std::string> patterns,
                                       FlaggedPage::FlagType type) {
-  auto config_proto = GetConfig();
+  auto config_proto = GetOrCreateSafetyTipsConfig();
   config_proto->clear_flagged_page();
 
   std::sort(patterns.begin(), patterns.end());
@@ -55,7 +50,7 @@
 void SetSafetyTipAllowlistPatterns(std::vector<std::string> patterns,
                                    std::vector<std::string> target_patterns,
                                    std::vector<std::string> common_words) {
-  auto config_proto = GetConfig();
+  auto config_proto = GetOrCreateSafetyTipsConfig();
   config_proto->clear_allowed_pattern();
   config_proto->clear_allowed_target_pattern();
   config_proto->clear_common_word();
@@ -85,7 +80,7 @@
 void AddSafetyTipHeuristicLaunchConfigForTesting(
     reputation::HeuristicLaunchConfig::Heuristic heuristic,
     int launch_percentage) {
-  auto config_proto = GetConfig();
+  auto config_proto = GetOrCreateSafetyTipsConfig();
   reputation::HeuristicLaunchConfig* launch_config =
       config_proto->add_launch_config();
   launch_config->set_heuristic(heuristic);
diff --git a/components/reputation/core/safety_tip_test_utils.h b/components/reputation/core/safety_tip_test_utils.h
index 30e1e20..465c1f0 100644
--- a/components/reputation/core/safety_tip_test_utils.h
+++ b/components/reputation/core/safety_tip_test_utils.h
@@ -12,6 +12,10 @@
 
 namespace reputation {
 
+// Retrieve any existing Safety Tips config proto if set, or create a new one
+// otherwise.
+std::unique_ptr<SafetyTipsConfig> GetOrCreateSafetyTipsConfig();
+
 // Initialize component configuration. Necessary to enable Safety Tips for
 // testing, as no heuristics trigger if the allowlist is inaccessible.
 void InitializeSafetyTipConfig();
diff --git a/components/reputation/core/safety_tips.proto b/components/reputation/core/safety_tips.proto
index 0c771a4..ddacfa8 100644
--- a/components/reputation/core/safety_tips.proto
+++ b/components/reputation/core/safety_tips.proto
@@ -25,6 +25,11 @@
   // example.test/test-path-for-safety-tips/test.html. Also see the comment for
   // |allowed_pattern| field.
   optional string pattern = 1;
+
+  // The index of any cohort[s] that this entry is allowed to spoof. If this
+  // field is unset and the pattern is allowlisted, the pattern may spoof any
+  // domain. Has no meaning when used in canonical_pattern.
+  repeated uint32 cohort_index = 2;
 }
 
 message HostPattern {
@@ -63,6 +68,17 @@
   optional uint32 launch_percentage = 2;
 }
 
+// A set of domains allowed to be spoofed by a given allowlist entry.
+// allowed_pattern or canonical_pattern indices may appear in multiple
+// Cohorts, and multiple allowed_patterns may point to the same Cohort.
+message Cohort {
+  // Indexes in `allowed_pattern` in this cohort.
+  repeated uint32 allowed_index = 1;
+
+  // Indexes in `canonical_pattern` in this cohort.
+  repeated uint32 canonical_index = 2;
+}
+
 // Configuration for the safety tips component. A binary version of this proto
 // will be distributed to Chrome clients via component updater. The binary will
 // contain a single instance of this message.
@@ -103,4 +119,12 @@
   // Launch configurations for new heuristics. Each new heuristic being launched
   // gets its own config. Multiple heuristics can be enabled at the same time.
   repeated HeuristicLaunchConfig launch_config = 6;
+
+  // canonical_pattern is a list of hostnames that are only ever spoofed, and do
+  // no spoofing of their own. Entries are pointed to by one or more Cohort.
+  repeated UrlPattern canonical_pattern = 7;
+
+  // A Cohort is a set of domains that may be spoofed by the allowed_pattern
+  // that points to it.
+  repeated Cohort cohort = 8;
 }
diff --git a/components/reputation/core/safety_tips_config.cc b/components/reputation/core/safety_tips_config.cc
index 15149e4..9a5f72a 100644
--- a/components/reputation/core/safety_tips_config.cc
+++ b/components/reputation/core/safety_tips_config.cc
@@ -80,6 +80,53 @@
   return security_state::SafetyTipStatus::kNone;
 }
 
+// Return whether |canonical_url| is a member of the designated cohort.
+bool IsUrlAllowedByCohort(const SafetyTipsConfig* proto,
+                          const GURL& canonical_url,
+                          unsigned cohort_index) {
+  DCHECK(proto);
+  DCHECK(canonical_url.is_valid());
+
+  // Ensure that the cohort index is valid before using it. If it isn't valid,
+  // we just pretend the cohort didn't include the canonical URL.
+  if (cohort_index >= static_cast<unsigned>(proto->cohort_size())) {
+    return false;
+  }
+
+  const auto& cohort = proto->cohort(cohort_index);
+
+  // For each possible URL pattern, see if any of the indicated allowed_index or
+  // canonical_index entries correspond to a matching pattern since both sets of
+  // indices are considered valid spoof targets.
+  std::vector<std::string> patterns;
+  UrlToSafetyTipPatterns(canonical_url, &patterns);
+  for (const auto& search_pattern : patterns) {
+    for (const unsigned allowed_index : cohort.allowed_index()) {
+      // Skip over invalid indices.
+      if (allowed_index >=
+          static_cast<unsigned>(proto->allowed_pattern_size())) {
+        continue;
+      }
+      const auto& pattern = proto->allowed_pattern(allowed_index).pattern();
+      if (pattern == search_pattern) {
+        return true;
+      }
+    }
+    for (const unsigned canonical_index : cohort.canonical_index()) {
+      // Skip over invalid indices.
+      if (canonical_index >=
+          static_cast<unsigned>(proto->canonical_pattern_size())) {
+        continue;
+      }
+      const auto& pattern = proto->canonical_pattern(canonical_index).pattern();
+      if (pattern == search_pattern) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
 }  // namespace
 
 // static
@@ -93,24 +140,35 @@
 }
 
 bool IsUrlAllowlistedBySafetyTipsComponent(const SafetyTipsConfig* proto,
-                                           const GURL& url) {
+                                           const GURL& visited_url,
+                                           const GURL& canonical_url) {
   DCHECK(proto);
-  DCHECK(url.is_valid());
+  DCHECK(visited_url.is_valid());
   std::vector<std::string> patterns;
-  UrlToSafetyTipPatterns(url, &patterns);
-  auto allowed_pages = proto->allowed_pattern();
+  UrlToSafetyTipPatterns(visited_url, &patterns);
+  auto allowed_patterns = proto->allowed_pattern();
   for (const auto& pattern : patterns) {
     UrlPattern search_target;
     search_target.set_pattern(pattern);
 
-    auto lower = std::lower_bound(
-        allowed_pages.begin(), allowed_pages.end(), search_target,
+    auto maybe_before = std::lower_bound(
+        allowed_patterns.begin(), allowed_patterns.end(), search_target,
         [](const UrlPattern& a, const UrlPattern& b) -> bool {
           return a.pattern() < b.pattern();
         });
 
-    if (lower != allowed_pages.end() && pattern == lower->pattern()) {
-      return true;
+    if (maybe_before != allowed_patterns.end() &&
+        pattern == maybe_before->pattern()) {
+      // If no cohorts are given, it's a universal allowlist entry.
+      if (maybe_before->cohort_index_size() == 0) {
+        return true;
+      }
+
+      for (const unsigned cohort_index : maybe_before->cohort_index()) {
+        if (IsUrlAllowedByCohort(proto, canonical_url, cohort_index)) {
+          return true;
+        }
+      }
     }
   }
   return false;
diff --git a/components/reputation/core/safety_tips_config.h b/components/reputation/core/safety_tips_config.h
index 67cd7932..3a80516 100644
--- a/components/reputation/core/safety_tips_config.h
+++ b/components/reputation/core/safety_tips_config.h
@@ -25,10 +25,14 @@
 // a safety tip.
 const SafetyTipsConfig* GetSafetyTipsRemoteConfigProto();
 
-// Checks SafeBrowsing-style permutations of |url| against the component updater
-// allowlist and returns whether the URL is explicitly allowed.
+// Checks permutations of |visited_url| against the component updater allowlist
+// and returns whether the URL is explicitly allowed to spoof |canonical_url|.
+//
+// Cases when canonical_url is unknown (as in kFailedSpoofChecks) are treated as
+// if they're trying to spoof themselves, so set canonical_url = visited_url.
 bool IsUrlAllowlistedBySafetyTipsComponent(const SafetyTipsConfig* proto,
-                                           const GURL& url);
+                                           const GURL& visited_url,
+                                           const GURL& canonical_url);
 
 // Checks |hostname| against the component updater target allowlist and returns
 // whether it is explicitly allowed.
diff --git a/components/reputation/core/safety_tips_config_unittest.cc b/components/reputation/core/safety_tips_config_unittest.cc
index 85ca115..56079ba 100644
--- a/components/reputation/core/safety_tips_config_unittest.cc
+++ b/components/reputation/core/safety_tips_config_unittest.cc
@@ -11,13 +11,137 @@
 
 namespace reputation {
 
-TEST(SafetyTipsConfigTest, TestUrlAllowlist) {
+// Build an allowlist with testable scoped allowlist entries.
+void ConfigureAllowlistWithScopes() {
+  auto config_proto = GetOrCreateSafetyTipsConfig();
+  config_proto->clear_allowed_pattern();
+  config_proto->clear_canonical_pattern();
+  config_proto->clear_cohort();
+
+  // Note that allowed_pattern *must* stay sorted.
+
+  // error-canonical-index.tld has a cohort with an invalid allowed index.
+  auto* pattern_bad_allowed = config_proto->add_allowed_pattern();
+  pattern_bad_allowed->set_pattern("error-allowed-index.tld/");
+  auto* cohort_bad_allowed = config_proto->add_cohort();
+  cohort_bad_allowed->add_allowed_index(100);
+  pattern_bad_allowed->add_cohort_index(0);  // cohort_bad_allowed
+
+  // error-canonical-index.tld has a cohort with an invalid canonical index.
+  auto* pattern_bad_canonical = config_proto->add_allowed_pattern();
+  pattern_bad_canonical->set_pattern("error-canonical-index.tld/");
+  auto* cohort_bad_canonical = config_proto->add_cohort();
+  cohort_bad_canonical->add_canonical_index(100);
+  pattern_bad_canonical->add_cohort_index(1);  // cohort_bad_canonical
+
+  // error-cohort-index.tld has an invalid index.
+  auto* pattern_bad_cohort = config_proto->add_allowed_pattern();
+  pattern_bad_cohort->set_pattern("error-cohort-index.tld/");
+  pattern_bad_cohort->add_cohort_index(100);
+
+  // siteA.tld is only a canonical_pattern, so can't spoof anyone.
+  config_proto->add_canonical_pattern()->set_pattern("sitea.tld/");
+
+  // siteB.tld is only allowed to spoof siteA.tld and itself.
+  auto* pattern_b = config_proto->add_allowed_pattern();
+  pattern_b->set_pattern("siteb.tld/");
+  auto* cohort_b = config_proto->add_cohort();
+  cohort_b->add_allowed_index(3);    // siteB
+  cohort_b->add_canonical_index(0);  // siteA
+  pattern_b->add_cohort_index(2);    // cohort_b
+
+  // siteC.tld is allowed to spoof siteB.tld and itself.
+  auto* pattern_c = config_proto->add_allowed_pattern();
+  pattern_c->set_pattern("sitec.tld/");
+  auto* cohort_c = config_proto->add_cohort();
+  cohort_c->add_allowed_index(3);  // siteB
+  cohort_c->add_allowed_index(4);  // siteC
+  pattern_c->add_cohort_index(3);  // cohort_c
+
+  // siteD.tld is allowed to spoof anyone, so has no cohort.
+  auto* pattern_d = config_proto->add_allowed_pattern();
+  pattern_d->set_pattern("sited.tld/");
+
+  // Implicitly, siteE.tld can't spoof anyone, since it isn't in the proto.
+
+  SetSafetyTipsRemoteConfigProto(std::move(config_proto));
+}
+
+// Minimal test for an unscoped allowlist entry.
+TEST(SafetyTipsConfigTest, TestBasicUrlAllowlist) {
   SetSafetyTipAllowlistPatterns({"example.com/"}, {}, {});
   auto* config = GetSafetyTipsRemoteConfigProto();
+
+  // Basic unscoped entries are allowed to spoof any ("canonical") domain.
   EXPECT_TRUE(IsUrlAllowlistedBySafetyTipsComponent(
-      config, GURL("http://example.com")));
+      config, GURL("http://example.com"), GURL("http://example.com")));
+  EXPECT_TRUE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://example.com"), GURL("http://example.org")));
+
   EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
-      config, GURL("http://example.org")));
+      config, GURL("http://example.org"), GURL("http://example.org")));
+}
+
+// Tests for a scoped allowlist (i.e. entries not permitted to spoof anything).
+TEST(SafetyTipsConfigTest, TestScopedUrlAllowlist) {
+  ConfigureAllowlistWithScopes();
+  auto* config = GetSafetyTipsRemoteConfigProto();
+
+  // Site A is only a canonical domain, so can't spoof anything.
+  EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sitea.tld"), GURL("http://sitea.tld")));
+  EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sitea.tld"), GURL("http://siteb.tld")));
+  EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sitea.tld"), GURL("http://sitee.tld")));
+
+  // Site B can spoof sites A & B, but not other stuff.
+  EXPECT_TRUE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://siteb.tld"), GURL("http://sitea.tld")));
+  EXPECT_TRUE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://siteb.tld"), GURL("http://siteb.tld")));
+  EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://siteb.tld"), GURL("http://sitec.tld")));
+  EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://siteb.tld"), GURL("http://sited.tld")));
+  EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://siteb.tld"), GURL("http://sitee.tld")));
+
+  // Site C can spoof sites B and C, but not anything else.
+  EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sitec.tld"), GURL("http://sitea.tld")));
+  EXPECT_TRUE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sitec.tld"), GURL("http://siteb.tld")));
+  EXPECT_TRUE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sitec.tld"), GURL("http://sitec.tld")));
+  EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sitec.tld"), GURL("http://sited.tld")));
+  EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sitec.tld"), GURL("http://sitee.tld")));
+
+  // Site D has a wildcard, and can spoof anyone.
+  EXPECT_TRUE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sited.tld"), GURL("http://sitea.tld")));
+  EXPECT_TRUE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sited.tld"), GURL("http://siteb.tld")));
+  EXPECT_TRUE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sited.tld"), GURL("http://sitec.tld")));
+  EXPECT_TRUE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sited.tld"), GURL("http://sited.tld")));
+  EXPECT_TRUE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://sited.tld"), GURL("http://sitee.tld")));
+
+  // These sites all have invalid indices in their entries. Each should
+  // invalidate the relevant part of their allowlist entry (i.e. act as if it's
+  // not there), rather than crashing.
+  EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://error-cohort-index.tld"), GURL("http://sitea.tld")));
+  EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://error-canonical-index.tld"),
+      GURL("http://sitea.tld")));
+  EXPECT_FALSE(IsUrlAllowlistedBySafetyTipsComponent(
+      config, GURL("http://error-allowed-index.tld"),
+      GURL("http://sitea.tld")));
 }
 
 TEST(SafetyTipsConfigTest, TestTargetUrlAllowlist) {
diff --git a/components/safe_browsing/content/browser/password_protection/password_protection_service_unittest.cc b/components/safe_browsing/content/browser/password_protection/password_protection_service_unittest.cc
index 42dac9e9..296284ad 100644
--- a/components/safe_browsing/content/browser/password_protection/password_protection_service_unittest.cc
+++ b/components/safe_browsing/content/browser/password_protection/password_protection_service_unittest.cc
@@ -1300,7 +1300,6 @@
   EXPECT_TRUE(actual_request->frames(0).has_password_field());
   ASSERT_TRUE(actual_request->has_password_reuse_event());
   const auto& reuse_event = actual_request->password_reuse_event();
-  EXPECT_TRUE(reuse_event.is_chrome_signin_password());
   EXPECT_EQ(0, reuse_event.domains_matching_password_size());
 #if !BUILDFLAG(IS_ANDROID)
   VerifyContentAreaSizeCollection(*actual_request);
@@ -1331,7 +1330,6 @@
       password_protection_service_->GetLatestRequestProto();
   ASSERT_TRUE(actual_request->has_password_reuse_event());
   const auto& reuse_event = actual_request->password_reuse_event();
-  EXPECT_FALSE(reuse_event.is_chrome_signin_password());
 
   if (password_protection_service_->IsExtendedReporting() &&
       !password_protection_service_->IsIncognito()) {
diff --git a/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc b/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
index 328abd2..259ca79 100644
--- a/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
+++ b/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
@@ -1595,8 +1595,6 @@
   event_dict.Set("domains_matching_password", std::move(domains_list));
 
   event_dict.Set("frame_id", event.frame_id());
-  event_dict.Set("is_chrome_signin_password",
-                 event.is_chrome_signin_password());
 
   std::string sync_account_type;
   switch (event.sync_account_type()) {
diff --git a/components/safe_browsing/core/browser/password_protection/password_protection_request.cc b/components/safe_browsing/core/browser/password_protection/password_protection_request.cc
index c0fe7698..51503d5 100644
--- a/components/safe_browsing/core/browser/password_protection/password_protection_request.cc
+++ b/components/safe_browsing/core/browser/password_protection/password_protection_request.cc
@@ -264,7 +264,6 @@
           request_proto_->mutable_password_reuse_event();
       bool matches_signin_password =
           password_type_ == PasswordType::PRIMARY_ACCOUNT_PASSWORD;
-      reuse_event->set_is_chrome_signin_password(matches_signin_password);
       reuse_event->set_reused_password_type(
           password_protection_service_->GetPasswordProtectionReusedPasswordType(
               password_type_));
diff --git a/components/safe_browsing/core/common/proto/csd.proto b/components/safe_browsing/core/common/proto/csd.proto
index 4112daa..1907291 100644
--- a/components/safe_browsing/core/common/proto/csd.proto
+++ b/components/safe_browsing/core/common/proto/csd.proto
@@ -321,12 +321,11 @@
     // The frame that the password reuse is detected.
     optional int32 frame_id = 2;
 
-    // TODO(crbug/914410): Remove once ReusedPasswordAccountType is implemented.
-    // Whether the reused password is used for Chrome signin.
-    optional bool is_chrome_signin_password = 3;
+    // Deprecated field.
+    reserved 3;
 
     // TODO(crbug/914410): Remove once ReusedPasswordAccountType is implemented.
-    // Sync account type. Only set if |is_chrome_signin_password| is true.
+    // Sync account type.
     enum SyncAccountType {
       // Not a sign-in user.
       NOT_SIGNED_IN = 0;
diff --git a/components/sync/base/features.h b/components/sync/base/features.h
index 43ceade2..26f0845b 100644
--- a/components/sync/base/features.h
+++ b/components/sync/base/features.h
@@ -31,7 +31,7 @@
 // The threshold for kIgnoreSyncEncryptionKeysLongMissing to start ignoring keys
 // (measured in number of GetUpdatesResponses messages).
 inline constexpr base::FeatureParam<int> kMinGuResponsesToIgnoreKey{
-    &kIgnoreSyncEncryptionKeysLongMissing, "MinGuResponsesToIgnoreKey", 50};
+    &kIgnoreSyncEncryptionKeysLongMissing, "MinGuResponsesToIgnoreKey", 3};
 
 // When enabled, Sync machinery will read and writes password notes to the
 // `encrypted_notes_backup` field inside the PasswordSpecifics proto. Together
diff --git a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
index 8347d29..25885d2 100644
--- a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
+++ b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
@@ -79,8 +79,8 @@
         int ANDROID_WEBVIEW = 1;
     }
 
-    private static final String VARIATIONS_SERVER_URL =
-            "https://clientservices.googleapis.com/chrome-variations/seed?osname=";
+    private static final String DEFAULT_VARIATIONS_SERVER_URL =
+            "https://clientservices.googleapis.com/chrome-variations/seed";
 
     private static final int READ_TIMEOUT = 3000; // time in ms
     private static final int REQUEST_TIMEOUT = 1000; // time in ms
@@ -205,7 +205,10 @@
     @VisibleForTesting
     protected String getConnectionString(@VariationsPlatform int platform, String restrictMode,
             String milestone, String channel) {
-        String urlString = VARIATIONS_SERVER_URL;
+        // TODO(crbug/1302862): Consider reusing native VariationsService::GetVariationsServerURL().
+        String urlString = CommandLine.getInstance().getSwitchValue(
+                VariationsSwitches.VARIATIONS_SERVER_URL, DEFAULT_VARIATIONS_SERVER_URL);
+        urlString += "?osname=";
         switch (platform) {
             case VariationsPlatform.ANDROID:
                 urlString += "android";
diff --git a/components/variations/android/junit/src/org/chromium/components/variations/firstrun/VariationsSeedFetcherTest.java b/components/variations/android/junit/src/org/chromium/components/variations/firstrun/VariationsSeedFetcherTest.java
index 06d77d99..6153bb40 100644
--- a/components/variations/android/junit/src/org/chromium/components/variations/firstrun/VariationsSeedFetcherTest.java
+++ b/components/variations/android/junit/src/org/chromium/components/variations/firstrun/VariationsSeedFetcherTest.java
@@ -537,6 +537,20 @@
     }
 
     /**
+     * Test method to make sure {@link VariationsSeedFetcher#getConnectionString()} honors the
+     * "--variations-server-url" switch.
+     */
+    @Test
+    @CommandLineFlags.Add(VariationsSwitches.VARIATIONS_SERVER_URL + "=http://localhost:8080/seed")
+    public void testGetConnectionString_HonorsServerUrlCommandlineSwitch() {
+        String urlString = mFetcher.getConnectionString(
+                VariationsSeedFetcher.VariationsPlatform.ANDROID, sRestrict, sMilestone, sChannel);
+
+        // The URL should start with the variations server URL passed as a switch.
+        assertTrue(urlString, urlString.startsWith("http://localhost:8080/seed"));
+    }
+
+    /**
      * Test method to make sure {@link VariationsSeedFetcher#getAvailableInstanceManipulations()}
      * honors the
      * "--enable-finch-seed-delta-compression" switch.
diff --git a/components/variations/service/variations_field_trial_creator.cc b/components/variations/service/variations_field_trial_creator.cc
index e32fc0f..0442f7f 100644
--- a/components/variations/service/variations_field_trial_creator.cc
+++ b/components/variations/service/variations_field_trial_creator.cc
@@ -202,6 +202,7 @@
 
 bool VariationsFieldTrialCreator::SetUpFieldTrials(
     const std::vector<std::string>& variation_ids,
+    const std::string& command_line_variation_ids,
     const std::vector<base::FeatureList::FeatureOverrideInfo>& extra_overrides,
     std::unique_ptr<const base::FieldTrial::EntropyProvider>
         low_entropy_provider,
@@ -231,13 +232,10 @@
   VariationsIdsProvider* http_header_provider =
       VariationsIdsProvider::GetInstance();
   http_header_provider->SetLowEntropySourceValue(low_entropy_source_value);
-  const base::CommandLine* command_line =
-      base::CommandLine::ForCurrentProcess();
   // Force the variation ids selected in chrome://flags and/or specified using
   // the command-line flag.
   auto result = http_header_provider->ForceVariationIds(
-      variation_ids,
-      command_line->GetSwitchValueASCII(switches::kForceVariationIds));
+      variation_ids, command_line_variation_ids);
 
   switch (result) {
     case VariationsIdsProvider::ForceIdsResult::INVALID_SWITCH_ENTRY:
@@ -253,6 +251,8 @@
       break;
   }
 
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
   bool success = http_header_provider->ForceDisableVariationIds(
       command_line->GetSwitchValueASCII(switches::kForceDisableVariationIds));
   if (!success) {
diff --git a/components/variations/service/variations_field_trial_creator.h b/components/variations/service/variations_field_trial_creator.h
index 15eb5dce..acb72ee 100644
--- a/components/variations/service/variations_field_trial_creator.h
+++ b/components/variations/service/variations_field_trial_creator.h
@@ -106,8 +106,11 @@
   // Sets up field trials based on stored variations seed data. Returns whether
   // setup completed successfully.
   //
-  // |variation_ids| allows for forcing ids selected in chrome://flags and/or
-  // specified using the command-line flag.
+  // |variation_ids| allows for forcing ids selected in chrome://flags.
+  // |command_line_variation_ids| allows for forcing ids through the
+  // "--force-variation-ids" command line flag. It should be a comma-separated
+  // list of variation ids. Ids prefixed with the character "t" will be treated
+  // as Trigger Variation Ids.
   // |extra_overrides| gives a list of feature overrides that should be applied
   // after the features explicitly disabled/enabled from the command line via
   // --disable-features and --enable-features, but before field trials.
@@ -130,6 +133,7 @@
   // field trials.
   bool SetUpFieldTrials(
       const std::vector<std::string>& variation_ids,
+      const std::string& command_line_variation_ids,
       const std::vector<base::FeatureList::FeatureOverrideInfo>&
           extra_overrides,
       std::unique_ptr<const base::FieldTrial::EntropyProvider>
diff --git a/components/variations/service/variations_field_trial_creator_unittest.cc b/components/variations/service/variations_field_trial_creator_unittest.cc
index 2242d0b..7208fe8 100644
--- a/components/variations/service/variations_field_trial_creator_unittest.cc
+++ b/components/variations/service/variations_field_trial_creator_unittest.cc
@@ -299,6 +299,8 @@
     TestPlatformFieldTrials platform_field_trials;
     return VariationsFieldTrialCreator::SetUpFieldTrials(
         /*variation_ids=*/std::vector<std::string>(),
+        base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+            switches::kForceVariationIds),
         std::vector<base::FeatureList::FeatureOverrideInfo>(),
         /*low_entropy_provider=*/nullptr, std::make_unique<base::FeatureList>(),
         metrics_state_manager_.get(), &platform_field_trials,
@@ -848,6 +850,8 @@
   // active.
   EXPECT_TRUE(field_trial_creator.SetUpFieldTrials(
       /*variation_ids=*/std::vector<std::string>(),
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          switches::kForceVariationIds),
       std::vector<base::FeatureList::FeatureOverrideInfo>(),
       /*low_entropy_provider=*/nullptr, std::make_unique<base::FeatureList>(),
       metrics_state_manager.get(), &platform_field_trials, &safe_seed_manager,
diff --git a/components/variations/service/variations_service.cc b/components/variations/service/variations_service.cc
index 87fe040..bc93203 100644
--- a/components/variations/service/variations_service.cc
+++ b/components/variations/service/variations_service.cc
@@ -952,13 +952,15 @@
 
 bool VariationsService::SetUpFieldTrials(
     const std::vector<std::string>& variation_ids,
+    const std::string& command_line_variation_ids,
     const std::vector<base::FeatureList::FeatureOverrideInfo>& extra_overrides,
     std::unique_ptr<base::FeatureList> feature_list,
     variations::PlatformFieldTrials* platform_field_trials) {
   return field_trial_creator_.SetUpFieldTrials(
-      variation_ids, extra_overrides, CreateLowEntropyProvider(),
-      std::move(feature_list), state_manager_, platform_field_trials,
-      &safe_seed_manager_, state_manager_->GetLowEntropySource());
+      variation_ids, command_line_variation_ids, extra_overrides,
+      CreateLowEntropyProvider(), std::move(feature_list), state_manager_,
+      platform_field_trials, &safe_seed_manager_,
+      state_manager_->GetLowEntropySource());
 }
 
 void VariationsService::OverrideCachedUIStrings() {
diff --git a/components/variations/service/variations_service.h b/components/variations/service/variations_service.h
index ef942c9..60a8332 100644
--- a/components/variations/service/variations_service.h
+++ b/components/variations/service/variations_service.h
@@ -189,6 +189,7 @@
   // Wrapper around VariationsFieldTrialCreator::SetUpFieldTrials().
   bool SetUpFieldTrials(
       const std::vector<std::string>& variation_ids,
+      const std::string& command_line_variation_ids,
       const std::vector<base::FeatureList::FeatureOverrideInfo>&
           extra_overrides,
       std::unique_ptr<base::FeatureList> feature_list,
diff --git a/components/variations/variations_ids_provider.cc b/components/variations/variations_ids_provider.cc
index 3cf51c5d..6c9083758 100644
--- a/components/variations/variations_ids_provider.cc
+++ b/components/variations/variations_ids_provider.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 
 #include "base/base64.h"
+#include "base/containers/contains.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/no_destructor.h"
 #include "base/observer_list.h"
@@ -15,6 +16,7 @@
 #include "base/strings/string_util.h"
 #include "base/synchronization/lock.h"
 #include "components/variations/proto/client_variations.pb.h"
+#include "components/variations/variations_associated_data.h"
 #include "components/variations/variations_client.h"
 #include "components/variations/variations_features.h"
 
@@ -159,10 +161,12 @@
     const std::string& command_line_variation_ids) {
   force_enabled_ids_set_.clear();
 
-  if (!AddVariationIdsToSet(variation_ids, &force_enabled_ids_set_))
+  if (!AddVariationIdsToSet(variation_ids, /*should_dedupe=*/true,
+                            &force_enabled_ids_set_))
     return ForceIdsResult::INVALID_VECTOR_ENTRY;
 
   if (!ParseVariationIdsParameter(command_line_variation_ids,
+                                  /*should_dedupe=*/true,
                                   &force_enabled_ids_set_)) {
     return ForceIdsResult::INVALID_SWITCH_ENTRY;
   }
@@ -178,7 +182,12 @@
 bool VariationsIdsProvider::ForceDisableVariationIds(
     const std::string& command_line_variation_ids) {
   force_disabled_ids_set_.clear();
+  // |should_dedupe| is false here in order to add the IDs specified in
+  // |command_line_variation_ids| to |force_disabled_ids_set_| even if they were
+  // defined before. The IDs are not marked as active; they are marked as
+  // disabled.
   if (!ParseVariationIdsParameter(command_line_variation_ids,
+                                  /*should_dedupe=*/false,
                                   &force_disabled_ids_set_)) {
     return false;
   }
@@ -252,6 +261,9 @@
   for (const SyntheticTrialGroup& group : groups) {
     VariationID id = GetGoogleVariationIDFromHashes(
         GOOGLE_WEB_PROPERTIES_ANY_CONTEXT, group.id());
+    // TODO(crbug/1294948): Handle duplicated IDs in such a way that is visible
+    // to developers, but non-intrusive to users. See
+    // crrev/c/3628020/comments/e278cd12_2bb863ef for discussions.
     if (id != EMPTY_ID) {
       synthetic_variation_ids_set_.insert(
           VariationIDEntry(id, GOOGLE_WEB_PROPERTIES_ANY_CONTEXT));
@@ -301,6 +313,9 @@
   for (int i = 0; i < ID_COLLECTION_COUNT; ++i) {
     IDCollectionKey key = static_cast<IDCollectionKey>(i);
     const VariationID id = GetGoogleVariationID(key, trial_name, group_name);
+    // TODO(crbug/1294948): Handle duplicated IDs in such a way that is visible
+    // to developers, but non-intrusive to users. See
+    // crrev/c/3628020/comments/e278cd12_2bb863ef for discussions.
     if (id != EMPTY_ID)
       variation_ids_set_.insert(VariationIDEntry(id, key));
   }
@@ -414,9 +429,9 @@
   return hashed;
 }
 
-// static
 bool VariationsIdsProvider::AddVariationIdsToSet(
     const std::vector<std::string>& variation_ids,
+    bool should_dedupe,
     std::set<VariationIDEntry>* target_set) {
   for (const std::string& entry : variation_ids) {
     if (entry.empty()) {
@@ -433,6 +448,14 @@
       target_set->clear();
       return false;
     }
+
+    if (should_dedupe && IsDuplicateId(variation_id)) {
+      DVLOG(1) << "Invalid variation ID specified: " << entry
+               << " (it is already in use)";
+      target_set->clear();
+      return false;
+    }
+
     target_set->insert(VariationIDEntry(
         variation_id, trigger_id ? GOOGLE_WEB_PROPERTIES_TRIGGER_ANY_CONTEXT
                                  : GOOGLE_WEB_PROPERTIES_ANY_CONTEXT));
@@ -440,9 +463,9 @@
   return true;
 }
 
-// static
 bool VariationsIdsProvider::ParseVariationIdsParameter(
     const std::string& command_line_variation_ids,
+    bool should_dedupe,
     std::set<VariationIDEntry>* target_set) {
   if (command_line_variation_ids.empty())
     return true;
@@ -450,7 +473,8 @@
   std::vector<std::string> variation_ids_from_command_line =
       base::SplitString(command_line_variation_ids, ",", base::TRIM_WHITESPACE,
                         base::SPLIT_WANT_ALL);
-  return AddVariationIdsToSet(variation_ids_from_command_line, target_set);
+  return AddVariationIdsToSet(variation_ids_from_command_line, should_dedupe,
+                              target_set);
 }
 
 std::string VariationsIdsProvider::GetClientDataHeaderWhileLocked(
@@ -530,4 +554,22 @@
   return result;
 }
 
+bool VariationsIdsProvider::IsDuplicateId(VariationID id) {
+  for (int i = 0; i < ID_COLLECTION_COUNT; ++i) {
+    IDCollectionKey key = static_cast<IDCollectionKey>(i);
+    // GOOGLE_APP ids may be duplicated. Further validation is done in
+    // GroupMapAccessor::ValidateID().
+    if (key == GOOGLE_APP)
+      continue;
+
+    VariationIDEntry entry(id, key);
+    if (base::Contains(variation_ids_set_, entry) ||
+        base::Contains(force_enabled_ids_set_, entry) ||
+        base::Contains(synthetic_variation_ids_set_, entry)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace variations
diff --git a/components/variations/variations_ids_provider.h b/components/variations/variations_ids_provider.h
index d812c43..73307202 100644
--- a/components/variations/variations_ids_provider.h
+++ b/components/variations/variations_ids_provider.h
@@ -222,16 +222,22 @@
   std::string GenerateBase64EncodedProto(bool is_signed_in,
                                          bool is_first_party_context);
 
-  // Adds variation ids and trigger variation ids to |target_set|.
-  static bool AddVariationIdsToSet(
-      const std::vector<std::string>& variation_ids,
-      std::set<VariationIDEntry>* target_set);
+  // Adds variation ids and trigger variation ids to |target_set|. If
+  // |should_dedupe| is true, the ids in |variation_ids| that have already been
+  // added as non-Google-app ids are not added to |target_set|. Returns false if
+  // any variation ids are malformed or duplicated. Returns true otherwise.
+  bool AddVariationIdsToSet(const std::vector<std::string>& variation_ids,
+                            bool should_dedupe,
+                            std::set<VariationIDEntry>* target_set);
 
   // Parses a comma-separated string of variation ids and trigger variation ids
-  // and adds them to |target_set|.
-  static bool ParseVariationIdsParameter(
-      const std::string& command_line_variation_ids,
-      std::set<VariationIDEntry>* target_set);
+  // and adds them to |target_set|. If |should_dedupe| is true, ids that have
+  // already been added as non-Google-app ids are not added to |target_set|.
+  // Returns false if any variation ids are malformed or duplicated. Returns
+  // true otherwise.
+  bool ParseVariationIdsParameter(const std::string& command_line_variation_ids,
+                                  bool should_dedupe,
+                                  std::set<VariationIDEntry>* target_set);
 
   // Returns the value of the X-Client-Data header corresponding to
   // |is_signed_in| and |web_visibility|. Considering |web_visibility| may allow
@@ -249,6 +255,12 @@
   std::vector<VariationID> GetVariationsVectorImpl(
       const std::set<IDCollectionKey>& key);
 
+  // Returns whether |id| has already been added to the active set of variation
+  // ids. This includes ids from field trials, synthetic trials, and forced ids.
+  // Note that Google app ids are treated differently. They may be reused as a
+  // Google Web id.
+  bool IsDuplicateId(VariationID id);
+
   const Mode mode_;
 
   // Guards access to variables below.
diff --git a/components/variations/variations_ids_provider_unittest.cc b/components/variations/variations_ids_provider_unittest.cc
index 38aee5a..b117318 100644
--- a/components/variations/variations_ids_provider_unittest.cc
+++ b/components/variations/variations_ids_provider_unittest.cc
@@ -119,6 +119,24 @@
             provider.ForceVariationIds({"12", "50"}, "tabc456"));
   provider.InitVariationIDsCacheIfNeeded();
   EXPECT_TRUE(provider.GetClientDataHeaders(/*is_signed_in=*/false).is_null());
+
+  // Duplicate experiment ids.
+  EXPECT_EQ(VariationsIdsProvider::ForceIdsResult::INVALID_VECTOR_ENTRY,
+            provider.ForceVariationIds({"1", "2", "t1"}, ""));
+  provider.InitVariationIDsCacheIfNeeded();
+  EXPECT_TRUE(provider.GetClientDataHeaders(/*is_signed_in=*/false).is_null());
+
+  // Duplicate command-line ids.
+  EXPECT_EQ(VariationsIdsProvider::ForceIdsResult::INVALID_SWITCH_ENTRY,
+            provider.ForceVariationIds({}, "t10,11,10"));
+  provider.InitVariationIDsCacheIfNeeded();
+  EXPECT_TRUE(provider.GetClientDataHeaders(/*is_signed_in=*/false).is_null());
+
+  // Duplicate experiment and command-line ids.
+  EXPECT_EQ(VariationsIdsProvider::ForceIdsResult::INVALID_SWITCH_ENTRY,
+            provider.ForceVariationIds({"20", "t21"}, "21"));
+  provider.InitVariationIDsCacheIfNeeded();
+  EXPECT_TRUE(provider.GetClientDataHeaders(/*is_signed_in=*/false).is_null());
 }
 
 TEST_F(VariationsIdsProviderTest, ForceDisableVariationIds_ValidCommandLine) {
diff --git a/content/browser/interest_group/auction_runner.h b/content/browser/interest_group/auction_runner.h
index 97f6edb..13c2a214 100644
--- a/content/browser/interest_group/auction_runner.h
+++ b/content/browser/interest_group/auction_runner.h
@@ -828,9 +828,8 @@
     size_t num_second_highest_bids_ = 0;
 
     // The numeric value of the bid that got the second highest score. When
-    // there's a tie for second highest score, just take the most recent one (
-    // any bid with the second highest score can be the most recent one since
-    // the order of bids getting scored is arbitrary).
+    // there's a tie for the second highest score, one of the second highest
+    // scoring bids is randomly chosen.
     double highest_scoring_other_bid_ = 0.0;
     double second_highest_score_ = 0.0;
     // Whether all bids of the highest score are from the same interest group
diff --git a/content/browser/loader/resource_scheduler_browsertest.cc b/content/browser/loader/resource_scheduler_browsertest.cc
index c1f707f..963808be4 100644
--- a/content/browser/loader/resource_scheduler_browsertest.cc
+++ b/content/browser/loader/resource_scheduler_browsertest.cc
@@ -32,7 +32,7 @@
 };
 
 IN_PROC_BROWSER_TEST_F(ResourceSchedulerBrowserTest,
-                       ResourceLoadingExperimentIncognito) {
+                       DISABLED_ResourceLoadingExperimentIncognito) {
   GURL url(embedded_test_server()->GetURL(
       "/resource_loading/resource_loading_non_mobile.html"));
 
@@ -43,7 +43,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ResourceSchedulerBrowserTest,
-                       ResourceLoadingExperimentNormal) {
+                       DISABLED_ResourceLoadingExperimentNormal) {
   GURL url(embedded_test_server()->GetURL(
       "/resource_loading/resource_loading_non_mobile.html"));
   Shell* browser = shell();
diff --git a/content/browser/message_port_provider.cc b/content/browser/message_port_provider.cc
index 1e9ab138..66ec89d 100644
--- a/content/browser/message_port_provider.cc
+++ b/content/browser/message_port_provider.cc
@@ -87,12 +87,9 @@
 }
 #endif
 
-// TODO(crbug.com/1329657): The last IS_FUCHSIA check will not be needed once
-// its build sets enable_cast_receiver.
-#if BUILDFLAG(ENABLE_CAST_RECEIVER) &&                    \
-        (BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_CASTOS) || \
-         BUILDFLAG(IS_CAST_ANDROID)) ||                   \
-    BUILDFLAG(IS_FUCHSIA)
+#if BUILDFLAG(IS_FUCHSIA) ||           \
+    BUILDFLAG(ENABLE_CAST_RECEIVER) && \
+        (BUILDFLAG(IS_CASTOS) || BUILDFLAG(IS_CAST_ANDROID))
 // static
 void MessagePortProvider::PostMessageToFrame(
     Page& page,
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index ec8b01e..7e6d83d 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -527,7 +527,6 @@
     sources += [
       "browser_ppapi_host.h",
       "pepper_vpn_provider_resource_host_proxy.h",
-      "plugin_data_remover.h",
       "plugin_service.h",
       "plugin_service_filter.h",
     ]
diff --git a/content/public/browser/message_port_provider.h b/content/public/browser/message_port_provider.h
index 15e99c9..7ce49765 100644
--- a/content/public/browser/message_port_provider.h
+++ b/content/public/browser/message_port_provider.h
@@ -18,12 +18,9 @@
 #include "base/android/scoped_java_ref.h"
 #endif
 
-// TODO(crbug.com/1329657): The last IS_FUCHSIA check will not be needed once
-// its build sets enable_cast_receiver.
-#if BUILDFLAG(ENABLE_CAST_RECEIVER) &&                    \
-        (BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_CASTOS) || \
-         BUILDFLAG(IS_CAST_ANDROID)) ||                   \
-    BUILDFLAG(IS_FUCHSIA)
+#if BUILDFLAG(IS_FUCHSIA) ||           \
+    BUILDFLAG(ENABLE_CAST_RECEIVER) && \
+        (BUILDFLAG(IS_CASTOS) || BUILDFLAG(IS_CAST_ANDROID))
 #include "third_party/blink/public/common/messaging/message_port_channel.h"
 #endif
 
@@ -57,12 +54,11 @@
       const base::android::JavaParamRef<jobjectArray>& ports);
 #endif  // BUILDFLAG(IS_ANDROID)
 
-// TODO(crbug.com/1329657): The last IS_FUCHSIA check will not be needed once
-// its build sets enable_cast_receiver.
-#if BUILDFLAG(ENABLE_CAST_RECEIVER) &&                    \
-        (BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_CASTOS) || \
-         BUILDFLAG(IS_CAST_ANDROID)) ||                   \
-    BUILDFLAG(IS_FUCHSIA)
+// Fuchsia WebEngine always uses this version.
+// Some Cast Receiver implementations use it too.
+#if BUILDFLAG(IS_FUCHSIA) ||           \
+    BUILDFLAG(ENABLE_CAST_RECEIVER) && \
+        (BUILDFLAG(IS_CASTOS) || BUILDFLAG(IS_CAST_ANDROID))
   // If |target_origin| is unset, then no origin scoping is applied.
   static void PostMessageToFrame(
       Page& page,
diff --git a/content/public/browser/plugin_data_remover.h b/content/public/browser/plugin_data_remover.h
deleted file mode 100644
index e242cf2..0000000
--- a/content/public/browser/plugin_data_remover.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_PUBLIC_BROWSER_PLUGIN_DATA_REMOVER_H_
-#define CONTENT_PUBLIC_BROWSER_PLUGIN_DATA_REMOVER_H_
-
-#include <vector>
-
-#include "base/time/time.h"
-#include "content/common/content_export.h"
-
-namespace base {
-class WaitableEvent;
-}
-
-namespace content {
-struct WebPluginInfo;
-
-class BrowserContext;
-
-class CONTENT_EXPORT PluginDataRemover {
- public:
-  static PluginDataRemover* Create(content::BrowserContext* browser_context);
-  virtual ~PluginDataRemover() {}
-
-  // Starts removing plugin data stored since |begin_time|.
-  virtual base::WaitableEvent* StartRemoving(base::Time begin_time) = 0;
-
-  // Returns a list of all plugins that support removing LSO data. This method
-  // will use cached plugin data. Call PluginService::GetPlugins() if the latest
-  // data is needed.
-  static void GetSupportedPlugins(std::vector<WebPluginInfo>* plugins);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_PUBLIC_BROWSER_PLUGIN_DATA_REMOVER_H_
diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc
index 9f5c6665..6a7bc3c 100644
--- a/content/shell/browser/shell_content_browser_client.cc
+++ b/content/shell/browser/shell_content_browser_client.cc
@@ -39,6 +39,7 @@
 #include "components/variations/service/variations_field_trial_creator.h"
 #include "components/variations/service/variations_service.h"
 #include "components/variations/service/variations_service_client.h"
+#include "components/variations/variations_switches.h"
 #include "content/public/browser/client_certificate_delegate.h"
 #include "content/public/browser/login_delegate.h"
 #include "content/public/browser/navigation_throttle.h"
@@ -715,13 +716,16 @@
 
   variations::SafeSeedManager safe_seed_manager(local_state_.get());
 
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
   // Since this is a test-only code path, some arguments to SetUpFieldTrials are
   // null.
   // TODO(crbug/1248066): Consider passing a low entropy provider and source.
   field_trial_creator.SetUpFieldTrials(
       variation_ids,
-      content::GetSwitchDependentFeatureOverrides(
-          *base::CommandLine::ForCurrentProcess()),
+      command_line->GetSwitchValueASCII(
+          variations::switches::kForceVariationIds),
+      content::GetSwitchDependentFeatureOverrides(*command_line),
       /*low_entropy_provider=*/nullptr, std::move(feature_list),
       metrics_state_manager.get(), field_trials_.get(), &safe_seed_manager,
       /*low_entropy_source_value=*/absl::nullopt);
diff --git a/device/fido/cable/fido_cable_discovery.cc b/device/fido/cable/fido_cable_discovery.cc
index 5ce361c6..cd8be64e 100644
--- a/device/fido/cable/fido_cable_discovery.cc
+++ b/device/fido/cable/fido_cable_discovery.cc
@@ -278,8 +278,11 @@
   switch (fido::mac::ProcessIsSigned()) {
     case fido::mac::CodeSigningState::kSigned:
       FIDO_LOG(DEBUG) << "Bluetooth authorized: "
-                      << (adapter_->GetOsPermissionStatus() !=
-                          BluetoothAdapter::PermissionStatus::kDenied);
+                      << static_cast<int>(adapter_->GetOsPermissionStatus());
+      if (adapter_->GetOsPermissionStatus() ==
+          BluetoothAdapter::PermissionStatus::kDenied) {
+        observer()->BleDenied();
+      }
       break;
     case fido::mac::CodeSigningState::kNotSigned:
       FIDO_LOG(DEBUG)
diff --git a/device/fido/fido_discovery_base.h b/device/fido/fido_discovery_base.h
index b338e552..09b1af7 100644
--- a/device/fido/fido_discovery_base.h
+++ b/device/fido/fido_discovery_base.h
@@ -42,6 +42,12 @@
                                     FidoAuthenticator* authenticator) = 0;
     virtual void AuthenticatorRemoved(FidoDiscoveryBase* discovery,
                                       FidoAuthenticator* authenticator) = 0;
+
+    // BleDenied is called if the user has denied access to the BLE hardware.
+    // This is macOS-specific and, unlike information like the power state, this
+    // information is only available once the caBLE discovery has opened the BLE
+    // adaptor. Thus the signal is plumbed via this observer interface.
+    virtual void BleDenied() {}
   };
 
   // Start authenticator discovery. The Observer must have been set before this
diff --git a/device/fido/fido_request_handler_base.cc b/device/fido/fido_request_handler_base.cc
index 9641d49f..ea1bb34 100644
--- a/device/fido/fido_request_handler_base.cc
+++ b/device/fido/fido_request_handler_base.cc
@@ -387,6 +387,10 @@
 #endif  // BUILDFLAG(IS_WIN)
 }
 
+void FidoRequestHandlerBase::BleDenied() {
+  transport_availability_info_.ble_access_denied = true;
+}
+
 void FidoRequestHandlerBase::GetPlatformCredentialStatus(
     FidoAuthenticator* platform_authenticator) {
   transport_availability_callback_readiness_
diff --git a/device/fido/fido_request_handler_base.h b/device/fido/fido_request_handler_base.h
index 9e52164..867cbb8 100644
--- a/device/fido/fido_request_handler_base.h
+++ b/device/fido/fido_request_handler_base.h
@@ -89,6 +89,10 @@
     bool is_ble_powered = false;
     bool can_power_on_ble_adapter = false;
 
+    // ble_access_denied is set to true if Chromium does not have permission
+    // to use the BLE adaptor. Resolving this is a platform-specific operation.
+    bool ble_access_denied = false;
+
     // Indicates whether the native Windows WebAuthn API is available.
     // Dispatching to it should be controlled by the embedder.
     //
@@ -294,6 +298,7 @@
                           FidoAuthenticator* authenticator) override;
   void AuthenticatorRemoved(FidoDiscoveryBase* discovery,
                             FidoAuthenticator* authenticator) override;
+  void BleDenied() override;
 
   // GetPlatformCredentialStatus is called to learn whether a platform
   // authenticator has credentials responsive to the current request. If this
diff --git a/extensions/browser/api/messaging/message_service.cc b/extensions/browser/api/messaging/message_service.cc
index dea3e83e..729869f 100644
--- a/extensions/browser/api/messaging/message_service.cc
+++ b/extensions/browser/api/messaging/message_service.cc
@@ -292,8 +292,7 @@
         is_web_connection = true;
 
         // Sites can only connect to the CryptoToken component extension if it
-        // has been enabled via feature flag, enterprise policy or deprecation
-        // trial.
+        // has been enabled via feature flag or deprecation trial.
         // TODO(1224886): Delete together with CryptoToken code.
         if (target_extension_id == extension_misc::kCryptotokenExtensionId) {
           blink::TrialTokenValidator validator;
@@ -302,8 +301,6 @@
           const bool u2f_api_enabled =
               base::FeatureList::IsEnabled(
                   extensions_features::kU2FSecurityKeyAPI) ||
-              ExtensionPrefs::Get(context)->pref_service()->GetBoolean(
-                  extensions::pref_names::kU2fSecurityKeyApiEnabled) ||
               (response_headers &&
                validator.RequestEnablesFeature(
                    source_render_frame_host->GetLastCommittedURL(),
diff --git a/extensions/browser/extension_event_histogram_value.h b/extensions/browser/extension_event_histogram_value.h
index cce873c..b53026ad 100644
--- a/extensions/browser/extension_event_histogram_value.h
+++ b/extensions/browser/extension_event_histogram_value.h
@@ -481,6 +481,7 @@
   CERTIFICATEPROVIDER_ON_CERTIFICATES_UPDATE_REQUESTED = 459,
   CERTIFICATEPROVIDER_ON_SIGNATURE_REQUESTED = 460,
   WINDOWS_ON_BOUNDS_CHANGED = 461,
+  // Obsolete since removing wallpaper extension b/193788853.
   WALLPAPER_PRIVATE_ON_CLOSE_PREVIEW_WALLPAPER = 462,
   PASSWORDS_PRIVATE_ON_WEAK_CREDENTIALS_CHANGED = 463,
   ACCESSIBILITY_PRIVATE_ON_MAGNIFIER_BOUNDS_CHANGED = 464,
diff --git a/extensions/docs/events.md b/extensions/docs/events.md
index 96a9940..3cc848e 100644
--- a/extensions/docs/events.md
+++ b/extensions/docs/events.md
@@ -10,8 +10,8 @@
 An event listener registered in the renderer process is sent to the browser
 process (via IPC). The browser process stores the listener information in
 `EventListenerMap`. Events are dispatched from the browser process to the
-renderer process via IPC. If browser process requires to persist any listener,
-it does so by storing the listener information in the prefs.
+renderer process via IPC. If the browser process requires persistence of any
+listener, it does so by storing the listener information in `ExtensionPrefs`.
 
 ## Relevant concepts
 
@@ -71,9 +71,9 @@
 
 ### Additional notes about lazy event dispatching
 
-Recall that a lazy listener is like a regular listeners, except that it is
+Recall that a lazy listener is like a regular listener, except that it is
 registered from a lazy context. A lazy context can be shut down. If an
-interesting event ocurrs while a lazy context (with a listener to that event)
+interesting event occurs while a lazy context (with a listener to that event)
 is no longer running, then the lazy context is woken up to dispatch the event.
 
 The following (simplified) steps describe how dispatch is performed.
diff --git a/gpu/command_buffer/service/gpu_fence_manager_unittest.cc b/gpu/command_buffer/service/gpu_fence_manager_unittest.cc
index 95f56497..d467b91 100644
--- a/gpu/command_buffer/service/gpu_fence_manager_unittest.cc
+++ b/gpu/command_buffer/service/gpu_fence_manager_unittest.cc
@@ -18,8 +18,9 @@
 #include "ui/gfx/gpu_fence.h"
 #include "ui/gfx/gpu_fence_handle.h"
 #include "ui/gl/egl_mock.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_egl_api_implementation.h"
-#include "ui/gl/gl_surface_egl.h"
+#include "ui/gl/gl_utils.h"
 
 #if BUILDFLAG(IS_POSIX)
 #include <unistd.h>
@@ -74,11 +75,13 @@
 
     gl::ClearBindingsEGL();
     gl::InitializeStaticGLBindingsEGL();
-    display_ = gl::GLSurfaceEGL::InitializeOneOffForTesting();
+    display_ = gl::GetDefaultDisplayEGL();
+    display_->InitializeForTesting();
   }
 
   void TeardownMockEGL() {
-    gl::GLSurfaceEGL::ShutdownOneOff(display_);
+    if (display_)
+      display_->Shutdown();
     egl_.reset();
   }
 
diff --git a/infra/config/generated/builders/ci/win32-archive-dbg/properties.json b/infra/config/generated/builders/ci/win32-archive-dbg/properties.json
index c518c1c5..b707d9d 100644
--- a/infra/config/generated/builders/ci/win32-archive-dbg/properties.json
+++ b/infra/config/generated/builders/ci/win32-archive-dbg/properties.json
@@ -1,4 +1,42 @@
 {
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "win32-archive-dbg",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "builder_group": "chromium",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "clobber",
+                  "mb"
+                ],
+                "build_config": "Debug",
+                "config": "chromium",
+                "target_bits": 32
+              },
+              "legacy_gclient_config": {
+                "config": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "win32-archive-dbg",
+          "project": "chromium"
+        }
+      ]
+    }
+  },
   "$build/reclient": {
     "instance": "rbe-chromium-trusted",
     "jobs": 250,
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 7754758..51f4d93 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -15606,7 +15606,7 @@
         '    "android"'
         '  ]'
         '}'
-      execution_timeout_secs: 50400
+      execution_timeout_secs: 54000
       build_numbers: YES
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
diff --git a/infra/config/subprojects/chromium/ci/chromium.android.star b/infra/config/subprojects/chromium/ci/chromium.android.star
index 16f9e3e..d20670c6 100644
--- a/infra/config/subprojects/chromium/ci/chromium.android.star
+++ b/infra/config/subprojects/chromium/ci/chromium.android.star
@@ -578,7 +578,7 @@
     ),
     # We have limited tablet capacity and thus limited ability to run
     # tests in parallel, hence the high timeout.
-    execution_timeout = 14 * time.hour,
+    execution_timeout = 15 * time.hour,
     triggered_by = ["ci/Android arm Builder (dbg)"],
 )
 
diff --git a/infra/config/subprojects/chromium/ci/chromium.star b/infra/config/subprojects/chromium/ci/chromium.star
index 15759d64..9707df4 100644
--- a/infra/config/subprojects/chromium/ci/chromium.star
+++ b/infra/config/subprojects/chromium/ci/chromium.star
@@ -580,6 +580,20 @@
 
 ci.builder(
     name = "win32-archive-dbg",
+    builder_spec = builder_config.builder_spec(
+        gclient_config = builder_config.gclient_config(
+            config = "chromium",
+        ),
+        chromium_config = builder_config.chromium_config(
+            config = "chromium",
+            apply_configs = [
+                "clobber",
+                "mb",
+            ],
+            build_config = builder_config.build_config.DEBUG,
+            target_bits = 32,
+        ),
+    ),
     console_view_entry = consoles.console_view_entry(
         category = "win|dbg",
         short_name = "32",
diff --git a/ios/chrome/browser/ios_chrome_main_parts.h b/ios/chrome/browser/ios_chrome_main_parts.h
index 56a12f1..83e9750 100644
--- a/ios/chrome/browser/ios_chrome_main_parts.h
+++ b/ios/chrome/browser/ios_chrome_main_parts.h
@@ -36,8 +36,12 @@
   void PostDestroyThreads() override;
 
   // Sets up the field trials and related initialization. Call only after
-  // about:flags have been converted to switches.
-  void SetUpFieldTrials();
+  // about:flags have been converted to switches. However,
+  // |command_line_variation_ids| should be the value of the
+  // "--force-variation-ids" switch before it is mutated. See
+  // VariationsFieldTrialCreator::SetUpFieldTrials() for the format of
+  // |command_line_variation_ids|.
+  void SetUpFieldTrials(const std::string& command_line_variation_ids);
 
   // Constructs the metrics service and initializes metrics recording.
   void SetupMetrics();
diff --git a/ios/chrome/browser/ios_chrome_main_parts.mm b/ios/chrome/browser/ios_chrome_main_parts.mm
index 325f737..b4248cf 100644
--- a/ios/chrome/browser/ios_chrome_main_parts.mm
+++ b/ios/chrome/browser/ios_chrome_main_parts.mm
@@ -45,6 +45,7 @@
 #include "components/variations/synthetic_trials_active_group_id_provider.h"
 #include "components/variations/variations_crash_keys.h"
 #include "components/variations/variations_ids_provider.h"
+#include "components/variations/variations_switches.h"
 #include "ios/chrome/browser/application_context_impl.h"
 #include "ios/chrome/browser/browser_state/browser_state_keyed_service_factories.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
@@ -213,10 +214,23 @@
       break;
   }
 
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  // Get the variation IDs passed through the command line. This is done early
+  // on because ConvertFlagsToSwitches() will append to the command line
+  // the variation IDs from flags (so that they are visible in about://version).
+  // This will be passed on to `VariationsService::SetUpFieldTrials()`, which
+  // will manually fetch the variation IDs from flags (hence the reason we do
+  // not pass the mutated command line, otherwise the IDs will be duplicated).
+  // It also distinguishes between variation IDs coming from the command line
+  // and from flags, so we cannot rely on simply putting them all in the
+  // command line.
+  const std::string command_line_variation_ids =
+      command_line->GetSwitchValueASCII(
+          variations::switches::kForceVariationIds);
+
   // Convert freeform experimental settings into switches before initializing
   // local state, in case any of the settings affect policy.
-  AppendSwitchesFromExperimentalSettings(
-      base::CommandLine::ForCurrentProcess());
+  AppendSwitchesFromExperimentalSettings(command_line);
 
   // Initialize local state.
   local_state_ = application_context_->GetLocalState();
@@ -224,14 +238,13 @@
 
   flags_ui::PrefServiceFlagsStorage flags_storage(
       application_context_->GetLocalState());
-  ConvertFlagsToSwitches(&flags_storage,
-                         base::CommandLine::ForCurrentProcess());
+  ConvertFlagsToSwitches(&flags_storage, command_line);
 
   // Now that the command line has been mutated based on about:flags, we can
   // initialize field trials. The field trials are needed by IOThread's
   // initialization which happens in BrowserProcess:PreCreateThreads. Metrics
   // initialization is handled in PreMainMessageLoopRun since it posts tasks.
-  SetUpFieldTrials();
+  SetUpFieldTrials(command_line_variation_ids);
 
   // Set metrics upload for stack/heap profiles.
   IOSThreadProfiler::SetBrowserProcessReceiverCallback(base::BindRepeating(
@@ -400,7 +413,8 @@
 }
 
 // This will be called after the command-line has been mutated by about:flags
-void IOSChromeMainParts::SetUpFieldTrials() {
+void IOSChromeMainParts::SetUpFieldTrials(
+    const std::string& command_line_variation_ids) {
   base::SetRecordActionTaskRunner(web::GetUIThreadTaskRunner({}));
 
   // FeatureList requires VariationsIdsProvider to be created.
@@ -421,7 +435,8 @@
       RegisterAllFeatureVariationParameters(&flags_storage, feature_list.get());
 
   application_context_->GetVariationsService()->SetUpFieldTrials(
-      variation_ids, std::vector<base::FeatureList::FeatureOverrideInfo>(),
+      variation_ids, command_line_variation_ids,
+      std::vector<base::FeatureList::FeatureOverrideInfo>(),
       std::move(feature_list), &ios_field_trials_);
 }
 
diff --git a/ios/chrome/browser/policy/BUILD.gn b/ios/chrome/browser/policy/BUILD.gn
index f5629646..41718683 100644
--- a/ios/chrome/browser/policy/BUILD.gn
+++ b/ios/chrome/browser/policy/BUILD.gn
@@ -75,6 +75,7 @@
     "//components/safe_browsing/core/common:safe_browsing_policy_handler",
     "//components/safe_browsing/core/common:safe_browsing_prefs",
     "//components/search_engines",
+    "//components/security_interstitials/core",
     "//components/strings:components_strings_grit",
     "//components/translate/core/browser:translate_pref_names",
     "//components/unified_consent:unified_consent",
diff --git a/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm b/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm
index 6279362..e010005 100644
--- a/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm
+++ b/ios/chrome/browser/policy/configuration_policy_handler_list_factory.mm
@@ -27,6 +27,7 @@
 #include "components/safe_browsing/core/common/safe_browsing_policy_handler.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/search_engines/default_search_policy_handler.h"
+#include "components/security_interstitials/core/https_only_mode_policy_handler.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/sync/driver/sync_policy_handler.h"
 #include "components/translate/core/browser/translate_pref_names.h"
@@ -158,6 +159,8 @@
       std::make_unique<policy::NewTabPageLocationPolicyHandler>());
   handlers->AddHandler(std::make_unique<policy::URLBlocklistPolicyHandler>(
       policy::key::kURLBlocklist));
+  handlers->AddHandler(std::make_unique<policy::HttpsOnlyModePolicyHandler>(
+      prefs::kHttpsOnlyModeEnabled));
 
   return handlers;
 }
diff --git a/ios/components/security_interstitials/lookalikes/lookalike_url_tab_helper.mm b/ios/components/security_interstitials/lookalikes/lookalike_url_tab_helper.mm
index a9157e1..24a37cad 100644
--- a/ios/components/security_interstitials/lookalikes/lookalike_url_tab_helper.mm
+++ b/ios/components/security_interstitials/lookalikes/lookalike_url_tab_helper.mm
@@ -81,12 +81,6 @@
     std::move(callback).Run(CreateAllowDecision());
     return;
   }
-  // If the URL is in the component updater allowlist, don't show any warning.
-  if (reputation::IsUrlAllowlistedBySafetyTipsComponent(
-          proto, response_url.GetWithEmptyPath())) {
-    std::move(callback).Run(CreateAllowDecision());
-    return;
-  }
 
   // TODO(crbug.com/1104386): If this is a reload and if the current
   // URL is the last URL of the stored redirect chain, the interstitial
@@ -115,7 +109,12 @@
       });
   if (!GetMatchingDomain(navigated_domain, engaged_sites, in_target_allowlist,
                          proto, &matched_domain, &match_type)) {
-    if (ShouldBlockBySpoofCheckResult(navigated_domain)) {
+    // If the URL fails a spoof check, and isn't in the component allowlist,
+    // then show a spoof check interstitial.
+    if (ShouldBlockBySpoofCheckResult(navigated_domain) &&
+        !reputation::IsUrlAllowlistedBySafetyTipsComponent(
+            proto, response_url.GetWithEmptyPath(),
+            response_url.GetWithEmptyPath())) {
       match_type = LookalikeUrlMatchType::kFailedSpoofChecks;
       RecordUMAFromMatchType(match_type);
       LookalikeUrlContainer* lookalike_container =
@@ -133,29 +132,37 @@
 
   RecordUMAFromMatchType(match_type);
 
-  if (ShouldBlockLookalikeUrlNavigation(match_type)) {
-    const std::string suggested_domain = GetETLDPlusOne(matched_domain);
-    DCHECK(!suggested_domain.empty());
-    GURL::Replacements replace_host;
-    replace_host.SetHostStr(suggested_domain);
-    const GURL suggested_url =
-        response_url.ReplaceComponents(replace_host).GetWithEmptyPath();
-    LookalikeUrlContainer* lookalike_container =
-        LookalikeUrlContainer::FromWebState(web_state());
-    lookalike_container->SetLookalikeUrlInfo(suggested_url, response_url,
-                                             match_type);
+  const std::string suggested_domain = GetETLDPlusOne(matched_domain);
+  DCHECK(!suggested_domain.empty());
+  GURL::Replacements replace_host;
+  replace_host.SetHostStr(suggested_domain);
+  const GURL suggested_url =
+      response_url.ReplaceComponents(replace_host).GetWithEmptyPath();
 
-    std::move(callback).Run(CreateLookalikeErrorDecision());
+  // If the URL is in the component updater allowlist, don't show any warning.
+  if (reputation::IsUrlAllowlistedBySafetyTipsComponent(
+          proto, response_url.GetWithEmptyPath(),
+          suggested_url.GetWithEmptyPath())) {
+    std::move(callback).Run(CreateAllowDecision());
     return;
   }
 
-  // Interstitial normally records UKM, but still record when it's not shown.
-  RecordUkmForLookalikeUrlBlockingPage(
-      ukm::GetSourceIdForWebStateDocument(web_state()), match_type,
-      LookalikeUrlBlockingPageUserAction::kInterstitialNotShown,
-      /*triggered_by_initial_url=*/false);
+  if (!ShouldBlockLookalikeUrlNavigation(match_type)) {
+    // Interstitial normally records UKM, but still record when it's not shown.
+    RecordUkmForLookalikeUrlBlockingPage(
+        ukm::GetSourceIdForWebStateDocument(web_state()), match_type,
+        LookalikeUrlBlockingPageUserAction::kInterstitialNotShown,
+        /*triggered_by_initial_url=*/false);
 
-  std::move(callback).Run(CreateAllowDecision());
+    std::move(callback).Run(CreateAllowDecision());
+    return;
+  }
+
+  LookalikeUrlContainer* lookalike_container =
+      LookalikeUrlContainer::FromWebState(web_state());
+  lookalike_container->SetLookalikeUrlInfo(suggested_url, response_url,
+                                           match_type);
+  std::move(callback).Run(CreateLookalikeErrorDecision());
 }
 
 WEB_STATE_USER_DATA_KEY_IMPL(LookalikeUrlTabHelper)
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index a8bd167..7993d90 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-b3be12d1dc7687a0918c420e60105a939d810323
\ No newline at end of file
+a772b3d6902fea1ae01076e95e48e3a29dbe0d69
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 8bc33cd..72c2386 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-0c641280e4efe1b832246f1c058acfedf4dfde21
\ No newline at end of file
+f249c785b5daedf48d84f25323638654f9866f15
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index 38857e9d..63eba1d 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-f7cafb609bfda98f82fa3c7ff08a01f7be9cde41
\ No newline at end of file
+a1f7e2376115f3c4f87dafeb54e5d00a4c66e501
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index 33f4cd4..0fd4c9c0 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-730abc018dfb9e451c6d0ecc86b5c373a810116e
\ No newline at end of file
+17db2e62db79c5e2506deaa68b7fe080ae362a5b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
index 5903037..a5f5c0f 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-bd7ec7ed6038a31d6d2ccd4e65e437996f56be76
\ No newline at end of file
+ced3fb93702e019496a9b3058d99b37d0fa01146
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
index 48dbbe2..15a6c27 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-d3505cc5cf770c62c0c05fae40a7cba3917efbd1
\ No newline at end of file
+f5e7cb805688f3c3725a88df68453c851292b98f
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index f9c0a3e..43d38a4 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-7dd7eba0aaf693ec6671807d36cedb1f2f93eee1
\ No newline at end of file
+383ef88835ff1e3a9f2853b10ad08d68a3777376
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index c0d370a..4d4836b 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-57fd4ef18bda74e8627dd7fbcf26e7574a56689b
\ No newline at end of file
+6071dcac78bc319ff66b8c54cb041022f9111557
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index b6c9b85..755db478 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-8dcee02f88c8aa2eb7d30ac1626f25d0f7a4ad1a
\ No newline at end of file
+254b64a71b2e14b18c1f161aadd55f906c6a08bd
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 65cf4e4..378d43b 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-e881e266d8d0ad69fbc17c28e7d211ad3b32caf1
\ No newline at end of file
+dd94367dad63a5cf1d98dd75f289f4d56a9e94f9
\ No newline at end of file
diff --git a/media/formats/hls/playlist_common.cc b/media/formats/hls/playlist_common.cc
index c35c933..3daf7d0 100644
--- a/media/formats/hls/playlist_common.cc
+++ b/media/formats/hls/playlist_common.cc
@@ -122,7 +122,8 @@
   }
 
   // URIs may be relative to the playlist URI, resolve it against that.
-  auto resolved_uri = playlist_uri.Resolve(std::move(uri_str_result).value());
+  auto resolved_uri =
+      playlist_uri.Resolve(std::move(uri_str_result).value().Str());
   if (!resolved_uri.is_valid()) {
     return ParseStatusCode::kInvalidUri;
   }
diff --git a/media/formats/hls/source_string.cc b/media/formats/hls/source_string.cc
index 7f847fb..d8dee3f 100644
--- a/media/formats/hls/source_string.cc
+++ b/media/formats/hls/source_string.cc
@@ -11,31 +11,67 @@
 
 namespace media::hls {
 
+template <>
 SourceString SourceString::Create(base::PassKey<SourceLineIterator>,
                                   size_t line,
                                   base::StringPiece str) {
-  return SourceString(line, 1, str);
+  return SourceString(line, 1, str, {});
 }
 
-SourceString SourceString::CreateForTesting(base::StringPiece str) {
-  return SourceString::CreateForTesting(1, 1, str);
+template <>
+ResolvedSourceString ResolvedSourceString::Create(
+    base::PassKey<VariableDictionary>,
+    size_t line,
+    size_t column,
+    base::StringPiece str,
+    ResolvedSourceStringState resolution_state) {
+  return ResolvedSourceString(line, column, str, resolution_state);
 }
 
+template <typename ResolutionState>
+GenericSourceString<ResolutionState>
+GenericSourceString<ResolutionState>::CreateForTesting(base::StringPiece str) {
+  return GenericSourceString::CreateForTesting(1, 1, str);
+}
+
+template <>
 SourceString SourceString::CreateForTesting(size_t line,
                                             size_t column,
                                             base::StringPiece str) {
-  return SourceString(line, column, str);
+  return SourceString::CreateForTesting(line, column, str, {});
 }
 
-SourceString::SourceString(size_t line, size_t column, base::StringPiece str)
-    : line_(line), column_(column), str_(str) {}
+template <>
+ResolvedSourceString ResolvedSourceString::CreateForTesting(
+    size_t line,
+    size_t column,
+    base::StringPiece str) {
+  return ResolvedSourceString::CreateForTesting(
+      line, column, str,
+      ResolvedSourceStringState{.contains_substitutions = false});
+}
 
-SourceString SourceString::Substr(size_t pos, size_t count) const {
+template <typename ResolutionState>
+GenericSourceString<ResolutionState>
+GenericSourceString<ResolutionState>::CreateForTesting(
+    size_t line,
+    size_t column,
+    base::StringPiece str,
+    ResolutionState resolution_state) {
+  return GenericSourceString(line, column, str, resolution_state);
+}
+
+template <typename ResolutionState>
+GenericSourceString<ResolutionState>
+GenericSourceString<ResolutionState>::Substr(size_t pos, size_t count) const {
   const auto column = column_ + pos;
-  return SourceString(line_, column, str_.substr(pos, count));
+  return GenericSourceString(line_, column, str_.substr(pos, count),
+                             resolution_state_);
 }
 
-SourceString SourceString::Consume(size_t count) {
+template <typename ResolutionState>
+GenericSourceString<ResolutionState>
+GenericSourceString<ResolutionState>::Consume(size_t count) {
   count = std::min(count, str_.size());
 
   auto consumed = Substr(0, count);
@@ -44,6 +80,34 @@
   return consumed;
 }
 
+template <>
+ResolvedSourceString SourceString::SkipVariableSubstitution() const {
+  return ResolvedSourceString(
+      Line(), Column(), Str(),
+      ResolvedSourceStringState{.contains_substitutions = false});
+}
+
+template <>
+bool SourceString::ContainsSubstitutions() const {
+  return false;
+}
+
+template <>
+bool ResolvedSourceString::ContainsSubstitutions() const {
+  return resolution_state_.contains_substitutions;
+}
+
+template <typename ResolutionState>
+GenericSourceString<ResolutionState>::GenericSourceString(
+    size_t line,
+    size_t column,
+    base::StringPiece str,
+    ResolutionState resolution_state)
+    : line_(line),
+      column_(column),
+      str_(str),
+      resolution_state_(resolution_state) {}
+
 SourceLineIterator::SourceLineIterator(base::StringPiece source)
     : current_line_(1), source_(source) {}
 
@@ -75,4 +139,12 @@
   return SourceString::Create({}, line_number, line_content);
 }
 
+// These forward declarations tell the compiler that we will use
+// `GenericSourceString` with these arguments, allowing us to keep these
+// definitions in our .cc without causing linker errors. This also means if
+// anyone tries to instantiate a `GenericSourceString` with anything but these
+// two specializations they'll most likely get linker errors.
+template class MEDIA_EXPORT GenericSourceString<SourceStringState>;
+template class MEDIA_EXPORT GenericSourceString<ResolvedSourceStringState>;
+
 }  // namespace media::hls
diff --git a/media/formats/hls/source_string.h b/media/formats/hls/source_string.h
index 48dfd215..9dd615d 100644
--- a/media/formats/hls/source_string.h
+++ b/media/formats/hls/source_string.h
@@ -14,18 +14,52 @@
 namespace media::hls {
 
 struct SourceLineIterator;
+class VariableDictionary;
+
+// Type representing the resolution state for a `SourceString`.
+// As there is only one state here (unresolved), this struct is empty.
+struct SourceStringState {};
+
+// Type containing the resolution state for a `ResolvedSourceString`.
+struct ResolvedSourceStringState {
+  // Whether this string has undergone variable substitution and has
+  // substitutions applied to the original source.
+  bool contains_substitutions;
+};
+
+template <typename T>
+class GenericSourceString;
+
+// A `SourceString` is a slice of the original manifest string that may contain
+// unresolved variable references.
+using SourceString = GenericSourceString<SourceStringState>;
+
+// A `ResolvedSourceString` is a string slice that has either undergone or
+// skipped variable substitution, and may differ from the original source.
+using ResolvedSourceString = GenericSourceString<ResolvedSourceStringState>;
 
 // This structure represents contents of a single line in an HLS manifest, not
 // including the line ending. This may be the entire line, or a substring of the
 // line (clipped at either/both ends).
-struct MEDIA_EXPORT SourceString {
-  static SourceString Create(base::PassKey<SourceLineIterator>,
-                             size_t line,
-                             base::StringPiece str);
-  static SourceString CreateForTesting(base::StringPiece str);
-  static SourceString CreateForTesting(size_t line,
-                                       size_t column,
-                                       base::StringPiece str);
+template <typename ResolutionState>
+class MEDIA_EXPORT GenericSourceString {
+ public:
+  static GenericSourceString Create(base::PassKey<SourceLineIterator>,
+                                    size_t line,
+                                    base::StringPiece str);
+  static GenericSourceString Create(base::PassKey<VariableDictionary>,
+                                    size_t line,
+                                    size_t column,
+                                    base::StringPiece str,
+                                    ResolutionState resolution_state);
+  static GenericSourceString CreateForTesting(base::StringPiece str);
+  static GenericSourceString CreateForTesting(size_t line,
+                                              size_t column,
+                                              base::StringPiece str);
+  static GenericSourceString CreateForTesting(size_t line,
+                                              size_t column,
+                                              base::StringPiece str,
+                                              ResolutionState resolution_state);
 
   // Returns the 1-based line index of this SourceString within the manifest.
   size_t Line() const { return line_; }
@@ -42,21 +76,58 @@
 
   size_t Size() const { return str_.size(); }
 
-  SourceString Substr(size_t pos = 0,
-                      size_t count = base::StringPiece::npos) const;
+  GenericSourceString Substr(size_t pos = 0,
+                             size_t count = base::StringPiece::npos) const;
 
   // Consumes this string up to the given count, which may be longer than this
   // string. Returns the substring that was consumed.
-  SourceString Consume(size_t count = base::StringPiece::npos);
+  GenericSourceString Consume(size_t count = base::StringPiece::npos);
+
+  // Produces a `ResolvedSourceString` by bypassing variable substitution.
+  // This is useful for passing strings that must not contain variables to
+  // functions consuming strings that may or may not have contained variable
+  // references.
+  ResolvedSourceString SkipVariableSubstitution() const;
+
+  // Returns whether this string contains variable substitutions, i.e. is
+  // different from the original source.
+  bool ContainsSubstitutions() const;
 
  private:
-  SourceString(size_t line, size_t column, base::StringPiece str);
+  template <typename>
+  friend class GenericSourceString;
+
+  GenericSourceString(size_t line,
+                      size_t column,
+                      base::StringPiece str,
+                      ResolutionState resolution_state);
 
   size_t line_;
   size_t column_;
   base::StringPiece str_;
+  ResolutionState resolution_state_;
 };
 
+// `SourceLineIterator` may not create resolved source strings
+template <>
+ResolvedSourceString ResolvedSourceString::Create(
+    base::PassKey<SourceLineIterator>,
+    size_t line,
+    base::StringPiece str) = delete;
+
+// `VariableDictionary` may not create unresolved source strings
+template <>
+SourceString SourceString::Create(base::PassKey<VariableDictionary>,
+                                  size_t line,
+                                  size_t column,
+                                  base::StringPiece str,
+                                  SourceStringState resolution_state) = delete;
+
+// Resolved source strings may not skip variable substitution
+template <>
+ResolvedSourceString ResolvedSourceString::SkipVariableSubstitution() const =
+    delete;
+
 // Exposes a line-based iteration API over the source text of an HLS manifest.
 struct MEDIA_EXPORT SourceLineIterator {
   explicit SourceLineIterator(base::StringPiece source);
diff --git a/media/formats/hls/tags.cc b/media/formats/hls/tags.cc
index 7b04bb2..b634262 100644
--- a/media/formats/hls/tags.cc
+++ b/media/formats/hls/tags.cc
@@ -35,7 +35,8 @@
     return ParseStatusCode::kMalformedTag;
   }
 
-  auto value = types::ParseDecimalInteger(*tag.GetContent());
+  auto value =
+      types::ParseDecimalInteger(tag.GetContent()->SkipVariableSubstitution());
   if (value.has_error()) {
     return ParseStatus(ParseStatusCode::kMalformedTag)
         .AddCause(std::move(value).error());
@@ -255,7 +256,8 @@
   // Extract duration
   // TODO(crbug.com/1284763): Below version 3 this should be rounded to an
   // integer
-  auto duration_result = types::ParseDecimalFloatingPoint(duration_str);
+  auto duration_result =
+      types::ParseDecimalFloatingPoint(duration_str.SkipVariableSubstitution());
   if (duration_result.has_error()) {
     return ParseStatus(ParseStatusCode::kMalformedTag)
         .AddCause(std::move(duration_result).error());
@@ -422,7 +424,8 @@
   // Extract the 'BANDWIDTH' attribute
   if (map.HasValue(XStreamInfTagAttribute::kBandwidth)) {
     auto bandwidth = types::ParseDecimalInteger(
-        map.GetValue(XStreamInfTagAttribute::kBandwidth));
+        map.GetValue(XStreamInfTagAttribute::kBandwidth)
+            .SkipVariableSubstitution());
     if (bandwidth.has_error()) {
       return ParseStatus(ParseStatusCode::kMalformedTag)
           .AddCause(std::move(bandwidth).error());
@@ -436,7 +439,8 @@
   // Extract the 'AVERAGE-BANDWIDTH' attribute
   if (map.HasValue(XStreamInfTagAttribute::kAverageBandwidth)) {
     auto average_bandwidth = types::ParseDecimalInteger(
-        map.GetValue(XStreamInfTagAttribute::kAverageBandwidth));
+        map.GetValue(XStreamInfTagAttribute::kAverageBandwidth)
+            .SkipVariableSubstitution());
     if (average_bandwidth.has_error()) {
       return ParseStatus(ParseStatusCode::kMalformedTag)
           .AddCause(std::move(average_bandwidth).error());
@@ -448,7 +452,8 @@
   // Extract the 'SCORE' attribute
   if (map.HasValue(XStreamInfTagAttribute::kScore)) {
     auto score = types::ParseDecimalFloatingPoint(
-        map.GetValue(XStreamInfTagAttribute::kScore));
+        map.GetValue(XStreamInfTagAttribute::kScore)
+            .SkipVariableSubstitution());
     if (score.has_error()) {
       return ParseStatus(ParseStatusCode::kMalformedTag)
           .AddCause(std::move(score).error());
@@ -466,13 +471,14 @@
       return ParseStatus(ParseStatusCode::kMalformedTag)
           .AddCause(std::move(codecs).error());
     }
-    out.codecs = std::string{std::move(codecs).value()};
+    out.codecs = std::string{std::move(codecs).value().Str()};
   }
 
   // Extract the 'RESOLUTION' attribute
   if (map.HasValue(XStreamInfTagAttribute::kResolution)) {
     auto resolution = types::DecimalResolution::Parse(
-        map.GetValue(XStreamInfTagAttribute::kResolution));
+        map.GetValue(XStreamInfTagAttribute::kResolution)
+            .SkipVariableSubstitution());
     if (resolution.has_error()) {
       return ParseStatus(ParseStatusCode::kMalformedTag)
           .AddCause(std::move(resolution).error());
@@ -483,7 +489,8 @@
   // Extract the 'FRAME-RATE' attribute
   if (map.HasValue(XStreamInfTagAttribute::kFrameRate)) {
     auto frame_rate = types::ParseDecimalFloatingPoint(
-        map.GetValue(XStreamInfTagAttribute::kFrameRate));
+        map.GetValue(XStreamInfTagAttribute::kFrameRate)
+            .SkipVariableSubstitution());
     if (frame_rate.has_error()) {
       return ParseStatus(ParseStatusCode::kMalformedTag)
           .AddCause(std::move(frame_rate).error());
@@ -500,7 +507,8 @@
     return ParseStatusCode::kMalformedTag;
   }
 
-  auto duration_result = types::ParseDecimalInteger(tag.GetContent().value());
+  auto duration_result = types::ParseDecimalInteger(
+      tag.GetContent().value().SkipVariableSubstitution());
   if (duration_result.has_error()) {
     return ParseStatus(ParseStatusCode::kMalformedTag)
         .AddCause(std::move(duration_result).error());
@@ -534,7 +542,8 @@
   base::TimeDelta part_target;
   if (map.HasValue(XPartInfTagAttribute::kPartTarget)) {
     auto result = types::ParseDecimalFloatingPoint(
-        map.GetValue(XPartInfTagAttribute::kPartTarget));
+        map.GetValue(XPartInfTagAttribute::kPartTarget)
+            .SkipVariableSubstitution());
 
     if (result.has_error()) {
       return ParseStatus(ParseStatusCode::kMalformedTag)
@@ -573,7 +582,8 @@
   absl::optional<base::TimeDelta> can_skip_until;
   if (map.HasValue(XServerControlTagAttribute::kCanSkipUntil)) {
     auto result = types::ParseDecimalFloatingPoint(
-        map.GetValue(XServerControlTagAttribute::kCanSkipUntil));
+        map.GetValue(XServerControlTagAttribute::kCanSkipUntil)
+            .SkipVariableSubstitution());
 
     if (result.has_error()) {
       return ParseStatus(ParseStatusCode::kMalformedTag)
@@ -606,7 +616,8 @@
   absl::optional<base::TimeDelta> hold_back;
   if (map.HasValue(XServerControlTagAttribute::kHoldBack)) {
     auto result = types::ParseDecimalFloatingPoint(
-        map.GetValue(XServerControlTagAttribute::kHoldBack));
+        map.GetValue(XServerControlTagAttribute::kHoldBack)
+            .SkipVariableSubstitution());
 
     if (result.has_error()) {
       return ParseStatus(ParseStatusCode::kMalformedTag)
@@ -624,7 +635,8 @@
   absl::optional<base::TimeDelta> part_hold_back;
   if (map.HasValue(XServerControlTagAttribute::kPartHoldBack)) {
     auto result = types::ParseDecimalFloatingPoint(
-        map.GetValue(XServerControlTagAttribute::kPartHoldBack));
+        map.GetValue(XServerControlTagAttribute::kPartHoldBack)
+            .SkipVariableSubstitution());
 
     if (result.has_error()) {
       return ParseStatus(ParseStatusCode::kMalformedTag)
@@ -671,7 +683,8 @@
     return ParseStatusCode::kMalformedTag;
   }
 
-  auto range = types::ByteRangeExpression::Parse(*tag.GetContent());
+  auto range = types::ByteRangeExpression::Parse(
+      tag.GetContent()->SkipVariableSubstitution());
   if (range.has_error()) {
     return ParseStatus(ParseStatusCode::kMalformedTag)
         .AddCause(std::move(range).error());
diff --git a/media/formats/hls/types.cc b/media/formats/hls/types.cc
index 264f44d..e1b530cf 100644
--- a/media/formats/hls/types.cc
+++ b/media/formats/hls/types.cc
@@ -139,7 +139,8 @@
 
 }  // namespace
 
-ParseStatus::Or<DecimalInteger> ParseDecimalInteger(SourceString source_str) {
+ParseStatus::Or<DecimalInteger> ParseDecimalInteger(
+    ResolvedSourceString source_str) {
   static const base::NoDestructor<re2::RE2> decimal_integer_regex("\\d{1,20}");
 
   const auto str = source_str.Str();
@@ -161,7 +162,7 @@
 }
 
 ParseStatus::Or<DecimalFloatingPoint> ParseDecimalFloatingPoint(
-    SourceString source_str) {
+    ResolvedSourceString source_str) {
   // Utilize signed parsing function
   auto result = ParseSignedDecimalFloatingPoint(source_str);
   if (result.has_error()) {
@@ -178,7 +179,7 @@
 }
 
 ParseStatus::Or<SignedDecimalFloatingPoint> ParseSignedDecimalFloatingPoint(
-    SourceString source_str) {
+    ResolvedSourceString source_str) {
   // Accept no decimal point, decimal point with leading digits, trailing
   // digits, or both
   static const base::NoDestructor<re2::RE2> decimal_floating_point_regex(
@@ -202,7 +203,7 @@
 }
 
 ParseStatus::Or<DecimalResolution> DecimalResolution::Parse(
-    SourceString source_str) {
+    ResolvedSourceString source_str) {
   // decimal-resolution values are in the format: DecimalInteger 'x'
   // DecimalInteger
   const auto x_index = source_str.Str().find_first_of('x');
@@ -229,7 +230,7 @@
 }
 
 ParseStatus::Or<ByteRangeExpression> ByteRangeExpression::Parse(
-    SourceString source_str) {
+    ResolvedSourceString source_str) {
   // If this ByteRange has an offset, it will be separated from the length by
   // '@'.
   const auto at_index = source_str.Str().find_first_of('@');
@@ -271,7 +272,7 @@
   return ByteRange(length, offset);
 }
 
-ParseStatus::Or<base::StringPiece> ParseQuotedString(
+ParseStatus::Or<ResolvedSourceString> ParseQuotedString(
     SourceString source_str,
     const VariableDictionary& variable_dict,
     VariableDictionary::SubstitutionBuffer& sub_buffer) {
diff --git a/media/formats/hls/types.h b/media/formats/hls/types.h
index 22f08ead..b8cd9d51a8 100644
--- a/media/formats/hls/types.h
+++ b/media/formats/hls/types.h
@@ -19,27 +19,28 @@
 using DecimalInteger = uint64_t;
 
 MEDIA_EXPORT ParseStatus::Or<DecimalInteger> ParseDecimalInteger(
-    SourceString source_str);
+    ResolvedSourceString source_str);
 
 // A `DecimalFloatingPoint` is an unsigned floating-point value.
 // https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#:~:text=on%20its%20AttributeNames.%0A%0A%20%20%20o-,decimal%2Dfloating%2Dpoint,-%3A%20an%20unquoted%20string
 using DecimalFloatingPoint = double;
 
 MEDIA_EXPORT ParseStatus::Or<DecimalFloatingPoint> ParseDecimalFloatingPoint(
-    SourceString source_str);
+    ResolvedSourceString source_str);
 
 // A `SignedDecimalFloatingPoint` is a signed floating-point value.
 // https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#:~:text=decimal%20positional%20notation.%0A%0A%20%20%20o-,signed%2Ddecimal%2Dfloating%2Dpoint,-%3A%20an%20unquoted%20string
 using SignedDecimalFloatingPoint = double;
 
 MEDIA_EXPORT ParseStatus::Or<SignedDecimalFloatingPoint>
-ParseSignedDecimalFloatingPoint(SourceString source_str);
+ParseSignedDecimalFloatingPoint(ResolvedSourceString source_str);
 
 // A `DecimalResolution` is a set of two `DecimalInteger`s describing width and
 // height.
 // https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#:~:text=enumerated%2Dstring%2Dlist.%0A%0A%20%20%20o-,decimal%2Dresolution,-%3A%20two%20decimal%2Dintegers
 struct MEDIA_EXPORT DecimalResolution {
-  static ParseStatus::Or<DecimalResolution> Parse(SourceString source_str);
+  static ParseStatus::Or<DecimalResolution> Parse(
+      ResolvedSourceString source_str);
 
   types::DecimalInteger width;
   types::DecimalInteger height;
@@ -49,7 +50,8 @@
 // in tags describing byte ranges of a resource.
 // https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.4.2
 struct MEDIA_EXPORT ByteRangeExpression {
-  static ParseStatus::Or<ByteRangeExpression> Parse(SourceString source_str);
+  static ParseStatus::Or<ByteRangeExpression> Parse(
+      ResolvedSourceString source_str);
 
   // The length of the sub-range, in bytes.
   types::DecimalInteger length;
@@ -88,7 +90,7 @@
 // Parses a string surrounded by double-quotes ("), returning the inner string.
 // These appear in the context of attribute-lists, and are subject to variable
 // substitution. `sub_buffer` must outlive the returned string.
-MEDIA_EXPORT ParseStatus::Or<base::StringPiece> ParseQuotedString(
+MEDIA_EXPORT ParseStatus::Or<ResolvedSourceString> ParseQuotedString(
     SourceString source_str,
     const VariableDictionary& variable_dict,
     VariableDictionary::SubstitutionBuffer& sub_buffer);
diff --git a/media/formats/hls/types_unittest.cc b/media/formats/hls/types_unittest.cc
index 1b482cdd..591604f 100644
--- a/media/formats/hls/types_unittest.cc
+++ b/media/formats/hls/types_unittest.cc
@@ -20,24 +20,23 @@
   const auto error_test = [](base::StringPiece input,
                              const base::Location& from =
                                  base::Location::Current()) {
-    auto result =
-        types::ParseDecimalInteger(SourceString::CreateForTesting(1, 1, input));
+    auto result = types::ParseDecimalInteger(
+        ResolvedSourceString::CreateForTesting(input));
     ASSERT_TRUE(result.has_error()) << from.ToString();
     auto error = std::move(result).error();
     EXPECT_EQ(error.code(), ParseStatusCode::kFailedToParseDecimalInteger)
         << from.ToString();
   };
 
-  const auto ok_test = [](base::StringPiece input,
-                          types::DecimalInteger expected,
-                          const base::Location& from =
-                              base::Location::Current()) {
-    auto result =
-        types::ParseDecimalInteger(SourceString::CreateForTesting(1, 1, input));
-    ASSERT_TRUE(result.has_value()) << from.ToString();
-    auto value = std::move(result).value();
-    EXPECT_EQ(value, expected) << from.ToString();
-  };
+  const auto ok_test =
+      [](base::StringPiece input, types::DecimalInteger expected,
+         const base::Location& from = base::Location::Current()) {
+        auto result = types::ParseDecimalInteger(
+            ResolvedSourceString::CreateForTesting(input));
+        ASSERT_TRUE(result.has_value()) << from.ToString();
+        auto value = std::move(result).value();
+        EXPECT_EQ(value, expected) << from.ToString();
+      };
 
   // Empty string is not allowed
   error_test("");
@@ -76,23 +75,22 @@
                              const base::Location& from =
                                  base::Location::Current()) {
     auto result = types::ParseDecimalFloatingPoint(
-        SourceString::CreateForTesting(1, 1, input));
+        ResolvedSourceString::CreateForTesting(input));
     ASSERT_TRUE(result.has_error()) << from.ToString();
     auto error = std::move(result).error();
     EXPECT_EQ(error.code(), ParseStatusCode::kFailedToParseDecimalFloatingPoint)
         << from.ToString();
   };
 
-  const auto ok_test = [](base::StringPiece input,
-                          types::DecimalFloatingPoint expected,
-                          const base::Location& from =
-                              base::Location::Current()) {
-    auto result = types::ParseDecimalFloatingPoint(
-        SourceString::CreateForTesting(1, 1, input));
-    ASSERT_TRUE(result.has_value()) << from.ToString();
-    auto value = std::move(result).value();
-    EXPECT_DOUBLE_EQ(value, expected) << from.ToString();
-  };
+  const auto ok_test =
+      [](base::StringPiece input, types::DecimalFloatingPoint expected,
+         const base::Location& from = base::Location::Current()) {
+        auto result = types::ParseDecimalFloatingPoint(
+            ResolvedSourceString::CreateForTesting(input));
+        ASSERT_TRUE(result.has_value()) << from.ToString();
+        auto value = std::move(result).value();
+        EXPECT_DOUBLE_EQ(value, expected) << from.ToString();
+      };
 
   // Empty string is not allowed
   error_test("");
@@ -128,7 +126,7 @@
                              const base::Location& from =
                                  base::Location::Current()) {
     auto result = types::ParseSignedDecimalFloatingPoint(
-        SourceString::CreateForTesting(1, 1, input));
+        ResolvedSourceString::CreateForTesting(input));
     ASSERT_TRUE(result.has_error()) << from.ToString();
     auto error = std::move(result).error();
     EXPECT_EQ(error.code(),
@@ -136,16 +134,15 @@
         << from.ToString();
   };
 
-  const auto ok_test = [](base::StringPiece input,
-                          types::SignedDecimalFloatingPoint expected,
-                          const base::Location& from =
-                              base::Location::Current()) {
-    auto result = types::ParseSignedDecimalFloatingPoint(
-        SourceString::CreateForTesting(1, 1, input));
-    ASSERT_TRUE(result.has_value()) << from.ToString();
-    auto value = std::move(result).value();
-    EXPECT_DOUBLE_EQ(value, expected) << from.ToString();
-  };
+  const auto ok_test =
+      [](base::StringPiece input, types::SignedDecimalFloatingPoint expected,
+         const base::Location& from = base::Location::Current()) {
+        auto result = types::ParseSignedDecimalFloatingPoint(
+            ResolvedSourceString::CreateForTesting(input));
+        ASSERT_TRUE(result.has_value()) << from.ToString();
+        auto value = std::move(result).value();
+        EXPECT_DOUBLE_EQ(value, expected) << from.ToString();
+      };
 
   // Empty string is not allowed
   error_test("");
@@ -520,7 +517,8 @@
         VariableDictionary::SubstitutionBuffer sub_buffer;
         auto out = types::ParseQuotedString(in_str, dict, sub_buffer);
         ASSERT_TRUE(out.has_value()) << from.ToString();
-        EXPECT_EQ(std::move(out).value(), expected_out) << from.ToString();
+        EXPECT_EQ(std::move(out).value().Str(), expected_out)
+            << from.ToString();
       };
 
   const auto error_test = [&dict](base::StringPiece in,
@@ -568,7 +566,7 @@
                              const base::Location& from =
                                  base::Location::Current()) {
     auto result = types::DecimalResolution::Parse(
-        SourceString::CreateForTesting(1, 1, input));
+        ResolvedSourceString::CreateForTesting(input));
     ASSERT_TRUE(result.has_error()) << from.ToString();
     auto error = std::move(result).error();
     EXPECT_EQ(error.code(), ParseStatusCode::kFailedToParseDecimalResolution)
@@ -579,7 +577,7 @@
       [](base::StringPiece input, types::DecimalResolution expected,
          const base::Location& from = base::Location::Current()) {
         auto result = types::DecimalResolution::Parse(
-            SourceString::CreateForTesting(1, 1, input));
+            ResolvedSourceString::CreateForTesting(input));
         ASSERT_TRUE(result.has_value()) << from.ToString();
         auto value = std::move(result).value();
         EXPECT_EQ(value.width, expected.width) << from.ToString();
@@ -643,7 +641,7 @@
                              const base::Location& from =
                                  base::Location::Current()) {
     auto result = types::ByteRangeExpression::Parse(
-        SourceString::CreateForTesting(input));
+        ResolvedSourceString::CreateForTesting(input));
     ASSERT_TRUE(result.has_error());
     auto error = std::move(result).error();
     EXPECT_EQ(error.code(), ParseStatusCode::kFailedToParseByteRange)
@@ -653,7 +651,7 @@
       [](base::StringPiece input, types::ByteRangeExpression expected,
          const base::Location& from = base::Location::Current()) {
         auto result = types::ByteRangeExpression::Parse(
-            SourceString::CreateForTesting(input));
+            ResolvedSourceString::CreateForTesting(input));
         ASSERT_TRUE(result.has_value());
         auto value = std::move(result).value();
         EXPECT_EQ(value.length, expected.length);
diff --git a/media/formats/hls/variable_dictionary.cc b/media/formats/hls/variable_dictionary.cc
index ffd513c..16ccaee 100644
--- a/media/formats/hls/variable_dictionary.cc
+++ b/media/formats/hls/variable_dictionary.cc
@@ -60,6 +60,10 @@
 
 }  // namespace
 
+VariableDictionary::SubstitutionBuffer::SubstitutionBuffer() = default;
+
+VariableDictionary::SubstitutionBuffer::~SubstitutionBuffer() = default;
+
 VariableDictionary::VariableDictionary() = default;
 
 VariableDictionary::~VariableDictionary() = default;
@@ -76,30 +80,48 @@
     return absl::nullopt;
   }
 
-  return iter->second;
+  return *iter->second;
 }
 
 bool VariableDictionary::Insert(types::VariableName name, std::string value) {
-  return entries_.try_emplace(std::move(name).GetName(), std::move(value))
+  return entries_
+      .try_emplace(std::move(name).GetName(),
+                   std::make_unique<std::string>(std::move(value)))
       .second;
 }
 
-ParseStatus::Or<base::StringPiece> VariableDictionary::Resolve(
+ParseStatus::Or<ResolvedSourceString> VariableDictionary::Resolve(
     SourceString input,
     SubstitutionBuffer& buffer) const {
   // Get the first variable reference. If this fails, there were no references
   // and we don't need to allocate anything.
   auto next_var = GetNextVariable(input);
   if (!next_var.tail) {
-    return next_var.head.Str();
+    return ResolvedSourceString::Create(
+        {}, input.Line(), input.Column(), input.Str(),
+        ResolvedSourceStringState{.contains_substitutions = false});
   }
 
-  buffer.buf_.clear();
+  // If there was a variable reference, but it consisted of the entire input
+  // string, then simply return a reference to the substitution string.
+  if (next_var.head.Empty() && next_var.tail->second.Empty()) {
+    auto value = Find(next_var.tail->first);
+    if (!value) {
+      return ParseStatus(ParseStatusCode::kVariableUndefined)
+          .WithData("key", next_var.tail->first.GetName());
+    }
+
+    return ResolvedSourceString::Create(
+        {}, input.Line(), input.Column(), *value,
+        ResolvedSourceStringState{.contains_substitutions = true});
+  }
+
+  auto& string_buf = buffer.strings_.emplace_back();
 
   while (true) {
     // Append the substring leading to the variable, and abort if there was no
     // variable reference
-    buffer.buf_.append(next_var.head.Str().data(), next_var.head.Str().size());
+    string_buf.append(next_var.head.Str().data(), next_var.head.Str().size());
     if (!next_var.tail) {
       break;
     }
@@ -107,16 +129,17 @@
     // Look up the variable value
     auto value = Find(next_var.tail->first);
     if (!value) {
-      // TODO(crbug.com/1311111): Create a more structured way of serializing
       return ParseStatus(ParseStatusCode::kVariableUndefined)
           .WithData("key", next_var.tail->first.GetName());
     }
-    buffer.buf_.append(value->data(), value->size());
+    string_buf.append(value->data(), value->size());
 
     next_var = GetNextVariable(next_var.tail->second);
   }
 
-  return base::StringPiece{buffer.buf_};
+  return ResolvedSourceString::Create(
+      {}, input.Line(), input.Column(), string_buf,
+      ResolvedSourceStringState{.contains_substitutions = true});
 }
 
 }  // namespace media::hls
diff --git a/media/formats/hls/variable_dictionary.h b/media/formats/hls/variable_dictionary.h
index e21e880..e5400de 100644
--- a/media/formats/hls/variable_dictionary.h
+++ b/media/formats/hls/variable_dictionary.h
@@ -5,34 +5,36 @@
 #ifndef MEDIA_FORMATS_HLS_VARIABLE_DICTIONARY_H_
 #define MEDIA_FORMATS_HLS_VARIABLE_DICTIONARY_H_
 
+#include <list>
 #include <string>
 
 #include "base/strings/string_piece.h"
+#include "base/types/pass_key.h"
 #include "media/base/media_export.h"
 #include "media/formats/hls/parse_status.h"
+#include "media/formats/hls/source_string.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace media::hls {
 
-struct SourceString;
-
 namespace types {
 class VariableName;
 }
 
 class MEDIA_EXPORT VariableDictionary {
  public:
-  class SubstitutionBuffer {
+  class MEDIA_EXPORT SubstitutionBuffer {
    public:
     friend VariableDictionary;
-    SubstitutionBuffer() = default;
+    SubstitutionBuffer();
+    ~SubstitutionBuffer();
     SubstitutionBuffer(const SubstitutionBuffer&) = delete;
     SubstitutionBuffer(const SubstitutionBuffer&&) = delete;
     SubstitutionBuffer& operator=(const SubstitutionBuffer&) = delete;
     SubstitutionBuffer& operator=(SubstitutionBuffer&&) = delete;
 
    private:
-    std::string buf_;
+    std::list<std::string> strings_;
   };
 
   VariableDictionary();
@@ -53,12 +55,13 @@
   bool Insert(types::VariableName name, std::string value);
 
   // Attempts to resolve all variable references within the given input string
-  // using this dictionary, returning a `base::StringPiece` with the fully
+  // using this dictionary, returning a `ResolvedSourceString` with the fully
   // resolved string, or an error if one occurred. `buffer` will be used to
   // build the resulting string if any substitutions occur, and the caller must
-  // ensure that it outlives the `base::StringPiece` returned by this function.
-  // As an optimization, the buffer will not be used if no substitutions are
-  // necessary.
+  // ensure that it outlives the `ResolvedSourceString` returned by this
+  // function. As an optimization, the buffer will not be used if no
+  // substitutions are necessary, or if the substitution consisted of the entire
+  // input string.
   //
   // This implementation is based on a somewhat pedantic interpretation of the
   // spec:
@@ -69,11 +72,12 @@
   // If a given sequence doesn't exactly match that format then it's ignored,
   // rather than treated as an error. However, if it does match that format and
   // the variable name is undefined, it's treated as an error.
-  ParseStatus::Or<base::StringPiece> Resolve(SourceString input,
-                                             SubstitutionBuffer& buffer) const;
+  ParseStatus::Or<ResolvedSourceString> Resolve(
+      SourceString input,
+      SubstitutionBuffer& buffer) const;
 
  private:
-  base::flat_map<std::string, std::string> entries_;
+  base::flat_map<std::string, std::unique_ptr<std::string>> entries_;
 };
 
 }  // namespace media::hls
diff --git a/media/formats/hls/variable_dictionary_unittest.cc b/media/formats/hls/variable_dictionary_unittest.cc
index c64ced4a..745cc7f2 100644
--- a/media/formats/hls/variable_dictionary_unittest.cc
+++ b/media/formats/hls/variable_dictionary_unittest.cc
@@ -33,12 +33,17 @@
 void OkTest(const VariableDictionary& dict,
             base::StringPiece in,
             base::StringPiece expected_out,
+            bool substitutions_expected,
             const base::Location& from = base::Location::Current()) {
   const auto source_str = SourceString::CreateForTesting(in);
+  EXPECT_FALSE(source_str.ContainsSubstitutions()) << from.ToString();
   VariableDictionary::SubstitutionBuffer buffer;
   auto result = dict.Resolve(source_str, buffer);
   ASSERT_TRUE(result.has_value()) << from.ToString();
-  EXPECT_EQ(std::move(result).value(), expected_out) << from.ToString();
+  auto result_str = std::move(result).value();
+  EXPECT_EQ(result_str.Str(), expected_out) << from.ToString();
+  EXPECT_EQ(result_str.ContainsSubstitutions(), substitutions_expected)
+      << from.ToString();
 }
 
 void ErrorTest(const VariableDictionary& dict,
@@ -52,14 +57,20 @@
   EXPECT_EQ(std::move(result).error(), expected_error) << from.ToString();
 }
 
+// Helper for cases where no substitutions should occur
+void NopTest(const VariableDictionary& dict,
+             base::StringPiece in,
+             const base::Location& from = base::Location::Current()) {
+  OkTest(dict, in, in, false, from);
+}
+
 }  // namespace
 
 TEST(HlsVariableDictionaryTest, BasicSubstitution) {
   VariableDictionary dict = CreateBasicDictionary();
   OkTest(dict, "The NAME's {$NAME}, {$_0THER-1dent} {$NAME}. Agent {$IDENT}",
-         "The NAME's bond, {$james} bond. Agent 007");
-  OkTest(dict, "This $tring {has} ${no} v{}{}ar}}s",
-         "This $tring {has} ${no} v{}{}ar}}s");
+         "The NAME's bond, {$james} bond. Agent 007", true);
+  NopTest(dict, "This $tring {has} ${no} v{}{}ar}}s");
 }
 
 TEST(HlsVariableDictionaryTest, VariableUndefined) {
@@ -72,7 +83,7 @@
   EXPECT_EQ(dict.Find(CreateVarName("test")), absl::nullopt);
 
   ErrorTest(dict, "Hello {$test}", ParseStatusCode::kVariableUndefined);
-  OkTest(dict, "Hello {$TEST}", "Hello FOO");
+  OkTest(dict, "Hello {$TEST}", "Hello FOO", true);
   ErrorTest(dict, "Hello {$TEST} {$TEST1}",
             ParseStatusCode::kVariableUndefined);
 }
@@ -109,25 +120,25 @@
   auto dict = CreateBasicDictionary();
 
   // Variable refs with invalid variable names are ignored
-  OkTest(dict, "http://{$}.com", "http://{$}.com");
-  OkTest(dict, "http://{$ NAME}.com", "http://{$ NAME}.com");
-  OkTest(dict, "http://{$NAME }.com", "http://{$NAME }.com");
-  OkTest(dict, "http://{$:NAME}.com", "http://{$:NAME}.com");
+  NopTest(dict, "http://{$}.com");
+  NopTest(dict, "http://{$ NAME}.com");
+  NopTest(dict, "http://{$NAME }.com");
+  NopTest(dict, "http://{$:NAME}.com");
 
   // Incomplete variable ref sequences are ignored
-  OkTest(dict, "http://{$NAME", "http://{$NAME");
-  OkTest(dict, "http://{NAME}.com", "http://{NAME}.com");
-  OkTest(dict, "http://${NAME}.com", "http://${NAME}.com");
-  OkTest(dict, "http://$NAME.com", "http://$NAME.com");
+  NopTest(dict, "http://{$NAME");
+  NopTest(dict, "http://{NAME}.com");
+  NopTest(dict, "http://${NAME}.com");
+  NopTest(dict, "http://$NAME.com");
 
   // Valid ref sequences surrounded by invalid ref sequences should *not* be
   // ignored
   OkTest(dict, "http://{$}{$ NAME}{$NAME}}{$NAME }.com",
-         "http://{$}{$ NAME}bond}{$NAME }.com");
+         "http://{$}{$ NAME}bond}{$NAME }.com", true);
 
   // Valid ref sequences nested within invalid ref sequences should *not* be
   // ignored
-  OkTest(dict, "http://{$ {$NAME}}.com", "http://{$ bond}.com");
+  OkTest(dict, "http://{$ {$NAME}}.com", "http://{$ bond}.com", true);
 }
 
 TEST(HlsVariableDictionaryTest, ExplosiveVariableDefs) {
@@ -137,11 +148,12 @@
   EXPECT_TRUE(dict.Insert(CreateVarName("LOL2"), "{$LOL1}{$LOL1}{$LOL1}"));
   EXPECT_TRUE(dict.Insert(CreateVarName("LOL3"), "{$LOL2}{$LOL2}{$LOL2}"));
   OkTest(dict, "{$LOL3}{$LOL3}{$LOL3}",
-         "{$LOL2}{$LOL2}{$LOL2}{$LOL2}{$LOL2}{$LOL2}{$LOL2}{$LOL2}{$LOL2}");
+         "{$LOL2}{$LOL2}{$LOL2}{$LOL2}{$LOL2}{$LOL2}{$LOL2}{$LOL2}{$LOL2}",
+         true);
 
   // Variable substitution is by design not cyclical
   EXPECT_TRUE(dict.Insert(CreateVarName("CYCLE"), "{$CYCLE}"));
-  OkTest(dict, "{$CYCLE}", "{$CYCLE}");
+  OkTest(dict, "{$CYCLE}", "{$CYCLE}", true);
 }
 
 }  // namespace media::hls
diff --git a/pdf/pdf_view_web_plugin.cc b/pdf/pdf_view_web_plugin.cc
index 72f7103..aec54f4 100644
--- a/pdf/pdf_view_web_plugin.cc
+++ b/pdf/pdf_view_web_plugin.cc
@@ -639,6 +639,10 @@
   return engine_->CanRedo();
 }
 
+bool PdfViewWebPlugin::CanCopy() const {
+  return engine_->HasPermission(DocumentPermission::kCopy);
+}
+
 bool PdfViewWebPlugin::ExecuteEditCommand(const blink::WebString& name,
                                           const blink::WebString& value) {
   if (name == "SelectAll")
diff --git a/pdf/pdf_view_web_plugin.h b/pdf/pdf_view_web_plugin.h
index 8c1a697..c546a41 100644
--- a/pdf/pdf_view_web_plugin.h
+++ b/pdf/pdf_view_web_plugin.h
@@ -241,6 +241,7 @@
   bool HasEditableText() const override;
   bool CanUndo() const override;
   bool CanRedo() const override;
+  bool CanCopy() const override;
   bool ExecuteEditCommand(const blink::WebString& name,
                           const blink::WebString& value) override;
   blink::WebURL LinkAtPosition(const gfx::Point& /*position*/) const override;
diff --git a/pdf/pdf_view_web_plugin_unittest.cc b/pdf/pdf_view_web_plugin_unittest.cc
index 914ed316..d7f149b 100644
--- a/pdf/pdf_view_web_plugin_unittest.cc
+++ b/pdf/pdf_view_web_plugin_unittest.cc
@@ -835,6 +835,7 @@
   EXPECT_EQ(kContentRestrictionCopy | kContentRestrictionCut |
                 kContentRestrictionPaste | kContentRestrictionPrint,
             plugin_->GetContentRestrictions());
+  EXPECT_FALSE(plugin_->CanCopy());
 }
 
 TEST_F(PdfViewWebPluginTest, GetContentRestrictionsWithCopyAllowed) {
@@ -845,6 +846,7 @@
   EXPECT_EQ(kContentRestrictionCut | kContentRestrictionPaste |
                 kContentRestrictionPrint,
             plugin_->GetContentRestrictions());
+  EXPECT_TRUE(plugin_->CanCopy());
 }
 
 TEST_F(PdfViewWebPluginTest, GetContentRestrictionsWithPrintLowQualityAllowed) {
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index 96cd371e..e226635 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -2168,9 +2168,6 @@
 }
 
 std::string PDFiumEngine::GetSelectedText() {
-  if (!HasPermission(DocumentPermission::kCopy))
-    return std::string();
-
   std::u16string result;
   for (size_t i = 0; i < selection_.size(); ++i) {
     std::u16string current_selection_text = selection_[i].GetText();
diff --git a/pdf/pdfium/pdfium_engine_unittest.cc b/pdf/pdfium/pdfium_engine_unittest.cc
index 1185ccca..d6e93b4 100644
--- a/pdf/pdfium/pdfium_engine_unittest.cc
+++ b/pdf/pdfium/pdfium_engine_unittest.cc
@@ -678,23 +678,43 @@
   EXPECT_TRUE(engine->HandleInputEvent(raw_key_down_event));
 }
 
+namespace {
+#if BUILDFLAG(IS_WIN)
+constexpr char kSelectTextExpectedText[] =
+    "Hello, world!\r\nGoodbye, world!\r\nHello, world!\r\nGoodbye, world!";
+#else
+constexpr char kSelectTextExpectedText[] =
+    "Hello, world!\nGoodbye, world!\nHello, world!\nGoodbye, world!";
+#endif
+}  // namespace
+
 TEST_F(PDFiumEngineTest, SelectText) {
   NiceMock<MockTestClient> client;
   std::unique_ptr<PDFiumEngine> engine =
       InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf"));
   ASSERT_TRUE(engine);
 
+  EXPECT_TRUE(engine->HasPermission(DocumentPermission::kCopy));
+
   EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
 
   engine->SelectAll();
-#if BUILDFLAG(IS_WIN)
-  constexpr char kExpectedText[] =
-      "Hello, world!\r\nGoodbye, world!\r\nHello, world!\r\nGoodbye, world!";
-#else
-  constexpr char kExpectedText[] =
-      "Hello, world!\nGoodbye, world!\nHello, world!\nGoodbye, world!";
-#endif
-  EXPECT_EQ(kExpectedText, engine->GetSelectedText());
+  EXPECT_EQ(kSelectTextExpectedText, engine->GetSelectedText());
+}
+
+TEST_F(PDFiumEngineTest, SelectTextWithCopyRestriction) {
+  NiceMock<MockTestClient> client;
+  std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
+      &client, FILE_PATH_LITERAL("hello_world2_with_copy_restriction.pdf"));
+  ASSERT_TRUE(engine);
+
+  EXPECT_FALSE(engine->HasPermission(DocumentPermission::kCopy));
+
+  // The copy restriction should not affect the text selection hehavior.
+  EXPECT_THAT(engine->GetSelectedText(), IsEmpty());
+
+  engine->SelectAll();
+  EXPECT_EQ(kSelectTextExpectedText, engine->GetSelectedText());
 }
 
 TEST_F(PDFiumEngineTest, SelectCroppedText) {
diff --git a/pdf/test/data/hello_world2_with_copy_restriction.pdf b/pdf/test/data/hello_world2_with_copy_restriction.pdf
new file mode 100644
index 0000000..090447b
--- /dev/null
+++ b/pdf/test/data/hello_world2_with_copy_restriction.pdf
Binary files differ
diff --git a/services/network/first_party_sets/first_party_sets_context_config.h b/services/network/first_party_sets/first_party_sets_context_config.h
index 88c6bee..7adca4c 100644
--- a/services/network/first_party_sets/first_party_sets_context_config.h
+++ b/services/network/first_party_sets/first_party_sets_context_config.h
@@ -11,7 +11,6 @@
 // info in the given network context.
 class FirstPartySetsContextConfig {
  public:
-  FirstPartySetsContextConfig() = default;
   explicit FirstPartySetsContextConfig(bool enabled);
 
   bool is_enabled() const { return enabled_; }
diff --git a/services/network/first_party_sets/first_party_sets_manager_unittest.cc b/services/network/first_party_sets/first_party_sets_manager_unittest.cc
index 93a9295..9fde2d4 100644
--- a/services/network/first_party_sets/first_party_sets_manager_unittest.cc
+++ b/services/network/first_party_sets/first_party_sets_manager_unittest.cc
@@ -37,7 +37,8 @@
 
 class FirstPartySetsManagerTest : public ::testing::Test {
  public:
-  explicit FirstPartySetsManagerTest(bool enabled) : manager_(enabled) {}
+  explicit FirstPartySetsManagerTest(bool enabled, bool context_enabled)
+      : manager_(enabled), fps_context_config_(context_enabled) {}
 
   void SetCompleteSets(
       const base::flat_map<net::SchemefulSite, net::SchemefulSite>& content) {
@@ -99,9 +100,8 @@
 
 class FirstPartySetsManagerDisabledTest : public FirstPartySetsManagerTest {
  public:
-  FirstPartySetsManagerDisabledTest() : FirstPartySetsManagerTest(false) {
-    // FPS setting by the browser overrules FPS setting by the context.
-    SetFirstPartySetsContextConfig(true);
+  FirstPartySetsManagerDisabledTest()
+      : FirstPartySetsManagerTest(/*enabled=*/false, /*context_enabled=*/true) {
   }
 };
 
@@ -176,9 +176,8 @@
 
 class FirstPartySetsEnabledTest : public FirstPartySetsManagerTest {
  public:
-  FirstPartySetsEnabledTest() : FirstPartySetsManagerTest(true) {
-    SetFirstPartySetsContextConfig(true);
-  }
+  FirstPartySetsEnabledTest()
+      : FirstPartySetsManagerTest(/*enabled=*/true, /*context_enabled=*/true) {}
 };
 
 TEST_F(FirstPartySetsEnabledTest, Sets_IsEmpty) {
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 6d3d15d..a7573a7 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5688,21 +5688,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5145.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -5715,7 +5715,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "isolate_profile_data": true,
@@ -5853,21 +5853,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5145.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -5879,7 +5879,7 @@
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "args": [
@@ -5999,21 +5999,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5145.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -6025,7 +6025,7 @@
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "isolate_profile_data": true,
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 2f0dae19..bcb5322 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -92911,21 +92911,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5145.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -92933,7 +92933,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "isolate_profile_data": true,
@@ -93046,28 +93046,28 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5145.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "args": [
@@ -93167,28 +93167,28 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5145.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "isolate_profile_data": true,
@@ -94526,20 +94526,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5145.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -94553,7 +94553,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "merge": {
@@ -94691,20 +94691,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5145.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -94717,7 +94717,7 @@
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "args": [
@@ -94837,20 +94837,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5145.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -94863,7 +94863,7 @@
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "merge": {
@@ -96359,20 +96359,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5145.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -96386,7 +96386,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "merge": {
@@ -96524,20 +96524,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5145.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -96550,7 +96550,7 @@
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "args": [
@@ -96670,20 +96670,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5145.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -96696,7 +96696,7 @@
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "merge": {
@@ -97431,20 +97431,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5145.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -97457,7 +97457,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       }
     ]
   },
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 091f30e..e42187f 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -19085,21 +19085,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5145.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -19112,7 +19112,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "isolate_profile_data": true,
@@ -19250,21 +19250,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5145.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -19276,7 +19276,7 @@
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "args": [
@@ -19396,21 +19396,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5145.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5146.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5145.0",
-              "revision": "version:105.0.5145.0"
+              "location": "lacros_version_skew_tests_v105.0.5146.0",
+              "revision": "version:105.0.5146.0"
             }
           ],
           "dimension_sets": [
@@ -19422,7 +19422,7 @@
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 105.0.5145.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5146.0"
       },
       {
         "isolate_profile_data": true,
diff --git a/testing/buildbot/internal.chrome.fyi.json b/testing/buildbot/internal.chrome.fyi.json
index ac37a34..7c0acf12 100644
--- a/testing/buildbot/internal.chrome.fyi.json
+++ b/testing/buildbot/internal.chrome.fyi.json
@@ -1,18 +1,23 @@
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
   "AAAAA2 See generate_buildbot_json.py to make changes": {},
-  "win-chrome-finch-fyi": {
-    "additional_compile_targets": [
-      "chrome"
-    ],
+  "linux-finch-smoke-chrome": {
     "isolated_scripts": [
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_name": "variations_smoke_tests",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "variations_smoke_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "result_format": "single"
@@ -21,8 +26,79 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "os": "Windows-10-15063",
-              "pool": "chrome.tests"
+              "os": "Ubuntu-18.04",
+              "pool": "chrome.tests.finch"
+            }
+          ],
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:variations_smoke_tests/"
+      }
+    ]
+  },
+  "mac-finch-smoke-chrome": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "variations_smoke_tests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "variations_smoke_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "result_format": "single"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "os": "Mac-11|Mac-12",
+              "pool": "chrome.tests.finch"
+            }
+          ],
+          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:variations_smoke_tests/"
+      }
+    ]
+  },
+  "win-finch-smoke-chrome": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "variations_smoke_tests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "variations_smoke_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "result_format": "single"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Windows-10-19042",
+              "pool": "chrome.tests.finch"
             }
           ],
           "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index 2ce7376..300b2747 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -224,6 +224,13 @@
       },
     },
   },
+  'chrome-finch-swarming-pool': {
+    'swarming': {
+      'dimensions': {
+        'pool': 'chrome.tests.finch',
+      },
+    },
+  },
   'chrome-swarming-pool': {
     'swarming': {
       'dimensions': {
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index dd53e64..3face2c 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -495,6 +495,19 @@
       },
     },
 
+    'chrome_finch_smoke_tests': {
+      'variations_smoke_tests': {
+        'isolate_name': 'variations_smoke_tests',
+        'mixins': [
+          'skia_gold_test',
+        ],
+        'resultdb': {
+          'enable': True,
+          'result_format': 'single'
+        },
+      },
+    },
+
     'chrome_isolated_script_tests': {
       'chrome_sizes': {
         'merge': {
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 80d294d..6396d317c 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -22,15 +22,15 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5145.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5146.0/test_ash_chrome',
     ],
-    'identifier': 'Lacros version skew testing ash 105.0.5145.0',
+    'identifier': 'Lacros version skew testing ash 105.0.5146.0',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v105.0.5145.0',
-          'revision': 'version:105.0.5145.0',
+          'location': 'lacros_version_skew_tests_v105.0.5146.0',
+          'revision': 'version:105.0.5146.0',
         },
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 54d5a11..fec90d9 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -6401,6 +6401,41 @@
   {
     'project': 'chrome',
     'bucket': 'ci',
+    'name': 'internal.chrome.fyi',
+    'mixins': ['chrome-tester-service-account'],
+    'machines': {
+      'linux-finch-smoke-chrome': {
+        'mixins': [
+          'chrome-finch-swarming-pool',
+          'linux-bionic',
+        ],
+        'test_suites': {
+          'isolated_scripts': 'chrome_finch_smoke_tests',
+        },
+      },
+      'mac-finch-smoke-chrome': {
+        'mixins': [
+          'chrome-finch-swarming-pool',
+          'mac_11_or_12_arm64',
+        ],
+        'test_suites': {
+          'isolated_scripts': 'chrome_finch_smoke_tests',
+        },
+      },
+      'win-finch-smoke-chrome': {
+        'mixins': [
+          'chrome-finch-swarming-pool',
+          'win10',
+        ],
+        'test_suites': {
+          'isolated_scripts': 'chrome_finch_smoke_tests',
+        },
+      },
+    },
+  },
+  {
+    'project': 'chrome',
+    'bucket': 'ci',
     'name': 'internal.chromeos.fyi',
     'mixins': ['chrome-tester-service-account'],
     'machines': {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 1e26506..703da39 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1096,6 +1096,28 @@
             ]
         }
     ],
+    "AutofillIgnoreEarlyClicksOnPopup": [
+        {
+            "platforms": [
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_500ms",
+                    "params": {
+                        "duration": "500ms"
+                    },
+                    "enable_features": [
+                        "AutofillIgnoreEarlyClicksOnPopup"
+                    ]
+                }
+            ]
+        }
+    ],
     "AutofillKeyboardAccessory": [
         {
             "platforms": [
@@ -1213,6 +1235,27 @@
             ]
         }
     ],
+    "AutofillProfileImportFromUnfocusableFields": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "ios",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AutofillProfileImportFromUnfocusableFields"
+                    ]
+                }
+            ]
+        }
+    ],
     "AutofillRationalizeStreetAddressAndAddressLine": [
         {
             "platforms": [
diff --git a/third_party/blink/public/web/web_plugin.h b/third_party/blink/public/web/web_plugin.h
index 9d8d55d..9773e8d 100644
--- a/third_party/blink/public/web/web_plugin.h
+++ b/third_party/blink/public/web/web_plugin.h
@@ -167,6 +167,7 @@
 
   virtual bool CanUndo() const { return false; }
   virtual bool CanRedo() const { return false; }
+  virtual bool CanCopy() const { return true; }
 
   virtual bool ExecuteEditCommand(const WebString& name,
                                   const WebString& value) {
diff --git a/third_party/blink/renderer/bindings/generated_in_core.gni b/third_party/blink/renderer/bindings/generated_in_core.gni
index 8dc00a6..8c0a232 100644
--- a/third_party/blink/renderer/bindings/generated_in_core.gni
+++ b/third_party/blink/renderer/bindings/generated_in_core.gni
@@ -335,8 +335,6 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_state_init.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_state_init.h",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_timeline_element_based_offset.cc",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_timeline_element_based_offset.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_timeline_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_timeline_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_to_options.cc",
@@ -416,8 +414,6 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_directive_type.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_document_ready_state.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_document_ready_state.h",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_edge.cc",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_edge.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_edit_context_enter_key_hint.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_edit_context_enter_key_hint.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_edit_context_input_mode.cc",
@@ -1594,8 +1590,6 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_bytestringbytestringrecord_bytestringsequencesequence.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_compositeoperationorauto_compositeoperationorautosequence.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_compositeoperationorauto_compositeoperationorautosequence.h",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_csskeywordvalue_cssnumericvalue_scrolltimelineelementbasedoffset_string.cc",
-  "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_csskeywordvalue_cssnumericvalue_scrolltimelineelementbasedoffset_string.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_csscolorvalue_cssstylevalue.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_csscolorvalue_cssstylevalue.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_union_csskeywordvalue_cssnumericvalue_string.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_core.gni b/third_party/blink/renderer/bindings/idl_in_core.gni
index b0172da..387be90 100644
--- a/third_party/blink/renderer/bindings/idl_in_core.gni
+++ b/third_party/blink/renderer/bindings/idl_in_core.gni
@@ -28,7 +28,6 @@
           "//third_party/blink/renderer/core/animation/optional_effect_timing.idl",
           "//third_party/blink/renderer/core/animation/scroll_direction.idl",
           "//third_party/blink/renderer/core/animation/scroll_timeline.idl",
-          "//third_party/blink/renderer/core/animation/scroll_timeline_element_based_offset.idl",
           "//third_party/blink/renderer/core/animation/scroll_timeline_options.idl",
           "//third_party/blink/renderer/core/animation/view_timeline.idl",
           "//third_party/blink/renderer/core/animation/view_timeline_options.idl",
diff --git a/third_party/blink/renderer/core/animation/BUILD.gn b/third_party/blink/renderer/core/animation/BUILD.gn
index 02fbe1789..3ebc157e 100644
--- a/third_party/blink/renderer/core/animation/BUILD.gn
+++ b/third_party/blink/renderer/core/animation/BUILD.gn
@@ -230,8 +230,6 @@
     "sampled_effect.h",
     "scroll_timeline.cc",
     "scroll_timeline.h",
-    "scroll_timeline_offset.cc",
-    "scroll_timeline_offset.h",
     "scroll_timeline_util.cc",
     "scroll_timeline_util.h",
     "side_index.h",
diff --git a/third_party/blink/renderer/core/animation/animation.cc b/third_party/blink/renderer/core/animation/animation.cc
index 67ca3b9..f0f97c5 100644
--- a/third_party/blink/renderer/core/animation/animation.cc
+++ b/third_party/blink/renderer/core/animation/animation.cc
@@ -457,7 +457,7 @@
 
   // Synchronously resolve pending pause task.
   if (pending_pause_) {
-    SetHoldTimeAndPhase(new_current_time, TimelinePhase::kActive);
+    hold_time_ = new_current_time;
     ApplyPendingPlaybackRate();
     start_time_ = absl::nullopt;
     pending_pause_ = false;
@@ -480,12 +480,11 @@
 void Animation::SetCurrentTimeInternal(AnimationTimeDelta new_current_time) {
   absl::optional<AnimationTimeDelta> previous_start_time = start_time_;
   absl::optional<AnimationTimeDelta> previous_hold_time = hold_time_;
-  absl::optional<TimelinePhase> previous_hold_phase = hold_phase_;
 
   // Update either the hold time or the start time.
   if (hold_time_ || !start_time_ || !timeline_ || !timeline_->IsActive() ||
       playback_rate_ == 0) {
-    SetHoldTimeAndPhase(new_current_time, TimelinePhase::kActive);
+    hold_time_ = new_current_time;
   } else {
     start_time_ = CalculateStartTime(new_current_time);
   }
@@ -499,23 +498,10 @@
   // Reset the previous current time.
   previous_current_time_ = absl::nullopt;
 
-  if (previous_start_time != start_time_ || previous_hold_time != hold_time_ ||
-      previous_hold_phase != hold_phase_)
+  if (previous_start_time != start_time_ || previous_hold_time != hold_time_)
     SetOutdated();
 }
 
-void Animation::SetHoldTimeAndPhase(
-    absl::optional<AnimationTimeDelta> new_hold_time,
-    TimelinePhase new_hold_phase) {
-  hold_time_ = new_hold_time;
-  hold_phase_ = new_hold_phase;
-}
-
-void Animation::ResetHoldTimeAndPhase() {
-  hold_time_ = absl::nullopt;
-  hold_phase_ = absl::nullopt;
-}
-
 V8CSSNumberish* Animation::startTime() const {
   if (start_time_) {
     return ConvertTimeToCSSNumberish(start_time_.value());
@@ -564,22 +550,10 @@
   return ConvertTimeToCSSNumberish(calculated_current_time);
 }
 
-bool Animation::ValidateHoldTimeAndPhase() const {
-  return hold_phase_ ||
-         ((!hold_phase_ || hold_phase_ == TimelinePhase::kInactive) &&
-          !hold_time_);
-}
-
 absl::optional<AnimationTimeDelta> Animation::CurrentTimeInternal() const {
-  DCHECK(ValidateHoldTimeAndPhase());
   return hold_time_ ? hold_time_ : CalculateCurrentTime();
 }
 
-TimelinePhase Animation::CurrentPhaseInternal() const {
-  DCHECK(ValidateHoldTimeAndPhase());
-  return hold_phase_ ? hold_phase_.value() : CalculateCurrentPhase();
-}
-
 absl::optional<AnimationTimeDelta> Animation::UnlimitedCurrentTime() const {
   return CalculateAnimationPlayState() == kPaused || !start_time_
              ? CurrentTimeInternal()
@@ -826,7 +800,7 @@
       start_time_ = ready_time;
     } else {
       start_time_ = ready_time - hold_time_.value() / playback_rate_;
-      ResetHoldTimeAndPhase();
+      hold_time_ = absl::nullopt;
     }
   } else if (start_time_ && pending_playback_rate_) {
     // B: If animation’s start time is resolved and animation has a pending
@@ -845,7 +819,7 @@
         (ready_time - start_time_.value()) * playback_rate_;
     ApplyPendingPlaybackRate();
     if (playback_rate_ == 0) {
-      SetHoldTimeAndPhase(current_time_to_match, CalculateCurrentPhase());
+      hold_time_ = current_time_to_match;
       start_time_ = ready_time;
     } else {
       start_time_ = ready_time - current_time_to_match / playback_rate_;
@@ -877,8 +851,7 @@
   //    let animation’s hold time be the result of evaluating
   //    (ready time - start time) × playback rate.
   if (start_time_ && !hold_time_) {
-    SetHoldTimeAndPhase((ready_time - start_time_.value()) * playback_rate_,
-                        CalculateCurrentPhase());
+    hold_time_ = (ready_time - start_time_.value()) * playback_rate_;
   }
 
   // 3. Apply any pending playback rate on animation.
@@ -983,8 +956,7 @@
           if (old_current_time) {
             reset_current_time_on_resume_ = true;
             start_time_ = absl::nullopt;
-            SetHoldTimeAndPhase(progress * EffectEnd(),
-                                TimelinePhase::kInactive);
+            hold_time_ = progress * EffectEnd();
           } else if (PendingInternal()) {
             start_time_ = boundary_time;
           }
@@ -1004,7 +976,7 @@
   //    animation is not “sticky” but is re-evaluated based on its updated
   //    current time.
   if (start_time_)
-    ResetHoldTimeAndPhase();
+    hold_time_ = absl::nullopt;
 
   // 5. Run the procedure to update an animation’s finished state for animation
   //    with the did seek flag set to false, and the synchronously notify flag
@@ -1048,12 +1020,6 @@
   return (timeline_time.value() - start_time_.value()) * playback_rate_;
 }
 
-TimelinePhase Animation::CalculateCurrentPhase() const {
-  if (!start_time_ || !timeline_)
-    return TimelinePhase::kInactive;
-  return timeline_->Phase();
-}
-
 // https://www.w3.org/TR/web-animations-1/#setting-the-start-time-of-an-animation
 void Animation::setStartTime(const V8CSSNumberish* start_time,
                              ExceptionState& exception_state) {
@@ -1080,13 +1046,12 @@
   // is only possible to set either the start time or the animation’s current
   // time.
   if (!timeline_time && new_start_time) {
-    ResetHoldTimeAndPhase();
+    hold_time_ = absl::nullopt;
   }
 
   // 3. Let previous current time be animation’s current time.
   absl::optional<AnimationTimeDelta> previous_current_time =
       CurrentTimeInternal();
-  TimelinePhase previous_current_phase = CurrentPhaseInternal();
 
   // 4. Apply any pending playback rate on animation.
   ApplyPendingPlaybackRate();
@@ -1114,10 +1079,10 @@
   //      current time is unresolved.
   if (start_time_) {
     if (playback_rate_ != 0) {
-      ResetHoldTimeAndPhase();
+      hold_time_ = absl::nullopt;
     }
   } else {
-    SetHoldTimeAndPhase(previous_current_time, previous_current_phase);
+    hold_time_ = previous_current_time;
   }
 
   // 7. If animation has a pending play task or a pending pause task, cancel
@@ -1389,7 +1354,7 @@
     if (has_finite_timeline) {
       start_time_ = seek_time;
     } else {
-      SetHoldTimeAndPhase(seek_time, TimelinePhase::kActive);
+      hold_time_ = seek_time;
     }
   }
 
@@ -1415,7 +1380,7 @@
   SetCompositorPending(false);
 
   // 11. Run the procedure to update an animation’s finished state for animation
-  //    with the did seek flag set to false (continuous) , and thesynchronously
+  //    with the did seek flag set to false (continuous), and synchronously
   //    notify flag set to false.
   UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync);
 
@@ -1519,10 +1484,10 @@
   if (seek_time) {
     if (has_finite_timeline) {
       start_time_ = seek_time;
-      ResetHoldTimeAndPhase();
+      hold_time_ = absl::nullopt;
       ApplyPendingPlaybackRate();
     } else {
-      SetHoldTimeAndPhase(seek_time, TimelinePhase::kActive);
+      hold_time_ = seek_time;
     }
   }
 
@@ -1631,7 +1596,7 @@
     start_time_ = CalculateStartTime(new_current_time);
 
   if (pending_pause_ && start_time_) {
-    ResetHoldTimeAndPhase();
+    hold_time_ = absl::nullopt;
     pending_pause_ = false;
     if (ready_promise_)
       ResolvePromiseMaybeAsync(ready_promise_.Get());
@@ -1671,7 +1636,6 @@
     // value.
     double playback_rate = EffectivePlaybackRate();
     absl::optional<AnimationTimeDelta> hold_time;
-    TimelinePhase hold_phase;
 
     if (playback_rate > 0 &&
         GreaterThanOrEqualWithinTimeTolerance(
@@ -1685,9 +1649,7 @@
           hold_time = EffectEnd();
         }
       }
-      hold_phase = did_seek ? TimelinePhase::kActive : CalculateCurrentPhase();
-
-      SetHoldTimeAndPhase(hold_time, hold_phase);
+      hold_time_ = hold_time;
     } else if (playback_rate < 0 &&
                unconstrained_current_time.value() <= AnimationTimeDelta()) {
       if (did_seek) {
@@ -1699,7 +1661,6 @@
           hold_time = AnimationTimeDelta();
         }
       }
-      hold_phase = did_seek ? TimelinePhase::kActive : CalculateCurrentPhase();
 
       // Hack for resolving precision issue at zero.
       if (hold_time.has_value() &&
@@ -1707,12 +1668,12 @@
         hold_time = AnimationTimeDelta();
       }
 
-      SetHoldTimeAndPhase(hold_time, hold_phase);
+      hold_time_ = hold_time;
     } else if (playback_rate != 0) {
       // Update start time and reset hold time.
       if (did_seek && hold_time_)
         start_time_ = CalculateStartTime(hold_time_.value());
-      ResetHoldTimeAndPhase();
+      hold_time_ = absl::nullopt;
     }
   }
 
@@ -2308,20 +2269,16 @@
 
   if (content_) {
     absl::optional<AnimationTimeDelta> inherited_time;
-    TimelinePhase inherited_phase = TimelinePhase::kInactive;
 
     if (!idle) {
       inherited_time = CurrentTimeInternal();
       // Special case for end-exclusivity when playing backwards.
       if (inherited_time == AnimationTimeDelta() && EffectivePlaybackRate() < 0)
         inherited_time = ANIMATION_TIME_DELTA_FROM_SECONDS(-1);
-
-      inherited_phase = CurrentPhaseInternal();
     }
 
-    content_->UpdateInheritedTime(inherited_time, inherited_phase,
-                                  AtScrollTimelineBoundary(), playback_rate_,
-                                  reason);
+    content_->UpdateInheritedTime(inherited_time, AtScrollTimelineBoundary(),
+                                  playback_rate_, reason);
 
     // After updating the animation time if the animation is no longer current
     // blink will no longer composite the element (see
@@ -2427,7 +2384,7 @@
     pending_pause_ = pending_play_ = false;
   }
 
-  ResetHoldTimeAndPhase();
+  hold_time_ = absl::nullopt;
   start_time_ = absl::nullopt;
 
   // Apply changes synchronously.
@@ -2548,7 +2505,7 @@
   is_paused_for_testing_ = true;
   pending_pause_ = false;
   pending_play_ = false;
-  SetHoldTimeAndPhase(pause_time, TimelinePhase::kActive);
+  hold_time_ = pause_time;
   start_time_ = absl::nullopt;
   UpdateCompositedPaintStatus();
 }
diff --git a/third_party/blink/renderer/core/animation/animation.h b/third_party/blink/renderer/core/animation/animation.h
index 78c0ce7f..e0519e4 100644
--- a/third_party/blink/renderer/core/animation/animation.h
+++ b/third_party/blink/renderer/core/animation/animation.h
@@ -320,7 +320,6 @@
   DispatchEventResult DispatchEventInternal(Event&) override;
   void AddedEventListener(const AtomicString& event_type,
                           RegisteredEventListener&) override;
-  TimelinePhase CurrentPhaseInternal() const;
   virtual AnimationEffect::EventDelegate* CreateEventDelegate(
       Element* target,
       const AnimationEffect::EventDelegate* old_event_delegate) {
@@ -328,11 +327,6 @@
   }
 
  private:
-  void SetHoldTimeAndPhase(absl::optional<AnimationTimeDelta> new_hold_time,
-                           TimelinePhase new_hold_phase);
-  void ResetHoldTimeAndPhase();
-  bool ValidateHoldTimeAndPhase() const;
-
   void ClearOutdated();
   void ForceServiceOnNextFrame();
 
@@ -348,7 +342,6 @@
   absl::optional<AnimationTimeDelta> CalculateStartTime(
       AnimationTimeDelta current_time) const;
   absl::optional<AnimationTimeDelta> CalculateCurrentTime() const;
-  TimelinePhase CalculateCurrentPhase() const;
 
   V8CSSNumberish* ConvertTimeToCSSNumberish(
       absl::optional<AnimationTimeDelta>) const;
@@ -432,7 +425,6 @@
   absl::optional<double> pending_playback_rate_;
   absl::optional<AnimationTimeDelta> start_time_;
   absl::optional<AnimationTimeDelta> hold_time_;
-  absl::optional<TimelinePhase> hold_phase_;
   absl::optional<AnimationTimeDelta> previous_current_time_;
   bool reset_current_time_on_resume_ = false;
 
diff --git a/third_party/blink/renderer/core/animation/animation_effect.cc b/third_party/blink/renderer/core/animation/animation_effect.cc
index 02328ae..3dd2d17a 100644
--- a/third_party/blink/renderer/core/animation/animation_effect.cc
+++ b/third_party/blink/renderer/core/animation/animation_effect.cc
@@ -255,31 +255,8 @@
   InvalidateAndNotifyOwner();
 }
 
-absl::optional<Timing::Phase> TimelinePhaseToTimingPhase(
-    absl::optional<TimelinePhase> phase) {
-  absl::optional<Timing::Phase> result;
-  if (phase) {
-    switch (phase.value()) {
-      case TimelinePhase::kBefore:
-        result = Timing::Phase::kPhaseBefore;
-        break;
-      case TimelinePhase::kActive:
-        result = Timing::Phase::kPhaseActive;
-        break;
-      case TimelinePhase::kAfter:
-        result = Timing::Phase::kPhaseAfter;
-        break;
-      case TimelinePhase::kInactive:
-        // Timing::Phase does not have an inactive phase.
-        break;
-    }
-  }
-  return result;
-}
-
 void AnimationEffect::UpdateInheritedTime(
     absl::optional<AnimationTimeDelta> inherited_time,
-    absl::optional<TimelinePhase> inherited_timeline_phase,
     bool at_progress_timeline_boundary,
     double inherited_playback_rate,
     TimingUpdateReason reason) const {
@@ -287,21 +264,15 @@
       (inherited_playback_rate < 0) ? Timing::AnimationDirection::kBackwards
                                     : Timing::AnimationDirection::kForwards;
 
-  absl::optional<Timing::Phase> timeline_phase =
-      TimelinePhaseToTimingPhase(inherited_timeline_phase);
-
   bool needs_update = needs_update_ || last_update_time_ != inherited_time ||
-                      (owner_ && owner_->EffectSuppressed()) ||
-                      last_update_phase_ != timeline_phase;
+                      (owner_ && owner_->EffectSuppressed());
   needs_update_ = false;
   last_update_time_ = inherited_time;
-  last_update_phase_ = timeline_phase;
 
   if (needs_update) {
     Timing::CalculatedTiming calculated = SpecifiedTiming().CalculateTimings(
-        inherited_time, timeline_phase, at_progress_timeline_boundary,
-        NormalizedTiming(), direction, IsA<KeyframeEffect>(this),
-        inherited_playback_rate);
+        inherited_time, at_progress_timeline_boundary, NormalizedTiming(),
+        direction, IsA<KeyframeEffect>(this), inherited_playback_rate);
 
     const bool was_canceled = calculated.phase != calculated_.phase &&
                               calculated.phase == Timing::kPhaseNone;
diff --git a/third_party/blink/renderer/core/animation/animation_effect.h b/third_party/blink/renderer/core/animation/animation_effect.h
index eb1ff83b..549989e4 100644
--- a/third_party/blink/renderer/core/animation/animation_effect.h
+++ b/third_party/blink/renderer/core/animation/animation_effect.h
@@ -151,7 +151,6 @@
   // it will (if necessary) recalculate timings and (if necessary) call
   // UpdateChildrenAndEffects.
   void UpdateInheritedTime(absl::optional<AnimationTimeDelta> inherited_time,
-                           absl::optional<TimelinePhase> inherited_phase,
                            bool at_progress_timeline_boundary,
                            double inherited_playback_rate,
                            TimingUpdateReason) const;
@@ -190,7 +189,6 @@
   mutable absl::optional<Timing::NormalizedTiming> normalized_;
   mutable bool needs_update_;
   mutable absl::optional<AnimationTimeDelta> last_update_time_;
-  mutable absl::optional<Timing::Phase> last_update_phase_;
   AnimationTimeDelta cancel_time_;
   const Timing::CalculatedTiming& EnsureCalculated() const;
   void EnsureNormalizedTiming() const;
diff --git a/third_party/blink/renderer/core/animation/animation_effect_test.cc b/third_party/blink/renderer/core/animation/animation_effect_test.cc
index ff4890801..ccbe306e 100644
--- a/third_party/blink/renderer/core/animation/animation_effect_test.cc
+++ b/third_party/blink/renderer/core/animation/animation_effect_test.cc
@@ -87,7 +87,6 @@
     event_delegate_->Reset();
     AnimationEffect::UpdateInheritedTime(
         ANIMATION_TIME_DELTA_FROM_SECONDS(time),
-        /* inherited_phase */ absl::nullopt,
         /* at_progress_timeline_boundary */ false,
         /* inherited_playback_rate */ 1.0, reason);
   }
diff --git a/third_party/blink/renderer/core/animation/animation_test_helpers.cc b/third_party/blink/renderer/core/animation/animation_test_helpers.cc
index f6baaae7..123b725 100644
--- a/third_party/blink/renderer/core/animation/animation_test_helpers.cc
+++ b/third_party/blink/renderer/core/animation/animation_test_helpers.cc
@@ -5,7 +5,6 @@
 #include "third_party/blink/renderer/core/animation/animation_test_helpers.h"
 
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_union_csskeywordvalue_cssnumericvalue_scrolltimelineelementbasedoffset_string.h"
 #include "third_party/blink/renderer/core/animation/css_interpolation_environment.h"
 #include "third_party/blink/renderer/core/animation/css_interpolation_types_map.h"
 #include "third_party/blink/renderer/core/animation/invalidatable_interpolation.h"
@@ -100,20 +99,5 @@
   cascade.Apply();
 }
 
-V8ScrollTimelineOffset* OffsetFromString(Document& document,
-                                         const String& string) {
-  const CSSValue* value = css_test_helpers::ParseValue(
-      document, "<length-percentage> | auto", string);
-
-  if (const auto* primitive = DynamicTo<CSSPrimitiveValue>(value)) {
-    return MakeGarbageCollected<V8ScrollTimelineOffset>(
-        CSSNumericValue::FromCSSValue(*primitive));
-  } else if (DynamicTo<CSSIdentifierValue>(value)) {
-    return MakeGarbageCollected<V8ScrollTimelineOffset>(
-        CSSKeywordValue::Create("auto"));
-  }
-  return MakeGarbageCollected<V8ScrollTimelineOffset>(string);
-}
-
 }  // namespace animation_test_helpers
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/animation_test_helpers.h b/third_party/blink/renderer/core/animation/animation_test_helpers.h
index 3aee9cb..8a7ca73 100644
--- a/third_party/blink/renderer/core/animation/animation_test_helpers.h
+++ b/third_party/blink/renderer/core/animation/animation_test_helpers.h
@@ -7,7 +7,6 @@
 
 #include "third_party/blink/renderer/bindings/core/v8/v8_typedefs.h"
 #include "third_party/blink/renderer/core/animation/interpolation.h"
-#include "third_party/blink/renderer/core/animation/scroll_timeline_offset.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_view.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "v8/include/v8.h"
@@ -49,14 +48,6 @@
 // InvalidatableInterpolation.
 void EnsureInterpolatedValueCached(ActiveInterpolations*, Document&, Element*);
 
-// Returns one of the following:
-//
-// - A CSSNumericValue, if the incoming string can be parsed as a
-//   <length-percentage>.
-// - A CSSKeywordValue. if the incoming string can be parsed as 'auto'.
-// - Otherwise, the incoming string.
-V8ScrollTimelineOffset* OffsetFromString(Document&, const String&);
-
 }  // namespace animation_test_helpers
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/core/animation/animation_timeline.h b/third_party/blink/renderer/core/animation/animation_timeline.h
index 0153316c..2a7ab31 100644
--- a/third_party/blink/renderer/core/animation/animation_timeline.h
+++ b/third_party/blink/renderer/core/animation/animation_timeline.h
@@ -19,7 +19,7 @@
 
 class Document;
 
-enum class TimelinePhase { kInactive, kBefore, kActive, kAfter };
+enum class TimelinePhase { kInactive, kActive };
 
 class CORE_EXPORT AnimationTimeline : public ScriptWrappable {
   DEFINE_WRAPPERTYPEINFO();
diff --git a/third_party/blink/renderer/core/animation/build.gni b/third_party/blink/renderer/core/animation/build.gni
index 1ddc7c9..4695b3d 100644
--- a/third_party/blink/renderer/core/animation/build.gni
+++ b/third_party/blink/renderer/core/animation/build.gni
@@ -28,7 +28,6 @@
   "keyframe_effect_test.cc",
   "list_interpolation_functions_test.cc",
   "property_handle_test.cc",
-  "scroll_timeline_offset_test.cc",
   "scroll_timeline_test.cc",
   "scroll_timeline_util_test.cc",
   "svg_number_interpolation_type_test.cc",
diff --git a/third_party/blink/renderer/core/animation/css/css_animations.cc b/third_party/blink/renderer/core/animation/css/css_animations.cc
index 9ee81c2..89cecf52 100644
--- a/third_party/blink/renderer/core/animation/css/css_animations.cc
+++ b/third_party/blink/renderer/core/animation/css/css_animations.cc
@@ -729,12 +729,10 @@
             timeline != existing_animation->Timeline()) {
           DCHECK(!is_animation_style_change);
 
-          absl::optional<TimelinePhase> inherited_phase;
           absl::optional<AnimationTimeDelta> inherited_time;
           absl::optional<AnimationTimeDelta> timeline_duration;
 
           if (timeline) {
-            inherited_phase = absl::make_optional(timeline->Phase());
             inherited_time = animation->UnlimitedCurrentTime();
             timeline_duration = timeline->GetDuration();
 
@@ -780,8 +778,8 @@
                   CreateKeyframeEffectModel(
                       resolver, element, animating_element, &style,
                       parent_style, name, keyframe_timing_function.get(), i),
-                  timing, is_paused, inherited_time, inherited_phase,
-                  timeline_duration, animation->playbackRate()),
+                  timing, is_paused, inherited_time, timeline_duration,
+                  animation->playbackRate()),
               specified_timing, keyframes_rule, timeline,
               animation_data->PlayStateList());
           if (toggle_pause_state)
@@ -790,7 +788,6 @@
       } else {
         DCHECK(!is_animation_style_change);
         AnimationTimeline* timeline = ComputeTimeline(&element, timeline_name);
-        absl::optional<TimelinePhase> inherited_phase;
         absl::optional<AnimationTimeDelta> inherited_time =
             AnimationTimeDelta();
 
@@ -798,7 +795,6 @@
         if (timeline) {
           timeline_duration = timeline->GetDuration();
           if (!timeline->IsMonotonicallyIncreasing()) {
-            inherited_phase = absl::make_optional(timeline->Phase());
             inherited_time = timeline->CurrentTime();
           }
         }
@@ -808,8 +804,7 @@
                 CreateKeyframeEffectModel(resolver, element, animating_element,
                                           &style, parent_style, name,
                                           keyframe_timing_function.get(), i),
-                timing, is_paused, inherited_time, inherited_phase,
-                timeline_duration, 1.0),
+                timing, is_paused, inherited_time, timeline_duration, 1.0),
             specified_timing, keyframes_rule, timeline,
             animation_data->PlayStateList());
       }
@@ -1430,9 +1425,8 @@
   state.update.StartTransition(
       property, state.before_change_style, state.cloned_style,
       reversing_adjusted_start_value, reversing_shortening_factor,
-      *MakeGarbageCollected<InertEffect>(model, timing, false,
-                                         AnimationTimeDelta(), absl::nullopt,
-                                         absl::nullopt, 1.0));
+      *MakeGarbageCollected<InertEffect>(
+          model, timing, false, AnimationTimeDelta(), absl::nullopt, 1.0));
   DCHECK(!state.animating_element.GetElementAnimations() ||
          !state.animating_element.GetElementAnimations()
               ->IsAnimationStyleChange());
@@ -1624,7 +1618,7 @@
 
       auto* inert_animation_for_sampling = MakeGarbageCollected<InertEffect>(
           effect->Model(), effect->SpecifiedTiming(), false, current_time,
-          absl::nullopt, absl::nullopt, animation->playbackRate());
+          /* timeline_duration */ absl::nullopt, animation->playbackRate());
 
       HeapVector<Member<Interpolation>> sample;
       inert_animation_for_sampling->Sample(sample);
diff --git a/third_party/blink/renderer/core/animation/css/css_scroll_timeline.cc b/third_party/blink/renderer/core/animation/css/css_scroll_timeline.cc
index 07cc9d3a..364d135 100644
--- a/third_party/blink/renderer/core/animation/css/css_scroll_timeline.cc
+++ b/third_party/blink/renderer/core/animation/css/css_scroll_timeline.cc
@@ -5,7 +5,7 @@
 #include "third_party/blink/renderer/core/animation/css/css_scroll_timeline.h"
 
 #include "third_party/blink/renderer/core/animation/document_animations.h"
-#include "third_party/blink/renderer/core/css/css_element_offset_value.h"
+#include "third_party/blink/renderer/core/css/css_function_value.h"
 #include "third_party/blink/renderer/core/css/css_id_selector_value.h"
 #include "third_party/blink/renderer/core/css/css_identifier_value.h"
 #include "third_party/blink/renderer/core/css/css_value_list.h"
@@ -32,14 +32,6 @@
   return IsIdentifier(value, CSSValueID::kNone);
 }
 
-bool IsStart(const CSSValue* value) {
-  return IsIdentifier(value, CSSValueID::kStart);
-}
-
-bool IsEnd(const CSSValue* value) {
-  return IsIdentifier(value, CSSValueID::kEnd);
-}
-
 const cssvalue::CSSIdSelectorValue* GetIdSelectorValue(const CSSValue* value) {
   if (const auto* selector = DynamicTo<CSSFunctionValue>(value)) {
     if (selector->FunctionType() != CSSValueID::kSelector)
@@ -61,39 +53,6 @@
   return absl::nullopt;
 }
 
-Element* ComputeElementOffsetTarget(Document& document, const CSSValue* value) {
-  if (const auto* id = GetIdSelectorValue(value))
-    return document.getElementById(id->Id());
-  return nullptr;
-}
-
-String ComputeElementOffsetEdge(const CSSValue* value) {
-  if (!value || IsStart(value))
-    return "start";
-  DCHECK(IsEnd(value));
-  return "end";
-}
-
-double ComputeElementOffsetThreshold(const CSSValue* value) {
-  if (auto* primitive_value = DynamicTo<CSSPrimitiveValue>(value)) {
-    DCHECK(primitive_value->IsNumber());
-    return primitive_value->GetDoubleValue();
-  }
-  return 0;
-}
-
-ScrollTimelineElementBasedOffset* ComputeElementBasedOffset(
-    Document& document,
-    const cssvalue::CSSElementOffsetValue* value) {
-  auto* offset = ScrollTimelineElementBasedOffset::Create();
-  Element* target = ComputeElementOffsetTarget(document, value->Target());
-  if (target)
-    offset->setTarget(target);
-  offset->setEdge(ComputeElementOffsetEdge(value->Edge()));
-  offset->setThreshold(ComputeElementOffsetThreshold(value->Threshold()));
-  return offset;
-}
-
 ScrollTimeline::ScrollDirection ComputeScrollDirection(const CSSValue* value) {
   CSSValueID value_id = CSSValueID::kAuto;
 
@@ -114,38 +73,6 @@
   }
 }
 
-ScrollTimelineOffset* ComputeScrollOffset(Document& document,
-                                          const CSSValue* value) {
-  if (auto* primitive_value = DynamicTo<CSSPrimitiveValue>(value))
-    return MakeGarbageCollected<ScrollTimelineOffset>(primitive_value);
-  if (auto* offset = DynamicTo<cssvalue::CSSElementOffsetValue>(value)) {
-    auto* element_based = ComputeElementBasedOffset(document, offset);
-    return MakeGarbageCollected<ScrollTimelineOffset>(element_based);
-  }
-  DCHECK(!value || IsAuto(value));
-  return MakeGarbageCollected<ScrollTimelineOffset>();
-}
-
-HeapVector<Member<ScrollTimelineOffset>> ComputeScrollOffsets(
-    Document& document,
-    const CSSValue* start,
-    const CSSValue* end) {
-  HeapVector<Member<ScrollTimelineOffset>> offsets;
-
-  const bool start_is_auto = !start || IsAuto(start);
-  const bool end_is_auto = !end || IsAuto(end);
-
-  // TODO(crbug.com/1094014): scroll_offsets will replace start and end
-  // offsets once spec decision on multiple scroll offsets is finalized.
-  // https://github.com/w3c/csswg-drafts/issues/4912
-  if (!start_is_auto)
-    offsets.push_back(ComputeScrollOffset(document, start));
-  if (!end_is_auto || !start_is_auto)
-    offsets.push_back(ComputeScrollOffset(document, end));
-
-  return offsets;
-}
-
 class ElementReferenceObserver : public IdTargetObserver {
  public:
   ElementReferenceObserver(Document* document,
@@ -181,22 +108,6 @@
         document, id->Id(), timeline));
   }
 
-  // TODO(crbug.com/1094014): The 'offsets' descriptor will replace the 'start'
-  // and 'end' descriptors eventually.
-  HeapVector<Member<const CSSValue>> offsets = {rule->GetStart(),
-                                                rule->GetEnd()};
-
-  for (const CSSValue* offset : offsets) {
-    const auto* element_offset =
-        DynamicTo<cssvalue::CSSElementOffsetValue>(offset);
-    if (!element_offset)
-      continue;
-    if (const auto* id = GetIdSelectorValue(element_offset->Target())) {
-      observers.push_back(MakeGarbageCollected<ElementReferenceObserver>(
-          document, id->Id(), timeline));
-    }
-  }
-
   return observers;
 }
 
@@ -206,7 +117,6 @@
                                     StyleRuleScrollTimeline& rule)
     : source_(ComputeScrollSource(document, rule.GetSource())),
       direction_(ComputeScrollDirection(rule.GetOrientation())),
-      offsets_(ComputeScrollOffsets(document, rule.GetStart(), rule.GetEnd())),
       rule_(&rule) {}
 
 // TODO(crbug.com/1329159): Support nearest scroll ancestor.
@@ -215,21 +125,15 @@
           document,
           ReferenceType::kSource,
           options.source_.value_or(document->ScrollingElementNoLayout()),
-          options.direction_,
-          std::move(options.offsets_)),
+          options.direction_),
       rule_(options.rule_) {
   DCHECK(rule_);
 }
 
-const AtomicString& CSSScrollTimeline::Name() const {
-  return rule_->GetName();
-}
-
 bool CSSScrollTimeline::Matches(const Options& options) const {
   // TODO(crbug.com/1060384): Support ReferenceType::kNearestAncestor.
   return HasExplicitSource() && (SourceInternal() == options.source_) &&
-         (GetOrientation() == options.direction_) &&
-         (ScrollOffsetsEqual(options.offsets_)) && (rule_ == options.rule_);
+         (GetOrientation() == options.direction_) && (rule_ == options.rule_);
 }
 
 void CSSScrollTimeline::AnimationAttached(Animation* animation) {
diff --git a/third_party/blink/renderer/core/animation/css/css_scroll_timeline.h b/third_party/blink/renderer/core/animation/css/css_scroll_timeline.h
index 11bef85..fd90291 100644
--- a/third_party/blink/renderer/core/animation/css/css_scroll_timeline.h
+++ b/third_party/blink/renderer/core/animation/css/css_scroll_timeline.h
@@ -31,7 +31,6 @@
 
     absl::optional<Element*> source_;
     ScrollTimeline::ScrollDirection direction_;
-    HeapVector<Member<ScrollTimelineOffset>> offsets_;
     StyleRuleScrollTimeline* rule_;
   };
 
diff --git a/third_party/blink/renderer/core/animation/css/css_scroll_timeline_test.cc b/third_party/blink/renderer/core/animation/css/css_scroll_timeline_test.cc
index 3a5b927..86d4ea19 100644
--- a/third_party/blink/renderer/core/animation/css/css_scroll_timeline_test.cc
+++ b/third_party/blink/renderer/core/animation/css/css_scroll_timeline_test.cc
@@ -87,8 +87,6 @@
   ASSERT_FALSE(HasObservers("scroller2"));
   ASSERT_FALSE(HasObservers("scroller3"));
   ASSERT_FALSE(HasObservers("redefined"));
-  ASSERT_FALSE(HasObservers("offset1"));
-  ASSERT_FALSE(HasObservers("offset2"));
 
   SetBodyInnerHTML(R"HTML(
     <style>
@@ -101,7 +99,6 @@
       }
       @scroll-timeline timeline2 {
         source: selector(#scroller2);
-        start: selector(#offset1);
       }
       div {
         animation: anim 10s;
@@ -120,7 +117,6 @@
 
   EXPECT_TRUE(HasObservers("scroller1"));
   EXPECT_TRUE(HasObservers("scroller2"));
-  EXPECT_TRUE(HasObservers("offset1"));
 
   Element* element1 = GetDocument().getElementById("element1");
   Element* element2 = GetDocument().getElementById("element2");
@@ -134,7 +130,6 @@
   style_element->setTextContent(R"CSS(
       @scroll-timeline timeline2 {
         source: selector(#redefined);
-        start: selector(#offset2);
       }
       @scroll-timeline timeline3 {
         source: selector(#scroller3);
@@ -150,8 +145,6 @@
   EXPECT_FALSE(HasObservers("scroller2"));
   EXPECT_TRUE(HasObservers("scroller3"));
   EXPECT_TRUE(HasObservers("redefined"));
-  EXPECT_FALSE(HasObservers("offset1"));
-  EXPECT_TRUE(HasObservers("offset2"));
 
   // Remove the <style> element again.
   style_element->remove();
@@ -161,8 +154,6 @@
   EXPECT_TRUE(HasObservers("scroller2"));
   EXPECT_FALSE(HasObservers("scroller3"));
   EXPECT_FALSE(HasObservers("redefined"));
-  EXPECT_TRUE(HasObservers("offset1"));
-  EXPECT_FALSE(HasObservers("offset2"));
 }
 
 TEST_F(CSSScrollTimelineTest, SharedTimelines) {
diff --git a/third_party/blink/renderer/core/animation/effect_stack_test.cc b/third_party/blink/renderer/core/animation/effect_stack_test.cc
index a446640..e20b918 100644
--- a/third_party/blink/renderer/core/animation/effect_stack_test.cc
+++ b/third_party/blink/renderer/core/animation/effect_stack_test.cc
@@ -74,9 +74,8 @@
   InertEffect* MakeInertEffect(KeyframeEffectModelBase* effect) {
     Timing timing;
     timing.fill_mode = Timing::FillMode::BOTH;
-    return MakeGarbageCollected<InertEffect>(effect, timing, false,
-                                             AnimationTimeDelta(),
-                                             absl::nullopt, absl::nullopt, 1.0);
+    return MakeGarbageCollected<InertEffect>(
+        effect, timing, false, AnimationTimeDelta(), absl::nullopt, 1.0);
   }
 
   KeyframeEffect* MakeKeyframeEffect(KeyframeEffectModelBase* effect,
diff --git a/third_party/blink/renderer/core/animation/inert_effect.cc b/third_party/blink/renderer/core/animation/inert_effect.cc
index bad9847..7c5857b 100644
--- a/third_party/blink/renderer/core/animation/inert_effect.cc
+++ b/third_party/blink/renderer/core/animation/inert_effect.cc
@@ -38,20 +38,18 @@
                          const Timing& timing,
                          bool paused,
                          absl::optional<AnimationTimeDelta> inherited_time,
-                         absl::optional<TimelinePhase> inherited_phase,
                          absl::optional<AnimationTimeDelta> timeline_duration,
                          double playback_rate)
     : AnimationEffect(timing),
       model_(model),
       paused_(paused),
       inherited_time_(inherited_time),
-      inherited_phase_(inherited_phase),
       timeline_duration_(timeline_duration),
       playback_rate_(playback_rate) {}
 
 void InertEffect::Sample(HeapVector<Member<Interpolation>>& result) const {
-  UpdateInheritedTime(inherited_time_, inherited_phase_, false, playback_rate_,
-                      kTimingUpdateOnDemand);
+  UpdateInheritedTime(inherited_time_, /* at_scroll_timeline_boundary */ false,
+                      playback_rate_, kTimingUpdateOnDemand);
   if (!IsInEffect()) {
     result.clear();
     return;
diff --git a/third_party/blink/renderer/core/animation/inert_effect.h b/third_party/blink/renderer/core/animation/inert_effect.h
index 79c2496..27efe66 100644
--- a/third_party/blink/renderer/core/animation/inert_effect.h
+++ b/third_party/blink/renderer/core/animation/inert_effect.h
@@ -46,7 +46,6 @@
               const Timing&,
               bool paused,
               absl::optional<AnimationTimeDelta> inherited_time,
-              absl::optional<TimelinePhase> inherited_phase,
               absl::optional<AnimationTimeDelta> timeline_duration,
               double playback_rate);
 
diff --git a/third_party/blink/renderer/core/animation/inert_effect_test.cc b/third_party/blink/renderer/core/animation/inert_effect_test.cc
index 767b8e0d..a621b19 100644
--- a/third_party/blink/renderer/core/animation/inert_effect_test.cc
+++ b/third_party/blink/renderer/core/animation/inert_effect_test.cc
@@ -24,8 +24,7 @@
 
     auto* inert_effect = MakeGarbageCollected<InertEffect>(
         opacity_model, timing, /* paused */ false, AnimationTimeDelta(),
-        TimelinePhase::kActive, absl::nullopt,
-        /* playback_rate */ 1.0);
+        /* timeline_duration */ absl::nullopt, /* playback_rate */ 1.0);
     HeapVector<Member<Interpolation>> interpolations;
     // Calling Sample ensures Timing is calculated.
     inert_effect->Sample(interpolations);
@@ -40,8 +39,7 @@
 
     auto* inert_effect = MakeGarbageCollected<InertEffect>(
         opacity_model, timing, /* paused */ false, AnimationTimeDelta(),
-        TimelinePhase::kActive, absl::nullopt,
-        /* playback_rate */ 1.0);
+        /* timeline_duration */ absl::nullopt, /* playback_rate */ 1.0);
     HeapVector<Member<Interpolation>> interpolations;
     // Calling Sample ensures Timing is calculated.
     inert_effect->Sample(interpolations);
@@ -56,8 +54,7 @@
 
     auto* inert_effect = MakeGarbageCollected<InertEffect>(
         opacity_model, timing, /* paused */ false, AnimationTimeDelta(),
-        TimelinePhase::kActive, absl::nullopt,
-        /* playback_rate */ -1.0);
+        /* timeline_duration */ absl::nullopt, /* playback_rate */ -1.0);
     HeapVector<Member<Interpolation>> interpolations;
     // Calling Sample ensures Timing is calculated.
     inert_effect->Sample(interpolations);
@@ -76,13 +73,11 @@
 
   auto* opacity_effect = MakeGarbageCollected<InertEffect>(
       opacity_model, timing, /* paused */ false, AnimationTimeDelta(),
-      TimelinePhase::kActive, absl::nullopt,
-      /* playback_rate */ 1.0);
+      /* timeline_duration */ absl::nullopt, /* playback_rate */ 1.0);
 
   auto* color_effect = MakeGarbageCollected<InertEffect>(
       color_model, timing, /* paused */ false, AnimationTimeDelta(),
-      TimelinePhase::kActive, absl::nullopt,
-      /* playback_rate */ 1.0);
+      /* timeline_duration */ absl::nullopt, /* playback_rate */ 1.0);
 
   EXPECT_TRUE(opacity_effect->Affects(PropertyHandle(GetCSSPropertyOpacity())));
   EXPECT_FALSE(opacity_effect->Affects(PropertyHandle(GetCSSPropertyColor())));
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline.cc b/third_party/blink/renderer/core/animation/scroll_timeline.cc
index 397b91c..8824f62 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline.cc
+++ b/third_party/blink/renderer/core/animation/scroll_timeline.cc
@@ -9,9 +9,7 @@
 #include "base/memory/values_equivalent.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_scroll_timeline_options.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_union_csskeywordvalue_cssnumericvalue_scrolltimelineelementbasedoffset_string.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_union_cssnumericvalue_double.h"
-#include "third_party/blink/renderer/core/animation/scroll_timeline_offset.h"
 #include "third_party/blink/renderer/core/animation/scroll_timeline_util.h"
 #include "third_party/blink/renderer/core/animation/worklet_animation_base.h"
 #include "third_party/blink/renderer/core/animation/worklet_animation_controller.h"
@@ -81,29 +79,6 @@
     return nullptr;
   }
 
-  HeapVector<Member<ScrollTimelineOffset>> scroll_offsets;
-  // TODO(crbug.com/1329159): Replace scrollOffsets once ratified in the spec
-  // rewrite.
-  // https://drafts.csswg.org/scroll-animations-1/#set-the-offset-value
-  for (auto& offset : options->scrollOffsets()) {
-    ScrollTimelineOffset* scroll_offset = ScrollTimelineOffset::Create(offset);
-    if (!scroll_offset) {
-      exception_state.ThrowTypeError("Invalid scroll offset");
-      return nullptr;
-    }
-    // 2.1 If val is a CSSKeywordValue and matches the grammar auto and pos
-    // equals to 0 or size - 1: Return val.
-    unsigned int pos = scroll_offsets.size();
-    if (scroll_offset->IsDefaultValue() &&
-        !(pos == 0 || pos == (options->scrollOffsets().size() - 1))) {
-      exception_state.ThrowTypeError(
-          "Invalid scrollOffsets: 'auto' can only be set as start or end "
-          "offset");
-      return nullptr;
-    }
-    scroll_offsets.push_back(scroll_offset);
-  }
-
   // The scrollingElement depends on style/layout-tree in quirks mode. Update
   // such that subsequent calls to ScrollingElementNoLayout returns up-to-date
   // information.
@@ -112,8 +87,7 @@
 
   return MakeGarbageCollected<ScrollTimeline>(
       &document, ReferenceType::kSource,
-      source.value_or(document.ScrollingElementNoLayout()), orientation,
-      scroll_offsets);
+      source.value_or(document.ScrollingElementNoLayout()), orientation);
 }
 
 bool ScrollTimeline::StringToScrollDirection(
@@ -138,17 +112,14 @@
   return false;
 }
 
-ScrollTimeline::ScrollTimeline(
-    Document* document,
-    ReferenceType reference_type,
-    Element* reference,
-    ScrollDirection orientation,
-    HeapVector<Member<ScrollTimelineOffset>> scroll_offsets)
+ScrollTimeline::ScrollTimeline(Document* document,
+                               ReferenceType reference_type,
+                               Element* reference,
+                               ScrollDirection orientation)
     : AnimationTimeline(document),
       reference_type_(reference_type),
       reference_element_(reference),
-      orientation_(orientation),
-      scroll_offsets_(std::move(scroll_offsets)) {
+      orientation_(orientation) {
   UpdateResolvedSource();
   SnapshotState();
 }
@@ -167,116 +138,18 @@
   return layout_box && layout_box->IsScrollContainer();
 }
 
-ScrollTimelineOffset* ScrollTimeline::StartScrollOffset() const {
-  // Single entry offset in scrollOffsets is considered as 'end'. Thus,
-  // resolving start offset only if there is at least 2 offsets.
-  return scroll_offsets_.size() >= 2 ? scroll_offsets_.at(0) : nullptr;
-}
-ScrollTimelineOffset* ScrollTimeline::EndScrollOffset() const {
-  // End offset is always the last offset in scrollOffsets if exists.
-  return scroll_offsets_.size() >= 1
-             ? scroll_offsets_.at(scroll_offsets_.size() - 1)
-             : nullptr;
+absl::optional<ScrollOffsets> ScrollTimeline::GetResolvedScrollOffsets() const {
+  return timeline_state_snapshotted_.scroll_offsets;
 }
 
-const std::vector<double> ScrollTimeline::GetResolvedScrollOffsets() const {
-  std::vector<double> resolved_offsets;
-  for (const auto& offset : timeline_state_snapshotted_.scroll_offsets)
-    resolved_offsets.push_back(offset);
-  return resolved_offsets;
-}
-
-// Resolves scroll offsets and stores them into resolved_offsets argument.
-// Returns true if the offsets are resolved.
-// https://drafts.csswg.org/scroll-animations-1/#effective-scroll-offsets-algorithm
-bool ScrollTimeline::ResolveScrollOffsets(
-    WTF::Vector<double>& resolved_offsets) const {
-  // 1. Let effective scroll offsets be an empty list of effective scroll
-  // offsets.
-  DCHECK(resolved_offsets.IsEmpty());
-  DCHECK(ComputeIsActive());
-  LayoutBox* layout_box = resolved_source_->GetLayoutBox();
-  DCHECK(layout_box);
-
-  double current_offset;
-  double max_offset;
-  GetCurrentAndMaxOffset(layout_box, current_offset, max_offset);
-
-  auto orientation = ToPhysicalScrollOrientation(orientation_, *layout_box);
-
-  // 2. Let first offset be true.
-  // first_offset signifies weather min or max scroll offset is pushed to
-  // effective scroll offsets.
-  bool first_offset = true;
-
-  // 3. If scrollOffsets is empty
-  if (scroll_offsets_.size() == 0) {
-    // Start and end offsets resolve to 'auto'.
-    // 3.1 Run the procedure to resolve a scroll timeline offset for auto with
-    // the is first flag set to first offset and add the resulted value into
-    // effective scroll offsets.
-    resolved_offsets.push_back(0);
-    // 3.2 Set first offset to false.
-    // 3.3 Run the procedure to resolve a scroll timeline offset for auto with
-    // the is first flag set to first offset and add the resulted value into
-    // effective scroll offsets.
-    resolved_offsets.push_back(max_offset);
-    return true;
-  }
-  // Single entry offset in scrollOffsets is considered as 'end'.
-  // 4. If scrollOffsets has exactly one element
-  if (scroll_offsets_.size() == 1) {
-    // 4.1 Run the procedure to resolve a scroll timeline offset for auto with
-    // the is first flag set to first offset and add the resulted value into
-    // effective scroll offsets.
-    resolved_offsets.push_back(0);
-    // 4.2 Set first offset to false.
-    first_offset = false;
-  }
-
-  // 5. For each scroll offset in the list of scrollOffsets, perform the
-  // following steps:
-  for (auto& offset : scroll_offsets_) {
-    // 5.1 Let effective offset be the result of applying the procedure to
-    // resolve a scroll timeline offset for scroll offset with the is first flag
-    // set to first offset.
-    auto resolved_offset =
-        offset->ResolveOffset(resolved_source_, orientation, max_offset,
-                              first_offset ? 0 : max_offset);
-    if (!resolved_offset) {
-      // 5.2 If effective offset is null, the effective scroll offsets is empty
-      // and abort the remaining steps.
-      resolved_offsets.clear();
-      return false;
-    }
-    // 5.3 Add effective offset into effective scroll offsets.
-    resolved_offsets.push_back(resolved_offset.value());
-
-    // 5.4 Set first offset to false.
-    first_offset = false;
-  }
-  DCHECK_GE(resolved_offsets.size(), 2u);
-  // 6. Return effective scroll offsets.
-  return true;
-}
-
+// TODO(crbug.com/1336260): Since phase can only be kActive or kInactive and
+// currentTime  can only be null if phase is inactive or before the first
+// snapshot we can probably drop phase.
 AnimationTimeline::PhaseAndTime ScrollTimeline::CurrentPhaseAndTime() {
   return {timeline_state_snapshotted_.phase,
           timeline_state_snapshotted_.current_time};
 }
 
-bool ScrollTimeline::ScrollOffsetsEqual(
-    const HeapVector<Member<ScrollTimelineOffset>>& other) const {
-  if (scroll_offsets_.size() != other.size())
-    return false;
-  wtf_size_t size = scroll_offsets_.size();
-  for (wtf_size_t i = 0; i < size; ++i) {
-    if (!base::ValuesEquivalent(scroll_offsets_.at(i), other.at(i)))
-      return false;
-  }
-  return true;
-}
-
 V8CSSNumberish* ScrollTimeline::ConvertTimeToProgress(
     AnimationTimeDelta time) const {
   return MakeGarbageCollected<V8CSSNumberish>(
@@ -298,6 +171,9 @@
   return MakeGarbageCollected<V8CSSNumberish>(CSSUnitValues::percent(100));
 }
 
+// TODO(crbug.com/1060384): This section is missing from the spec rewrite.
+// Resolved to remove the before and after phases in
+// https://github.com/w3c/csswg-drafts/issues/7240.
 // https://drafts.csswg.org/scroll-animations-1/#current-time-algorithm
 ScrollTimeline::TimelineState ScrollTimeline::ComputeTimelineState() {
   UpdateResolvedSource();
@@ -305,70 +181,76 @@
   // 1. If scroll timeline is inactive, return an unresolved time value.
   // https://github.com/WICG/scroll-animations/issues/31
   // https://wicg.github.io/scroll-animations/#current-time-algorithm
-  WTF::Vector<double> resolved_offsets;
   if (!ComputeIsActive()) {
     return {TimelinePhase::kInactive, /*current_time*/ absl::nullopt,
-            resolved_offsets};
+            /* scroll_offsets */ absl::nullopt};
   }
+  DCHECK(resolved_source_);
   LayoutBox* layout_box = resolved_source_->GetLayoutBox();
-  // 2. Otherwise, let current scroll offset be the current scroll offset of
-  // scrollSource in the direction specified by orientation.
 
-  double current_offset;
-  double max_offset;
-  GetCurrentAndMaxOffset(layout_box, current_offset, max_offset);
+  // Layout box and scrollable area must exist since the timeline is active.
+  DCHECK(layout_box);
+  DCHECK(layout_box->GetScrollableArea());
 
-  bool resolved = ResolveScrollOffsets(resolved_offsets);
+  // Depending on the writing-mode and direction, the scroll origin shifts and
+  // the scroll offset may be negative. The easiest way to deal with this is to
+  // use only the magnitude of the scroll offset, and compare it to (max_offset
+  // - min_offset).
+  PaintLayerScrollableArea* scrollable_area = layout_box->GetScrollableArea();
 
-  if (!resolved) {
-    DCHECK(resolved_offsets.IsEmpty());
-    return {TimelinePhase::kInactive, /*current_time*/ absl::nullopt,
-            resolved_offsets};
-  }
+  // Using the absolute value of the scroll offset only makes sense if either
+  // the max or min scroll offset for a given axis is 0. This should be
+  // guaranteed by the scroll origin code, but these DCHECKs ensure that.
+  DCHECK(scrollable_area->MaximumScrollOffset().y() == 0 ||
+         scrollable_area->MinimumScrollOffset().y() == 0);
+  DCHECK(scrollable_area->MaximumScrollOffset().x() == 0 ||
+         scrollable_area->MinimumScrollOffset().x() == 0);
 
-  // TODO(crbug.com/1060384): Support determination of offsets for a
-  // ViewTimeline.
+  ScrollOffset scroll_offset = scrollable_area->GetScrollOffset();
+  auto physical_orientation =
+      ToPhysicalScrollOrientation(orientation_, *layout_box);
+  double current_offset = (physical_orientation == kHorizontalScroll)
+                              ? scroll_offset.x()
+                              : scroll_offset.y();
+  // When using a rtl direction, current_offset grows correctly from 0 to
+  // max_offset, but is negative. Since our offsets are all just deltas along
+  // the orientation direction, we can just take the absolute current_offset and
+  // use that everywhere.
+  current_offset = std::abs(current_offset);
 
-  double start_offset = resolved_offsets[0];
-  double end_offset = resolved_offsets[resolved_offsets.size() - 1];
+  // TODO(crbug.com/1329159): Override in ViewTimeline to compute offsets
+  // corresponding to the 'cover' range.
+  double start_offset = GetStartOffset(scrollable_area, physical_orientation);
+  double end_offset = GetEndOffset(scrollable_area, physical_orientation);
 
-  // TODO(crbug.com/1060384): Once the spec has been updated to state what the
-  // expected result is when startScrollOffset >= endScrollOffset, we might need
-  // to add a special case here. See
-  // https://github.com/WICG/scroll-animations/issues/20
-
-  // 3. The current time is the result corresponding to the first matching
-  // condition from below:
-  // 3.1 If current scroll offset is less than effective start offset:
-  //     The current time is 0.
-  if (current_offset < start_offset) {
-    return {TimelinePhase::kBefore, base::TimeDelta(), resolved_offsets};
-  }
+  // TODO(crbug.com/1338167): Update once
+  // github.com/w3c/csswg-drafts/issues/7401 is resolved.
+  double progress =
+      (end_offset == start_offset)
+          ? 1
+          : (current_offset - start_offset) / (end_offset - start_offset);
 
   base::TimeDelta duration = base::Seconds(GetDuration()->InSecondsF());
+  absl::optional<base::TimeDelta> calculated_current_time =
+      base::Milliseconds(progress * duration.InMillisecondsF());
 
-  // 3.2 If current scroll offset is greater than or equal to effective end
-  // offset:
-  //    The current time is the effective time range.
-  if (current_offset >= end_offset) {
-    // If end_offset is greater than or equal to the maximum scroll offset of
-    // scrollSource in orientation then return active phase, otherwise return
-    // after phase.
-    TimelinePhase phase = end_offset >= max_offset ? TimelinePhase::kActive
-                                                   : TimelinePhase::kAfter;
-    return {phase, duration, resolved_offsets};
-  }
+  return {TimelinePhase::kActive, calculated_current_time,
+          absl::make_optional<ScrollOffsets>(start_offset, end_offset)};
+}
 
-  // 3.3 Otherwise,
-  // 3.3.1 Let progress be a result of applying calculate scroll timeline
-  // progress procedure for current scroll offset.
-  // 3.3.2 The current time is the result of evaluating the following
-  // expression:
-  //     progress × effective time range
-  absl::optional<base::TimeDelta> calculated_current_time = base::Milliseconds(
-      scroll_timeline_util::ComputeProgress(current_offset, resolved_offsets) *
-      duration.InMillisecondsF());
-  return {TimelinePhase::kActive, calculated_current_time, resolved_offsets};
+double ScrollTimeline::GetStartOffset(
+    PaintLayerScrollableArea* scrollable_area,
+    ScrollOrientation physical_orientation) const {
+  return 0;
+}
+
+double ScrollTimeline::GetEndOffset(
+    PaintLayerScrollableArea* scrollable_area,
+    ScrollOrientation physical_orientation) const {
+  ScrollOffset scroll_dimensions = scrollable_area->MaximumScrollOffset() -
+                                   scrollable_area->MinimumScrollOffset();
+  return physical_orientation == kHorizontalScroll ? scroll_dimensions.x()
+                                                   : scroll_dimensions.y();
 }
 
 // Scroll-linked animations are initialized with the start time of zero.
@@ -483,17 +365,6 @@
   }
 }
 
-const HeapVector<Member<V8ScrollTimelineOffset>> ScrollTimeline::scrollOffsets()
-    const {
-  HeapVector<Member<V8ScrollTimelineOffset>> scroll_offsets;
-  for (auto& offset : scroll_offsets_) {
-    scroll_offsets.push_back(offset->ToV8ScrollTimelineOffset());
-    // 'auto' can only be the end offset.
-    DCHECK(!offset->IsDefaultValue() || scroll_offsets.size() == 2);
-  }
-  return scroll_offsets;
-}
-
 void ScrollTimeline::GetCurrentAndMaxOffset(const LayoutBox* layout_box,
                                             double& current_offset,
                                             double& max_offset) const {
@@ -588,7 +459,6 @@
 void ScrollTimeline::Trace(Visitor* visitor) const {
   visitor->Trace(reference_element_);
   visitor->Trace(resolved_source_);
-  visitor->Trace(scroll_offsets_);
   visitor->Trace(attached_worklet_animations_);
   AnimationTimeline::Trace(visitor);
 }
@@ -644,6 +514,7 @@
 void ScrollTimeline::UpdateCompositorTimeline() {
   if (!compositor_timeline_)
     return;
+
   ToScrollTimeline(compositor_timeline_.get())
       ->UpdateScrollerIdAndScrollOffsets(
           scroll_timeline_util::GetCompositorScrollElementId(resolved_source_),
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline.h b/third_party/blink/renderer/core/animation/scroll_timeline.h
index 26573b8a..e3cdcc5 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline.h
+++ b/third_party/blink/renderer/core/animation/scroll_timeline.h
@@ -7,18 +7,20 @@
 
 #include "base/gtest_prod_util.h"
 #include "base/time/time.h"
+#include "cc/animation/scroll_timeline.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_typedefs.h"
 #include "third_party/blink/renderer/core/animation/animation_timeline.h"
-#include "third_party/blink/renderer/core/animation/scroll_timeline_offset.h"
 #include "third_party/blink/renderer/core/animation/timing.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/css_primitive_value.h"
 #include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/scroll/scroll_types.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
 
+class PaintLayerScrollableArea;
 class ScrollTimelineOptions;
 class WorkletAnimationBase;
 
@@ -34,6 +36,8 @@
 class CORE_EXPORT ScrollTimeline : public AnimationTimeline {
   DEFINE_WRAPPERTYPEINFO();
 
+  using ScrollOffsets = cc::ScrollTimeline::ScrollOffsets;
+
  public:
   enum ScrollDirection {
     kBlock,
@@ -57,8 +61,7 @@
   ScrollTimeline(Document*,
                  ReferenceType reference_type,
                  Element* reference,
-                 ScrollDirection,
-                 HeapVector<Member<ScrollTimelineOffset>>);
+                 ScrollDirection);
 
   static bool StringToScrollDirection(String scroll_direction,
                                       ScrollTimeline::ScrollDirection& result);
@@ -79,7 +82,6 @@
   // IDL API implementation.
   Element* source() const;
   String orientation();
-  const HeapVector<Member<V8ScrollTimelineOffset>> scrollOffsets() const;
 
   V8CSSNumberish* currentTime() override;
   V8CSSNumberish* duration() override;
@@ -93,7 +95,7 @@
 
   // Return the latest resolved scroll offsets. This will be empty when
   // timeline is inactive.
-  const std::vector<double> GetResolvedScrollOffsets() const;
+  absl::optional<ScrollOffsets> GetResolvedScrollOffsets() const;
 
   ScrollDirection GetOrientation() const { return orientation_; }
 
@@ -147,8 +149,6 @@
 
  protected:
   PhaseAndTime CurrentPhaseAndTime() override;
-  bool ScrollOffsetsEqual(
-      const HeapVector<Member<ScrollTimelineOffset>>& other) const;
 
   virtual Element* ReferenceElement() const { return reference_element_.Get(); }
 
@@ -164,6 +164,13 @@
 
   void UpdateResolvedSource();
 
+  // Scroll offsets corresponding to 0% and 100% progress. By default, these
+  // correspond to the scroll range of the container.
+  virtual double GetStartOffset(PaintLayerScrollableArea* scrollable_area,
+                                ScrollOrientation physical_orientation) const;
+  virtual double GetEndOffset(PaintLayerScrollableArea* scrollable_area,
+                              ScrollOrientation physical_orientation) const;
+
  private:
   FRIEND_TEST_ALL_PREFIXES(ScrollTimelineTest, MultipleScrollOffsetsClamping);
   FRIEND_TEST_ALL_PREFIXES(ScrollTimelineTest, ResolveScrollOffsets);
@@ -174,19 +181,12 @@
   bool ComputeIsActive() const;
   PhaseAndTime ComputeCurrentPhaseAndTime() const;
 
-  // Resolve scroll offsets The resolution process turns length-based values
-  // into concrete length values resolving percentages and zoom factor. For
-  // element-based values it computes the corresponding length value that maps
-  // to the particular element intersection. See
-  // |ScrollTimelineOffset::ResolveOffset()| for more details.
-  bool ResolveScrollOffsets(WTF::Vector<double>& resolved_offsets) const;
-
   struct TimelineState {
+    // TODO(crbug.com/1338167): Remove phase as it can be inferred from
+    // current_time.
     TimelinePhase phase;
     absl::optional<base::TimeDelta> current_time;
-    // The resolved version of scroll offset. The vector is empty
-    // when timeline is inactive (e.g., when source does not overflow).
-    WTF::Vector<double> scroll_offsets;
+    absl::optional<ScrollOffsets> scroll_offsets;
 
     bool operator==(const TimelineState& other) const {
       return phase == other.phase && current_time == other.current_time &&
@@ -195,8 +195,6 @@
   };
 
   TimelineState ComputeTimelineState();
-  ScrollTimelineOffset* StartScrollOffset() const;
-  ScrollTimelineOffset* EndScrollOffset() const;
 
   // Use time_check true to request next service if time has changed.
   // false - regardless of time change.
@@ -206,7 +204,6 @@
   Member<Element> reference_element_;
   Member<Node> resolved_source_;
   ScrollDirection orientation_;
-  HeapVector<Member<ScrollTimelineOffset>> scroll_offsets_;
 
   // Snapshotted value produced by the last SnapshotState call.
   TimelineState timeline_state_snapshotted_;
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline.idl b/third_party/blink/renderer/core/animation/scroll_timeline.idl
index ac7bb94..3d453969 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline.idl
+++ b/third_party/blink/renderer/core/animation/scroll_timeline.idl
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-typedef (CSSNumericValue or CSSKeywordish) ScrollTimelineContainerBasedOffset;
-typedef (ScrollTimelineContainerBasedOffset or ScrollTimelineElementBasedOffset) ScrollTimelineOffset;
-
 // https://wicg.github.io/scroll-animations/#scrolltimeline-interface
 [
     RuntimeEnabled=ScrollTimeline,
@@ -13,5 +10,4 @@
     [CallWith=Document, RaisesException, MeasureAs=ScrollTimelineConstructor] constructor(optional ScrollTimelineOptions options = {});
     readonly attribute Element? source;
     readonly attribute ScrollDirection orientation;
-    readonly attribute FrozenArray<ScrollTimelineOffset> scrollOffsets;
 };
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline_element_based_offset.idl b/third_party/blink/renderer/core/animation/scroll_timeline_element_based_offset.idl
deleted file mode 100644
index 189c0fc..0000000
--- a/third_party/blink/renderer/core/animation/scroll_timeline_element_based_offset.idl
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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.
-
-// Experimental IDL of element-based offsets based on this proposal:
-// https://github.com/w3c/csswg-drafts/issues/4337
-
-enum Edge {"start", "end"};
-
-dictionary ScrollTimelineElementBasedOffset {
-    Element target;
-    double threshold = 0.0;
-    Edge edge = "start";
-    // TODO(majidvp): Add other values from proposal. http://crbug.com/1023375
-    // DOMString rootMargin;
-};
\ No newline at end of file
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline_offset.cc b/third_party/blink/renderer/core/animation/scroll_timeline_offset.cc
deleted file mode 100644
index ddd0b69..0000000
--- a/third_party/blink/renderer/core/animation/scroll_timeline_offset.cc
+++ /dev/null
@@ -1,245 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/core/animation/scroll_timeline_offset.h"
-
-#include "base/memory/values_equivalent.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_scroll_timeline_element_based_offset.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_union_csskeywordvalue_cssnumericvalue_scrolltimelineelementbasedoffset_string.h"
-#include "third_party/blink/renderer/core/css/css_to_length_conversion_data.h"
-#include "third_party/blink/renderer/core/css/cssom/css_keyword_value.h"
-#include "third_party/blink/renderer/core/css/cssom/css_numeric_value.h"
-#include "third_party/blink/renderer/core/dom/node_computed_style.h"
-#include "third_party/blink/renderer/core/layout/layout_box.h"
-#include "third_party/blink/renderer/core/layout/layout_view.h"
-#include "third_party/blink/renderer/platform/geometry/layout_unit.h"
-#include "third_party/blink/renderer/platform/geometry/length_functions.h"
-
-namespace blink {
-
-namespace {
-
-bool ValidateElementBasedOffset(
-    const ScrollTimelineElementBasedOffset* offset) {
-  if (!offset->hasTarget())
-    return false;
-
-  if (offset->hasThreshold()) {
-    if (offset->threshold() < 0 || offset->threshold() > 1)
-      return false;
-  }
-
-  return true;
-}
-
-// TODO(majidvp): Dedup. This is a copy of the function in
-// third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc
-// http://crbug.com/1023375
-
-// Return true if ancestor is in the containing block chain above descendant.
-bool IsContainingBlockChainDescendant(const LayoutObject* descendant,
-                                      const LayoutObject* ancestor) {
-  if (!ancestor || !descendant)
-    return false;
-  LocalFrame* ancestor_frame = ancestor->GetDocument().GetFrame();
-  LocalFrame* descendant_frame = descendant->GetDocument().GetFrame();
-  if (ancestor_frame != descendant_frame)
-    return false;
-
-  while (descendant && descendant != ancestor)
-    descendant = descendant->ContainingBlock();
-  return descendant;
-}
-
-bool ElementBasedOffsetsEqual(ScrollTimelineElementBasedOffset* o1,
-                              ScrollTimelineElementBasedOffset* o2) {
-  if (o1 == o2)
-    return true;
-  if (!o1 || !o2)
-    return false;
-  // TODO(crbug.com/1070871): Use targetOr(nullptr) after migration is done.
-  Element* target_or_null1 = o1->hasTarget() ? o1->target() : nullptr;
-  Element* target_or_null2 = o2->hasTarget() ? o2->target() : nullptr;
-  return target_or_null1 == target_or_null2 && o1->edge() == o2->edge() &&
-         o1->threshold() == o2->threshold();
-}
-
-}  // namespace
-
-// static
-ScrollTimelineOffset* ScrollTimelineOffset::Create(
-    const V8ScrollTimelineOffset* offset) {
-  switch (offset->GetContentType()) {
-    case V8ScrollTimelineOffset::ContentType::kCSSKeywordValue: {
-      const auto* keyword = offset->GetAsCSSKeywordValue();
-      if (keyword->KeywordValueID() != CSSValueID::kAuto)
-        return nullptr;
-      return MakeGarbageCollected<ScrollTimelineOffset>();
-    }
-    case V8ScrollTimelineOffset::ContentType::kCSSNumericValue: {
-      const auto* value =
-          To<CSSPrimitiveValue>(offset->GetAsCSSNumericValue()->ToCSSValue());
-      bool matches_length_percentage =
-          !value || value->IsLength() || value->IsPercentage() ||
-          value->IsCalculatedPercentageWithLength();
-      if (!matches_length_percentage)
-        return nullptr;
-      return MakeGarbageCollected<ScrollTimelineOffset>(value);
-    }
-    case V8ScrollTimelineOffset::ContentType::
-        kScrollTimelineElementBasedOffset: {
-      auto* value = offset->GetAsScrollTimelineElementBasedOffset();
-      if (!ValidateElementBasedOffset(value))
-        return nullptr;
-      return MakeGarbageCollected<ScrollTimelineOffset>(value);
-    }
-    case V8ScrollTimelineOffset::ContentType::kString: {
-      if (offset->GetAsString().IsEmpty())
-        return nullptr;
-      const auto* keyword = CSSKeywordValue::Create(offset->GetAsString());
-      if (keyword->KeywordValueID() != CSSValueID::kAuto)
-        return nullptr;
-      return MakeGarbageCollected<ScrollTimelineOffset>();
-    }
-  }
-  NOTREACHED();
-  return nullptr;
-}
-
-absl::optional<double> ScrollTimelineOffset::ResolveOffset(
-    Node* scroll_source,
-    ScrollOrientation orientation,
-    double max_offset,
-    double default_offset) {
-  const LayoutBox* root_box = scroll_source->GetLayoutBox();
-  DCHECK(root_box);
-  Document& document = root_box->GetDocument();
-
-  if (length_based_offset_) {
-    // Resolve scroll based offset.
-    const ComputedStyle& computed_style = root_box->StyleRef();
-    const ComputedStyle* root_style =
-        document.documentElement()
-            ? document.documentElement()->GetComputedStyle()
-            : document.GetComputedStyle();
-
-    // TOOD(crbug.com/1223030): Handle container relative units.
-    CSSToLengthConversionData conversion_data = CSSToLengthConversionData(
-        &computed_style, root_style, document.GetLayoutView(),
-        CSSToLengthConversionData::ContainerSizes(),
-        computed_style.EffectiveZoom());
-    double resolved = FloatValueForLength(
-        length_based_offset_->ConvertToLength(conversion_data), max_offset);
-
-    return resolved;
-  } else if (element_based_offset_) {
-    if (!element_based_offset_->hasTarget())
-      return absl::nullopt;
-    Element* target = element_based_offset_->target();
-    const LayoutBox* target_box = target->GetLayoutBox();
-
-    // It is possible for target to not have a layout box e.g., if it is an
-    // unattached element. In which case we return the default offset for now.
-    //
-    // See the spec discussion here:
-    // https://github.com/w3c/csswg-drafts/issues/4337#issuecomment-610997231
-    if (!target_box)
-      return absl::nullopt;
-
-    if (!IsContainingBlockChainDescendant(target_box, root_box))
-      return absl::nullopt;
-
-    PhysicalRect target_rect = target_box->PhysicalBorderBoxRect();
-    target_rect = target_box->LocalToAncestorRect(
-        target_rect, root_box,
-        kTraverseDocumentBoundaries | kIgnoreScrollOffset);
-
-    PhysicalRect root_rect(root_box->PhysicalBorderBoxRect());
-
-    LayoutUnit root_edge;
-    LayoutUnit target_edge;
-
-    // Here is the simple diagram that shows the computation.
-    //
-    //                 +-----+
-    //                 |     |     +------+
-    //                 |     |     |      |
-    // edge:start +----+-----+-------------------+-----+-------+
-    //            |                |xxxxxx|      |xxxxx|       |
-    //            |                +------+      |xxxxx|       |
-    //            |                              +-----+       |
-    //            |                                            |
-    // threshold: |    A) 0       B) 0.5         C) 1          |
-    //            |                                            |
-    //            |                              +-----+       |
-    //            |                +------+      |xxxxx|       |
-    //            |                |xxxxxx|      |xxxxx|       |
-    // edge: end  +----+-----+-------------------+-----+-------+
-    //                 |     |     |      |
-    //                 |     |     +------+
-    //                 +-----+
-    //
-    // We always take the target top edge and compute the distance to the
-    // root's selected edge. This give us (C) in start edge case and (A) in
-    // end edge case.
-    //
-    // To take threshold into account we simply add (1-threshold) or threshold
-    // in start and end edge cases respectively.
-    bool is_start = element_based_offset_->edge() == "start";
-    float threshold_adjustment = is_start
-                                     ? (1 - element_based_offset_->threshold())
-                                     : element_based_offset_->threshold();
-
-    if (orientation == kVerticalScroll) {
-      root_edge = is_start ? root_rect.Y() : root_rect.Bottom();
-      target_edge = target_rect.Y();
-      // Note that threshold is considered as a portion of target and not as a
-      // portion of root. IntersectionObserver has option to allow both.
-      target_edge += (threshold_adjustment * target_rect.Height());
-    } else {  // kHorizontalScroll
-      root_edge = is_start ? root_rect.X() : root_rect.Right();
-      target_edge = target_rect.X();
-      target_edge += (threshold_adjustment * target_rect.Width());
-    }
-
-    LayoutUnit offset = target_edge - root_edge;
-    return std::min(std::max(offset.ToDouble(), 0.0), max_offset);
-  } else {
-    // Resolve the default case (i.e., 'auto' value)
-    return default_offset;
-  }
-}
-
-V8ScrollTimelineOffset* ScrollTimelineOffset::ToV8ScrollTimelineOffset() const {
-  if (length_based_offset_) {
-    return MakeGarbageCollected<V8ScrollTimelineOffset>(
-        CSSNumericValue::FromCSSValue(*length_based_offset_.Get()));
-  } else if (element_based_offset_) {
-    return MakeGarbageCollected<V8ScrollTimelineOffset>(element_based_offset_);
-  }
-  // This is the default value (i.e., 'auto' value)
-  return MakeGarbageCollected<V8ScrollTimelineOffset>(
-      CSSKeywordValue::Create("auto"));
-}
-
-bool ScrollTimelineOffset::operator==(const ScrollTimelineOffset& o) const {
-  return base::ValuesEquivalent(length_based_offset_, o.length_based_offset_) &&
-         ElementBasedOffsetsEqual(element_based_offset_,
-                                  o.element_based_offset_);
-}
-
-ScrollTimelineOffset::ScrollTimelineOffset(const CSSPrimitiveValue* offset)
-    : length_based_offset_(offset), element_based_offset_(nullptr) {}
-
-ScrollTimelineOffset::ScrollTimelineOffset(
-    ScrollTimelineElementBasedOffset* offset)
-    : length_based_offset_(nullptr), element_based_offset_(offset) {}
-
-void ScrollTimelineOffset::Trace(blink::Visitor* visitor) const {
-  visitor->Trace(length_based_offset_);
-  visitor->Trace(element_based_offset_);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline_offset.h b/third_party/blink/renderer/core/animation/scroll_timeline_offset.h
deleted file mode 100644
index 2021780..0000000
--- a/third_party/blink/renderer/core/animation/scroll_timeline_offset.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// 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 THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_SCROLL_TIMELINE_OFFSET_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_SCROLL_TIMELINE_OFFSET_H_
-
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_scroll_timeline_element_based_offset.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_typedefs.h"
-#include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/css/css_style_sheet.h"
-#include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
-#include "third_party/blink/renderer/core/scroll/scroll_types.h"
-
-namespace blink {
-
-// Represent a scroll timeline start/end offset which can be an
-// scroll offset or an element based offset
-class CORE_EXPORT ScrollTimelineOffset final
-    : public GarbageCollected<ScrollTimelineOffset> {
- public:
-  static ScrollTimelineOffset* Create(const V8ScrollTimelineOffset* offset);
-
-  // Create a default offset representing 'auto'.
-  ScrollTimelineOffset() = default;
-  // Create a scroll based offset.
-  explicit ScrollTimelineOffset(const CSSPrimitiveValue*);
-  // Create an element based offset.
-  explicit ScrollTimelineOffset(ScrollTimelineElementBasedOffset*);
-
-  void Trace(blink::Visitor*) const;
-
-  // Resolves this offset against the scroll source and in the given orientation
-  // returning eqiuvalent concrete scroll offset.
-  //
-  //  - Length-based values are converted into concrete length values resolving
-  //    percentages and zoom factor.
-  //  - Element-based values are resolved to the equivalent scroll offset that
-  //    satisfy the requirement.
-  //  - Auto value simply returns the |detfault_offset|.
-  //
-  // max offset is expected to be the maximum scroll offset in the scroll
-  // orientation.
-  //
-  // Returns nullopt if the offset cannot be resolved.
-  absl::optional<double> ResolveOffset(Node* scroll_source,
-                                       ScrollOrientation,
-                                       double max_offset,
-                                       double default_offset);
-
-  V8ScrollTimelineOffset* ToV8ScrollTimelineOffset() const;
-  bool IsDefaultValue() const {
-    return !length_based_offset_ && !element_based_offset_;
-  }
-
-  bool operator==(const ScrollTimelineOffset&) const;
-  bool operator!=(const ScrollTimelineOffset& o) const { return !(*this == o); }
-
- private:
-  // We either have an scroll or element based offset so at any time one of
-  // these is null. If both are null, it represents the default value of
-  // 'auto'.
-  Member<const CSSPrimitiveValue> length_based_offset_;
-  Member<ScrollTimelineElementBasedOffset> element_based_offset_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_SCROLL_TIMELINE_OFFSET_H_
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline_offset_test.cc b/third_party/blink/renderer/core/animation/scroll_timeline_offset_test.cc
deleted file mode 100644
index 41b620e..0000000
--- a/third_party/blink/renderer/core/animation/scroll_timeline_offset_test.cc
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/core/animation/scroll_timeline_offset.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_union_csskeywordvalue_cssnumericvalue_scrolltimelineelementbasedoffset_string.h"
-#include "third_party/blink/renderer/core/animation/animation_test_helpers.h"
-#include "third_party/blink/renderer/core/animation/scroll_timeline_offset.h"
-#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
-#include "third_party/blink/renderer/core/dom/document.h"
-#include "third_party/blink/renderer/core/html/html_element.h"
-#include "third_party/blink/renderer/core/testing/page_test_base.h"
-
-namespace blink {
-
-class ScrollTimelineOffsetTest : public PageTestBase {
- public:
-  ScrollTimelineOffset* ScrollBasedOffsetFrom(String string) {
-    return ScrollTimelineOffset::Create(
-        animation_test_helpers::OffsetFromString(GetDocument(), string));
-  }
-
-  ScrollTimelineOffset* ElementBasedOffsetFrom(Element* target,
-                                               String edge,
-                                               double threshold) {
-    auto* inner = CreateElementBasedOffset(target, edge, threshold);
-    if (!inner)
-      return nullptr;
-    auto* param = MakeGarbageCollected<V8ScrollTimelineOffset>(inner);
-    return ScrollTimelineOffset::Create(param);
-  }
-
- private:
-  ScrollTimelineElementBasedOffset* CreateElementBasedOffset(Element* target,
-                                                             String edge,
-                                                             double threshold) {
-    auto* value = ScrollTimelineElementBasedOffset::Create();
-    value->setTarget(target);
-    value->setEdge(edge);
-    value->setThreshold(threshold);
-    return value;
-  }
-};
-
-TEST_F(ScrollTimelineOffsetTest, Equality) {
-  GetDocument().body()->setInnerHTML("<i id=e1></i><i id=e2></i>");
-  UpdateAllLifecyclePhasesForTest();
-  Element* e1 = GetDocument().getElementById("e1");
-  Element* e2 = GetDocument().getElementById("e2");
-
-  ASSERT_TRUE(e1);
-  ASSERT_TRUE(e2);
-
-  EXPECT_EQ(*ScrollBasedOffsetFrom("10px"), *ScrollBasedOffsetFrom("10px"));
-  EXPECT_EQ(*ScrollBasedOffsetFrom("10%"), *ScrollBasedOffsetFrom("10%"));
-  EXPECT_EQ(*ElementBasedOffsetFrom(e1, "start", 0),
-            *ElementBasedOffsetFrom(e1, "start", 0));
-
-  // Different types of offset:
-  EXPECT_NE(*ScrollBasedOffsetFrom("10px"),
-            *ElementBasedOffsetFrom(e1, "start", 0));
-  EXPECT_NE(*ElementBasedOffsetFrom(e1, "start", 0),
-            *ScrollBasedOffsetFrom("10px"));
-
-  // Different unit:
-  EXPECT_NE(*ScrollBasedOffsetFrom("10px"), *ScrollBasedOffsetFrom("10%"));
-  EXPECT_NE(*ScrollBasedOffsetFrom("10em"), *ScrollBasedOffsetFrom("10px"));
-
-  // Different value:
-  EXPECT_NE(*ScrollBasedOffsetFrom("10em"), *ScrollBasedOffsetFrom("50em"));
-  EXPECT_NE(*ScrollBasedOffsetFrom("10px"), *ScrollBasedOffsetFrom("10.5px"));
-
-  // Different target:
-  EXPECT_NE(*ElementBasedOffsetFrom(e1, "start", 0),
-            *ElementBasedOffsetFrom(e2, "start", 0));
-
-  // Different edge:
-  EXPECT_NE(*ElementBasedOffsetFrom(e1, "start", 0),
-            *ElementBasedOffsetFrom(e1, "end", 0));
-
-  // Different threshold:
-  EXPECT_NE(*ElementBasedOffsetFrom(e1, "start", 0),
-            *ElementBasedOffsetFrom(e1, "start", 1));
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline_options.idl b/third_party/blink/renderer/core/animation/scroll_timeline_options.idl
index e6d8ff2..6a219b8 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline_options.idl
+++ b/third_party/blink/renderer/core/animation/scroll_timeline_options.idl
@@ -7,5 +7,4 @@
 dictionary ScrollTimelineOptions {
         Element? source;
         ScrollDirection orientation = "block";
-        sequence<ScrollTimelineOffset> scrollOffsets = [];
 };
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline_test.cc b/third_party/blink/renderer/core/animation/scroll_timeline_test.cc
index b7c8b1db..5bac6d7 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline_test.cc
+++ b/third_party/blink/renderer/core/animation/scroll_timeline_test.cc
@@ -6,7 +6,6 @@
 
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_scroll_timeline_options.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_union_csskeywordvalue_cssnumericvalue_scrolltimelineelementbasedoffset_string.h"
 #include "third_party/blink/renderer/core/animation/animation_clock.h"
 #include "third_party/blink/renderer/core/animation/animation_test_helpers.h"
 #include "third_party/blink/renderer/core/animation/document_animations.h"
@@ -35,24 +34,6 @@
 #define EXPECT_TIME_NEAR(expected, value) \
   EXPECT_NEAR(expected, value, time_error_ms)
 
-void ExpectVectorDoubleEqual(const WTF::Vector<double>& expected,
-                             const WTF::Vector<double>& value) {
-  EXPECT_EQ(expected.size(), value.size());
-  for (unsigned int i = 0; i < expected.size(); i++)
-    EXPECT_DOUBLE_EQ(expected[i], value[i]);
-}
-
-HeapVector<Member<ScrollTimelineOffset>> CreateScrollOffsets(
-    ScrollTimelineOffset* start_scroll_offset =
-        MakeGarbageCollected<ScrollTimelineOffset>(),
-    ScrollTimelineOffset* end_scroll_offset =
-        MakeGarbageCollected<ScrollTimelineOffset>()) {
-  HeapVector<Member<ScrollTimelineOffset>> scroll_offsets;
-  scroll_offsets.push_back(start_scroll_offset);
-  scroll_offsets.push_back(end_scroll_offset);
-  return scroll_offsets;
-}
-
 Animation* CreateTestAnimation(AnimationTimeline* timeline) {
   Timing timing;
   timing.iteration_duration = ANIMATION_TIME_DELTA_FROM_SECONDS(0.1);
@@ -87,23 +68,15 @@
     }
     return count;
   }
-
-  V8ScrollTimelineOffset* OffsetFromString(const String& value) {
-    return animation_test_helpers::OffsetFromString(GetDocument(), value);
-  }
 };
 
 class TestScrollTimeline : public ScrollTimeline {
  public:
-  TestScrollTimeline(Document* document,
-                     Element* source,
-                     HeapVector<Member<ScrollTimelineOffset>> scroll_offsets =
-                         CreateScrollOffsets())
+  TestScrollTimeline(Document* document, Element* source)
       : ScrollTimeline(document,
                        ScrollTimeline::ReferenceType::kSource,
                        source,
-                       ScrollTimeline::kVertical,
-                       std::move(scroll_offsets)),
+                       ScrollTimeline::kVertical),
         next_service_scheduled_(false) {}
 
   void ScheduleServiceOnNextFrame() override {
@@ -140,104 +113,6 @@
 }
 
 TEST_F(ScrollTimelineTest,
-       CurrentTimeIsNullIfScrollOffsetIsBeyondStartAndEndScrollOffset) {
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      #scroller { overflow: scroll; width: 100px; height: 100px; }
-      #spacer { height: 1000px; }
-    </style>
-    <div id='scroller'>
-      <div id ='spacer'></div>
-    </div>
-  )HTML");
-
-  auto* scroller =
-      To<LayoutBoxModelObject>(GetLayoutObjectByElementId("scroller"));
-  ASSERT_TRUE(scroller);
-  ASSERT_TRUE(scroller->IsScrollContainer());
-  PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
-  ASSERT_TRUE(scrollable_area);
-  ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
-  options->setSource(GetElementById("scroller"));
-  options->setScrollOffsets(
-      {OffsetFromString("10px"), OffsetFromString("90px")});
-  ScrollTimeline* scroll_timeline =
-      ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 5),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  // Simulate a new animation frame  which allows the timeline to compute new
-  // current time.
-  SimulateFrame();
-  EXPECT_EQ(scroll_timeline->CurrentTimeSeconds(), 0);
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 10),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  EXPECT_EQ(scroll_timeline->CurrentTimeSeconds(), 0);
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 50),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  EXPECT_EQ(scroll_timeline->CurrentTimeSeconds(), 50);
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 90),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  EXPECT_EQ(scroll_timeline->CurrentTime(), scroll_timeline->GetDuration());
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 100),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  EXPECT_EQ(scroll_timeline->CurrentTime(), scroll_timeline->GetDuration());
-  EXPECT_TRUE(scroll_timeline->IsActive());
-}
-
-TEST_F(ScrollTimelineTest,
-       CurrentTimeIsNullIfEndScrollOffsetIsLessThanStartScrollOffset) {
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      #scroller { overflow: scroll; width: 100px; height: 100px; }
-      #spacer { height: 1000px; }
-    </style>
-    <div id='scroller'>
-      <div id ='spacer'></div>
-    </div>
-  )HTML");
-
-  auto* scroller =
-      To<LayoutBoxModelObject>(GetLayoutObjectByElementId("scroller"));
-  ASSERT_TRUE(scroller);
-  ASSERT_TRUE(scroller->IsScrollContainer());
-  PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
-  ASSERT_TRUE(scrollable_area);
-  ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
-  options->setSource(GetElementById("scroller"));
-  options->setScrollOffsets(
-      {OffsetFromString("80px"), OffsetFromString("40px")});
-  ScrollTimeline* scroll_timeline =
-      ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 20),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  // Simulate a new animation frame  which allows the timeline to compute new
-  // current time.
-  SimulateFrame();
-  EXPECT_EQ(0, scroll_timeline->CurrentTimeSeconds());
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 60),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  EXPECT_EQ(0, scroll_timeline->CurrentTimeSeconds());
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 100),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  EXPECT_EQ(scroll_timeline->CurrentTime(), scroll_timeline->GetDuration());
-  EXPECT_TRUE(scroll_timeline->IsActive());
-}
-
-TEST_F(ScrollTimelineTest,
        UsingDocumentScrollingElementShouldCorrectlyResolveToDocument) {
   SetBodyInnerHTML(R"HTML(
     <style>
@@ -299,7 +174,7 @@
   Persistent<ScrollTimeline> scroll_timeline =
       MakeGarbageCollected<ScrollTimeline>(
           &GetDocument(), ScrollTimeline::ReferenceType::kSource, scroll_source,
-          ScrollTimeline::kBlock, CreateScrollOffsets());
+          ScrollTimeline::kBlock);
 
   // Sanity checks.
   ASSERT_EQ(scroll_timeline->source(), nullptr);
@@ -546,10 +421,8 @@
 
   // Use empty offsets as 'auto'.
   TestScrollTimeline* scroll_timeline =
-      MakeGarbageCollected<TestScrollTimeline>(
-          &GetDocument(), scroller_element,
-          CreateScrollOffsets(MakeGarbageCollected<ScrollTimelineOffset>(),
-                              MakeGarbageCollected<ScrollTimelineOffset>()));
+      MakeGarbageCollected<TestScrollTimeline>(&GetDocument(),
+                                               scroller_element);
   NonThrowableExceptionState exception_state;
   Timing timing;
   timing.iteration_duration = ANIMATION_TIME_DELTA_FROM_SECONDS(30);
@@ -592,10 +465,8 @@
 
   // Use empty offsets as 'auto'.
   TestScrollTimeline* scroll_timeline =
-      MakeGarbageCollected<TestScrollTimeline>(
-          &GetDocument(), scroller_element,
-          CreateScrollOffsets(MakeGarbageCollected<ScrollTimelineOffset>(),
-                              MakeGarbageCollected<ScrollTimelineOffset>()));
+      MakeGarbageCollected<TestScrollTimeline>(&GetDocument(),
+                                               scroller_element);
   NonThrowableExceptionState exception_state;
   Timing timing;
   timing.iteration_duration = ANIMATION_TIME_DELTA_FROM_SECONDS(30);
@@ -688,6 +559,7 @@
                         scroll_timeline, exception_state);
   scroll_animation->play();
   UpdateAllLifecyclePhasesForTest();
+
   // Scroll to finished state.
   scrollable_area->SetScrollOffset(ScrollOffset(0, 100),
                                    mojom::blink::ScrollType::kProgrammatic);
@@ -700,7 +572,7 @@
   EXPECT_EQ(1u, scroll_timeline->AnimationsNeedingUpdateCount());
 
   // Scroll back.
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 80),
+  scrollable_area->SetScrollOffset(ScrollOffset(0, 50),
                                    mojom::blink::ScrollType::kProgrammatic);
   SimulateFrame();
   // Verify that the animation as back to running.
@@ -823,220 +695,6 @@
   EXPECT_FALSE(event_listener->EventReceived());
 }
 
-TEST_F(ScrollTimelineTest, ResolveScrollOffsets) {
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      #scroller { overflow: scroll; width: 100px; height: 100px; }
-      #spacer { height: 1000px; }
-    </style>
-    <div id='scroller'>
-      <div id ='spacer'></div>
-    </div>
-  )HTML");
-
-  auto* scroller =
-      To<LayoutBoxModelObject>(GetLayoutObjectByElementId("scroller"));
-  ASSERT_TRUE(scroller);
-  PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
-  ASSERT_TRUE(scrollable_area);
-  ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
-  options->setSource(GetElementById("scroller"));
-  // Empty scroll offsets resolve into [0, 100%].
-  HeapVector<Member<V8ScrollTimelineOffset>> scroll_offsets = {};
-  options->setScrollOffsets(scroll_offsets);
-
-  ScrollTimeline* scroll_timeline =
-      ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
-
-  WTF::Vector<double> resolved_offsets;
-  WTF::Vector<double> expected_offsets = {0, 900.0};
-  scroll_timeline->ResolveScrollOffsets(resolved_offsets);
-  ExpectVectorDoubleEqual(expected_offsets, resolved_offsets);
-
-  // Single 'auto' offset resolve into [0, 100%].
-  scroll_offsets = {OffsetFromString("auto")};
-  options->setScrollOffsets(scroll_offsets);
-  scroll_timeline =
-      ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
-  resolved_offsets.clear();
-  scroll_timeline->ResolveScrollOffsets(resolved_offsets);
-  expected_offsets = {0, 900.0};
-  ExpectVectorDoubleEqual(expected_offsets, resolved_offsets);
-
-  // Start and end 'auto' offsets resolve into [0, 100%].
-  scroll_offsets = {OffsetFromString("auto"), OffsetFromString("auto")};
-  options->setScrollOffsets(scroll_offsets);
-  scroll_timeline =
-      ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
-  resolved_offsets.clear();
-  scroll_timeline->ResolveScrollOffsets(resolved_offsets);
-  expected_offsets = {0, 900.0};
-  ExpectVectorDoubleEqual(expected_offsets, resolved_offsets);
-
-  // Three offsets, start and end are 'auto' resolve into [0, middle offset,
-  // 100%].
-  scroll_offsets = {OffsetFromString("auto"), OffsetFromString("500px"),
-                    OffsetFromString("auto")};
-  options->setScrollOffsets(scroll_offsets);
-  scroll_timeline =
-      ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
-  resolved_offsets.clear();
-  scroll_timeline->ResolveScrollOffsets(resolved_offsets);
-  expected_offsets = {0, 500.0, 900.0};
-  ExpectVectorDoubleEqual(expected_offsets, resolved_offsets);
-}
-
-TEST_F(ScrollTimelineTest, MultipleScrollOffsetsCurrentTimeCalculations) {
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      #scroller { overflow: scroll; width: 100px; height: 100px; }
-      #spacer { height: 1000px; }
-    </style>
-    <div id='scroller'>
-      <div id ='spacer'></div>
-    </div>
-  )HTML");
-
-  auto* scroller =
-      To<LayoutBoxModelObject>(GetLayoutObjectByElementId("scroller"));
-  ASSERT_TRUE(scroller);
-  ASSERT_TRUE(scroller->IsScrollContainer());
-  PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
-  ASSERT_TRUE(scrollable_area);
-  ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
-  options->setSource(GetElementById("scroller"));
-  HeapVector<Member<V8ScrollTimelineOffset>> scroll_offsets;
-  scroll_offsets.push_back(OffsetFromString("10px"));
-  scroll_offsets.push_back(OffsetFromString("20px"));
-  scroll_offsets.push_back(OffsetFromString("40px"));
-  scroll_offsets.push_back(OffsetFromString("90px"));
-  options->setScrollOffsets(scroll_offsets);
-
-  ScrollTimeline* scroll_timeline =
-      ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
-
-  EXPECT_EQ(scroll_timeline->CurrentTimeSeconds(), 0);
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 10),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  // Simulate a new animation frame  which allows the timeline to compute new
-  // current phase and time.
-  SimulateFrame();
-  EXPECT_EQ(0, scroll_timeline->CurrentTimeSeconds().value());
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 12),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-
-  unsigned int offset = 0;
-  double w = 1.0 / 3.0;                      // offset weight
-  double p = (12.0 - 10.0) / (20.0 - 10.0);  // progress within the offset
-  double duration = 100.0;
-  EXPECT_TIME_NEAR((offset + p) * w * duration,
-                   scroll_timeline->CurrentTimeSeconds().value());
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 20),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  offset = 1;
-  p = 0;
-  EXPECT_TIME_NEAR((offset + p) * w * duration,
-                   scroll_timeline->CurrentTimeSeconds().value());
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 30),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  p = (30.0 - 20.0) / (40.0 - 20.0);
-  EXPECT_TIME_NEAR((offset + p) * w * duration,
-                   scroll_timeline->CurrentTimeSeconds().value());
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 40),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  offset = 2;
-  p = 0;
-  EXPECT_TIME_NEAR((offset + p) * w * duration,
-                   scroll_timeline->CurrentTimeSeconds().value());
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 80),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  p = (80.0 - 40.0) / (90.0 - 40.0);
-  EXPECT_TIME_NEAR((offset + p) * w * duration,
-                   scroll_timeline->CurrentTimeSeconds().value());
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 90),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  EXPECT_EQ(100, scroll_timeline->CurrentTimeSeconds().value());
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 100),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  EXPECT_EQ(100, scroll_timeline->CurrentTimeSeconds().value());
-}
-
-TEST_F(ScrollTimelineTest, OverlappingScrollOffsets) {
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      #scroller { overflow: scroll; width: 100px; height: 100px; }
-      #spacer { height: 1000px; }
-    </style>
-    <div id='scroller'>
-      <div id ='spacer'></div>
-    </div>
-  )HTML");
-
-  auto* scroller =
-      To<LayoutBoxModelObject>(GetLayoutObjectByElementId("scroller"));
-  ASSERT_TRUE(scroller);
-  PaintLayerScrollableArea* scrollable_area = scroller->GetScrollableArea();
-  ASSERT_TRUE(scrollable_area);
-  ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
-  options->setSource(GetElementById("scroller"));
-  HeapVector<Member<V8ScrollTimelineOffset>> scroll_offsets = {
-      OffsetFromString("90px"), OffsetFromString("40px"),
-      OffsetFromString("10px")};
-  options->setScrollOffsets(scroll_offsets);
-
-  ScrollTimeline* scroll_timeline =
-      ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 80),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  EXPECT_EQ(0, scroll_timeline->CurrentTimeSeconds().value());
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 95),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  EXPECT_EQ(100, scroll_timeline->CurrentTimeSeconds().value());
-
-  scroll_offsets = {OffsetFromString("0px"), OffsetFromString("100px"),
-                    OffsetFromString("50px")};
-  options->setScrollOffsets(scroll_offsets);
-
-  scroll_timeline =
-      ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 40),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  EXPECT_EQ(20, scroll_timeline->CurrentTimeSeconds().value());
-
-  scroll_offsets = {OffsetFromString("50px"), OffsetFromString("0px"),
-                    OffsetFromString("100px")};
-  options->setScrollOffsets(scroll_offsets);
-
-  scroll_timeline =
-      ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
-
-  scrollable_area->SetScrollOffset(ScrollOffset(0, 60),
-                                   mojom::blink::ScrollType::kProgrammatic);
-  SimulateFrame();
-  EXPECT_EQ(80, scroll_timeline->CurrentTimeSeconds().value());
-}
-
 TEST_F(ScrollTimelineTest, WeakReferences) {
   SetBodyInnerHTML(R"HTML(
     <style>
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline_util.cc b/third_party/blink/renderer/core/animation/scroll_timeline_util.cc
index 070097c..24e0de6cd 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline_util.cc
+++ b/third_party/blink/renderer/core/animation/scroll_timeline_util.cc
@@ -95,12 +95,6 @@
                           : CompositorScrollTimeline::ScrollUp;
 }
 
-double ComputeProgress(double current_offset,
-                       const WTF::Vector<double>& resolved_offsets) {
-  return cc::ComputeProgress<WTF::Vector<double>>(current_offset,
-                                                  resolved_offsets);
-}
-
 }  // namespace scroll_timeline_util
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline_util.h b/third_party/blink/renderer/core/animation/scroll_timeline_util.h
index d53c61f..b0ff626 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline_util.h
+++ b/third_party/blink/renderer/core/animation/scroll_timeline_util.h
@@ -14,6 +14,7 @@
 namespace blink {
 
 using CompositorScrollTimeline = cc::ScrollTimeline;
+using ScrollOffsets = cc::ScrollTimeline::ScrollOffsets;
 
 class AnimationTimeline;
 class ComputedStyle;
@@ -38,8 +39,7 @@
 CompositorScrollTimeline::ScrollDirection CORE_EXPORT
 ConvertOrientation(ScrollTimeline::ScrollDirection, const ComputedStyle*);
 
-double ComputeProgress(double current_offset,
-                       const WTF::Vector<double>& resolved_offsets);
+absl::optional<ScrollOffsets> CreateScrollOffsets(ScrollTimeline* timeline);
 
 }  // namespace scroll_timeline_util
 
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline_util_test.cc b/third_party/blink/renderer/core/animation/scroll_timeline_util_test.cc
index aa47b9b..dc50248 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline_util_test.cc
+++ b/third_party/blink/renderer/core/animation/scroll_timeline_util_test.cc
@@ -6,7 +6,6 @@
 
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_scroll_timeline_options.h"
-#include "third_party/blink/renderer/bindings/core/v8/v8_union_csskeywordvalue_cssnumericvalue_scrolltimelineelementbasedoffset_string.h"
 #include "third_party/blink/renderer/core/animation/animation_test_helpers.h"
 #include "third_party/blink/renderer/core/animation/document_timeline.h"
 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
@@ -16,29 +15,15 @@
 
 namespace blink {
 
-namespace {
-
-HeapVector<Member<ScrollTimelineOffset>> CreateScrollOffsets(
-    ScrollTimelineOffset* start_scroll_offset,
-    ScrollTimelineOffset* end_scroll_offset) {
-  HeapVector<Member<ScrollTimelineOffset>> scroll_offsets;
-  scroll_offsets.push_back(start_scroll_offset);
-  scroll_offsets.push_back(end_scroll_offset);
-  return scroll_offsets;
-}
-
-}  // namespace
-
 namespace scroll_timeline_util {
 
 using ScrollTimelineUtilTest = PageTestBase;
 
 // This test covers only the basic conversions for element id, time range,
-// orientation, and start and end scroll offset. Complex orientation conversions
-// are tested in the GetOrientation* tests, and complex start/end scroll offset
-// resolutions are tested in blink::ScrollTimelineTest.
+// and orientation. Complex orientation conversions are tested in the
+// GetOrientation* tests.
 TEST_F(ScrollTimelineUtilTest, ToCompositorScrollTimeline) {
-  using animation_test_helpers::OffsetFromString;
+  // using animation_test_helpers::OffsetFromString;
 
   SetBodyInnerHTML(R"HTML(
     <style>
@@ -62,8 +47,6 @@
   ScrollTimelineOptions* options = ScrollTimelineOptions::Create();
   options->setSource(scroller);
   options->setOrientation("block");
-  options->setScrollOffsets({OffsetFromString(GetDocument(), "50px"),
-                             OffsetFromString(GetDocument(), "auto")});
   ScrollTimeline* timeline =
       ScrollTimeline::Create(GetDocument(), options, ASSERT_NO_EXCEPTION);
 
@@ -73,9 +56,6 @@
   EXPECT_EQ(compositor_timeline->GetPendingIdForTest(), element_id);
   EXPECT_EQ(compositor_timeline->GetDirectionForTest(),
             CompositorScrollTimeline::ScrollDown);
-  EXPECT_EQ(compositor_timeline->GetStartScrollOffsetForTest(), 50);
-  // 900 is contents-size - scroller-viewport == 1000 - 100
-  EXPECT_EQ(compositor_timeline->GetEndScrollOffsetForTest(), 900);
 }
 
 TEST_F(ScrollTimelineUtilTest, ToCompositorScrollTimelineNullParameter) {
@@ -94,14 +74,9 @@
   // source. The alternative approach would require us to remove the
   // documentElement from the document.
   Element* source = nullptr;
-  ScrollTimelineOffset* start_scroll_offset =
-      MakeGarbageCollected<ScrollTimelineOffset>();
-  ScrollTimelineOffset* end_scroll_offset =
-      MakeGarbageCollected<ScrollTimelineOffset>();
   ScrollTimeline* timeline = MakeGarbageCollected<ScrollTimeline>(
       &GetDocument(), ScrollTimeline::ReferenceType::kSource, source,
-      ScrollTimeline::kBlock,
-      CreateScrollOffsets(start_scroll_offset, end_scroll_offset));
+      ScrollTimeline::kBlock);
 
   scoped_refptr<CompositorScrollTimeline> compositor_timeline =
       ToCompositorScrollTimeline(timeline);
@@ -121,11 +96,6 @@
   scoped_refptr<CompositorScrollTimeline> compositor_timeline =
       ToCompositorScrollTimeline(timeline);
   EXPECT_TRUE(compositor_timeline.get());
-  // Here we just want to test the start/end scroll offset.
-  // ToCompositorScrollTimelineNullSource covers the expected pending id
-  // and ConvertOrientationNullStyle covers the orientation conversion.
-  EXPECT_EQ(compositor_timeline->GetStartScrollOffsetForTest(), absl::nullopt);
-  EXPECT_EQ(compositor_timeline->GetEndScrollOffsetForTest(), absl::nullopt);
 }
 
 TEST_F(ScrollTimelineUtilTest, ConvertOrientationPhysicalCases) {
diff --git a/third_party/blink/renderer/core/animation/timing.cc b/third_party/blink/renderer/core/animation/timing.cc
index 8dead5bb..e39f1bb 100644
--- a/third_party/blink/renderer/core/animation/timing.cc
+++ b/third_party/blink/renderer/core/animation/timing.cc
@@ -176,7 +176,6 @@
 
 Timing::CalculatedTiming Timing::CalculateTimings(
     absl::optional<AnimationTimeDelta> local_time,
-    absl::optional<Phase> timeline_phase,
     bool at_progress_timeline_boundary,
     const NormalizedTiming& normalized_timing,
     AnimationDirection animation_direction,
@@ -186,7 +185,7 @@
   const AnimationTimeDelta duration = normalized_timing.iteration_duration;
 
   Timing::Phase current_phase =
-      CalculatePhase(normalized_timing, local_time, timeline_phase,
+      CalculatePhase(normalized_timing, local_time,
                      at_progress_timeline_boundary, animation_direction);
 
   const absl::optional<AnimationTimeDelta> active_time = CalculateActiveTime(
diff --git a/third_party/blink/renderer/core/animation/timing.h b/third_party/blink/renderer/core/animation/timing.h
index 2c1b9f3..4ce9c90 100644
--- a/third_party/blink/renderer/core/animation/timing.h
+++ b/third_party/blink/renderer/core/animation/timing.h
@@ -184,7 +184,6 @@
 
   CalculatedTiming CalculateTimings(
       absl::optional<AnimationTimeDelta> local_time,
-      absl::optional<Phase> timeline_phase,
       bool at_progress_timeline_boundary,
       const NormalizedTiming& normalized_timing,
       AnimationDirection animation_direction,
diff --git a/third_party/blink/renderer/core/animation/timing_calculations.h b/third_party/blink/renderer/core/animation/timing_calculations.h
index 2e32511..2235c33 100644
--- a/third_party/blink/renderer/core/animation/timing_calculations.h
+++ b/third_party/blink/renderer/core/animation/timing_calculations.h
@@ -112,7 +112,6 @@
 static inline Timing::Phase CalculatePhase(
     const Timing::NormalizedTiming& normalized,
     absl::optional<AnimationTimeDelta> local_time,
-    absl::optional<Timing::Phase> timeline_phase,
     bool at_progress_timeline_boundary,
     Timing::AnimationDirection direction) {
   DCHECK(GreaterThanOrEqualToWithinTimeTolerance(normalized.active_duration,
@@ -123,27 +122,21 @@
   AnimationTimeDelta before_active_boundary_time =
       std::max(std::min(normalized.start_delay, normalized.end_time),
                AnimationTimeDelta());
-
-  if ((timeline_phase && timeline_phase.value() == Timing::kPhaseBefore) ||
-      ((!timeline_phase ||
-        (timeline_phase && timeline_phase.value() == Timing::kPhaseActive)) &&
-       (local_time.value() < before_active_boundary_time ||
-        (direction == Timing::AnimationDirection::kBackwards &&
-         local_time.value() == before_active_boundary_time &&
-         !at_progress_timeline_boundary)))) {
+  if (local_time.value() < before_active_boundary_time ||
+      (direction == Timing::AnimationDirection::kBackwards &&
+       local_time.value() == before_active_boundary_time &&
+       !at_progress_timeline_boundary)) {
     return Timing::kPhaseBefore;
   }
+
   AnimationTimeDelta active_after_boundary_time =
       std::max(std::min(normalized.start_delay + normalized.active_duration,
                         normalized.end_time),
                AnimationTimeDelta());
-  if ((timeline_phase && timeline_phase.value() == Timing::kPhaseAfter) ||
-      ((!timeline_phase ||
-        (timeline_phase && timeline_phase.value() == Timing::kPhaseActive)) &&
-       (local_time.value() > active_after_boundary_time ||
-        (direction == Timing::AnimationDirection::kForwards &&
-         local_time.value() == active_after_boundary_time &&
-         !at_progress_timeline_boundary)))) {
+  if (local_time.value() > active_after_boundary_time ||
+      (direction == Timing::AnimationDirection::kForwards &&
+       local_time.value() == active_after_boundary_time &&
+       !at_progress_timeline_boundary)) {
     return Timing::kPhaseAfter;
   }
   return Timing::kPhaseActive;
diff --git a/third_party/blink/renderer/core/animation/timing_test.cc b/third_party/blink/renderer/core/animation/timing_test.cc
index 795d92d..ae3157b91 100644
--- a/third_party/blink/renderer/core/animation/timing_test.cc
+++ b/third_party/blink/renderer/core/animation/timing_test.cc
@@ -17,10 +17,10 @@
     Timing::AnimationDirection animation_direction =
         playback_rate < 0 ? Timing::AnimationDirection::kBackwards
                           : Timing::AnimationDirection::kForwards;
-    return timing_.CalculateTimings(
-        local_time, /*timeline_phase*/ absl::nullopt,
-        /* at_progress_timeline_boundary */ false, normalized_timing_,
-        animation_direction, is_keyframe_effect, playback_rate);
+    return timing_.CalculateTimings(local_time,
+                                    /* at_progress_timeline_boundary */ false,
+                                    normalized_timing_, animation_direction,
+                                    is_keyframe_effect, playback_rate);
   }
   bool IsCurrent(absl::optional<double> local_time, double playback_rate) {
     absl::optional<AnimationTimeDelta> local_time_delta;
diff --git a/third_party/blink/renderer/core/animation/view_timeline.cc b/third_party/blink/renderer/core/animation/view_timeline.cc
index 97e1fd1..b75685a 100644
--- a/third_party/blink/renderer/core/animation/view_timeline.cc
+++ b/third_party/blink/renderer/core/animation/view_timeline.cc
@@ -24,23 +24,15 @@
     return nullptr;
   }
 
-  // TODO(crbug.com/1329159): Remove scroll_offsets. Currently needed for
-  // the ScrollTimeline constructor.
-  HeapVector<Member<ScrollTimelineOffset>> scroll_offsets;
-
-  return MakeGarbageCollected<ViewTimeline>(&document, subject, orientation,
-                                            scroll_offsets);
+  return MakeGarbageCollected<ViewTimeline>(&document, subject, orientation);
 }
 
-ViewTimeline::ViewTimeline(
-    Document* document,
-    Element* subject,
-    ScrollDirection orientation,
-    HeapVector<Member<ScrollTimelineOffset>> scroll_offsets)
+ViewTimeline::ViewTimeline(Document* document,
+                           Element* subject,
+                           ScrollDirection orientation)
     : ScrollTimeline(document,
                      ReferenceType::kNearestAncestor,
                      subject,
-                     orientation,
-                     scroll_offsets) {}
+                     orientation) {}
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/view_timeline.h b/third_party/blink/renderer/core/animation/view_timeline.h
index 7812a6f..18a4912 100644
--- a/third_party/blink/renderer/core/animation/view_timeline.h
+++ b/third_party/blink/renderer/core/animation/view_timeline.h
@@ -29,13 +29,7 @@
  public:
   static ViewTimeline* Create(Document&, ViewTimelineOptions*, ExceptionState&);
 
-  // TODO(crbug.com/1329159): Add additional arguments as ratified.
-  // TODO(crbug.com/1329159): Remove scroll-offsets. Presently needed for the
-  // ScrollTimeline constructor.
-  ViewTimeline(Document*,
-               Element* subject,
-               ScrollDirection orientation,
-               HeapVector<Member<ScrollTimelineOffset>> scroll_offsets);
+  ViewTimeline(Document*, Element* subject, ScrollDirection orientation);
 
   bool IsViewTimeline() const override { return true; }
 
diff --git a/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.cc b/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.cc
index 94f5df3..29f2a68 100644
--- a/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.cc
+++ b/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/core/document_transition/document_transition_style_tracker.h"
 
+#include <limits>
+
 #include "components/viz/common/shared_element_resource_id.h"
 #include "third_party/blink/public/resources/grit/blink_resources.h"
 #include "third_party/blink/renderer/core/animation/element_animations.h"
@@ -416,7 +418,6 @@
     element_data->cached_border_box_size_in_css_space =
         element_data->border_box_size_in_css_space;
     element_data->cached_viewport_matrix = element_data->viewport_matrix;
-    element_data->cached_device_pixel_ratio = element_data->device_pixel_ratio;
     element_data->cached_visual_overflow_rect_in_layout_space =
         element_data->visual_overflow_rect_in_layout_space;
     element_data->effect_node = nullptr;
@@ -685,7 +686,7 @@
       continue;
     }
 
-    float device_pixel_ratio = document_->DevicePixelRatio();
+    const float device_pixel_ratio = document_->DevicePixelRatio();
     TransformationMatrix viewport_matrix =
         layout_object->LocalToAbsoluteTransform();
     viewport_matrix.Zoom(1.0 / device_pixel_ratio);
@@ -701,6 +702,11 @@
                          LayoutUnit(entry_size->blockSize()))
             : LayoutSize(LayoutUnit(entry_size->blockSize()),
                          LayoutUnit(entry_size->inlineSize()));
+    if (float effective_zoom = layout_object->StyleRef().EffectiveZoom();
+        std::abs(effective_zoom - device_pixel_ratio) >=
+        std::numeric_limits<float>::epsilon()) {
+      border_box_size_in_css_space.Scale(effective_zoom / device_pixel_ratio);
+    }
 
     PhysicalRect visual_overflow_rect_in_layout_space;
     if (auto* box = DynamicTo<LayoutBox>(layout_object))
@@ -711,7 +717,6 @@
     if (viewport_matrix == element_data->viewport_matrix &&
         border_box_size_in_css_space ==
             element_data->border_box_size_in_css_space &&
-        device_pixel_ratio == element_data->device_pixel_ratio &&
         visual_overflow_rect_in_layout_space ==
             element_data->visual_overflow_rect_in_layout_space &&
         writing_mode == element_data->container_writing_mode) {
@@ -720,7 +725,6 @@
 
     element_data->viewport_matrix = viewport_matrix;
     element_data->border_box_size_in_css_space = border_box_size_in_css_space;
-    element_data->device_pixel_ratio = device_pixel_ratio;
     element_data->visual_overflow_rect_in_layout_space =
         visual_overflow_rect_in_layout_space;
     element_data->container_writing_mode = writing_mode;
@@ -962,6 +966,7 @@
         })CSS");
   }
 
+  float device_pixel_ratio = document_->DevicePixelRatio();
   for (auto& entry : element_data_map_) {
     const auto& document_transition_tag = entry.key.GetString();
     auto& element_data = entry.value;
@@ -985,8 +990,7 @@
           height: %dpx;
           transform: %s;
           writing-mode: %s;
-        }
-        )CSS",
+        })CSS",
         border_box_in_css_space.width(), border_box_in_css_space.height(),
         ComputedStyleUtils::ValueForTransformationMatrix(
             element_data->viewport_matrix, 1, false)
@@ -995,7 +999,6 @@
             .c_str(),
         writing_mode_stream.str().c_str());
 
-    float device_pixel_ratio = document_->DevicePixelRatio();
     absl::optional<String> incoming_inset = ComputeInsetDifference(
         element_data->visual_overflow_rect_in_layout_space,
         border_box_in_css_space, device_pixel_ratio);
@@ -1005,8 +1008,7 @@
       builder.AppendFormat(
           R"CSS({
             object-view-box: %s;
-          }
-          )CSS",
+          })CSS",
           incoming_inset->Utf8().c_str());
     }
 
@@ -1019,8 +1021,7 @@
       builder.AppendFormat(
           R"CSS({
             object-view-box: %s;
-          }
-          )CSS",
+          })CSS",
           outgoing_inset->Utf8().c_str());
     }
 
@@ -1039,8 +1040,7 @@
              width: %dpx;
              height: %dpx;
             }
-           }
-           )CSS",
+          })CSS",
           ComputedStyleUtils::ValueForTransformationMatrix(
               element_data->cached_viewport_matrix, 1, false)
               ->CssText()
diff --git a/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.h b/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.h
index 0fc5fc3d..8ca40a6 100644
--- a/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.h
+++ b/third_party/blink/renderer/core/document_transition/document_transition_style_tracker.h
@@ -142,12 +142,10 @@
     // |target_element|. This information is mirrored into the UA stylesheet.
     LayoutSize border_box_size_in_css_space;
     TransformationMatrix viewport_matrix;
-    float device_pixel_ratio = 1.f;
 
     // Computed info cached before the DOM switches to the new state.
     LayoutSize cached_border_box_size_in_css_space;
     TransformationMatrix cached_viewport_matrix;
-    float cached_device_pixel_ratio = 1.f;
 
     // Valid if there is an element in the old DOM generating a snapshot.
     viz::SharedElementResourceId old_snapshot_id;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
index decd35c..cbc618f6 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
@@ -37,6 +37,7 @@
 #include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h"
+#include "third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.h"
 #include "third_party/blink/renderer/core/mathml/mathml_element.h"
 #include "third_party/blink/renderer/core/mathml_names.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
@@ -1026,8 +1027,10 @@
   }
 
   // At this point, perform any final table-cell adjustments needed.
-  if (ConstraintSpace().IsTableCell())
-    FinalizeForTableCell(unconstrained_intrinsic_block_size);
+  if (ConstraintSpace().IsTableCell()) {
+    NGTableAlgorithmUtils::FinalizeTableCellLayout(
+        unconstrained_intrinsic_block_size, &container_builder_);
+  }
 
   NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), &container_builder_).Run();
 
@@ -2300,76 +2303,6 @@
   return child_bfc_block_offset;
 }
 
-void NGBlockLayoutAlgorithm::FinalizeForTableCell(
-    LayoutUnit unconstrained_intrinsic_block_size) {
-  const bool has_inflow_children = !container_builder_.Children().IsEmpty();
-
-  // Hide table-cells if:
-  //  - They are within a collapsed column(s).
-  //  - They have "empty-cells: hide", non-collapsed borders, and no children.
-  container_builder_.SetIsHiddenForPaint(
-      ConstraintSpace().IsTableCellHiddenForPaint() ||
-      (ConstraintSpace().HideTableCellIfEmpty() && !has_inflow_children));
-
-  container_builder_.SetHasCollapsedBorders(
-      ConstraintSpace().IsTableCellWithCollapsedBorders());
-
-  container_builder_.SetIsTableNGPart();
-
-  container_builder_.SetTableCellColumnIndex(
-      ConstraintSpace().TableCellColumnIndex());
-
-  // If we're resuming after a break, there'll be no alignment, since the
-  // fragment will start at the block-start edge of the fragmentainer then.
-  if (IsResumingLayout(BreakToken()))
-    return;
-
-  switch (Style().VerticalAlign()) {
-    case EVerticalAlign::kTop:
-      // Do nothing for 'top' vertical alignment.
-      break;
-    case EVerticalAlign::kBaselineMiddle:
-    case EVerticalAlign::kSub:
-    case EVerticalAlign::kSuper:
-    case EVerticalAlign::kTextTop:
-    case EVerticalAlign::kTextBottom:
-    case EVerticalAlign::kLength:
-      // All of the above are treated as 'baseline' for the purposes of
-      // table-cell vertical alignment.
-    case EVerticalAlign::kBaseline:
-      // Table-cells (with baseline vertical alignment) always produce a
-      // baseline of their end-content edge (even if the content doesn't have
-      // any baselines).
-      if (!container_builder_.Baseline() ||
-          Node().ShouldApplyLayoutContainment()) {
-        container_builder_.SetBaseline(unconstrained_intrinsic_block_size -
-                                       BorderScrollbarPadding().block_end);
-      }
-
-      // Only adjust if we have *inflow* children. If we only have
-      // OOF-positioned children don't align them to the alignment baseline.
-      if (has_inflow_children) {
-        if (auto alignment_baseline =
-                ConstraintSpace().TableCellAlignmentBaseline()) {
-          container_builder_.MoveChildrenInBlockDirection(
-              *alignment_baseline - *container_builder_.Baseline());
-        }
-      }
-      break;
-    case EVerticalAlign::kMiddle:
-      container_builder_.MoveChildrenInBlockDirection(
-          (container_builder_.FragmentBlockSize() -
-           unconstrained_intrinsic_block_size) /
-          2);
-      break;
-    case EVerticalAlign::kBottom:
-      container_builder_.MoveChildrenInBlockDirection(
-          container_builder_.FragmentBlockSize() -
-          unconstrained_intrinsic_block_size);
-      break;
-  };
-}
-
 LayoutUnit NGBlockLayoutAlgorithm::FragmentainerSpaceAvailable() const {
   return FragmentainerSpaceAtBfcStart(ConstraintSpace()) -
          container_builder_.BfcBlockOffset().value_or(
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h
index 45fc071..0e96f2a 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h
@@ -212,9 +212,6 @@
       NGInlineChildLayoutContext*,
       const NGInlineBreakToken** previous_inline_break_token);
 
-  // Performs any final adjustments for table-cells.
-  void FinalizeForTableCell(LayoutUnit unconstrained_intrinsic_block_size);
-
   // Return the amount of block space available in the current fragmentainer
   // for the node being laid out by this algorithm.
   LayoutUnit FragmentainerSpaceAvailable() const;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc
index 3049470..899b92f 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc
@@ -18,6 +18,7 @@
 #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
+#include "third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 
 namespace blink {
@@ -293,6 +294,7 @@
   if (const auto* token = BreakToken())
     previously_consumed_block_size = token->ConsumedBlockSize();
 
+  LayoutUnit unconstrained_intrinsic_block_size = intrinsic_block_size_;
   intrinsic_block_size_ =
       ClampIntrinsicBlockSize(ConstraintSpace(), Node(), BreakToken(),
                               BorderScrollbarPadding(), intrinsic_block_size_);
@@ -330,10 +332,10 @@
 #endif
   }
 
-  // TODO(mstensho): We need to do more here (vertical alignment, for instance),
-  // if this is a table cell.
-  if (ConstraintSpace().IsTableCell())
-    container_builder_.SetIsTableNGPart();
+  if (ConstraintSpace().IsTableCell()) {
+    NGTableAlgorithmUtils::FinalizeTableCellLayout(
+        unconstrained_intrinsic_block_size, &container_builder_);
+  }
 
   NGOutOfFlowLayoutPart(Node(), ConstraintSpace(), &container_builder_).Run();
 
@@ -1288,6 +1290,14 @@
     size = std::min(size, available_outer_space.ClampNegativeToZero());
   }
 
+  // Table-cell sizing is special. The aspects of specified block-size (and its
+  // min/max variants) that are actually honored by table cells is taken care of
+  // in the table layout algorithm. A constraint space with fixed block-size
+  // will be passed from the table layout algorithm if necessary. Leave it
+  // alone.
+  if (ConstraintSpace().IsTableCell())
+    return size;
+
   // The {,min-,max-}block-size properties are specified on the multicol
   // container, but here we're calculating the column block sizes inside the
   // multicol container, which isn't exactly the same. We may shrink the column
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.cc
index 4be9128e..6cc5f5b 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm.cc
@@ -631,6 +631,10 @@
 
   if (result->Status() == NGLayoutResult::kNeedsRelayoutAsLastTableBox)
     return RelayoutAsLastTableBox();
+  if (result->Status() == NGLayoutResult::kNeedsEarlierBreak) {
+    return RelayoutAndBreakEarlier<NGTableLayoutAlgorithm>(
+        *result->GetEarlyBreak());
+  }
 
   return result;
 }
@@ -1251,9 +1255,6 @@
     }
   }
 
-  if (!child_iterator.NextChild())
-    container_builder_.SetHasSeenAllChildren();
-
   if (table_box_extent) {
     // If we broke inside a section, the block-end border/padding shouldn't be
     // added to this fragment.
@@ -1289,7 +1290,23 @@
 
   if (pending_repeated_footer && table_box_extent) {
     DCHECK(table_box_will_continue);
-    // We broke before we got to the footer. Add it now.
+    // We broke before we got to the footer. Add it now. Before doing that,
+    // though, also insert break tokens for the sections that we didn't get to
+    // (if any), so that things will be resumed correctly when laying out the
+    // next table fragment (inserting a break token for the repeated footer
+    // alone would make the table child iterator skip any preceding sections).
+    auto entry = child_iterator.NextChild();
+    for (; NGBlockNode child = entry.GetNode();
+         entry = child_iterator.NextChild()) {
+      if (child == grouped_children.footer)
+        break;
+
+      auto* token = NGBlockBreakToken::CreateBreakBefore(
+          child, /* is_forced_break */ false);
+      container_builder_.AddBreakToken(token);
+    }
+    DCHECK_EQ(entry.GetNode(), grouped_children.footer);
+
     LogicalOffset offset(section_inline_offset, child_block_offset);
     NGConstraintSpace child_space = CreateSectionConstraintSpace(
         grouped_children.footer, offset.block_offset,
@@ -1317,6 +1334,9 @@
     }
   }
 
+  if (!child_iterator.NextChild())
+    container_builder_.SetHasSeenAllChildren();
+
   LayoutUnit column_block_size;
   LogicalRect table_grid_rect;
 
@@ -1385,7 +1405,9 @@
     NGBreakStatus status = FinishFragmentation(
         Node(), ConstraintSpace(), BlockEndBorderPadding(),
         FragmentainerSpaceAtBfcStart(ConstraintSpace()), &container_builder_);
-    // TODO(mstensho): Deal with early-breaks.
+    if (status == NGBreakStatus::kNeedsEarlierBreak)
+      return container_builder_.Abort(NGLayoutResult::kNeedsEarlierBreak);
+
     DCHECK_EQ(status, NGBreakStatus::kContinue);
 
     // Which side to include is normally handled by FinishFragmentation(), but
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.cc
index 79aefdc..9c32021 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.cc
@@ -7,8 +7,11 @@
 #include "third_party/blink/renderer/core/layout/geometry/logical_size.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_disable_side_effects_scope.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
@@ -694,6 +697,73 @@
                                   section_block_size, treat_section_as_tbody));
 }
 
+void NGTableAlgorithmUtils::FinalizeTableCellLayout(
+    LayoutUnit unconstrained_intrinsic_block_size,
+    NGBoxFragmentBuilder* builder) {
+  const NGBlockNode& node = builder->Node();
+  const NGConstraintSpace& space = builder->ConstraintSpace();
+  const bool has_inflow_children = !builder->Children().IsEmpty();
+
+  // Hide table-cells if:
+  //  - They are within a collapsed column(s).
+  //  - They have "empty-cells: hide", non-collapsed borders, and no children.
+  builder->SetIsHiddenForPaint(
+      space.IsTableCellHiddenForPaint() ||
+      (space.HideTableCellIfEmpty() && !has_inflow_children));
+
+  builder->SetHasCollapsedBorders(space.IsTableCellWithCollapsedBorders());
+
+  builder->SetIsTableNGPart();
+
+  builder->SetTableCellColumnIndex(space.TableCellColumnIndex());
+
+  // If we're resuming after a break, there'll be no alignment, since the
+  // fragment will start at the block-start edge of the fragmentainer then.
+  if (IsResumingLayout(builder->PreviousBreakToken()))
+    return;
+
+  switch (node.Style().VerticalAlign()) {
+    case EVerticalAlign::kTop:
+      // Do nothing for 'top' vertical alignment.
+      break;
+    case EVerticalAlign::kBaselineMiddle:
+    case EVerticalAlign::kSub:
+    case EVerticalAlign::kSuper:
+    case EVerticalAlign::kTextTop:
+    case EVerticalAlign::kTextBottom:
+    case EVerticalAlign::kLength:
+      // All of the above are treated as 'baseline' for the purposes of
+      // table-cell vertical alignment.
+    case EVerticalAlign::kBaseline:
+      // Table-cells (with baseline vertical alignment) always produce a
+      // baseline of their end-content edge (even if the content doesn't have
+      // any baselines).
+      if (!builder->Baseline() || node.ShouldApplyLayoutContainment()) {
+        builder->SetBaseline(unconstrained_intrinsic_block_size -
+                             builder->BorderScrollbarPadding().block_end);
+      }
+
+      // Only adjust if we have *inflow* children. If we only have
+      // OOF-positioned children don't align them to the alignment baseline.
+      if (has_inflow_children) {
+        if (auto alignment_baseline = space.TableCellAlignmentBaseline()) {
+          builder->MoveChildrenInBlockDirection(*alignment_baseline -
+                                                *builder->Baseline());
+        }
+      }
+      break;
+    case EVerticalAlign::kMiddle:
+      builder->MoveChildrenInBlockDirection(
+          (builder->FragmentBlockSize() - unconstrained_intrinsic_block_size) /
+          2);
+      break;
+    case EVerticalAlign::kBottom:
+      builder->MoveChildrenInBlockDirection(builder->FragmentBlockSize() -
+                                            unconstrained_intrinsic_block_size);
+      break;
+  };
+}
+
 void NGColspanCellTabulator::StartRow() {
   current_column_ = 0;
 }
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.h b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.h
index 83a4c96..0ebddb3c1 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.h
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.h
@@ -83,6 +83,11 @@
       NGTableTypes::Sections* sections,
       NGTableTypes::Rows* rows,
       NGTableTypes::CellBlockConstraints* cell_block_constraints);
+
+  // Performs any final adjustments for table-cells at the end of layout.
+  static void FinalizeTableCellLayout(
+      LayoutUnit unconstrained_intrinsic_block_size,
+      NGBoxFragmentBuilder*);
 };
 
 // NGColspanCellTabulator keeps track of columns occupied by colspanned cells
diff --git a/third_party/blink/renderer/core/page/context_menu_controller.cc b/third_party/blink/renderer/core/page/context_menu_controller.cc
index 857d783b..8835cf4 100644
--- a/third_party/blink/renderer/core/page/context_menu_controller.cc
+++ b/third_party/blink/renderer/core/page/context_menu_controller.cc
@@ -559,7 +559,8 @@
         WebString text = plugin->SelectionAsText();
         if (!text.IsEmpty()) {
           data.selected_text = text.Utf8();
-          data.edit_flags |= ContextMenuDataEditFlags::kCanCopy;
+          if (plugin->CanCopy())
+            data.edit_flags |= ContextMenuDataEditFlags::kCanCopy;
         }
         bool plugin_can_edit_text = plugin->CanEditText();
         if (plugin_can_edit_text) {
@@ -766,19 +767,25 @@
   if (from_touch && !ShouldShowContextMenuFromTouch(data))
     return false;
 
-  absl::optional<gfx::Point> host_context_menu_location;
-  auto* main_frame =
-      WebLocalFrameImpl::FromFrame(DynamicTo<LocalFrame>(page_->MainFrame()));
-  if (main_frame) {
-    host_context_menu_location =
-        main_frame->FrameWidgetImpl()->GetAndResetContextMenuLocation();
-  }
-
   WebLocalFrameImpl* selected_web_frame =
       WebLocalFrameImpl::FromFrame(selected_frame);
   if (!selected_web_frame || !selected_web_frame->Client())
     return false;
 
+  absl::optional<gfx::Point> host_context_menu_location;
+  if (selected_web_frame->FrameWidgetImpl()) {
+    host_context_menu_location =
+        selected_web_frame->FrameWidgetImpl()->GetAndResetContextMenuLocation();
+  }
+  if (!host_context_menu_location.has_value()) {
+    auto* main_frame =
+        WebLocalFrameImpl::FromFrame(DynamicTo<LocalFrame>(page_->MainFrame()));
+    if (main_frame && main_frame != selected_web_frame) {
+      host_context_menu_location =
+          main_frame->FrameWidgetImpl()->GetAndResetContextMenuLocation();
+    }
+  }
+
   selected_web_frame->ShowContextMenu(
       context_menu_client_receiver_.BindNewEndpointAndPassRemote(
           selected_web_frame->GetTaskRunner(TaskType::kInternalDefault)),
diff --git a/third_party/blink/renderer/core/page/context_menu_controller_test.cc b/third_party/blink/renderer/core/page/context_menu_controller_test.cc
index 66dea2d0..c8cca3cd 100644
--- a/third_party/blink/renderer/core/page/context_menu_controller_test.cc
+++ b/third_party/blink/renderer/core/page/context_menu_controller_test.cc
@@ -4,6 +4,10 @@
 
 #include "third_party/blink/renderer/core/page/context_menu_controller.h"
 
+#include <algorithm>
+#include <limits>
+#include <utility>
+
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
@@ -30,7 +34,7 @@
 #include "third_party/blink/renderer/core/html/html_document.h"
 #include "third_party/blink/renderer/core/html/media/html_video_element.h"
 #include "third_party/blink/renderer/core/input/context_menu_allowed_scope.h"
-#include "third_party/blink/renderer/core/page/context_menu_controller.h"
+#include "third_party/blink/renderer/core/page/focus_controller.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
@@ -63,8 +67,9 @@
  public:
   void UpdateContextMenuDataForTesting(
       const ContextMenuData& data,
-      const absl::optional<gfx::Point>&) override {
+      const absl::optional<gfx::Point>& host_context_menu_location) override {
     context_menu_data_ = data;
+    host_context_menu_location_ = host_context_menu_location;
   }
 
   WebMediaPlayer* CreateMediaPlayer(
@@ -82,8 +87,13 @@
     return context_menu_data_;
   }
 
+  const absl::optional<gfx::Point>& host_context_menu_location() const {
+    return host_context_menu_location_;
+  }
+
  private:
   ContextMenuData context_menu_data_;
+  absl::optional<gfx::Point> host_context_menu_location_;
 };
 
 void RegisterMockedImageURLLoad(const String& url) {
@@ -92,13 +102,13 @@
       test::CoreTestDataPath(kTestResourceFilename), kTestResourceMimeType);
 }
 
-}  // anonymous namespace
+}  // namespace
 
 class ContextMenuControllerTest : public testing::Test,
                                   public ::testing::WithParamInterface<bool> {
  public:
-  explicit ContextMenuControllerTest(
-      bool penetrating_image_selection_enabled = GetParam()) {
+  ContextMenuControllerTest() {
+    bool penetrating_image_selection_enabled = GetParam();
     feature_list_.InitWithFeatureState(
         features::kEnablePenetratingImageSelection,
         penetrating_image_selection_enabled);
@@ -1811,4 +1821,64 @@
 
 // TODO(crbug.com/1184996): Add additional unit test for blocking frame logging.
 
+class ContextMenuControllerRemoteParentFrameTest
+    : public testing::Test,
+      public ::testing::WithParamInterface<bool> {
+ public:
+  ContextMenuControllerRemoteParentFrameTest() {
+    bool penetrating_image_selection_enabled = GetParam();
+    feature_list_.InitWithFeatureState(
+        features::kEnablePenetratingImageSelection,
+        penetrating_image_selection_enabled);
+  }
+
+  void SetUp() override {
+    web_view_helper_.InitializeRemote();
+    web_view_helper_.RemoteMainFrame()->View()->DisableAutoResizeForTesting(
+        gfx::Size(640, 480));
+
+    child_frame_ = web_view_helper_.CreateLocalChild(
+        *web_view_helper_.RemoteMainFrame(),
+        /*name=*/"child",
+        /*properties=*/{},
+        /*previous_sibling=*/nullptr, &child_web_frame_client_);
+    frame_test_helpers::LoadFrame(child_frame_, "data:text/html,some page");
+
+    auto& focus_controller =
+        child_frame_->GetFrame()->GetPage()->GetFocusController();
+    focus_controller.SetActive(true);
+    focus_controller.SetFocusedFrame(child_frame_->GetFrame());
+  }
+
+  void ShowContextMenu(const gfx::Point& point) {
+    child_frame_->LocalRootFrameWidget()->ShowContextMenu(
+        ui::mojom::MenuSourceType::MOUSE, point);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  const TestWebFrameClientImpl& child_web_frame_client() const {
+    return child_web_frame_client_;
+  }
+
+ protected:
+  base::test::ScopedFeatureList feature_list_;
+  TestWebFrameClientImpl child_web_frame_client_;
+  frame_test_helpers::WebViewHelper web_view_helper_;
+  Persistent<WebLocalFrameImpl> child_frame_;
+};
+
+INSTANTIATE_TEST_SUITE_P(,
+                         ContextMenuControllerRemoteParentFrameTest,
+                         ::testing::Bool());
+
+TEST_P(ContextMenuControllerRemoteParentFrameTest, ShowContextMenuInChild) {
+  const gfx::Point kPoint(123, 234);
+  ShowContextMenu(kPoint);
+
+  const absl::optional<gfx::Point>& host_context_menu_location =
+      child_web_frame_client().host_context_menu_location();
+  ASSERT_TRUE(host_context_menu_location.has_value());
+  EXPECT_EQ(kPoint, host_context_menu_location.value());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc
index b2a8a140..78df104c 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc
@@ -37,23 +37,6 @@
   }
 };
 
-HeapVector<Member<ScrollTimelineOffset>> CreateScrollOffsets(
-    ScrollTimelineOffset* start_scroll_offset =
-        MakeGarbageCollected<ScrollTimelineOffset>(
-            CSSNumericLiteralValue::Create(
-                10.0,
-                CSSPrimitiveValue::UnitType::kPixels)),
-    ScrollTimelineOffset* end_scroll_offset =
-        MakeGarbageCollected<ScrollTimelineOffset>(
-            CSSNumericLiteralValue::Create(
-                90.0,
-                CSSPrimitiveValue::UnitType::kPixels))) {
-  HeapVector<Member<ScrollTimelineOffset>> scroll_offsets;
-  scroll_offsets.push_back(start_scroll_offset);
-  scroll_offsets.push_back(end_scroll_offset);
-  return scroll_offsets;
-}
-
 }  // namespace
 
 #if BUILDFLAG(IS_FUCHSIA)
@@ -1440,15 +1423,11 @@
 
 class ScrollTimelineForTest : public ScrollTimeline {
  public:
-  ScrollTimelineForTest(Document* document,
-                        Element* scroll_source,
-                        HeapVector<Member<ScrollTimelineOffset>>
-                            scroll_offsets = CreateScrollOffsets())
+  ScrollTimelineForTest(Document* document, Element* scroll_source)
       : ScrollTimeline(document,
                        ScrollTimeline::ReferenceType::kSource,
                        scroll_source,
-                       ScrollTimeline::kVertical,
-                       std::move(scroll_offsets)),
+                       ScrollTimeline::kVertical),
         invalidated_(false) {}
   void Invalidate() override {
     ScrollTimeline::Invalidate();
diff --git a/third_party/blink/renderer/modules/animationworklet/worklet_animation.cc b/third_party/blink/renderer/modules/animationworklet/worklet_animation.cc
index 8eaaac3..0f678110 100644
--- a/third_party/blink/renderer/modules/animationworklet/worklet_animation.cc
+++ b/third_party/blink/renderer/modules/animationworklet/worklet_animation.cc
@@ -416,7 +416,8 @@
   // update the value in the next frame.
   if (IsActive(play_state_)) {
     for (auto& effect : effects_) {
-      effect->UpdateInheritedTime(absl::nullopt, absl::nullopt, false,
+      effect->UpdateInheritedTime(absl::nullopt,
+                                  /* at_scroll_timeline_boundary */ false,
                                   playback_rate_, kTimingUpdateOnDemand);
     }
   }
@@ -503,7 +504,7 @@
         local_times_[i]
             ? absl::make_optional(AnimationTimeDelta(local_times_[i].value()))
             : absl::nullopt,
-        absl::nullopt, false, playback_rate_, reason);
+        /* at_scroll_timeline_boundary */ false, playback_rate_, reason);
   }
 }
 
diff --git a/third_party/blink/renderer/modules/animationworklet/worklet_animation_effect.cc b/third_party/blink/renderer/modules/animationworklet/worklet_animation_effect.cc
index 92687d2..5a668793 100644
--- a/third_party/blink/renderer/modules/animationworklet/worklet_animation_effect.cc
+++ b/third_party/blink/renderer/modules/animationworklet/worklet_animation_effect.cc
@@ -38,8 +38,7 @@
       local_time = AnimationTimeDelta(local_time_.value());
     }
     calculated_ = specified_timing_.CalculateTimings(
-        local_time, /*timeline_phase*/ absl::nullopt,
-        /*at_progress_timeline_boundary*/ false, normalized_timing_,
+        local_time, /*at_progress_timeline_boundary*/ false, normalized_timing_,
         Timing::AnimationDirection::kForwards, false, playback_rate);
   }
 
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
index 2e4dfc3..6167f1e 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
@@ -348,11 +348,6 @@
   }
   // https://w3c.github.io/webrtc-svc/
   if (encoding->hasScalabilityMode()) {
-    if (encoding->scalabilityMode() == "L1T2") {
-      webrtc_encoding.num_temporal_layers = 2;
-    } else if (encoding->scalabilityMode() == "L1T3") {
-      webrtc_encoding.num_temporal_layers = 3;
-    }
     webrtc_encoding.scalability_mode = encoding->scalabilityMode().Utf8();
   }
   webrtc_encoding.adaptive_ptime = encoding->adaptivePtime();
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_impl.cc b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_impl.cc
index b17bb55..cc3df85 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_impl.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_impl.cc
@@ -289,8 +289,6 @@
       new_parameters.encodings[i].rid = encoding.rid;
       new_parameters.encodings[i].scale_resolution_down_by =
           encoding.scale_resolution_down_by;
-      new_parameters.encodings[i].num_temporal_layers =
-          encoding.num_temporal_layers;
       new_parameters.encodings[i].scalability_mode = encoding.scalability_mode;
       new_parameters.encodings[i].adaptive_ptime = encoding.adaptive_ptime;
     }
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.cc b/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.cc
index 0adc01b3..15e9e49 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.cc
@@ -131,9 +131,16 @@
   }
 }
 
-WGPUDepthStencilState AsDawnType(const GPUDepthStencilState* webgpu_desc) {
+WGPUDepthStencilState AsDawnType(GPUDevice* device,
+                                 const GPUDepthStencilState* webgpu_desc,
+                                 ExceptionState& exception_state) {
   DCHECK(webgpu_desc);
 
+  if (!device->ValidateTextureFormatUsage(webgpu_desc->format(),
+                                          exception_state)) {
+    return {};
+  }
+
   WGPUDepthStencilState dawn_desc = {};
   dawn_desc.nextInChain = nullptr;
   dawn_desc.format = AsDawnEnum(webgpu_desc->format());
@@ -307,7 +314,8 @@
 
   // DepthStencil
   if (webgpu_desc->hasDepthStencil()) {
-    dawn_desc_info->depth_stencil = AsDawnType(webgpu_desc->depthStencil());
+    dawn_desc_info->depth_stencil =
+        AsDawnType(device, webgpu_desc->depthStencil(), exception_state);
     dawn_desc_info->dawn_desc.depthStencil = &dawn_desc_info->depth_stencil;
   }
 
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 9353a27..37f7d88 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -1796,6 +1796,8 @@
 
 crbug.com/1303102 external/wpt/css/css-images/object-view-box* [ Skip ]
 
+crbug.com/1303102 virtual/document-transition/wpt_internal/document-transition/new-content-with-overflow-zoomed.html [ Skip ]
+crbug.com/1303102 virtual/document-transition/wpt_internal/document-transition/old-content-with-overflow-zoomed.html [ Skip ]
 crbug.com/1321217 virtual/document-transition/wpt_internal/document-transition/old-content-with-overflow.html [ Skip ]
 crbug.com/1321217 virtual/document-transition/wpt_internal/document-transition/new-content-with-overflow.html [ Skip ]
 crbug.com/1303102 virtual/document-transition/wpt_internal/document-transition/no-root-capture.html [ Skip ]
diff --git a/third_party/blink/web_tests/FlagExpectations/highdpi b/third_party/blink/web_tests/FlagExpectations/highdpi
index ab21373..4201be0 100644
--- a/third_party/blink/web_tests/FlagExpectations/highdpi
+++ b/third_party/blink/web_tests/FlagExpectations/highdpi
@@ -1376,6 +1376,8 @@
 crbug.com/1295281 virtual/document-transition/wpt_internal/document-transition/old-content-object-fit-fill.html [ Failure ]
 crbug.com/1295281 virtual/document-transition/wpt_internal/document-transition/new-content-object-fit-fill.html [ Failure ]
 crbug.com/1329180 virtual/document-transition/wpt_internal/document-transition/new-and-old-sizes-match.html [ Failure ]
+crbug.com/1295280 virtual/document-transition/wpt_internal/document-transition/old-content-with-overflow-zoomed.html [ Failure ]
+crbug.com/1295280 virtual/document-transition/wpt_internal/document-transition/new-content-with-overflow-zoomed.html [ Failure ]
 
 crbug.com/1314903 external/wpt/css/css-sizing/contain-intrinsic-size/animation/contain-intrinsic-size-interpolation.html [ Failure ]
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 6f64c12..bc4867aa 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1610,7 +1610,9 @@
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/hit-test.tentative.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/inline-block.tentative.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/multicol.tentative.html [ Pass ]
+virtual/layout_ng_table_frag/external/wpt/css/css-break/table/repeated-section/multiple-row-groups.tentative.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/table-border-005.html [ Pass ]
+virtual/layout_ng_table_frag/external/wpt/css/css-break/table/table-border-006.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/table-cell-expansion-003.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/table-col-paint-htb-ltr.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/table-collapsed-borders-paint-at-boundary.tentative.html [ Pass ]
@@ -1620,14 +1622,14 @@
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/table-row-paint-vlr-rtl.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/table-row-paint-vrl-rtl.html [ Pass ]
 virtual/layout_ng_table_frag/external/wpt/css/css-break/table/table-section-paint-vrl-rtl.html [ Pass ]
+virtual/layout_ng_table_frag/external/wpt/css/css-multicol/table/table-cell-multicol-nested-002.html [ Pass ]
+virtual/layout_ng_table_frag/external/wpt/css/css-multicol/table/table-cell-multicol-nested-003.html [ Pass ]
 virtual/layout_ng_table_frag/fragmentation/repeating-thead-under-repeating-thead.html [ Pass ]
 virtual/layout_ng_table_frag/fragmentation/single-line-cells-repeating-thead-cell-straddles-page-unsplittable-div.html [ Pass ]
 
 ### Tests failing with LayoutNGTableFragmentation enabled:
 crbug.com/1295905 [ Mac11-arm64 ] virtual/layout_ng_table_frag/external/wpt/css/css-break/table/sections-and-captions-mixed-order.html [ Failure ]
 crbug.com/1078927 virtual/layout_ng_table_frag/external/wpt/css/css-multicol/table/balance-table-with-border-spacing.html [ Failure ]
-crbug.com/1078927 virtual/layout_ng_table_frag/external/wpt/css/css-multicol/table/multicol-table-cell-height-002.xht [ Failure ]
-crbug.com/1078927 virtual/layout_ng_table_frag/external/wpt/css/css-multicol/table/multicol-table-cell-vertical-align-001.xht [ Failure ]
 crbug.com/1078927 virtual/layout_ng_table_frag/fragmentation/border-spacing-break-before-unbreakable-row.html [ Failure ]
 crbug.com/1078927 virtual/layout_ng_table_frag/fragmentation/break-in-second-table-section.html [ Failure ]
 crbug.com/1078927 virtual/layout_ng_table_frag/fragmentation/break-in-tbody-after-caption.html [ Failure ]
@@ -1644,7 +1646,7 @@
 crbug.com/1078927 virtual/layout_ng_table_frag/fragmentation/single-line-cells-multiple-tables-caption-repeating-thead-tfoot-with-border-spacing-at-top-of-row-3.html [ Failure ]
 crbug.com/1078927 virtual/layout_ng_table_frag/fragmentation/single-line-cells-multiple-tables-caption-repeating-thead-tfoot-with-border-spacing-at-top-of-row-4.html [ Failure ]
 crbug.com/1078927 virtual/layout_ng_table_frag/fragmentation/single-line-cells-multiple-tables-caption-repeating-thead-tfoot-with-border-spacing-at-top-of-row.html [ Failure ]
-crbug.com/1078927 virtual/layout_ng_table_frag/fragmentation/single-line-cells-multiple-tables-repeating-thead-with-border-spacing-at-top-of-row.html [ Crash Pass ]
+crbug.com/1335870 virtual/layout_ng_table_frag/fragmentation/single-line-cells-multiple-tables-repeating-thead-with-border-spacing-at-top-of-row.html [ Failure ]
 crbug.com/1078927 virtual/layout_ng_table_frag/fragmentation/single-line-cells-nested-repeating-thead-2.html [ Failure ]
 crbug.com/1078927 virtual/layout_ng_table_frag/fragmentation/single-line-cells-repeating-thead-cell-straddles-page.html [ Failure ]
 crbug.com/1078927 virtual/layout_ng_table_frag/fragmentation/single-line-cells-repeating-thead-starts-middle-of-page-break-after-avoid-3.html [ Failure ]
@@ -2213,6 +2215,15 @@
 
 crbug.com/1067031 external/wpt/css/css-overflow/webkit-line-clamp-035.html [ Failure ]
 
+crbug.com/1339525 [ Win ] external/wpt/css/css-shapes/shape-outside/values/shape-margin-001.html [ Pass Failure ]
+crbug.com/1339525 [ Win ] external/wpt/css/css-shapes/shape-outside/values/shape-outside-circle-004.html [ Pass Failure ]
+crbug.com/1339525 [ Win ] external/wpt/css/css-shapes/shape-outside/values/shape-outside-circle-005.html [ Pass Failure ]
+crbug.com/1339525 [ Win ] external/wpt/css/css-shapes/shape-outside/values/shape-outside-ellipse-004.html [ Pass Failure ]
+crbug.com/1339525 [ Win ] external/wpt/css/css-shapes/shape-outside/values/shape-outside-ellipse-005.html [ Pass Failure ]
+crbug.com/1339525 [ Win ] external/wpt/css/css-shapes/shape-outside/values/shape-outside-inset-003.html [ Pass Failure ]
+crbug.com/1339525 [ Win ] external/wpt/css/css-shapes/shape-outside/values/shape-outside-polygon-004.html [ Pass Failure ]
+crbug.com/1339525 [ Win ] external/wpt/css/css-shapes/shape-outside/values/shape-outside-shape-arguments-000.html [ Pass Failure ]
+
 crbug.com/424365 external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-024.html [ Failure ]
 crbug.com/1129522 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-008.html [ Failure ]
 crbug.com/1129522 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-006.html [ Failure ]
@@ -3935,7 +3946,9 @@
 crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/hit-test.tentative.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/inline-block.tentative.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/multicol.tentative.html [ Failure ]
+crbug.com/1078927 external/wpt/css/css-break/table/repeated-section/multiple-row-groups.tentative.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/table-border-005.html [ Failure ]
+crbug.com/1078927 external/wpt/css/css-break/table/table-border-006.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/table-cell-expansion-003.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/table-col-paint-htb-ltr.html [ Failure ]
 crbug.com/1078927 external/wpt/css/css-break/table/table-collapsed-borders-paint-at-boundary.tentative.html [ Failure ]
@@ -3976,6 +3989,8 @@
 crbug.com/481431 external/wpt/css/css-multicol/multicol-zero-height-003.html [ Failure ]
 crbug.com/1191124 external/wpt/css/css-multicol/spanner-fragmentation-012.html [ Failure ]
 crbug.com/1224888 external/wpt/css/css-multicol/spanner-in-opacity.html [ Failure ]
+crbug.com/1078927 external/wpt/css/css-multicol/table/table-cell-multicol-nested-002.html [ Failure ]
+crbug.com/1078927 external/wpt/css/css-multicol/table/table-cell-multicol-nested-003.html [ Failure ]
 crbug.com/849459 fragmentation/repeating-thead-under-repeating-thead.html [ Failure ]
 crbug.com/1078927 fragmentation/single-line-cells-repeating-thead-cell-straddles-page-unsplittable-div.html [ Failure ]
 
@@ -7071,7 +7086,3 @@
 crbug.com/1339293 [ Linux ] virtual/threaded-preload-scanner/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/029.html [ Failure Pass ]
 crbug.com/1339293 [ Linux ] virtual/threaded/external/wpt/requestidlecallback/deadline-max-rAF-dynamic.html [ Failure Pass ]
 
-crbug.com/1339755 [ Mac10.13 ] compositing/overflow/nested-render-surfaces-with-rotation.html [ Skip ]
-crbug.com/1339755 [ Mac10.13 ] media/video-zoom-controls.html [ Skip ]
-crbug.com/1339755 [ Mac10.13 ] virtual/oopr-canvas2d/fast/canvas/image-object-in-canvas.html [ Skip ]
-crbug.com/1339755 [ Mac10.13 ] virtual/oopr-canvas2d/fast/canvas/quadraticCurveTo.xml [ Skip ]
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/support/fedcm-manifest-not-in-list/fedcm.json b/third_party/blink/web_tests/external/wpt/credential-management/support/fedcm-manifest-not-in-list/fedcm.json
index 9d919790..c044a7f 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/support/fedcm-manifest-not-in-list/fedcm.json
+++ b/third_party/blink/web_tests/external/wpt/credential-management/support/fedcm-manifest-not-in-list/fedcm.json
@@ -2,5 +2,4 @@
   "accounts_endpoint": "../accounts.py",
   "client_metadata_endpoint": "../client_metadata.py",
   "id_token_endpoint": "../id_token.py",
-  "revocation_endpoint": "../revoke.py"
 }
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/support/revoke.py b/third_party/blink/web_tests/external/wpt/credential-management/support/revoke.py
deleted file mode 100644
index ed6fe00d..0000000
--- a/third_party/blink/web_tests/external/wpt/credential-management/support/revoke.py
+++ /dev/null
@@ -1,6 +0,0 @@
-def main(request, response):
-  if not b"hint" in request.POST:
-    return (500, [], "Missing hint")
-  if request.POST[b"hint"] == b"fail":
-    return (500, [], "Fail requested")
-  return (204, [], "")
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/multiple-row-groups.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/multiple-row-groups.tentative.html
new file mode 100644
index 0000000..96e6173
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/table/repeated-section/multiple-row-groups.tentative.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-tables-3/#repeated-headers">
+<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="columns:4; gap:0; column-fill:auto; width:100px; height:100px; background:red;">
+  <div style="display:table; width:100%;">
+    <div style="display:table-footer-group; break-inside:avoid;">
+      <div style="height:10px; background:green;"></div>
+    </div>
+    <div style="display:table-row-group;">
+      <div style="height:150px; background:green;"></div>
+    </div>
+    <div style="display:table-row-group;">
+      <div style="height:20px; background:green;"></div>
+    </div>
+    <div style="display:table-row-group;">
+      <div style="height:90px; background:green;"></div>
+    </div>
+    <div style="display:table-row-group;">
+      <div style="height:100px; background:green;"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/table/table-border-006.html b/third_party/blink/web_tests/external/wpt/css/css-break/table/table-border-006.html
new file mode 100644
index 0000000..f0ebf25
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/table/table-border-006.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-break-3/#box-splitting">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:100px; height:100px; background:red;">
+  <div style="columns:2; gap:0; column-fill:auto; height:170px;">
+    <div style="display:table; width:100%; border-bottom:30px solid green;">
+      <div style="break-inside:avoid; display:table-row;">
+        <div style="height:100px; background:green;"></div>
+      </div>
+      <div style="break-inside:avoid; display:table-row;">
+        <div style="height:70px; background:green;"></div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-scroll-timeline-override.html b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-scroll-timeline-override.html
index 59b8590..abdbed0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-scroll-timeline-override.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-cascade/layer-scroll-timeline-override.html
@@ -7,7 +7,7 @@
 <script src="/web-animations/testcommon.js"></script>
 <style>
 #scroller {
-  overflow: scroll;
+  overflow: hidden;
   width: 100px;
   height: 100px;
 }
@@ -26,7 +26,7 @@
 }
 
 #reference {
-  width: 150px;
+  width: 125px;
 }
 
 #target {
@@ -54,15 +54,13 @@
 
       @scroll-timeline timeline {
         source: selector(#scroller);
-        start: 0px;
-        end: 50px;
+        orientation: block;
       }
 
       @layer {
         @scroll-timeline timeline {
           source: selector(#scroller);
-          start: 0px;
-          end: 100px;
+          orientation: inline;
         }
       }
     `
@@ -80,16 +78,14 @@
       @layer override {
         @scroll-timeline timeline {
           source: selector(#scroller);
-          start: 0px;
-          end: 50px;
+          orientation: block;
         }
       }
 
       @layer base {
         @scroll-timeline timeline {
           source: selector(#scroller);
-          start: 0px;
-          end: 100px;
+          orientation: inline;
         }
       }
     `
@@ -107,8 +103,7 @@
       @layer override {
         @scroll-timeline timeline {
           source: selector(#scroller);
-          start: 0px;
-          end: 50px;
+          orientation: block;
         }
       }
     `,
@@ -116,8 +111,7 @@
       @layer base {
         @scroll-timeline timeline {
           source: selector(#scroller);
-          start: 0px;
-          end: 100px;
+          orientation: inline;
         }
       }
     `
@@ -135,8 +129,7 @@
       @layer base {
         @scroll-timeline timeline {
           source: selector(#scroller);
-          start: 0px;
-          end: 100px;
+          orientation: inline;
         }
       }
     `,
@@ -144,8 +137,7 @@
       @layer override {
         @scroll-timeline timeline {
           source: selector(#scroller);
-          start: 0px;
-          end: 50px;
+          orientation: block;
         }
       }
     `
diff --git a/third_party/blink/web_tests/external/wpt/css/css-multicol/table/table-cell-multicol-nested-001.html b/third_party/blink/web_tests/external/wpt/css/css-multicol/table/table-cell-multicol-nested-001.html
new file mode 100644
index 0000000..5cb9eaf
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-multicol/table/table-cell-multicol-nested-001.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-multicol-1/#propdef-column-count">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<meta name="assert" content="Test that a balanced multicol table cell inside another fragmentation context is sized and fragmented correctly">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="columns:2; gap:0; column-fill:auto; width:100px; height:100px; background:red;">
+  <div style="height:50px; background:green;"></div>
+  <div style="display:table-cell; columns:2; gap:0; width:50px; background:red;">
+    <div style="height:200px; background:green;"></div>
+  </div>
+  <div style="height:50px; background:green;"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-multicol/table/table-cell-multicol-nested-002.html b/third_party/blink/web_tests/external/wpt/css/css-multicol/table/table-cell-multicol-nested-002.html
new file mode 100644
index 0000000..b250841
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-multicol/table/table-cell-multicol-nested-002.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-multicol-1/#propdef-column-count">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<meta name="assert" content="Test that a balanced multicol table cell inside another fragmentation context is sized and fragmented correctly - its height and max-height should be ignored, since the intrinsic size is larger">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="columns:2; gap:0; column-fill:auto; width:100px; height:100px; background:red;">
+  <div style="height:50px; background:green;"></div>
+  <div style="display:table-cell; columns:2; gap:0; height:10px; max-height:10px; width:50px; background:red;">
+    <div style="height:200px; background:green;"></div>
+  </div>
+  <div style="height:50px; background:green;"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-multicol/table/table-cell-multicol-nested-003.html b/third_party/blink/web_tests/external/wpt/css/css-multicol/table/table-cell-multicol-nested-003.html
new file mode 100644
index 0000000..f4e52910f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-multicol/table/table-cell-multicol-nested-003.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-multicol-1/#propdef-column-count">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<meta name="assert" content="Test that a balanced multicol table cell inside another fragmentation context is sized and fragmented correctly, and that it gets stretched by the specified height, which is larger than the intrinsic size">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="columns:2; gap:0; column-fill:auto; width:100px; height:100px; background:red;">
+  <div style="height:50px; background:green;"></div>
+  <div style="display:table-cell; columns:2; gap:0; height:100px; width:50px; background:red;">
+    <div style="height:150px; background:green;"></div>
+  </div>
+  <div style="margin-top:-25px; height:75px; background:green;"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-before-phase.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-before-phase.html
deleted file mode 100644
index d2a978b2..0000000
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-before-phase.html
+++ /dev/null
@@ -1,61 +0,0 @@
-<!DOCTYPE html>
-<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule">
-<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#phase-algorithm">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/web-animations/testcommon.js"></script>
-<style>
-  #scroller {
-    overflow: scroll;
-    width: 100px;
-    height: 100px;
-  }
-  #contents {
-    height: 200px;
-  }
-  @keyframes expand {
-    from { width: 100px; }
-    to { width: 200px; }
-  }
-  #element {
-    width: 0px;
-  }
-  /* Ensure stable expectations if feature is not supported */
-  @supports not (animation-timeline:foo) {
-    #element { animation-play-state: paused; }
-  }
-</style>
-<div id=scroller>
-  <div id=contents></div>
-</div>
-<div id=container></div>
-<script>
-  promise_test(async (t) => {
-    try {
-      // Make sure scroller has a layout box.
-      await waitForNextFrame();
-
-      container.innerHTML = `
-        <div id=element></div>
-        <style>
-          @scroll-timeline timeline {
-            source: selector(#scroller);
-            start: 50px;
-            end: 100px;
-          }
-          #element {
-            animation: expand 10s linear;
-            animation-timeline: timeline;
-          }
-        </style>
-      `;
-      // Animation should not apply in before phase.
-      assert_equals(getComputedStyle(element).width, '0px');
-      await waitForNextFrame();
-      // Animation should still not apply.
-      assert_equals(getComputedStyle(element).width, '0px');
-    } finally {
-      container.innerHTML = '';
-    }
-  }, 'Animation does not apply when timeline phase is before');
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-element-offsets.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-element-offsets.html
deleted file mode 100644
index f0301cf..0000000
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-element-offsets.html
+++ /dev/null
@@ -1,247 +0,0 @@
-<!DOCTYPE html>
-<title>@scroll-timeline: Element-based offsets</title>
-<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule">
-<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#element-based-offset-section">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/web-animations/testcommon.js"></script>
-<style>
-  #scroller {
-    overflow: scroll;
-    width: 100px;
-    height: 100px;
-  }
-  .filler {
-    height: 150px;
-    background-color: darkgray;
-  }
-  .offset {
-    height: 50px;
-    background-color: green;
-  }
-  @keyframes expand {
-    from { width: 100px; }
-    to { width: 200px; }
-  }
-  @scroll-timeline timeline_start_start {
-    source: selector(#scroller);
-    start: selector(#offset1);
-    end: selector(#offset2);
-  }
-  @scroll-timeline timeline_end_end {
-    source: selector(#scroller);
-    start: selector(#offset1) end;
-    end: selector(#offset2) end;
-  }
-  @scroll-timeline timeline_end_start {
-    source: selector(#scroller);
-    start: selector(#offset1) end;
-    end: selector(#offset2) start;
-  }
-  @scroll-timeline timeline_end_1_end {
-    source: selector(#scroller);
-    start: selector(#offset1) end 1;
-    end: selector(#offset2) end;
-  }
-  @scroll-timeline timeline_end_start_05 {
-    source: selector(#scroller);
-    start: selector(#offset1) end;
-    end: selector(#offset2) 0.5;
-  }
-  @scroll-timeline timeline_start_400px {
-    source: selector(#scroller);
-    start: selector(#offset1);
-    end: 400px;
-  }
-  @scroll-timeline timeline_50px_end {
-    source: selector(#scroller);
-    start: 50px;
-    end: selector(#offset2) end;
-  }
-  @scroll-timeline timeline_outside {
-    source: selector(#scroller);
-    start: selector(#offset_outside);
-    end: auto;
-  }
-  @scroll-timeline timeline_display_none {
-    source: selector(#scroller);
-    start: selector(#offset_display_none);
-    end: auto;
-  }
-  @scroll-timeline timeline_null_target {
-    source: selector(#scroller);
-    start: selector(#no_such_id);
-    end: selector(#no_such_id);
-  }
-
-  #container > div {
-    width: 0px;
-    animation-name: expand;
-    animation-duration: 10s;
-    animation-timing-function: linear;
-  }
-  /* Ensure stable expectations if feature is not supported */
-  @supports not (animation-timeline:foo) {
-    #container > div { animation-play-state: paused; }
-  }
-  #element_start_start { animation-timeline: timeline_start_start; }
-  #element_end_end { animation-timeline: timeline_end_end; }
-  #element_end_start { animation-timeline: timeline_end_start; }
-  #element_end_1_end { animation-timeline: timeline_end_1_end; }
-  #element_end_start_05 { animation-timeline: timeline_end_start_05; }
-  #element_start_400px { animation-timeline: timeline_start_400px; }
-  #element_50px_end { animation-timeline: timeline_50px_end; }
-  #element_outside { animation-timeline: timeline_outside; }
-  #element_display_none { animation-timeline: timeline_display_none; }
-  #element_null_target { animation-timeline: timeline_null_target; }
-</style>
-<div id=scroller>
-  <div id=contents>
-    <div class=filler></div>
-    <div class=offset id=offset1></div>
-    <div class=filler></div>
-    <div class=offset id=offset2></div>
-    <div class=filler></div>
-  </div>
-</div>
-<div id=offset_outside></div>
-<div id=offset_display_none style="display:none"></div>
-<div id=container>
-  <div id=element_start_start></div>
-  <div id=element_end_end></div>
-  <div id=element_end_start></div>
-  <div id=element_end_1_end></div>
-  <div id=element_end_start_05></div>
-  <div id=element_start_400px></div>
-  <div id=element_50px_end></div>
-  <div id=element_outside></div>
-  <div id=element_display_none></div>
-  <div id=element_null_target></div>
-</div>
-<script>
-
-  // The contents of the scroller looks approximately like this:
-  //
-  //  +-------+
-  //  |       |
-  //  | 150px | filler
-  //  |       |
-  //  +-------+
-  //  +-------+
-  //  | 50px  | #offset1
-  //  +-------+
-  //  +-------+
-  //  |       |
-  //  | 150px | filler
-  //  |       |
-  //  +-------+
-  //  +-------+
-  //  | 50px  | #offset2
-  //  +-------+
-  //  +-------+
-  //  |       |
-  //  | 150px | filler
-  //  |       |
-  //  +-------+
-  //
-  // The height of the scrollport is 100px.
-
-  // Scrolls top to 'offset', waits for a frame, then call the provided
-  // assertions function.
-  function test_scroll(element, offset, assertions, description) {
-    promise_test(async (t) => {
-      scroller.scrollTop = offset;
-      await waitForNextFrame();
-      assertions();
-    }, `${description} [${element.id}]`);
-  }
-
-  // Tests that the computed value of 'width' on element is the expected value
-  // after scrolling top to the specifed offset.
-  function test_width_at_scroll_top(element, offset, expected) {
-    test_scroll(element, offset, () => {
-      assert_equals(getComputedStyle(element).width, expected);
-    }, `Scroll at offset ${offset} updates animation correctly`);
-  }
-
-  // [200, 400]
-  test_width_at_scroll_top(element_start_start, 0, '0px');
-  test_width_at_scroll_top(element_start_start, 199, '0px');
-  test_width_at_scroll_top(element_start_start, 200, '100px');
-  test_width_at_scroll_top(element_start_start, 300, '150px');
-  test_width_at_scroll_top(element_start_start, 398, '199px');
-  test_width_at_scroll_top(element_start_start, 400, '0px');
-
-  // [50, 250]
-  test_width_at_scroll_top(element_end_end, 0, '0px');
-  test_width_at_scroll_top(element_end_end, 49, '0px');
-  test_width_at_scroll_top(element_end_end, 50, '100px');
-  test_width_at_scroll_top(element_end_end, 150, '150px');
-  test_width_at_scroll_top(element_end_end, 248, '199px');
-  test_width_at_scroll_top(element_end_end, 250, '0px');
-
-  // [50, 400]
-  test_width_at_scroll_top(element_end_start, 0, '0px');
-  test_width_at_scroll_top(element_end_start, 49, '0px');
-  test_width_at_scroll_top(element_end_start, 50, '100px');
-  test_width_at_scroll_top(element_end_start, 225, '150px');
-  test_width_at_scroll_top(element_end_start, 393, '198px');
-  test_width_at_scroll_top(element_end_start, 400, '0px');
-
-  // [100, 250]
-  test_width_at_scroll_top(element_end_1_end, 0, '0px');
-  test_width_at_scroll_top(element_end_1_end, 99, '0px');
-  test_width_at_scroll_top(element_end_1_end, 100, '100px');
-  test_width_at_scroll_top(element_end_1_end, 175, '150px');
-  test_width_at_scroll_top(element_end_1_end, 247, '198px');
-  test_width_at_scroll_top(element_end_1_end, 250, '0px');
-
-  // [50, 375]
-  test_width_at_scroll_top(element_end_start_05, 0, '0px');
-  test_width_at_scroll_top(element_end_start_05, 49, '0px');
-  test_width_at_scroll_top(element_end_start_05, 50, '100px');
-  test_width_at_scroll_top(element_end_start_05, 206, '148px');
-  test_width_at_scroll_top(element_end_start_05, 362, '196px');
-  test_width_at_scroll_top(element_end_start_05, 375, '0px');
-
-  // [200, 300]
-  test_width_at_scroll_top(element_start_400px, 0, '0px');
-  test_width_at_scroll_top(element_start_400px, 199, '0px');
-  test_width_at_scroll_top(element_start_400px, 200, '100px');
-  test_width_at_scroll_top(element_start_400px, 300, '150px');
-  test_width_at_scroll_top(element_start_400px, 398, '199px');
-  test_width_at_scroll_top(element_start_400px, 400, '0px');
-
-  // [50, 250]
-  test_width_at_scroll_top(element_50px_end, 0, '0px');
-  test_width_at_scroll_top(element_50px_end, 49, '0px');
-  test_width_at_scroll_top(element_50px_end, 50, '100px');
-  test_width_at_scroll_top(element_50px_end, 150, '150px');
-  test_width_at_scroll_top(element_50px_end, 248, '199px');
-  test_width_at_scroll_top(element_50px_end, 250, '0px');
-
-  // Offset not a decendant of scroller (=> no effect value)
-  test_width_at_scroll_top(element_outside, 0, '0px');
-  test_width_at_scroll_top(element_outside, 100, '0px');
-  test_width_at_scroll_top(element_outside, 200, '0px');
-  test_width_at_scroll_top(element_outside, 300, '0px');
-  test_width_at_scroll_top(element_outside, 400, '0px');
-  test_width_at_scroll_top(element_outside, 450, '0px');
-
-  // Target of element-based offset has no layout box (=> no effect value)
-  test_width_at_scroll_top(element_display_none, 0, '0px');
-  test_width_at_scroll_top(element_display_none, 100, '0px');
-  test_width_at_scroll_top(element_display_none, 200, '0px');
-  test_width_at_scroll_top(element_display_none, 300, '0px');
-  test_width_at_scroll_top(element_display_none, 400, '0px');
-  test_width_at_scroll_top(element_display_none, 450, '0px');
-
-  // Target of element-based offset is null (=> no effect value)
-  test_width_at_scroll_top(element_null_target, 0, '0px');
-  test_width_at_scroll_top(element_null_target, 100, '0px');
-  test_width_at_scroll_top(element_null_target, 200, '0px');
-  test_width_at_scroll_top(element_null_target, 300, '0px');
-  test_width_at_scroll_top(element_null_target, 400, '0px');
-  test_width_at_scroll_top(element_null_target, 450, '0px');
-
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-offset-invalidation.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-offset-invalidation.tentative.html
deleted file mode 100644
index 13d816a..0000000
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-offset-invalidation.tentative.html
+++ /dev/null
@@ -1,207 +0,0 @@
-<!DOCTYPE html>
-<title>@scroll-timeline element offset invalidation</title>
-<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#typedef-element-offset">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/web-animations/testcommon.js"></script>
-<style>
-  #scroller {
-    overflow: scroll;
-    width: 100px;
-    height: 100px;
-  }
-  #scroller > div {
-    height: 50px;
-  }
-  @keyframes expand {
-    from { width: 100px; }
-    to { width: 200px; }
-  }
-  @scroll-timeline timeline {
-    source: selector(#scroller);
-    start: selector(#offset1) end;
-    end: selector(#offset2) end;
-  }
-  #element {
-    width: 0px;
-    height: 20px;
-    animation: expand 1000s linear;
-    animation-timeline: timeline;
-  }
-  /* Ensure stable expectations if feature is not supported */
-  @supports not (animation-timeline:foo) {
-    #element { animation-play-state: paused; }
-  }
-</style>
-<div id=scroller></div>
-<div id=element></div>
-<p class=sibling1></p>
-<p class=sibling2></p>
-<script>
-
-  function setup() {
-    while (scroller.firstChild)
-      scroller.firstChild.remove();
-    for (let i = 0; i < 10; i++)
-      scroller.append(document.createElement('div'));
-  }
-
-  // The contents of the scroller look like this:
-  //
-  //  +-------+
-  //  | 50px  | div (0)
-  //  +-------+
-  //  +-------+
-  //  | 50px  | div (1)
-  //  +-------+
-  //  +-------+
-  //  | 50px  | div (2)
-  //  +-------+
-  //  +-------+
-  //  | 50px  | div (3)
-  //  +-------+
-  //  +-------+
-  //  | 50px  | div (4)
-  //  +-------+
-  //  +-------+
-  //  | 50px  | div (5)
-  //  +-------+
-  //  +-------+
-  //  | 50px  | div (6)
-  //  +-------+
-  //  +-------+
-  //  | 50px  | div (7)
-  //  +-------+
-  //  +-------+
-  //  | 50px  | div (8)
-  //  +-------+
-  //  +-------+
-  //  | 50px  | div (9)
-  //  +-------+
-  //
-  // The height of the scrollport is 100px.
-
-  function invalidation_test(func, description) {
-    promise_test(async (t) => {
-      setup();
-      await func();
-    }, description);
-  }
-
-  function remove(id) {
-    let old_element = document.getElementById(id);
-    if (old_element)
-      old_element.removeAttribute('id');
-  }
-
-  function reassign(id, element) {
-    remove(id);
-    element.setAttribute('id', id);
-  }
-
-  async function assert_element_width_at_scroll(expected_width, scroll) {
-    scroller.scrollTop = scroll;
-    await waitForNextFrame();
-    assert_equals(getComputedStyle(element).width, expected_width);
-  }
-
-  invalidation_test(async () => {
-    await assert_element_width_at_scroll('0px', 0);
-  }, 'Offsets missing');
-
-  invalidation_test(async () => {
-    // [50, 150]
-    reassign('offset1', scroller.children[3]);
-    reassign('offset2', scroller.children[5]);
-    await assert_element_width_at_scroll('150px', 100);
-
-    // [100, 150]
-    reassign('offset1', scroller.children[4]);
-    await assert_element_width_at_scroll('100px', 100);
-  }, 'Change first offset');
-
-  invalidation_test(async () => {
-    // [50, 150]
-    reassign('offset1', scroller.children[3]);
-    reassign('offset2', scroller.children[5]);
-    await assert_element_width_at_scroll('150px', 100);
-
-    // [50, 250]
-    reassign('offset2', scroller.children[7]);
-    await assert_element_width_at_scroll('125px', 100);
-  }, 'Change second offset');
-
-  invalidation_test(async () => {
-    // [50, 250]
-    reassign('offset1', scroller.children[3]);
-    reassign('offset2', scroller.children[7]);
-    await assert_element_width_at_scroll('125px', 100);
-
-    // [0, 200]
-    reassign('offset1', scroller.children[2]);
-    reassign('offset2', scroller.children[4]);
-    await assert_element_width_at_scroll('150px', 50);
-  }, 'Change both offsets');
-
-  invalidation_test(async () => {
-    // [50, 150]
-    reassign('offset1', scroller.children[3]);
-    reassign('offset2', scroller.children[5]);
-    await assert_element_width_at_scroll('150px', 100);
-
-    remove('offset1');
-    await assert_element_width_at_scroll('0px', 0);
-  }, 'Remove first offset');
-
-  invalidation_test(async () => {
-    // [50, 150]
-    reassign('offset1', scroller.children[3]);
-    reassign('offset2', scroller.children[5]);
-    await assert_element_width_at_scroll('150px', 100);
-
-    remove('offset2');
-    await assert_element_width_at_scroll('0px', 0);
-  }, 'Remove second offset');
-
-  invalidation_test(async () => {
-    // [50, 150]
-    reassign('offset1', scroller.children[3]);
-    reassign('offset2', scroller.children[5]);
-    await assert_element_width_at_scroll('150px', 100);
-
-    remove('offset1');
-    remove('offset2');
-    await assert_element_width_at_scroll('0px', 0);
-  }, 'Remove both offsets');
-
-  invalidation_test(async () => {
-    // [50, 150]
-    reassign('offset1', scroller.children[3]);
-    reassign('offset2', scroller.children[5]);
-    await assert_element_width_at_scroll('150px', 100);
-
-    reassign('offset1', document.querySelector('.sibling1'));
-    await assert_element_width_at_scroll('0px', 0);
-  }, 'Reassign first offset to sibling of scroller');
-
-  invalidation_test(async () => {
-    // [50, 150]
-    reassign('offset1', scroller.children[3]);
-    reassign('offset2', scroller.children[5]);
-    await assert_element_width_at_scroll('150px', 100);
-
-    reassign('offset2', document.querySelector('.sibling2'));
-    await assert_element_width_at_scroll('0px', 0);
-  }, 'Reassign second offset to sibling of scroller');
-
-  invalidation_test(async () => {
-    // [50, 150]
-    reassign('offset1', scroller.children[3]);
-    reassign('offset2', scroller.children[5]);
-    await assert_element_width_at_scroll('150px', 100);
-
-    reassign('offset1', document.querySelector('.sibling1'));
-    reassign('offset2', document.querySelector('.sibling2'));
-    await assert_element_width_at_scroll('0px', 0);
-  }, 'Reassign both offsets to sibling of scroller');
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/constructor.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/constructor.html
index 0ba2f53..c541256 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/constructor.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/constructor.html
@@ -92,193 +92,4 @@
   };
   assert_throws_js(TypeError, constructorFunc);
 }, 'Creating a ScrollTimeline with an invalid orientation value should throw');
-
-// scrollOffsets
-
-function formatOffset(v) {
-  if (typeof(v) == 'object')
-    return `${v.constructor.name}(${v.toString()})`;
-  return `'${v.toString()}'`;
-}
-
-function assert_offsets_equal(a, b) {
-  assert_equals(formatOffset(a), formatOffset(b));
-}
-
-test(t => {
-  assert_array_equals(new ScrollTimeline().scrollOffsets, []);
-}, 'A ScrollTimeline created with the default scrollOffsets should default ' +
-   'to []');
-
-test(t => {
-  assert_array_equals(
-      new ScrollTimeline({scrollOffsets: []}).scrollOffsets, []);
-}, 'A ScrollTimeline created with empty scrollOffsets should resolve to []');
-
-test(t => {
-  let offsets =
-      new ScrollTimeline({scrollOffsets: [CSS.percent(20), 'auto']})
-      .scrollOffsets;
-  assert_offsets_equal(offsets[0], CSS.percent(20));
-  assert_offsets_equal(offsets[1], new CSSKeywordValue('auto'));
-}, 'A ScrollTimeline created with last \'auto\' offset in scrollOffsets ' +
-   'should be allowed.');
-
-test(t => {
-  let constructorFunc = function() {
-    new ScrollTimeline({scrollOffsets: null})
-  };
-  assert_throws_js(TypeError, constructorFunc);
-}, 'Creating a ScrollTimeline with an invalid scrollOffsets value should ' +
-   'throw.');
-
-test(t => {
-  let constructorFunc = function() {
-    new ScrollTimeline(
-        {scrollOffsets: [CSS.percent(20), 'auto', CSS.percent(50)]})
-  };
-  assert_throws_js(TypeError, constructorFunc);
-}, 'Creating a ScrollTimeline with an scrollOffsets value of ' +
-   '[CSS.percent(20), \'auto\', CSS.percent(50)] should throw');
-
-const gValidScrollOffsetValues = [
-  CSS.px(0),
-  CSS.percent(100).sub(CSS.px(80)),
-];
-
-const gValidScrollOffsetSuffixes = [
-  // Relative lengths.
-  'em',
-  'ex',
-  'ch',
-  'rem',
-  'vw',
-  'vh',
-  'vmin',
-  'vmax',
-  // Absolute lengths.
-  'cm',
-  'mm',
-  'q',
-  'in',
-  'pc',
-  'pt',
-  'px',
-  // Percentage.
-  '%',
-];
-
-for (let offset of gValidScrollOffsetValues) {
-  test(function() {
-    const scrollTimeline =
-        new ScrollTimeline({scrollOffsets: [offset, offset]});
-
-    // Special case for 'auto'. This is valid input because of CSSKeywordish,
-    // but when read back we expect a real CSSKeywordValue.
-    if (offset === 'auto')
-      offset = new CSSKeywordValue('auto');
-
-    assert_offsets_equal(scrollTimeline.scrollOffsets[0], offset);
-    assert_offsets_equal(scrollTimeline.scrollOffsets[1], offset);
-  }, '\'' + offset + '\' is a valid scroll offset value');
-}
-
-for (const suffix of gValidScrollOffsetSuffixes) {
-  test(function() {
-    const offset = new CSSUnitValue(75, suffix);
-    const scrollTimeline =
-        new ScrollTimeline({scrollOffsets: [offset, offset]});
-
-    assert_offsets_equal(scrollTimeline.scrollOffsets[0], offset);
-    assert_offsets_equal(scrollTimeline.scrollOffsets[1], offset);
-  }, '\'' + suffix + '\' is a valid scroll offset unit');
-}
-
-// These are deliberately incomplete, just a random sampling of invalid
-// values/units.
-const gInvalidScrollOffsetValues = [
-  '',
-  'calc(360deg / 4)',
-  'left',
-  '#ff0000',
-  'rgb(0, 128, 0)',
-  'url("http://www.example.com/pinkish.gif")',
-  'this_is_garbage',
-  CSS.number(0),
-  // Multiple valid values.
-  '100px 5%',
-  // Values that would be valid if represented with CSS Typed OM:
-   0,
-   '10px',
-   '10%',
-   'calc(100% - 80px)',
-];
-
-const gInvalidScrollOffsetSuffixes = [
-  'deg',
-  's',
-  'Hz',
-  'dpi',
-];
-
-for (const offset of gInvalidScrollOffsetValues) {
-  test(function() {
-    const constructorFunc = function() {
-      new ScrollTimeline({scrollOffsets: ['0px', offset]})
-    };
-    assert_throws_js(TypeError, constructorFunc);
-  }, formatOffset(offset) + ' is an invalid scroll offset value in scrollOffsets');
-}
-
-for (const suffix of gInvalidScrollOffsetSuffixes) {
-  test(function() {
-    const offset = '75' + suffix;
-    const constructorFunc = function() {
-      new ScrollTimeline({scrollOffsets: ['0px', offset]});
-    };
-    assert_throws_js(TypeError, constructorFunc);
-  }, '\'' + suffix + '\' is an invalid scroll offset unit in scrollOffsets');
-}
-
-const offset_target = document.createElement('div');
-
-const gValidElementBasedScrollOffsetValues = [
-  {target: offset_target},
-  {target: offset_target, threshold: 0},
-  {target: offset_target, threshold: 0.5},
-  {target: offset_target, threshold: 1},
-];
-
-for (let offset of gValidElementBasedScrollOffsetValues) {
-  test(function() {
-    const scrollTimeline =
-        new ScrollTimeline({scrollOffsets: [offset, offset]});
-
-    // Special case unspecified threshold since it gets initialized to 0.
-    if (!offset.hasOwnProperty('threshold'))
-      offset.threshold = 0;
-
-    assert_equals(scrollTimeline.scrollOffsets[0].target, offset.target);
-    assert_equals(scrollTimeline.scrollOffsets[0].threshold, offset.threshold);
-    assert_equals(scrollTimeline.scrollOffsets[1].target, offset.target);
-    assert_equals(scrollTimeline.scrollOffsets[1].threshold, offset.threshold);
-  }, '\'' + JSON.stringify(offset) + '\' is a valid scroll offset value');
-}
-
-const gInvalidElementBasedScrollOffsetValues = [
-  {}, // empty
-  {target: offset_target, threshold: "test"},
-  {target: offset_target, threshold: 2},
-  {target: offset_target, threshold: -0.2},
-];
-
-for (let offset of gInvalidElementBasedScrollOffsetValues) {
-  test(function() {
-    const constructorFunc = function() {
-      new ScrollTimeline({scrollOffsets: [offset]})
-    };
-    assert_throws_js(TypeError, constructorFunc);
-  }, `'${JSON.stringify(offset)}' is an invalid scroll offset value in ` +
-     `scrollOffsets`);
-}
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time-writing-modes.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time-writing-modes.html
index ca66aa6..651d5fc4 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time-writing-modes.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time-writing-modes.html
@@ -145,228 +145,4 @@
   assert_percents_equal(verticalScrollTimeline.currentTime, 10,
                         'Scrolled vertical timeline');
 }, 'currentTime handles writing-mode: vertical-lr correctly');
-
-promise_test(async t => {
-  const scrollerOverrides = new Map([['direction', 'rtl']]);
-  const scroller = setupScrollTimelineTest(scrollerOverrides);
-  const horizontalScrollRange = scroller.scrollWidth - scroller.clientWidth;
-
-  const lengthScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'horizontal',
-    scrollOffsets: [CSS.px(20), 'auto']
-  });
-  const percentageScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'horizontal',
-    scrollOffsets: [CSS.percent(20), 'auto']
-  });
-  const calcScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'horizontal',
-    scrollOffsets: [CSS.percent(20).sub(CSS.px(5)), 'auto']
-  });
-
-  // Unscrolled, all timelines should read a current time of 0, since
-  // the current offset (0) will be less than the startScrollOffset.
-  assert_percents_equal(lengthScrollTimeline.currentTime, 0,
-                        'Unscrolled length-based timeline');
-  assert_percents_equal(percentageScrollTimeline.currentTime, 0,
-                        'Unscrolled percentage-based timeline');
-  assert_percents_equal(calcScrollTimeline.currentTime, 0,
-                        'Unscrolled calc-based timeline');
-
-  // With direction rtl offsets are inverted, such that scrollLeft == 0
-  // is the 'zero' point for currentTime. However the
-  // startScrollOffset is an absolute distance along the offset, so doesn't
-  // need adjusting.
-
-  // Check the length-based ScrollTimeline.
-  scroller.scrollLeft = 0;
-  // Wait for new animation frame  which allows the timeline to compute new
-  // current time.
-  await waitForNextFrame();
-  assert_percents_equal(lengthScrollTimeline.currentTime, 0,
-      'Length-based timeline before the startScrollOffset point');
-  scroller.scrollLeft = -20;
-  await waitForNextFrame();
-  assert_percents_equal(lengthScrollTimeline.currentTime, 0,
-      'Length-based timeline at the startScrollOffset point');
-  scroller.scrollLeft = -50;
-  await waitForNextFrame();
-  assert_percents_equal(
-      lengthScrollTimeline.currentTime,
-      calculateCurrentTime(50, 20, horizontalScrollRange),
-      'Length-based timeline after the startScrollOffset point');
-
-  // Check the percentage-based ScrollTimeline.
-  scroller.scrollLeft = -(0.19 * horizontalScrollRange);
-  await waitForNextFrame();
-  assert_percents_equal(percentageScrollTimeline.currentTime, 0,
-      'Percentage-based timeline before the startScrollOffset point');
-  scroller.scrollLeft = -(0.20 * horizontalScrollRange);
-  await waitForNextFrame();
-  assert_percents_equal(percentageScrollTimeline.currentTime, 0,
-      'Percentage-based timeline at the startScrollOffset point');
-  scroller.scrollLeft = -(0.4 * horizontalScrollRange);
-  await waitForNextFrame();
-  assert_percents_equal(
-      percentageScrollTimeline.currentTime,
-      calculateCurrentTime(
-          0.4 * horizontalScrollRange, 0.2 * horizontalScrollRange,
-          horizontalScrollRange),
-      'Percentage-based timeline after the startScrollOffset point');
-
-  // Check the calc-based ScrollTimeline.
-  scroller.scrollLeft = -(0.2 * horizontalScrollRange - 10);
-  await waitForNextFrame();
-  assert_percents_equal(calcScrollTimeline.currentTime, 0,
-      'Calc-based timeline before the startScrollOffset point');
-  scroller.scrollLeft = -(0.2 * horizontalScrollRange - 5);
-  await waitForNextFrame();
-  assert_percents_equal(calcScrollTimeline.currentTime, 0,
-      'Calc-based timeline at the startScrollOffset point');
-  scroller.scrollLeft = -(0.2 * horizontalScrollRange);
-  await waitForNextFrame();
-  assert_percents_equal(
-      calcScrollTimeline.currentTime,
-      calculateCurrentTime(
-          0.2 * horizontalScrollRange, 0.2 * horizontalScrollRange - 5,
-          horizontalScrollRange),
-      'Calc-based timeline after the startScrollOffset point');
-}, 'currentTime handles startScrollOffset with direction: rtl correctly');
-
-promise_test(async t => {
-  const scrollerOverrides = new Map([['direction', 'rtl']]);
-  const scroller = setupScrollTimelineTest(scrollerOverrides);
-  const horizontalScrollRange = scroller.scrollWidth - scroller.clientWidth;
-
-  const lengthScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'horizontal',
-    scrollOffsets: [CSS.px(horizontalScrollRange - 20)]
-  });
-  const percentageScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'horizontal',
-    scrollOffsets: [CSS.percent(80)]
-  });
-  const calcScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'horizontal',
-    scrollOffsets: [CSS.percent(80).add(CSS.px(5))]
-  });
-
-  // With direction rtl offsets are inverted, such that scrollLeft == 0
-  // is the 'zero' point for currentTime. However the
-  // endScrollOffset is an absolute distance along the offset, so doesn't need
-  // adjusting.
-
-  // Check the length-based ScrollTimeline.
-  scroller.scrollLeft = -horizontalScrollRange;
-  // Wait for new animation frame  which allows the timeline to compute new
-  // current time.
-  await waitForNextFrame();
-  assert_percents_equal(lengthScrollTimeline.currentTime, 100,
-      'Length-based timeline after the endScrollOffset point');
-  scroller.scrollLeft = 20 - horizontalScrollRange;
-  await waitForNextFrame();
-  assert_percents_equal(lengthScrollTimeline.currentTime, 100,
-      'Length-based timeline at the endScrollOffset point');
-  scroller.scrollLeft = 50 - horizontalScrollRange;
-  await waitForNextFrame();
-  assert_percents_equal(
-      lengthScrollTimeline.currentTime,
-      calculateCurrentTime(
-          horizontalScrollRange - 50, 0, horizontalScrollRange - 20),
-      'Length-based timeline before the endScrollOffset point');
-
-  // Check the percentage-based ScrollTimeline.
-  scroller.scrollLeft = 0.19 * horizontalScrollRange - horizontalScrollRange;
-  await waitForNextFrame();
-  assert_percents_equal(percentageScrollTimeline.currentTime, 100,
-      'Percentage-based timeline after the endScrollOffset point');
-  scroller.scrollLeft = 0.20 * horizontalScrollRange - horizontalScrollRange;
-  await waitForNextFrame();
-  assert_percents_equal(percentageScrollTimeline.currentTime, 100,
-      'Percentage-based timeline at the endScrollOffset point');
-  scroller.scrollLeft = 0.4 * horizontalScrollRange - horizontalScrollRange;
-  await waitForNextFrame();
-  assert_percents_equal(
-      percentageScrollTimeline.currentTime,
-      calculateCurrentTime(
-          0.6 * horizontalScrollRange, 0, 0.8 * horizontalScrollRange),
-      'Percentage-based timeline before the endScrollOffset point');
-
-  // Check the calc-based ScrollTimeline. 80% + 5px
-  scroller.scrollLeft = -0.8 * horizontalScrollRange - 10;
-  await waitForNextFrame();
-  assert_percents_equal(calcScrollTimeline.currentTime, 100,
-      'Calc-based timeline after the endScrollOffset point');
-  scroller.scrollLeft = -0.8 * horizontalScrollRange - 5;
-  await waitForNextFrame();
-  assert_percents_equal(calcScrollTimeline.currentTime, 100,
-      'Calc-based timeline at the endScrollOffset point');
-  scroller.scrollLeft = -0.8 * horizontalScrollRange;
-  await waitForNextFrame();
-  assert_percents_equal(
-      calcScrollTimeline.currentTime,
-      calculateCurrentTime(
-          0.8 * horizontalScrollRange, 0, 0.8 * horizontalScrollRange + 5),
-      'Calc-based timeline before the endScrollOffset point');
-}, 'currentTime handles endScrollOffset with direction: rtl correctly');
-
-promise_test(async t => {
-  const scrollerOverrides = new Map([['direction', 'rtl']]);
-  const scroller = setupScrollTimelineTest(scrollerOverrides);
-  const horizontalScrollRange = scroller.scrollWidth - scroller.clientWidth;
-
-  // When the endScrollOffset is equal to the maximum scroll offset (and there
-  // are no fill modes), the endScrollOffset is treated as inclusive.
-  const inclusiveAutoScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'inline',
-  });
-  const inclusiveLengthScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'inline',
-    scrollOffsets: [CSS.px(horizontalScrollRange)]
-  });
-  const inclusivePercentageScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'inline',
-    scrollOffsets: [CSS.percent(100)]
-  });
-  const inclusiveCalcScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'inline',
-    scrollOffsets: [CSS.percent(80).sub(CSS.px(0.2 * horizontalScrollRange))]
-  });
-
-  // With direction rtl offsets are inverted, such that scrollLeft ==
-  // horizontalScrollRange is the 'zero' point for currentTime. However the
-  // endScrollOffset is an absolute distance along the offset, so doesn't need
-  // adjusting.
-
-  scroller.scrollLeft = 0;
-  let expectedCurrentTime = calculateCurrentTime(
-      scroller.scrollLeft, 0, horizontalScrollRange);
-  // Wait for new animation frame  which allows the timeline to compute new
-  // current time.
-  await waitForNextFrame();
-
-  assert_percents_equal(
-      inclusiveAutoScrollTimeline.currentTime, expectedCurrentTime,
-      'Inclusive auto timeline at the endScrollOffset point');
-  assert_percents_equal(
-      inclusiveLengthScrollTimeline.currentTime, expectedCurrentTime,
-      'Inclusive length-based timeline at the endScrollOffset point');
-  assert_percents_equal(
-      inclusivePercentageScrollTimeline.currentTime, expectedCurrentTime,
-      'Inclusive percentage-based timeline at the endScrollOffset point');
-  assert_percents_equal(
-      inclusiveCalcScrollTimeline.currentTime, expectedCurrentTime,
-      'Inclusive calc-based timeline at the endScrollOffset point');
-}, 'currentTime handles endScrollOffset (inclusive case) with direction: rtl' +
-   ' correctly');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time.html
deleted file mode 100644
index f011b40c1..0000000
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/current-time.html
+++ /dev/null
@@ -1,417 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>ScrollTimeline current time algorithm</title>
-<link rel="help" href="https://wicg.github.io/scroll-animations/#current-time-algorithm">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/web-animations/testcommon.js"></script>
-<script src="./testcommon.js"></script>
-
-<body></body>
-
-<script>
-'use strict';
-
-promise_test(async t => {
-  const scroller = setupScrollTimelineTest();
-  const scrollTimeline = new ScrollTimeline({ source: scroller });
-
-  assert_equals(scrollTimeline.duration.unit, "percent",
-      "duration returns as a percent for scroll timelines");
-  assert_equals(scrollTimeline.duration.value, 100, "duration is 100%");
-}, "Scroll timeline correctly returns duration as 100%");
-
-promise_test(async t => {
-  const scroller = setupScrollTimelineTest();
-  const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
-
-  const blockScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block'
-  });
-  const inlineScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'inline'
-  });
-  const horizontalScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'horizontal'
-  });
-  const verticalScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'vertical'
-  });
-
-  // Unscrolled, all timelines should read a currentTime of 0.
-  assert_percents_equal(blockScrollTimeline.currentTime, 0,
-                        'Unscrolled block timeline');
-  assert_percents_equal(inlineScrollTimeline.currentTime, 0,
-                        'Unscrolled inline timeline');
-  assert_percents_equal(horizontalScrollTimeline.currentTime, 0,
-                        'Unscrolled horizontal timeline');
-  assert_percents_equal(verticalScrollTimeline.currentTime, 0,
-                        'Unscrolled vertical timeline');
-
-  // Do some scrolling and make sure that the ScrollTimelines update.
-  scroller.scrollTop = 50;
-  scroller.scrollLeft = 75;
-  // Wait for new animation frame  which allows the timeline to compute new
-  // current time.
-  await waitForNextFrame();
-
-  const inlineScrollRange = scroller.scrollWidth - scroller.clientWidth;
-  const expectedInlineCurrentTime =
-      100 * scroller.scrollLeft / inlineScrollRange;
-
-  const blockScrollRange = scroller.scrollHeight - scroller.clientHeight;
-  const expectedBlockCurrentTime =
-      100 * scroller.scrollTop / blockScrollRange;
-
-  assert_percents_approx_equal(blockScrollTimeline.currentTime,
-                               expectedBlockCurrentTime,
-                               blockScrollRange,
-                               'Scrolled block timeline');
-  assert_percents_approx_equal(inlineScrollTimeline.currentTime,
-                               expectedInlineCurrentTime,
-                               inlineScrollRange,
-                               'Scrolled inline timeline');
-  assert_percents_approx_equal(horizontalScrollTimeline.currentTime,
-                               expectedInlineCurrentTime,
-                               inlineScrollRange,
-                               'Scrolled horizontal timeline');
-  assert_percents_approx_equal(verticalScrollTimeline.currentTime,
-                               expectedBlockCurrentTime,
-                               blockScrollRange,
-                               'Scrolled vertical timeline');
-}, 'currentTime calculates the correct time based on scroll progress');
-
-
-promise_test(async t => {
-  const scroller = setupScrollTimelineTest();
-  const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
-
-  const lengthScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.px(20), 'auto']
-  });
-  const percentageScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.percent(20), 'auto']
-  });
-  const calcScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.percent(20).sub(CSS.px(5)), 'auto']
-  });
-
-  // Unscrolled all timelines should read a current time of 0, as the
-  // current offset (0) will be less than the startScrollOffset.
-  assert_percents_equal(lengthScrollTimeline.currentTime, 0,
-                        'Unscrolled length-based timeline current time');
-  assert_percents_equal(percentageScrollTimeline.currentTime, 0,
-                        'Unscrolled percentage-based timeline current time');
-  assert_percents_equal(calcScrollTimeline.currentTime, 0,
-                        'Unscrolled calc-based timeline current time');
-
-  // Check the length-based ScrollTimeline.
-  scroller.scrollTop = 19;
-  // Wait for new animation frame  which allows the timeline to compute new
-  // current time.
-  await waitForNextFrame();
-  assert_percents_equal(lengthScrollTimeline.currentTime, 0,
-      'Length-based timeline current time before the startScrollOffset point');
-  scroller.scrollTop = 20;
-  await waitForNextFrame();
-  assert_percents_equal(lengthScrollTimeline.currentTime, 0,
-      'Length-based timeline current time at the startScrollOffset point');
-  scroller.scrollTop = 50;
-  await waitForNextFrame();
-  assert_percents_equal(
-      lengthScrollTimeline.currentTime,
-      calculateCurrentTime(50, 20, scrollerSize),
-      'Length-based timeline current time after the startScrollOffset point');
-
-  // Check the percentage-based ScrollTimeline.
-  scroller.scrollTop = 0.19 * scrollerSize;
-  await waitForNextFrame();
-  assert_percents_equal(percentageScrollTimeline.currentTime, 0,
-      'Percentage-based timeline current time before the startScrollOffset ' +
-      'point');
-  scroller.scrollTop = 0.20 * scrollerSize;
-  await waitForNextFrame();
-  assert_percents_equal(percentageScrollTimeline.currentTime, 0,
-      'Percentage-based timeline current time at the startScrollOffset point');
-  scroller.scrollTop = 0.50 * scrollerSize;
-  await waitForNextFrame();
-  assert_percents_equal(
-      percentageScrollTimeline.currentTime,
-      calculateCurrentTime(
-          scroller.scrollTop, 0.2 * scrollerSize, scrollerSize),
-      'Percentage-based timeline current time after the startScrollOffset ' +
-      'point');
-
-  // Check the calc-based ScrollTimeline.
-  scroller.scrollTop = 0.2 * scrollerSize - 10;
-  await waitForNextFrame();
-  assert_percents_equal(calcScrollTimeline.currentTime, 0,
-      'Calc-based timeline current time before the startScrollOffset point');
-  scroller.scrollTop = 0.2 * scrollerSize - 5;
-  await waitForNextFrame();
-  assert_percents_equal(calcScrollTimeline.currentTime, 0,
-      'Calc-based timeline current time at the startScrollOffset point');
-  scroller.scrollTop = 0.2 * scrollerSize;
-  await waitForNextFrame();
-  assert_percents_equal(
-      calcScrollTimeline.currentTime,
-      calculateCurrentTime(
-          scroller.scrollTop, 0.2 * scrollerSize - 5, scrollerSize),
-      'Calc-based timeline current time after the startScrollOffset point');
-}, 'currentTime handles startScrollOffset correctly');
-
-promise_test(async t => {
-  const scroller = setupScrollTimelineTest();
-  const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
-
-  // When the endScrollOffset is equal to the maximum scroll offset (and there
-  // are no fill modes), the endScrollOffset is treated as inclusive.
-  const inclusiveAutoScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-  });
-  const inclusiveLengthScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.px(scrollerSize)]
-  });
-  const inclusivePercentageScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.percent(100)]
-  });
-  const inclusiveCalcScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.percent(80).add(CSS.px(0.2 * scrollerSize))]
-  });
-
-  scroller.scrollTop = scrollerSize;
-  let expectedCurrentTime = calculateCurrentTime(
-      scroller.scrollTop, 0, scrollerSize);
-
-  // Wait for new animation frame  which allows the timeline to compute new
-  // current time.
-  await waitForNextFrame();
-
-  assert_percents_equal(
-      inclusiveAutoScrollTimeline.currentTime, expectedCurrentTime,
-      'Inclusive auto timeline at the endScrollOffset point');
-  assert_percents_equal(
-      inclusiveLengthScrollTimeline.currentTime, expectedCurrentTime,
-      'Inclusive length-based timeline at the endScrollOffset point');
-  assert_percents_equal(
-      inclusivePercentageScrollTimeline.currentTime, expectedCurrentTime,
-      'Inclusive percentage-based timeline at the endScrollOffset point');
-  assert_percents_equal(
-      inclusiveCalcScrollTimeline.currentTime, expectedCurrentTime,
-      'Inclusive calc-based timeline at the endScrollOffset point');
-}, 'currentTime handles endScrollOffset correctly (inclusive cases)');
-
-promise_test(async t => {
-  const scroller = setupScrollTimelineTest();
-  const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
-
-  const lengthScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.px(scrollerSize - 20)]
-  });
-  const percentageScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.percent(80)]
-  });
-  const calcScrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.percent(80).add(CSS.px(5))]
-  });
-
-  // Check the length-based ScrollTimeline.
-  scroller.scrollTop = scrollerSize;
-  // Wait for new animation frame  which allows the timeline to compute new
-  // current time.
-  await waitForNextFrame();
-  assert_percents_equal(lengthScrollTimeline.currentTime, 100,
-      'Length-based timeline current time after the endScrollOffset point');
-  scroller.scrollTop = scrollerSize - 20;
-  await waitForNextFrame();
-  assert_percents_equal(lengthScrollTimeline.currentTime, 100,
-      'Length-based timeline current time at the endScrollOffset point');
-  scroller.scrollTop = scrollerSize - 50;
-  await waitForNextFrame();
-  assert_percents_equal(
-      lengthScrollTimeline.currentTime,
-      calculateCurrentTime(scrollerSize - 50, 0, scrollerSize - 20),
-      'Length-based timeline current time before the endScrollOffset point');
-
-  // Check the percentage-based ScrollTimeline.
-  scroller.scrollTop = 0.81 * scrollerSize;
-  await waitForNextFrame();
-  assert_percents_equal(percentageScrollTimeline.currentTime, 100,
-      'Percentage-based timeline current time after the endScrollOffset point');
-  scroller.scrollTop = 0.80 * scrollerSize;
-  await waitForNextFrame();
-  assert_percents_equal(percentageScrollTimeline.currentTime, 100,
-      'Percentage-based timeline current time at the endScrollOffset point');
-  scroller.scrollTop = 0.50 * scrollerSize;
-  await waitForNextFrame();
-  assert_percents_equal(
-      percentageScrollTimeline.currentTime,
-      calculateCurrentTime(scroller.scrollTop, 0, 0.8 * scrollerSize),
-      'Percentage-based timeline current time before the endScrollOffset ' +
-      'point');
-
-  // Check the calc-based ScrollTimeline.
-  scroller.scrollTop = 0.8 * scrollerSize + 6;
-  await waitForNextFrame();
-  assert_percents_equal(calcScrollTimeline.currentTime, 100,
-      'Calc-based timeline current time after the endScrollOffset point');
-  scroller.scrollTop = 0.8 * scrollerSize + 5;
-  await waitForNextFrame();
-  assert_percents_equal(calcScrollTimeline.currentTime, 100,
-      'Calc-based timeline current time at the endScrollOffset point');
-  scroller.scrollTop = 0.5 * scrollerSize;
-  await waitForNextFrame();
-  assert_percents_equal(
-      calcScrollTimeline.currentTime,
-      calculateCurrentTime(scroller.scrollTop, 0, 0.8 * scrollerSize + 5),
-      'Calc-based timeline current time before the endScrollOffset point');
-}, 'currentTime handles endScrollOffset correctly');
-
-promise_test(async t => {
-  const scroller = setupScrollTimelineTest();
-  const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
-
-  const scrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.px(20), CSS.px(scrollerSize - 50)]
-  });
-
-  scroller.scrollTop = 150;
-  // Wait for new animation frame  which allows the timeline to compute new
-  // current time.
-  await waitForNextFrame();
-  assert_percents_equal(
-      scrollTimeline.currentTime,
-      calculateCurrentTime(150, 20, scrollerSize - 50));
-}, 'currentTime handles startScrollOffset and endScrollOffset together' +
-   ' correctly');
-
-promise_test(async t => {
-  const scroller = setupScrollTimelineTest();
-  const scrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.px(20), CSS.px(20)]
-  });
-
-  scroller.scrollTop = 150;
-  // Wait for new animation frame  which allows the timeline to compute new
-  // current time.
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, 100);
-}, 'currentTime handles startScrollOffset == endScrollOffset correctly');
-
-promise_test(async t => {
-  const scroller = setupScrollTimelineTest();
-  const scrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.px(50), CSS.px(10)]
-  });
-
-  scroller.scrollTop = 40;
-  // Wait for new animation frame  which allows the timeline to compute new
-  // current time.
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, 0);
-  scroller.scrollTop = 60;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, 100);
-}, 'currentTime handles startScrollOffset > endScrollOffset correctly');
-
-promise_test(async t => {
-  const scroller = setupScrollTimelineTest();
-  const scrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.px(10), CSS.px(20), CSS.px(40), CSS.px(70), CSS.px(90)],
-  });
-
-  var offset = 0;
-  var w = 1 / 4;  // offset weight
-  var p = 0;  // progress within the offset
-
-  scroller.scrollTop = 10;
-  // Wait for new animation frame  which allows the timeline to compute new
-  // current time.
-  await waitForNextFrame();
-
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  p = (12 - 10) / (20 - 10);
-  scroller.scrollTop = 12;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  offset = 1;
-  p = 0;
-  scroller.scrollTop = 20;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  p = (35 - 20) / (40 - 20);
-  scroller.scrollTop = 35;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  offset = 2;
-  p = 0;
-  scroller.scrollTop = 40;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  p = (60 - 40) / (70 - 40);
-  scroller.scrollTop = 60;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  offset = 3;
-  p = 0;
-  scroller.scrollTop = 70;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  p = (80 - 70) / (90 - 70);
-  scroller.scrollTop = 80;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  scroller.scrollTop = 90;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-}, 'currentTime calculations when multiple scroll offsets are specified');
-
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset-clamp.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset-clamp.html
deleted file mode 100644
index 6aea8217..0000000
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset-clamp.html
+++ /dev/null
@@ -1,221 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Test clamping logic of element-based scroll offset for scroll timeline.</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/web-animations/testcommon.js"></script>
-<script src="testcommon.js"></script>
-
-<style>
-/*
- * Overflow hidden prevents user scroll including mouse wheel; however, the
- * element is still a scrollable container and can be scrolled programmatically.
- * Removing the visible scrollbars in this manner simplifies the position
- * calculations in the text.
- */
-.scroller {
-  overflow: hidden;
-  height: 500px;
-  width: 500px;
-  will-change: transform;
-}
-
-.contents {
-  height: 1200px;
-  width: 1200px;
-  position: relative;
-}
-
-.vertical #target {
-  background: blue;
-  border-top: 0px solid pink;
-  border-bottom: 0px solid pink;
-  box-sizing: border-box;
-  position: absolute;
-  width: 100%;
-  top: var(--start-position);
-  height: calc(var(--end-position) - var(--start-position));
-}
-
-.horizontal #target {
-  background: blue;
-  border-left: 0px solid pink;
-  border-right: 0px solid pink;
-  box-sizing: border-box;
-  position: absolute;
-  height: 100%;
-  left: var(--start-position);
-  width: calc(var(--end-position) - var(--start-position));
-}
-</style>
-<div id="log"></div>
-<script>
-  'use strict';
-
-  function createScrollerWithTarget(test, config) {
-    const orientationClass = config.orientation;
-    const positions = `
-    --start-position: ${config.startElementPosition};
-    --end-position: ${config.endElementPosition};`
-
-    var scroller = createDiv(test);
-    scroller.innerHTML =
-     `<div class='contents' style="${positions}">
-        <div id='target'></div>
-      </div>`;
-    scroller.classList.add('scroller');
-    scroller.classList.add(orientationClass);
-
-    return scroller;
-  }
-
-  async function createScrollAnimationTest(description, config) {
-    promise_test(async t => {
-      const scroller = createScrollerWithTarget(t, config);
-      t.add_cleanup(() => scroller.remove());
-
-      const target = scroller.querySelector("#target");
-
-      // Force layout before creating the scroll timeline to ensure the correct
-      // scroll range.
-      target.offsetHeight;
-
-      const timeline = createScrollTimeline(t, {
-        source: scroller,
-        orientation: config.orientation,
-        fill: 'both',
-        scrollOffsets: [{target: target, edge: 'end', ...config.start},
-                        {target: target, edge:'start', ...config.end }]
-      });
-
-      // Wait for new animation frame which allows the timeline to compute new
-      // current time.
-      await waitForNextFrame();
-
-      const animation = createScrollLinkedAnimation(t, timeline);
-
-      // Verify initial start and current times in Idle state.
-      assert_equals(animation.currentTime, null,
-                    "The current time is null in Idle state.");
-      assert_equals(animation.startTime, null,
-                    "The start time is null in Idle state.");
-
-      animation.play();
-      assert_true(animation.pending, "Animation is in pending state.");
-      // Verify initial start and current times in Pending state.
-      assert_percents_equal(animation.currentTime, 0,
-                            "The current time is zero in Pending state.");
-      assert_percents_equal(animation.startTime, 0,
-                            "The start time is zero in Pending state.");
-
-      await animation.ready;
-      // Verify initial start and current times in Playing state.
-      assert_percents_equal(animation.currentTime, 0,
-                            "The current time is zero in Playing state.");
-      assert_percents_equal(animation.startTime, 0,
-                            "The start time is zero in Playing state.");
-
-      // Now do some scrolling and make sure that the Animation current time is
-      // correct.
-      if (config.orientation == 'vertical') {
-        scroller.scrollTo({top: config.scrollTo});
-        assert_equals(scroller.scrollTop, config.scrollTo);
-      } else {
-        scroller.scrollTo({left: config.scrollTo});
-        assert_equals(scroller.scrollLeft, config.scrollTo);
-      }
-
-      await waitForNextFrame();
-
-      assert_percents_equal(
-          animation.timeline.currentTime,
-          config.expectedCurrentTime,
-          "The timeline current time corresponds to the scroll position of " +
-          "the scroller.");
-      assert_percents_equal(
-          animation.currentTime,
-          config.expectedCurrentTime,
-          "The animation current time corresponds to the scroll position of " +
-          "the scroller.");
-      assert_percents_equal(
-          animation.effect.getComputedTiming().localTime,
-          config.expectedCurrentTime,
-          "Effect local time corresponds to the scroll position of the " +
-          "scroller.");
-    }, description);
-  }
-
-  // We have no scrollbar and the scroller is symmetric on x & y axis so this
-  // static value is axis and platform agnostic.
-  const scroll_max = 700;
-
-  // For this test we setup a single target, and scroll timeline in a way that
-  // our animation runs from when target enters the scroll port until it fully
-  // exits it. Then we create various edgecase scenarios to see the clamping
-  // logic.
-  //
-  // Scroller has 500px heights with 1200px content which translates to
-  // 0 < scroll < 700px
-  //
-  //  +----------+  ^
-  //  |          |  |
-  //  | Scroller |  |
-  //  |          |  | scrollRange
-  //  |          |  |
-  //  +----------+  |     +--+
-  //      |TT|      |     |TT|
-  //      +--+      v +----------+
-  //                  |          |
-  //                  | Scroller |
-  //                  |          |
-  //                  |          |
-  //                  +----------+
-  //
-  // For each test the expected timeline start/end is in the comment to help
-  // with the verification.
-  //
-  // Note: expectedCurrentTime values are percentages. They are used as such:
-  //    CSS.percent(expectedCurrentTime)
-  const tests = {
-    // offsets: [0, 600]
-    "no clamping is expected": {
-      startElementPosition: '500px',
-      endElementPosition: '600px',
-      scrollTo: 300,
-      expectedCurrentTime: 50,
-    },
-    // offsets: [0, 600]
-    "start is visible at zero offset and should get clamped": {
-      startElementPosition: '400px',
-      endElementPosition: '600px',
-      scrollTo: 300,
-      expectedCurrentTime: 50,
-    },
-
-    // offsets: [0, scroll_max]
-    "end is not reachable and should be clamped": {
-      startElementPosition: '500px',
-      endElementPosition: '800px',
-      scrollTo: scroll_max / 2,
-      expectedCurrentTime: 50,
-    },
-
-    // offsets: [0, scroll_max]
-    "both start and end are clamped": {
-      startElementPosition: '400px',
-      endElementPosition: '800px',
-      scrollTo: scroll_max / 2,
-      expectedCurrentTime: 50,
-    },
-  };
-
-  for (let orientation of ['vertical', 'horizontal']) {
-    for (let testName in tests) {
-      const description = `Animation start and current times are correct given
-          element-based offsets for orienation ${orientation} and ${testName}.`;
-      const config = tests[testName];
-      config.orientation = orientation;
-      createScrollAnimationTest(description, config);
-    }
-  }
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset-unresolved.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset-unresolved.html
deleted file mode 100644
index 5b746d4..0000000
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset-unresolved.html
+++ /dev/null
@@ -1,88 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Validate cases where element-based scroll offsets are unresolved.</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/web-animations/testcommon.js"></script>
-<script src="testcommon.js"></script>
-
-<style>
-    .scroller {
-        overflow: auto;
-        height: 500px;
-        width: 500px;
-        will-change: transform;
-    }
-
-    .contents {
-        height: 2000px;
-        width: 2000px;
-        position: relative;
-    }
-
-    #start, #end {
-        background: blue;
-        border-top: 5px solid pink;
-        box-sizing: border-box;
-        width: 100%;
-        height: 50px;
-    }
-
-    #start {
-        position: absolute;
-        top: 50px;
-    }
-
-    #end {
-        position: absolute;
-        top: 1050px;
-    }
-</style>
-<div id="log"></div>
-
-<div id="not_a_descendant"></div>
-
-<script>
-'use strict';
-
-promise_test(async t => {
-    const scroller = createScrollerWithStartAndEnd(t);
-    t.add_cleanup(() => scroller.remove());
-    scroller.scrollTo({ top: 500 });
-
-    const not_a_descendant = document.querySelector("#not_a_descendant");
-    const end = document.querySelector("#end")
-
-    const timeline = createScrollTimeline(t, {
-        source: scroller,
-        orientation: 'block',
-        scrollOffsets: [{ target: not_a_descendant }, { target: end }]
-    });
-
-    await waitForNextFrame();
-    assert_equals(timeline.currentTime, null, "The timeline should not be active.");
-}, "A valid element-based offset's target should be a descendant of timeline's source");
-
-promise_test(async t => {
-    const scroller = createScrollerWithStartAndEnd(t);
-    t.add_cleanup(() => scroller.remove());
-    scroller.scrollTo({ top: 500 });
-
-    const start = document.querySelector("#start");
-    const end = document.querySelector("#end")
-
-    const timeline = createScrollTimeline(t, {
-        source: scroller,
-        orientation: 'block',
-        scrollOffsets: [{ target: start }, { target: end }]
-    });
-
-    start.style.display = "none";
-    await waitForNextFrame();
-    assert_equals(timeline.currentTime, null, "The timeline should not be active.");
-
-    start.style.display = "block";
-    await waitForNextFrame();
-    assert_not_equals(timeline.currentTime, null, "The timeline should be active.");
-}, "A valid element-based offset's target should have a layout box");
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset.html
deleted file mode 100644
index 7f18ce8e..0000000
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/element-based-offset.html
+++ /dev/null
@@ -1,222 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Test element-based scroll offset for scroll timeline.</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/web-animations/testcommon.js"></script>
-<script src="testcommon.js"></script>
-
-<style>
-  .scroller {
-    overflow: auto;
-    height: 500px;
-    width: 500px;
-    will-change: transform;
-  }
-
-  .contents {
-    height: 2000px;
-    width: 2000px;
-    position: relative;
-  }
-
-  .vertical #start, .vertical #end {
-    background: blue;
-    border-top: 5px solid pink;
-    box-sizing: border-box;
-    width: 100%;
-    height: 50px;
-  }
-
-  .vertical #start {
-    position: absolute;
-    top: 50px;
-  }
-
-  .vertical #end {
-    position: absolute;
-    top: 1050px;
-  }
-
-  .horizontal #start, .horizontal #end {
-    background: blue;
-    border-left:5px solid pink;
-    box-sizing: border-box;
-    height: 100%;
-    width: 50px;
-  }
-
-  .horizontal #start {
-    position: absolute;
-    left: 50px;
-  }
-
-  .horizontal #end {
-    position: absolute;
-    left: 1050px;
-  }
-</style>
-<div id="log"></div>
-<script>
-  'use strict';
-
-  async function createScrollAnimationTest(description, config) {
-    promise_test(async t => {
-      const scroller = createScrollerWithStartAndEnd(t, config.orientation);
-      t.add_cleanup(() => scroller.remove());
-
-      const start = scroller.querySelector("#start");
-      const end = scroller.querySelector("#end");
-
-      // Force layout to ensure the scroll timeline gets the correct scroll
-      // range.
-      start.offsetHeight;
-
-      const timeline = createScrollTimeline(t, {
-        source: scroller,
-        orientation: config.orientation,
-        fill: 'both',
-        scrollOffsets:
-            [{target: start, ...config.start}, {target: end, ...config.end }]
-      });
-
-      // Wait for new animation frame  which allows the timeline to compute new
-      // current time.
-      await waitForNextFrame();
-
-      const animation = createScrollLinkedAnimation(t, timeline);
-      const scrollRange = (config.orientation == 'vertical')
-          ? end.offsetTop - start.offsetTop
-          : end.offsetLeft - start.offsetLeft;
-
-      // Verify initial start and current times in Idle state.
-      assert_equals(animation.currentTime, null,
-                    "The current time is null in Idle state.");
-      assert_equals(animation.startTime, null,
-                    "The start time is null in Idle state.");
-
-      animation.play();
-      assert_true(animation.pending, "Animation is in pending state.");
-      // Verify initial start and current times in Pending state.
-      assert_percents_equal(animation.currentTime, 0,
-                            "The current time is zero in Pending state.");
-      assert_percents_equal(animation.startTime, 0,
-                            "The start time is zero in Pending state.");
-
-      await animation.ready;
-      // Verify initial start and current times in Playing state.
-      assert_percents_equal(animation.currentTime, 0,
-                            "The current time is zero in Playing state.");
-      assert_percents_equal(animation.startTime, 0,
-                            "The start time is zero in Playing state.");
-
-      // Now do some scrolling and make sure that the Animation current time is
-      // correct.
-      if (config.orientation == 'vertical') {
-        scroller.scrollTo({top: config.scrollTo});
-        assert_equals(scroller.scrollTop, config.scrollTo);
-      } else {
-        scroller.scrollTo({left: config.scrollTo});
-        assert_equals(scroller.scrollLeft, config.scrollTo);
-      }
-
-      await waitForNextFrame();
-      assert_percents_approx_equal(
-          animation.timeline.currentTime,
-          config.expectedCurrentTime,
-          scrollRange,
-          "The timeline current time corresponds to the scroll position of " +
-          "the scroller.");
-      assert_percents_approx_equal(
-          animation.currentTime,
-          config.expectedCurrentTime,
-          scrollRange,
-          "The animation current time corresponds to the scroll position of " +
-          "the scroller.");
-      assert_percents_approx_equal(
-          animation.effect.getComputedTiming().localTime,
-          config.expectedCurrentTime,
-          scrollRange,
-          "Effect local time corresponds to the scroll position of the " +
-          "scroller.");
-    }, description);
-  }
-
-  // start is @   50px
-  // end is   @   1050px
-  // both have    50px heights
-  // scroller has 500px heights
-  // For each test the expected start/end is in the comment to help with the
-  // verification.
-  //
-  // Note: expectedCurrentTime values are percentages. They are used as such:
-  //    CSS.percent(expectedCurrentTime)
-  const tests = {
-    // offsets: [100, 1100]
-    "at start": {
-      scrollTo: 100,
-      expectedCurrentTime: 0,
-    },
-    // offsets: [100, 1100]
-    "after start": {
-      scrollTo: 200,
-      expectedCurrentTime: 10,
-    },
-    // offsets: [100, 1100]
-    "at middle" : {
-      scrollTo: 600,
-      expectedCurrentTime: 50,
-    },
-    // offsets: [100, 1100]
-    "at end" : {
-      scrollTo: 1099,
-      expectedCurrentTime: 99.9,
-    },
-    // offsets: [100, 1100]
-    "after end" : {
-      scrollTo: 1150,
-      expectedCurrentTime: 100,
-    },
-    // offsets: [75, 1075]
-    "with threshold 0.5" : {
-      // give threshold to both start and end to keep scrollRange
-      // 1000 which simplifies the calculation.
-      start: {threshold: 0.5},
-      end: {threshold: 0.5},
-      scrollTo: 600 - 25,
-      expectedCurrentTime: 50,
-    },
-    // offsets: [50, 1050]
-    "with threshold 1.0": {
-      start: {threshold: 1.0},
-      end: {threshold: 1.0},
-      scrollTo: 600 - 50,
-      expectedCurrentTime: 50,
-    },
-    // offset: [100, 550]
-    "with end edge" : {
-      end: {edge: "end"},
-      scrollTo: 325,
-      expectedCurrentTime: 50,
-    },
-    // offset: [100, 600]
-     "with end edge and threshold 1.0": {
-      end: {
-        threshold: 1.0,
-        edge: "end"
-      },
-      scrollTo: 350,
-      expectedCurrentTime: 50,
-    },
-  };
-
-  for (let orientation of ['vertical', 'horizontal']) {
-    for (let testName in tests) {
-      const description = `Animation start and current times are correct given
-          element-based offsets for orienation ${orientation} and ${testName}.`;
-      const config = tests[testName];
-      config.orientation = orientation;
-      createScrollAnimationTest(description, config);
-    }
-  }
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/layout-changes-on-percentage-based-timeline.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/layout-changes-on-percentage-based-timeline.html
index c69bee2..c5a46a5 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/layout-changes-on-percentage-based-timeline.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/layout-changes-on-percentage-based-timeline.html
@@ -56,31 +56,29 @@
     [
       { transform: 'translateY(0)', opacity: 1 },
       { transform: 'translateY(200px)', opacity: 0 }
-    ], {
-      duration: 1000,
-    }
+    ]
   );
 
   const scroller = document.getElementById('scroller');
   const timeline = new ScrollTimeline({
-    source: scroller,
-    scrollOffsets: [CSS.percent(20), CSS.percent(80)]
+    source: scroller
   });
   const animation = new Animation(effect, timeline);
   animation.play();
+  animation.ready.then(_ => {
+    // Moves the scroller to the end point (240px).
+    const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+    scroller.scrollTop = maxScroll * 0.6;
 
-  // Moves the scroller to the end point (240px).
-  const maxScroll = scroller.scrollHeight - scroller.clientHeight;
-  scroller.scrollTop = maxScroll * 0.6;
-
-  // Makes sure that the animation runs on compositor with current scroll offset
-  waitForAnimationFrames(2).then(_ => {
-    // Adds 80px to scroll height which pushes scroll progress back to 50%.
-    const spacer = document.getElementById('spacer');
-    spacer.classList.remove('invisible');
-    // Makes sure that the change is propagated to the compositor.
+    // Makes sure that the animation runs on compositor with current scroll offset
     waitForAnimationFrames(2).then(_ => {
-      takeScreenshot();
+      // Adds 80px to scroll height which pushes scroll progress back to 50%.
+      const spacer = document.getElementById('spacer');
+      spacer.classList.remove('invisible');
+      // Makes sure that the change is propagated to the compositor.
+      waitForAnimationFrames(2).then(_ => {
+        takeScreenshot();
+      });
     });
   });
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/multiple-scroll-offsets.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/multiple-scroll-offsets.html
deleted file mode 100644
index 4a39548..0000000
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/multiple-scroll-offsets.html
+++ /dev/null
@@ -1,126 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>ScrollTimeline current time algorithm</title>
-<link rel="help" href="https://wicg.github.io/scroll-animations/#current-time-algorithm">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/web-animations/testcommon.js"></script>
-<script src="./testcommon.js"></script>
-
-<body></body>
-
-<script>
-'use strict';
-
-promise_test(async t => {
-  const scroller = setupScrollTimelineTest();
-  const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
-
-  const scrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.px(10), CSS.px(20), CSS.px(40), CSS.px(70), CSS.px(90)],
-  });
-
-  var offset = 0;
-  var w = 1 / 4; // offset weight
-  var p = 0;     // progress within the offset
-
-  scroller.scrollTop = 10;
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  p = (12 - 10) / (20 - 10);
-  scroller.scrollTop = 12;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  offset = 1;
-  p = 0;
-  scroller.scrollTop = 20;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  p = (35 - 20) / (40 - 20);
-  scroller.scrollTop = 35;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  offset = 2;
-  p = 0;
-  scroller.scrollTop = 40;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  p = (60 - 40) / (70 - 40);
-  scroller.scrollTop = 60;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  offset = 3;
-  p = 0;
-  scroller.scrollTop = 70;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  p = (80 - 70) / (90 - 70);
-  scroller.scrollTop = 80;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, (offset + p) * w * 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  scroller.scrollTop = 90;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-}, 'currentTime calculations when multiple scroll offsets are specified');
-
-promise_test(async t => {
-  const scroller = setupScrollTimelineTest();
-  const scrollerSize = scroller.scrollHeight - scroller.clientHeight;
-
-  var scrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.px(300), CSS.px(200), CSS.px(10)],
-  });
-
-  scroller.scrollTop = 250;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, 0,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  scroller.scrollTop = 400;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, 100,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  scrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: ['auto', CSS.px(400), CSS.px(200)],
-  });
-
-  scroller.scrollTop = 100;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, 12.5,
-      "current time calculation when scroll = " + scroller.scrollTop);
-
-  scrollTimeline = new ScrollTimeline({
-    source: scroller,
-    orientation: 'block',
-    scrollOffsets: [CSS.px(200), CSS.px(0), CSS.px(400)],
-  });
-
-  scroller.scrollTop = 200;
-  await waitForNextFrame();
-  assert_percents_equal(scrollTimeline.currentTime, 75,
-      "current time calculation when scroll = " + scroller.scrollTop);
-}, 'currentTime calculations when overlapping scroll offsets are specified');
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-animation-effect-fill-modes.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-animation-effect-fill-modes.tentative.html
index e4e536a..b9cc154 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-animation-effect-fill-modes.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/scroll-animation-effect-fill-modes.tentative.html
@@ -31,31 +31,12 @@
   }, "Scroll based animation effect fill mode should return 'auto' for" +
      " getTiming() and should return 'none' for getComputedTiming().")
 
-  // Test cases are included where effect delay causes the effect iteration to
-  // overlap with the timeline start time and also the timeline end time.
-  //                  Timeline
-  //   BEFORE   +-----------------+     AFTER
-  //   time:    0                 timeline.duration
-  //   1)       +-----------------+
-  //   2)            +------------+
-  //   3)   +---------------------+
-  //   4)       +---------------------+
-  //   5)       +-------------+
-  //   6)           +---------+
-  //   7)           +-----------------+
-  //   8)   +-----------------+
-  //   9)   +-------------------------+
-
-  // Note: effects are scaled to fill the timeline so that the start of the
-  // effect is after the start offset of the timeline, and that the end of the
-  // effect is at the beginning of the end offset of the timeline.
-
   /* All interesting transitions:
-      before timeline start
-      at timeline start
-      in timeline range
-      at timeline end
-      after timeline end
+      before start delay
+      at start delay
+      within active phase
+      at effect end
+      after effect end
 
       test_case data structure:
       fill_mode: {
@@ -64,33 +45,19 @@
   */
   const test_cases = {
     "none": {
-      0.10: ["before timeline start", 1],
-      0.25: ["at timeline start", 0.3],
-      0.50: ["in timeline range", 0.5],
-      0.75: ["at timeline end", 1],
-      0.90: ["after timeline end", 1]
+      0.10: ["before start delay", 1],
+      0.25: ["at start delay", 0.3],
+      0.50: ["at midpoint", 0.5],
+      0.75: ["at effect end", 1],
+      0.90: ["after effect end", 1]
     },
     "backwards": {
-      0.10: ["before timeline start", 0.3],
-      0.25: ["at timeline start", 0.3],
-      0.50: ["in timeline range", 0.5],
-      0.75: ["at timeline end", 1],
-      0.90: ["after timeline end", 1]
+      0.10: ["before start delay", 0.3],
+      0.25: ["at start delay", 0.3],
+      0.50: ["at midpoint", 0.5],
+      0.75: ["at effect end", 1],
+      0.90: ["after effect end", 1]
     },
-    /*
-      There is an asymmetry between these 2 mirrored scenarios:
-        1. fillmode: forwards + "at timeline end"
-        2. fillmode: backwards + "at timeline start"
-
-      In scenario 1, effect is not applied
-      In scenario 2, effect is applied
-
-      The difference is accounted for by the equality at the end versus strict
-      inequality at the start when computing the timeline phase.
-
-      This is currently in line with spec expectations but is an issue that
-      should be addressed.
-    */
     "forwards": {
       0.10: ["before timeline start", 1],
       0.25: ["at timeline start", 0.3],
@@ -105,8 +72,6 @@
       0.75: ["at timeline end", 0.7],
       0.90: ["after timeline end", 0.7]
     },
-    // "auto" behaves differently for different effect delay values. These
-    // cases are handled in scroll-animation-effect-phases.tentative.html
     "auto": {
       0.10: ["before timeline start", 1],
       0.25: ["at timeline start", 0.3],
@@ -138,17 +103,18 @@
     return async t => {
       const target = createDiv(t);
 
-      const timeline =
-          createScrollTimelineWithOffsets(t, CSS.percent(25), CSS.percent(75));
-      const effect = new KeyframeEffect(
-        target,
-        {
-          opacity: [0.3, 0.7]
-        },
-        {
-          fill: fill_mode
-        }
-      );
+      const timeline = createScrollTimeline(t);
+      const effect =
+          new KeyframeEffect(target,
+                             { opacity: [0.3, 0.7] },
+                             {
+                               fill: fill_mode,
+                               /* Animation times normalized to fill scroll
+                                  range */
+                               duration: 2000,
+                               delay: 1000,
+                               endDelay: 1000
+                             });
       const animation = new Animation(effect, timeline);
       const scroller = timeline.source;
       const maxScroll = scroller.scrollHeight - scroller.clientHeight;
@@ -163,10 +129,9 @@
       // new current time.
       await waitForNextFrame();
 
-      assert_equals(
-        parseFloat(window.getComputedStyle(target).opacity),
-        expected,
-        "animation effect applied property value");
+      assert_equals(parseFloat(window.getComputedStyle(target).opacity),
+                    expected,
+                    "animation effect applied property value");
     }
   }
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/responsive/resources/block.html b/third_party/blink/web_tests/external/wpt/web-animations/responsive/resources/block.html
new file mode 100644
index 0000000..82840559
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-animations/responsive/resources/block.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+  .testBlock {
+    width: 100px;
+    height: 100px;
+    background: #00cc66;
+    border-top: 50px solid #ffff00;
+  }
+</style>
+<div class="testBlock" id="testBlock"></div>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/responsive/toggle-animated-iframe-visibility-ref.html b/third_party/blink/web_tests/external/wpt/web-animations/responsive/toggle-animated-iframe-visibility-ref.html
new file mode 100644
index 0000000..dab5bed
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-animations/responsive/toggle-animated-iframe-visibility-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+  .rotated {
+    transform: rotate(90deg);
+  }
+</style>
+<div id="container">
+  <iframe class="rotated" src="resources/block.html">
+  </iframe>
+</div>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/responsive/toggle-animated-iframe-visibility.html b/third_party/blink/web_tests/external/wpt/web-animations/responsive/toggle-animated-iframe-visibility.html
new file mode 100644
index 0000000..f50ffaad
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-animations/responsive/toggle-animated-iframe-visibility.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta name="assert" content="This should resume the animation after unhiding the iframe.">
+<title>CSS Test (Animations): Unhiding iframe visibility should restart animation. </title>
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=616270">
+<link rel="match" href="toggle-animated-iframe-visibility-ref.html">
+<script src="/common/reftest-wait.js"></script>
+
+<div id="container"></div>
+
+<div id="log"></div>
+
+<script>
+  var container;
+  var block;
+  var logDiv;
+
+  function verifyVisibility(expected_visibility, message) {
+    if (getComputedStyle(block).visibility !== expected_visibility)
+      logDiv.innerHTML = `FAIL: ${message}`;
+  }
+
+  async function runTest() {
+    var animation = block.animate(
+    { transform: [ 'rotate(0deg)', 'rotate(180deg)' ] },
+    {
+      duration: 10000000,
+      delay: -5000000,
+      easing: 'cubic-bezier(0, 1, 1, 0)'
+    });
+
+    await animation.ready;
+
+    container.style.visibility = 'hidden';
+    requestAnimationFrame(() => {
+      verifyVisibility('hidden', 'style.visibility should be hidden');
+      container.style.visibility = 'visible';
+
+      requestAnimationFrame(() => {
+        verifyVisibility('visible', 'style.visiblity should be visible');
+        takeScreenshot();
+      });
+    });
+  }
+
+  window.onload = function () {
+    logDiv = document.getElementById('log');
+    container = document.getElementById('container');
+    block = document.createElement('iframe');
+
+    container.appendChild(block);
+    block.onload = runTest;
+    block.src = 'resources/block.html';
+  };
+</script>
diff --git a/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt
index 4d01ed2..8c1e5a4 100644
--- a/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt
@@ -8386,7 +8386,6 @@
 interface ScrollTimeline : AnimationTimeline
     attribute @@toStringTag
     getter orientation
-    getter scrollOffsets
     getter source
     method constructor
 interface SecurityPolicyViolationEvent : Event
diff --git a/third_party/blink/web_tests/platform/mac-mac10.13/compositing/overflow/nested-render-surfaces-with-rotation-expected.png b/third_party/blink/web_tests/platform/mac-mac10.13/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
index dc9db7c..054cbe9 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.13/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.13/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.13/media/video-zoom-controls-expected.png b/third_party/blink/web_tests/platform/mac-mac10.13/media/video-zoom-controls-expected.png
deleted file mode 100644
index 31d2cc6..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.13/media/video-zoom-controls-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.13/virtual/backface-visibility-interop/compositing/overflow/nested-render-surfaces-with-rotation-expected.png b/third_party/blink/web_tests/platform/mac-mac10.13/virtual/backface-visibility-interop/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
index dc9db7c..054cbe9 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.13/virtual/backface-visibility-interop/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.13/virtual/backface-visibility-interop/compositing/overflow/nested-render-surfaces-with-rotation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.13/virtual/oopr-canvas2d/fast/canvas/image-object-in-canvas-expected.png b/third_party/blink/web_tests/platform/mac-mac10.13/virtual/oopr-canvas2d/fast/canvas/image-object-in-canvas-expected.png
deleted file mode 100644
index a9b1dae..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.13/virtual/oopr-canvas2d/fast/canvas/image-object-in-canvas-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.13/virtual/oopr-canvas2d/fast/canvas/quadraticCurveTo-expected.png b/third_party/blink/web_tests/platform/mac-mac10.13/virtual/oopr-canvas2d/fast/canvas/quadraticCurveTo-expected.png
deleted file mode 100644
index 6f4c626..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.13/virtual/oopr-canvas2d/fast/canvas/quadraticCurveTo-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/content-with-overflow-zoomed-ref.html b/third_party/blink/web_tests/wpt_internal/document-transition/content-with-overflow-zoomed-ref.html
new file mode 100644
index 0000000..cbeaa31
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/content-with-overflow-zoomed-ref.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Shared transitions: shared element with overflow (ref)</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:khushalsagar@chromium.org">
+<style>
+.target {
+  width: 80px;
+  height: 80px;
+  contain: paint;
+  background: blue;
+  overflow-clip-margin: 50px;
+  page-transition-tag: target;
+  zoom: 1.5;
+  border: 2px solid black;
+}
+.child {
+  width: 200px;
+  height: 200px;
+  position: relative;
+  top: 50px;
+  left: 50px;
+  background: green;
+  zoom: 1.2;
+}
+body { background: lightpink; }
+</style>
+
+<div class=ancestor>
+  <div class=target>
+    <div class=child>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/new-content-with-overflow-zoomed.html b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-with-overflow-zoomed.html
new file mode 100644
index 0000000..98de13b
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/new-content-with-overflow-zoomed.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<title>Shared transitions: shared element with overflow</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:khushalsagar@chromium.org">
+<link rel="match" href="content-with-overflow-zoomed-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<style>
+.target {
+  width: 80px;
+  height: 80px;
+  contain: paint;
+  background: blue;
+  overflow-clip-margin: 50px;
+  page-transition-tag: target;
+  zoom: 1.5;
+}
+.child {
+  width: 200px;
+  height: 200px;
+  position: relative;
+  top: 50px;
+  left: 50px;
+  background: green;
+  zoom: 1.2;
+}
+
+html::page-transition-container(target) { animation-duration: 300s; }
+html::page-transition-outgoing-image(target) { animation: unset; opacity: 0; }
+html::page-transition-incoming-image(target) {
+  animation: unset;
+  opacity: 1;
+  border: 3px solid black;
+}
+
+html::page-transition-container(root) { animation: unset; opacity: 0; }
+html::page-transition { background: lightpink; }
+</style>
+
+<div class=ancestor>
+  <div class=target>
+    <div class=child>
+    </div>
+  </div>
+</div>
+
+<script>
+async function runTest() {
+  document.createDocumentTransition().start(() =>
+    requestAnimationFrame(() => requestAnimationFrame(takeScreenshot)));
+}
+onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest));
+</script>
+
diff --git a/third_party/blink/web_tests/wpt_internal/document-transition/old-content-with-overflow-zoomed.html b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-with-overflow-zoomed.html
new file mode 100644
index 0000000..b95aa6d
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/document-transition/old-content-with-overflow-zoomed.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html class=reftest-wait>
+<title>Shared transitions: shared element with overflow</title>
+<link rel="help" href="https://github.com/WICG/shared-element-transitions">
+<link rel="author" href="mailto:khushalsagar@chromium.org">
+<link rel="match" href="content-with-overflow-zoomed-ref.html">
+<script src="/common/reftest-wait.js"></script>
+<style>
+.target {
+  width: 80px;
+  height: 80px;
+  contain: paint;
+  background: blue;
+  overflow-clip-margin: 50px;
+  page-transition-tag: target;
+  zoom: 1.5;
+}
+.child {
+  width: 200px;
+  height: 200px;
+  position: relative;
+  top: 50px;
+  left: 50px;
+  background: green;
+  zoom: 1.2;
+}
+
+html::page-transition-container(target) { animation-duration: 300s; }
+html::page-transition-incoming-image(target) { animation: unset; opacity: 0; }
+html::page-transition-outgoing-image(target) {
+  animation: unset;
+  opacity: 1;
+  border: 3px solid black;
+}
+
+html::page-transition-container(root) { animation: unset; opacity: 0; }
+html::page-transition { background: lightpink; }
+</style>
+
+<div class=ancestor>
+  <div class=target>
+    <div class=child>
+    </div>
+  </div>
+</div>
+
+<script>
+async function runTest() {
+  document.createDocumentTransition().start(() =>
+    requestAnimationFrame(() => requestAnimationFrame(takeScreenshot)));
+}
+onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest));
+</script>
+
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium
index d0c37db..cd435e8 100644
--- a/third_party/crashpad/README.chromium
+++ b/third_party/crashpad/README.chromium
@@ -2,7 +2,7 @@
 Short Name: crashpad
 URL: https://crashpad.chromium.org/
 Version: unknown
-Revision: 816c5572b8c6d7aac8fb85db770496bc1f1da439
+Revision: 80f383327eb5c55bc11d5d1d4917bd00a860871b
 License: Apache 2.0
 License File: crashpad/LICENSE
 Security Critical: yes
diff --git a/third_party/crashpad/crashpad/AUTHORS b/third_party/crashpad/crashpad/AUTHORS
index 8dcac32..02103924 100644
--- a/third_party/crashpad/crashpad/AUTHORS
+++ b/third_party/crashpad/crashpad/AUTHORS
@@ -12,3 +12,4 @@
 Vewd Software AS
 LG Electronics, Inc.
 MIPS Technologies, Inc.
+Darshan Sen <raisinten@gmail.com>
diff --git a/third_party/crashpad/crashpad/DEPS b/third_party/crashpad/crashpad/DEPS
index 2299223..4a20125 100644
--- a/third_party/crashpad/crashpad/DEPS
+++ b/third_party/crashpad/crashpad/DEPS
@@ -14,6 +14,7 @@
 
 vars = {
   'chromium_git': 'https://chromium.googlesource.com',
+  'gn_version': 'git_revision:2ecd43a10266bd091c98e6dcde507c64f6a0dad3',
   'pull_linux_clang': False,
   'pull_win_toolchain': False,
   # Controls whether crashpad/build/ios/setup-ios-gn.py is run as part of
@@ -25,7 +26,11 @@
 deps = {
   'buildtools':
       Var('chromium_git') + '/chromium/src/buildtools.git@' +
-      '9e121212d42be62a7cce38072f925f8398d11e49',
+      '8b16338d17cd71b04a6ba28da7322ab6739892c2',
+  'buildtools/clang_format/script':
+      Var('chromium_git') +
+      '/external/github.com/llvm/llvm-project/clang/tools/clang-format.git@' +
+      'c912837e0d82b5ca4b6e790b573b3956d3744c1c',
   'crashpad/third_party/edo/edo': {
       'url': Var('chromium_git') + '/external/github.com/google/eDistantObject.git@' +
       '727e556705278598fce683522beedbb9946bfda0',
@@ -47,7 +52,37 @@
       Var('chromium_git') + '/chromium/src/third_party/zlib@' +
       '13dc246a58e4b72104d35f9b1809af95221ebda7',
 
-  # CIPD packages below.
+  # CIPD packages.
+  'buildtools/linux64': {
+    'packages': [
+      {
+        'package': 'gn/gn/linux-amd64',
+        'version': Var('gn_version'),
+      }
+    ],
+    'dep_type': 'cipd',
+    'condition': 'host_os == "linux"',
+  },
+  'buildtools/mac': {
+    'packages': [
+      {
+        'package': 'gn/gn/mac-${{arch}}',
+        'version': Var('gn_version'),
+      }
+    ],
+    'dep_type': 'cipd',
+    'condition': 'host_os == "mac"',
+  },
+  'buildtools/win': {
+    'packages': [
+      {
+        'package': 'gn/gn/windows-amd64',
+        'version': Var('gn_version'),
+      }
+    ],
+    'dep_type': 'cipd',
+    'condition': 'host_os == "win"',
+  },
   'crashpad/third_party/linux/clang/linux-amd64': {
     'packages': [
       {
@@ -125,7 +160,9 @@
       '--no_auth',
       '--bucket=chromium-clang-format',
       '--sha1_file',
-      'buildtools/mac/clang-format.sha1',
+      'buildtools/mac/clang-format.{host_cpu}.sha1',
+      '--output',
+      'buildtools/mac/clang-format',
     ],
   },
   {
diff --git a/third_party/crashpad/crashpad/client/crash_report_database_generic.cc b/third_party/crashpad/crashpad/client/crash_report_database_generic.cc
index c4e040109..742850a 100644
--- a/third_party/crashpad/crashpad/client/crash_report_database_generic.cc
+++ b/third_party/crashpad/crashpad/client/crash_report_database_generic.cc
@@ -18,6 +18,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
+#include <mutex>
 #include <tuple>
 #include <utility>
 
@@ -258,15 +259,15 @@
   static bool WriteMetadata(const base::FilePath& path, const Report& report);
 
   Settings& SettingsInternal() {
-    if (!settings_init_)
+    std::call_once(settings_init_, [this]() {
       settings_.Initialize(base_dir_.Append(kSettings));
-    settings_init_ = true;
+    });
     return settings_;
   }
 
   base::FilePath base_dir_;
   Settings settings_;
-  bool settings_init_ = false;
+  std::once_flag settings_init_;
   InitializationStateDcheck initialized_;
 };
 
diff --git a/third_party/crashpad/crashpad/client/crash_report_database_mac.mm b/third_party/crashpad/crashpad/client/crash_report_database_mac.mm
index 4c9e5e7..e33b932 100644
--- a/third_party/crashpad/crashpad/client/crash_report_database_mac.mm
+++ b/third_party/crashpad/crashpad/client/crash_report_database_mac.mm
@@ -27,6 +27,7 @@
 
 #include <array>
 #include <iterator>
+#include <mutex>
 #include <tuple>
 
 #include "base/logging.h"
@@ -263,15 +264,15 @@
   void CleanOrphanedAttachments();
 
   Settings& SettingsInternal() {
-    if (!settings_init_)
+    std::call_once(settings_init_, [this]() {
       settings_.Initialize(base_dir_.Append(kSettings));
-    settings_init_ = true;
+    });
     return settings_;
   }
 
   base::FilePath base_dir_;
   Settings settings_;
-  bool settings_init_;
+  std::once_flag settings_init_;
   bool xattr_new_names_;
   InitializationStateDcheck initialized_;
 };
@@ -280,7 +281,7 @@
     : CrashReportDatabase(),
       base_dir_(path),
       settings_(),
-      settings_init_(false),
+      settings_init_(),
       xattr_new_names_(false),
       initialized_() {}
 
diff --git a/third_party/crashpad/crashpad/client/crash_report_database_win.cc b/third_party/crashpad/crashpad/client/crash_report_database_win.cc
index e16aa10..e111d8e 100644
--- a/third_party/crashpad/crashpad/client/crash_report_database_win.cc
+++ b/third_party/crashpad/crashpad/client/crash_report_database_win.cc
@@ -21,6 +21,7 @@
 #include <time.h>
 #include <wchar.h>
 
+#include <mutex>
 #include <tuple>
 #include <utility>
 
@@ -663,15 +664,15 @@
   std::unique_ptr<Metadata> AcquireMetadata();
 
   Settings& SettingsInternal() {
-    if (!settings_init_)
+    std::call_once(settings_init_, [this]() {
       settings_.Initialize(base_dir_.Append(kSettings));
-    settings_init_ = true;
+    });
     return settings_;
   }
 
   base::FilePath base_dir_;
   Settings settings_;
-  bool settings_init_;
+  std::once_flag settings_init_;
   InitializationStateDcheck initialized_;
 };
 
@@ -679,7 +680,7 @@
     : CrashReportDatabase(),
       base_dir_(path),
       settings_(),
-      settings_init_(false),
+      settings_init_(),
       initialized_() {}
 
 CrashReportDatabaseWin::~CrashReportDatabaseWin() {
diff --git a/third_party/crashpad/crashpad/client/crashpad_client_linux.cc b/third_party/crashpad/crashpad/client/crashpad_client_linux.cc
index 295ec16..9cd452b 100644
--- a/third_party/crashpad/crashpad/client/crashpad_client_linux.cc
+++ b/third_party/crashpad/crashpad/client/crashpad_client_linux.cc
@@ -45,9 +45,9 @@
 #include "util/linux/socket.h"
 #include "util/misc/address_sanitizer.h"
 #include "util/misc/from_pointer_cast.h"
-#include "util/posix/double_fork_and_exec.h"
 #include "util/posix/scoped_mmap.h"
 #include "util/posix/signals.h"
+#include "util/posix/spawn_subprocess.h"
 
 namespace crashpad {
 
@@ -459,7 +459,7 @@
 
   argv.push_back(FormatArgumentInt("initial-client-fd", handler_sock.get()));
   argv.push_back("--shared-client-connection");
-  if (!DoubleForkAndExec(argv, nullptr, handler_sock.get(), false, nullptr)) {
+  if (!SpawnSubprocess(argv, nullptr, handler_sock.get(), false, nullptr)) {
     return false;
   }
 
@@ -614,7 +614,7 @@
     int socket) {
   std::vector<std::string> argv = BuildAppProcessArgs(
       class_name, database, metrics_dir, url, annotations, arguments, socket);
-  return DoubleForkAndExec(argv, env, socket, false, nullptr);
+  return SpawnSubprocess(argv, env, socket, false, nullptr);
 }
 
 bool CrashpadClient::StartHandlerWithLinkerAtCrash(
@@ -663,7 +663,7 @@
                                   annotations,
                                   arguments,
                                   socket);
-  return DoubleForkAndExec(argv, env, socket, false, nullptr);
+  return SpawnSubprocess(argv, env, socket, false, nullptr);
 }
 
 #endif
@@ -697,7 +697,7 @@
 
   argv.push_back(FormatArgumentInt("initial-client-fd", socket));
 
-  return DoubleForkAndExec(argv, nullptr, socket, true, nullptr);
+  return SpawnSubprocess(argv, nullptr, socket, true, nullptr);
 }
 
 // static
diff --git a/third_party/crashpad/crashpad/client/crashpad_client_mac.cc b/third_party/crashpad/crashpad/client/crashpad_client_mac.cc
index 39e3567..ba43ed8 100644
--- a/third_party/crashpad/crashpad/client/crashpad_client_mac.cc
+++ b/third_party/crashpad/crashpad/client/crashpad_client_mac.cc
@@ -36,7 +36,7 @@
 #include "util/mach/notify_server.h"
 #include "util/misc/clock.h"
 #include "util/misc/implicit_cast.h"
-#include "util/posix/double_fork_and_exec.h"
+#include "util/posix/spawn_subprocess.h"
 
 namespace crashpad {
 
@@ -343,7 +343,7 @@
     // this parent process, which was probably using the exception server now
     // being restarted. The handler can’t monitor itself for its own crashes via
     // this interface.
-    if (!DoubleForkAndExec(
+    if (!SpawnSubprocess(
             argv,
             nullptr,
             server_write_fd.get(),
diff --git a/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler.cc b/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler.cc
index 2869c21..e7e9003 100644
--- a/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler.cc
+++ b/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler.cc
@@ -811,6 +811,22 @@
       CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_BASIC_INFO");
     }
 
+    thread_extended_info extended_info;
+    count = THREAD_EXTENDED_INFO_COUNT;
+    kr = thread_info(thread,
+                     THREAD_EXTENDED_INFO,
+                     reinterpret_cast<thread_info_t>(&extended_info),
+                     &count);
+    if (kr == KERN_SUCCESS) {
+      WritePropertyBytes(
+          writer,
+          IntermediateDumpKey::kThreadName,
+          reinterpret_cast<const void*>(extended_info.pth_name),
+          strnlen(extended_info.pth_name, sizeof(extended_info.pth_name)));
+    } else {
+      CRASHPAD_RAW_LOG_ERROR(kr, "thread_info::THREAD_EXTENDED_INFO");
+    }
+
     thread_precedence_policy precedence;
     count = THREAD_PRECEDENCE_POLICY_COUNT;
     boolean_t get_default = FALSE;
diff --git a/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler_test.cc b/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler_test.cc
index bccc930..f1d8a54 100644
--- a/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler_test.cc
+++ b/third_party/crashpad/crashpad/client/ios_handler/in_process_intermediate_dump_handler_test.cc
@@ -26,6 +26,7 @@
 #include "client/simple_string_dictionary.h"
 #include "gtest/gtest.h"
 #include "snapshot/ios/process_snapshot_ios_intermediate_dump.h"
+#include "test/scoped_set_thread_name.h"
 #include "test/scoped_temp_dir.h"
 #include "test/test_paths.h"
 #include "util/file/filesystem.h"
@@ -206,6 +207,8 @@
 }
 
 TEST_F(InProcessIntermediateDumpHandlerTest, TestThreads) {
+  const ScopedSetThreadName scoped_set_thread_name("TestThreads");
+
   WriteReport();
   internal::ProcessSnapshotIOSIntermediateDump process_snapshot;
   ASSERT_TRUE(process_snapshot.InitializeWithFilePath(path(), {}));
@@ -221,6 +224,7 @@
                         &count),
             0);
   EXPECT_EQ(threads[0]->ThreadID(), identifier_info.thread_id);
+  EXPECT_EQ(threads[0]->ThreadName(), "TestThreads");
 }
 
 TEST_F(InProcessIntermediateDumpHandlerTest, TestProcess) {
diff --git a/third_party/crashpad/crashpad/handler/linux/cros_crash_report_exception_handler.cc b/third_party/crashpad/crashpad/handler/linux/cros_crash_report_exception_handler.cc
index 9e58d94..4804eac 100644
--- a/third_party/crashpad/crashpad/handler/linux/cros_crash_report_exception_handler.cc
+++ b/third_party/crashpad/crashpad/handler/linux/cros_crash_report_exception_handler.cc
@@ -29,7 +29,7 @@
 #include "util/linux/ptrace_client.h"
 #include "util/misc/metrics.h"
 #include "util/misc/uuid.h"
-#include "util/posix/double_fork_and_exec.h"
+#include "util/posix/spawn_subprocess.h"
 
 namespace crashpad {
 
@@ -266,12 +266,11 @@
     argv.push_back("--always_allow_feedback");
   }
 
-  if (!DoubleForkAndExec(argv,
-                         nullptr /* envp */,
-                         file_writer.fd() /* preserve_fd */,
-                         false /* use_path */,
-                         nullptr /* child_function */)) {
-    LOG(ERROR) << "DoubleForkAndExec failed";
+  if (!SpawnSubprocess(argv,
+                       nullptr /* envp */,
+                       file_writer.fd() /* preserve_fd */,
+                       false /* use_path */,
+                       nullptr /* child_function */)) {
     Metrics::ExceptionCaptureResult(
         Metrics::CaptureResult::kFinishedWritingCrashReportFailed);
     return false;
diff --git a/third_party/crashpad/crashpad/minidump/minidump_file_writer.cc b/third_party/crashpad/crashpad/minidump/minidump_file_writer.cc
index 43abca8..bc5bf02 100644
--- a/third_party/crashpad/crashpad/minidump/minidump_file_writer.cc
+++ b/third_party/crashpad/crashpad/minidump/minidump_file_writer.cc
@@ -26,6 +26,7 @@
 #include "minidump/minidump_module_writer.h"
 #include "minidump/minidump_system_info_writer.h"
 #include "minidump/minidump_thread_id_map.h"
+#include "minidump/minidump_thread_name_list_writer.h"
 #include "minidump/minidump_thread_writer.h"
 #include "minidump/minidump_unloaded_module_writer.h"
 #include "minidump/minidump_user_extension_stream_data_source.h"
@@ -34,6 +35,7 @@
 #include "snapshot/exception_snapshot.h"
 #include "snapshot/module_snapshot.h"
 #include "snapshot/process_snapshot.h"
+#include "snapshot/thread_snapshot.h"
 #include "util/file/file_writer.h"
 #include "util/numeric/safe_assignment.h"
 
@@ -94,6 +96,21 @@
   add_stream_result = AddStream(std::move(thread_list));
   DCHECK(add_stream_result);
 
+  bool has_thread_name = false;
+  for (const ThreadSnapshot* thread_snapshot : process_snapshot->Threads()) {
+    if (!thread_snapshot->ThreadName().empty()) {
+      has_thread_name = true;
+      break;
+    }
+  }
+  if (has_thread_name) {
+    auto thread_name_list = std::make_unique<MinidumpThreadNameListWriter>();
+    thread_name_list->InitializeFromSnapshot(process_snapshot->Threads(),
+                                             thread_id_map);
+    add_stream_result = AddStream(std::move(thread_name_list));
+    DCHECK(add_stream_result);
+  }
+
   const ExceptionSnapshot* exception_snapshot = process_snapshot->Exception();
   if (exception_snapshot) {
     auto exception = std::make_unique<MinidumpExceptionWriter>();
diff --git a/third_party/crashpad/crashpad/minidump/minidump_thread_name_list_writer.cc b/third_party/crashpad/crashpad/minidump/minidump_thread_name_list_writer.cc
index a955c27..6f49f0c 100644
--- a/third_party/crashpad/crashpad/minidump/minidump_thread_name_list_writer.cc
+++ b/third_party/crashpad/crashpad/minidump/minidump_thread_name_list_writer.cc
@@ -17,21 +17,47 @@
 #include <utility>
 
 #include "base/logging.h"
+#include "minidump/minidump_thread_id_map.h"
+#include "snapshot/thread_snapshot.h"
 #include "util/file/file_writer.h"
 #include "util/numeric/safe_assignment.h"
 
 namespace crashpad {
 
 MinidumpThreadNameWriter::MinidumpThreadNameWriter()
-    : MinidumpWritable(), thread_name_(), name_() {}
+    : MinidumpWritable(), rva_of_thread_name_(), thread_id_(), name_() {}
 
 MinidumpThreadNameWriter::~MinidumpThreadNameWriter() {}
 
-const MINIDUMP_THREAD_NAME* MinidumpThreadNameWriter::MinidumpThreadName()
-    const {
+void MinidumpThreadNameWriter::InitializeFromSnapshot(
+    const ThreadSnapshot* thread_snapshot,
+    const MinidumpThreadIDMap& thread_id_map) {
+  DCHECK_EQ(state(), kStateMutable);
+
+  const auto it = thread_id_map.find(thread_snapshot->ThreadID());
+  DCHECK(it != thread_id_map.end());
+  SetThreadId(it->second);
+  SetThreadName(thread_snapshot->ThreadName());
+}
+
+RVA64 MinidumpThreadNameWriter::RvaOfThreadName() const {
   DCHECK_EQ(state(), kStateWritable);
 
-  return &thread_name_;
+  return rva_of_thread_name_;
+}
+
+uint32_t MinidumpThreadNameWriter::ThreadId() const {
+  DCHECK_EQ(state(), kStateWritable);
+
+  return thread_id_;
+}
+
+bool MinidumpThreadNameWriter::Freeze() {
+  DCHECK_EQ(state(), kStateMutable);
+
+  name_->RegisterRVA(&rva_of_thread_name_);
+
+  return MinidumpWritable::Freeze();
 }
 
 void MinidumpThreadNameWriter::SetThreadName(const std::string& name) {
@@ -46,10 +72,9 @@
 size_t MinidumpThreadNameWriter::SizeOfObject() {
   DCHECK_GE(state(), kStateFrozen);
 
-  // This object doesn’t directly write anything itself. Its
-  // MINIDUMP_THREAD_NAME is written by its parent as part of a
-  // MINIDUMP_THREAD_NAME_LIST, and its children are responsible for writing
-  // themselves.
+  // This object doesn’t directly write anything itself. Its parent writes the
+  // MINIDUMP_THREAD_NAME objects as part of a MINIDUMP_THREAD_NAME_LIST, and
+  // its children are responsible for writing themselves.
   return 0;
 }
 
@@ -63,24 +88,6 @@
   return children;
 }
 
-bool MinidumpThreadNameWriter::WillWriteAtOffsetImpl(FileOffset offset) {
-  DCHECK_EQ(state(), kStateFrozen);
-
-  // This cannot use RegisterRVA(&thread_name_.RvaOfThreadName), since
-  // &MINIDUMP_THREAD_NAME_LIST::RvaOfThreadName is not aligned on a pointer
-  // boundary, so it causes failures on 32-bit ARM.
-  //
-  // Instead, manually update the RVA64 to the current file offset since the
-  // child thread_name_ will write its contents at that offset.
-  decltype(thread_name_.RvaOfThreadName) local_rva_of_thread_name;
-  if (!AssignIfInRange(&local_rva_of_thread_name, offset)) {
-    LOG(ERROR) << "offset " << offset << " out of range";
-    return false;
-  }
-  thread_name_.RvaOfThreadName = local_rva_of_thread_name;
-  return MinidumpWritable::WillWriteAtOffsetImpl(offset);
-}
-
 bool MinidumpThreadNameWriter::WriteObject(FileWriterInterface* file_writer) {
   DCHECK_EQ(state(), kStateWritable);
 
@@ -96,6 +103,19 @@
 
 MinidumpThreadNameListWriter::~MinidumpThreadNameListWriter() {}
 
+void MinidumpThreadNameListWriter::InitializeFromSnapshot(
+    const std::vector<const ThreadSnapshot*>& thread_snapshots,
+    const MinidumpThreadIDMap& thread_id_map) {
+  DCHECK_EQ(state(), kStateMutable);
+  DCHECK(thread_names_.empty());
+
+  for (const ThreadSnapshot* thread_snapshot : thread_snapshots) {
+    auto thread = std::make_unique<MinidumpThreadNameWriter>();
+    thread->InitializeFromSnapshot(thread_snapshot, thread_id_map);
+    AddThreadName(std::move(thread));
+  }
+}
+
 void MinidumpThreadNameListWriter::AddThreadName(
     std::unique_ptr<MinidumpThreadNameWriter> thread_name) {
   DCHECK_EQ(state(), kStateMutable);
@@ -150,10 +170,15 @@
   std::vector<WritableIoVec> iovecs(1, iov);
   iovecs.reserve(thread_names_.size() + 1);
 
+  std::vector<MINIDUMP_THREAD_NAME> minidump_thread_names;
+  minidump_thread_names.reserve(thread_names_.size());
   for (const auto& thread_name : thread_names_) {
-    iov.iov_base = thread_name->MinidumpThreadName();
-    iov.iov_len = sizeof(MINIDUMP_THREAD_NAME);
-    iovecs.emplace_back(iov);
+    auto& minidump_thread_name = minidump_thread_names.emplace_back();
+    minidump_thread_name.ThreadId = thread_name->ThreadId();
+    minidump_thread_name.RvaOfThreadName = thread_name->RvaOfThreadName();
+    iov.iov_base = &minidump_thread_name;
+    iov.iov_len = sizeof(minidump_thread_name);
+    iovecs.push_back(iov);
   }
 
   return file_writer->WriteIoVec(&iovecs);
diff --git a/third_party/crashpad/crashpad/minidump/minidump_thread_name_list_writer.h b/third_party/crashpad/crashpad/minidump/minidump_thread_name_list_writer.h
index 5afab981..f573111c 100644
--- a/third_party/crashpad/crashpad/minidump/minidump_thread_name_list_writer.h
+++ b/third_party/crashpad/crashpad/minidump/minidump_thread_name_list_writer.h
@@ -25,6 +25,7 @@
 
 #include "minidump/minidump_stream_writer.h"
 #include "minidump/minidump_string_writer.h"
+#include "minidump/minidump_thread_id_map.h"
 #include "minidump/minidump_writable.h"
 
 namespace crashpad {
@@ -45,28 +46,57 @@
 
   ~MinidumpThreadNameWriter() override;
 
-  //! \brief Returns a MINIDUMP_THREAD_NAME referencing this object’s data.
+  //! \brief Initializes the MINIDUMP_THREAD_NAME based on \a thread_snapshot.
   //!
-  //! This method is expected to be called by a MinidumpThreadNameListWriter in
-  //! order to obtain a MINIDUMP_THREAD_NAME to include in its list.
+  //! \param[in] thread_snapshot The thread snapshot to use as source data.
+  //! \param[in] thread_id_map A MinidumpThreadIDMap to be consulted to
+  //!     determine the 32-bit minidump thread ID to use for \a thread_snapshot.
+  //!
+  //! \note Valid in #kStateMutable.
+  void InitializeFromSnapshot(const ThreadSnapshot* thread_snapshot,
+                              const MinidumpThreadIDMap& thread_id_map);
+
+  //! \brief Sets the ThreadId for MINIDUMP_THREAD_NAME::ThreadId.
+  void SetThreadId(uint32_t thread_id) { thread_id_ = thread_id; }
+
+  //! \brief Gets the ThreadId for MINIDUMP_THREAD_NAME::ThreadId.
   //!
   //! \note Valid in #kStateWritable.
-  const MINIDUMP_THREAD_NAME* MinidumpThreadName() const;
-
-  //! \brief Sets MINIDUMP_THREAD_NAME::ThreadId.
-  void SetThreadId(uint32_t thread_id) { thread_name_.ThreadId = thread_id; }
+  uint32_t ThreadId() const;
 
   //! \brief Sets MINIDUMP_THREAD_NAME::RvaOfThreadName.
   void SetThreadName(const std::string& thread_name);
 
+  //! \brief Returns an RVA64 which has been updated with the relative address
+  //!    of the thread name.
+  //!
+  //! This method is expected to be called by a MinidumpThreadNameListWriter in
+  //! order to obtain the RVA64 of the thread name.
+  //!
+  //! \note Valid in #kStateWritable.
+  RVA64 RvaOfThreadName() const;
+
  private:
   // MinidumpWritable:
+  bool Freeze() override;
   size_t SizeOfObject() override;
   std::vector<MinidumpWritable*> Children() override;
-  bool WillWriteAtOffsetImpl(FileOffset offset) override;
   bool WriteObject(FileWriterInterface* file_writer) override;
 
-  MINIDUMP_THREAD_NAME thread_name_;
+  // This exists as a separate field so MinidumpWritable::RegisterRVA() can be
+  // used on a guaranteed-aligned pointer (MINIDUMP_THREAD_NAME::RvaOfThreadName
+  // is not 64-bit aligned, causing issues on ARM).
+  RVA64 rva_of_thread_name_;
+
+  // Although this class manages the data for a MINIDUMP_THREAD_NAME, it does
+  // not directly hold a MINIDUMP_THREAD_NAME, as that struct contains a
+  // non-aligned RVA64 field which prevents it use with
+  // MinidumpWritable::RegisterRVA().
+  //
+  // Instead, this class individually holds the fields of the
+  // MINIDUMP_THREAD_NAME which are fetched by MinidumpThreadNameListWriter.
+  uint32_t thread_id_;
+
   std::unique_ptr<internal::MinidumpUTF16StringWriter> name_;
 };
 
@@ -83,6 +113,18 @@
 
   ~MinidumpThreadNameListWriter() override;
 
+  //! \brief Adds an initialized MINIDUMP_THREAD_NAME for each thread in \a
+  //!     thread_snapshots to the MINIDUMP_THREAD_NAME_LIST.
+  //!
+  //! \param[in] thread_snapshots The thread snapshots to use as source data.
+  //! \param[in] thread_id_map A MinidumpThreadIDMap previously built by
+  //!     MinidumpThreadListWriter::InitializeFromSnapshot().
+  //!
+  //! \note Valid in #kStateMutable.
+  void InitializeFromSnapshot(
+      const std::vector<const ThreadSnapshot*>& thread_snapshots,
+      const MinidumpThreadIDMap& thread_id_map);
+
   //! \brief Adds a MinidumpThreadNameWriter to the MINIDUMP_THREAD_LIST.
   //!
   //! This object takes ownership of \a thread_name and becomes its parent in
diff --git a/third_party/crashpad/crashpad/minidump/minidump_thread_name_list_writer_test.cc b/third_party/crashpad/crashpad/minidump/minidump_thread_name_list_writer_test.cc
index b61b1bb..265c12e 100644
--- a/third_party/crashpad/crashpad/minidump/minidump_thread_name_list_writer_test.cc
+++ b/third_party/crashpad/crashpad/minidump/minidump_thread_name_list_writer_test.cc
@@ -24,6 +24,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "gtest/gtest.h"
 #include "minidump/minidump_file_writer.h"
+#include "minidump/minidump_system_info_writer.h"
 #include "minidump/test/minidump_file_writer_test_util.h"
 #include "minidump/test/minidump_string_writer_test_util.h"
 #include "minidump/test/minidump_writable_test_util.h"
@@ -137,6 +138,64 @@
                                            kThreadName));
 }
 
+TEST(MinidumpThreadNameListWriter, OneThreadWithLeadingPadding) {
+  MinidumpFileWriter minidump_file_writer;
+
+  // Add a stream before the MINIDUMP_THREAD_NAME_LIST to ensure the thread name
+  // MINIDUMP_STRING requires leading padding to align to a 4-byte boundary.
+  auto system_info_writer = std::make_unique<MinidumpSystemInfoWriter>();
+  system_info_writer->SetCSDVersion("");
+  ASSERT_TRUE(minidump_file_writer.AddStream(std::move(system_info_writer)));
+
+  auto thread_list_writer = std::make_unique<MinidumpThreadNameListWriter>();
+
+  constexpr uint32_t kThreadID = 0x11111111;
+  const std::string kThreadName = "ariadne";
+
+  auto thread_name_list_writer =
+      std::make_unique<MinidumpThreadNameListWriter>();
+  auto thread_name_writer = std::make_unique<MinidumpThreadNameWriter>();
+  thread_name_writer->SetThreadId(kThreadID);
+  thread_name_writer->SetThreadName(kThreadName);
+  thread_name_list_writer->AddThreadName(std::move(thread_name_writer));
+
+  ASSERT_TRUE(
+      minidump_file_writer.AddStream(std::move(thread_name_list_writer)));
+
+  StringFile string_file;
+  ASSERT_TRUE(minidump_file_writer.WriteEverything(&string_file));
+
+  ASSERT_GT(string_file.string().size(),
+            sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) +
+                sizeof(MINIDUMP_THREAD_NAME_LIST) +
+                1 * sizeof(MINIDUMP_THREAD_NAME));
+
+  const uint32_t kExpectedStreams = 2;
+  const MINIDUMP_DIRECTORY* directory;
+  const MINIDUMP_HEADER* header =
+      MinidumpHeaderAtStart(string_file.string(), &directory);
+  ASSERT_NO_FATAL_FAILURE(VerifyMinidumpHeader(header, kExpectedStreams, 0));
+  ASSERT_TRUE(directory);
+
+  ASSERT_EQ(directory[0].StreamType, kMinidumpStreamTypeSystemInfo);
+  ASSERT_EQ(directory[1].StreamType, kMinidumpStreamTypeThreadNameList);
+
+  const MINIDUMP_THREAD_NAME_LIST* thread_name_list =
+      MinidumpWritableAtLocationDescriptor<MINIDUMP_THREAD_NAME_LIST>(
+          string_file.string(), directory[1].Location);
+  ASSERT_TRUE(thread_name_list);
+
+  EXPECT_EQ(thread_name_list->NumberOfThreadNames, 1u);
+
+  MINIDUMP_THREAD_NAME expected = {};
+  expected.ThreadId = kThreadID;
+
+  ASSERT_NO_FATAL_FAILURE(ExpectThreadName(&expected,
+                                           &thread_name_list->ThreadNames[0],
+                                           string_file.string(),
+                                           kThreadName));
+}
+
 TEST(MinidumpThreadNameListWriter, TwoThreads_DifferentNames) {
   MinidumpFileWriter minidump_file_writer;
   auto thread_list_writer = std::make_unique<MinidumpThreadNameListWriter>();
diff --git a/third_party/crashpad/crashpad/snapshot/fuchsia/process_reader_fuchsia_test.cc b/third_party/crashpad/crashpad/snapshot/fuchsia/process_reader_fuchsia_test.cc
index 362be5b..b8e71afb 100644
--- a/third_party/crashpad/crashpad/snapshot/fuchsia/process_reader_fuchsia_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/fuchsia/process_reader_fuchsia_test.cc
@@ -22,8 +22,10 @@
 
 #include <iterator>
 
+#include "base/strings/stringprintf.h"
 #include "gtest/gtest.h"
 #include "test/multiprocess_exec.h"
+#include "test/scoped_set_thread_name.h"
 #include "test/test_paths.h"
 #include "util/fuchsia/scoped_task_suspend.h"
 
@@ -32,6 +34,8 @@
 namespace {
 
 TEST(ProcessReaderFuchsia, SelfBasic) {
+  const ScopedSetThreadName scoped_set_thread_name("SelfBasic");
+
   ProcessReaderFuchsia process_reader;
   ASSERT_TRUE(process_reader.Initialize(*zx::process::self()));
 
@@ -75,7 +79,7 @@
             ZX_OK);
   EXPECT_EQ(threads[0].id, info.koid);
   EXPECT_EQ(threads[0].state, ZX_THREAD_STATE_RUNNING);
-  EXPECT_EQ(threads[0].name, "initial-thread");
+  EXPECT_EQ(threads[0].name, "SelfBasic");
 }
 
 constexpr char kTestMemory[] = "Read me from another process";
@@ -118,27 +122,44 @@
   test.Run();
 }
 
+struct ThreadData {
+  zx_handle_t port;
+  std::string name;
+};
+
 void* SignalAndSleep(void* arg) {
+  const ThreadData* thread_data = reinterpret_cast<const ThreadData*>(arg);
+  const ScopedSetThreadName scoped_set_thread_name(thread_data->name);
   zx_port_packet_t packet = {};
   packet.type = ZX_PKT_TYPE_USER;
-  zx_port_queue(*reinterpret_cast<zx_handle_t*>(arg), &packet);
+  zx_port_queue(thread_data->port, &packet);
   zx_nanosleep(ZX_TIME_INFINITE);
   return nullptr;
 }
 
 CRASHPAD_CHILD_TEST_MAIN(ProcessReaderChildThreadsTestMain) {
+  const ScopedSetThreadName scoped_set_thread_name(
+      "ProcessReaderChildThreadsTest-Main");
+
   // Create 5 threads with stack sizes of 4096, 8192, ...
   zx_handle_t port;
   zx_status_t status = zx_port_create(0, &port);
   EXPECT_EQ(status, ZX_OK);
 
   constexpr size_t kNumThreads = 5;
+  struct ThreadData thread_data[kNumThreads] = {{0, 0}};
+
   for (size_t i = 0; i < kNumThreads; ++i) {
+    thread_data[i] = {
+        .port = port,
+        .name = base::StringPrintf("ProcessReaderChildThreadsTest-%zu", i + 1),
+    };
     pthread_attr_t attr;
     EXPECT_EQ(pthread_attr_init(&attr), 0);
     EXPECT_EQ(pthread_attr_setstacksize(&attr, (i + 1) * 4096), 0);
     pthread_t thread;
-    EXPECT_EQ(pthread_create(&thread, &attr, &SignalAndSleep, &port), 0);
+    EXPECT_EQ(pthread_create(&thread, &attr, &SignalAndSleep, &thread_data[i]),
+              0);
   }
 
   // Wait until all threads are ready.
@@ -179,10 +200,14 @@
     const auto& threads = process_reader.Threads();
     EXPECT_EQ(threads.size(), 6u);
 
+    EXPECT_EQ(threads[0].name, "ProcessReaderChildThreadsTest-main");
+
     for (size_t i = 1; i < 6; ++i) {
       ASSERT_GT(threads[i].stack_regions.size(), 0u);
       EXPECT_GT(threads[i].stack_regions[0].size(), 0u);
       EXPECT_LE(threads[i].stack_regions[0].size(), i * 4096u);
+      EXPECT_EQ(threads[i].name,
+                base::StringPrintf("ProcessReaderChildThreadsTest-%zu", i));
     }
   }
 };
diff --git a/third_party/crashpad/crashpad/snapshot/fuchsia/thread_snapshot_fuchsia.cc b/third_party/crashpad/crashpad/snapshot/fuchsia/thread_snapshot_fuchsia.cc
index 369203a..b12ee861 100644
--- a/third_party/crashpad/crashpad/snapshot/fuchsia/thread_snapshot_fuchsia.cc
+++ b/third_party/crashpad/crashpad/snapshot/fuchsia/thread_snapshot_fuchsia.cc
@@ -25,6 +25,7 @@
       context_arch_(),
       context_(),
       stack_(),
+      thread_name_(),
       thread_id_(ZX_KOID_INVALID),
       thread_specific_data_address_(0),
       initialized_() {}
@@ -60,6 +61,7 @@
     // TODO(scottmg): Handle split stack by adding other parts to ExtraMemory().
   }
 
+  thread_name_ = thread.name;
   thread_id_ = thread.id;
 
   INITIALIZATION_STATE_SET_VALID(initialized_);
@@ -81,6 +83,11 @@
   return thread_id_;
 }
 
+std::string ThreadSnapshotFuchsia::ThreadName() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return thread_name_;
+}
+
 int ThreadSnapshotFuchsia::SuspendCount() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   // There is not (currently) a suspend count for threads on Fuchsia.
diff --git a/third_party/crashpad/crashpad/snapshot/fuchsia/thread_snapshot_fuchsia.h b/third_party/crashpad/crashpad/snapshot/fuchsia/thread_snapshot_fuchsia.h
index b91a514d..5c804fb1 100644
--- a/third_party/crashpad/crashpad/snapshot/fuchsia/thread_snapshot_fuchsia.h
+++ b/third_party/crashpad/crashpad/snapshot/fuchsia/thread_snapshot_fuchsia.h
@@ -18,6 +18,8 @@
 #include <stdint.h>
 #include <zircon/types.h>
 
+#include <string>
+
 #include "build/build_config.h"
 #include "snapshot/cpu_context.h"
 #include "snapshot/fuchsia/process_reader_fuchsia.h"
@@ -56,6 +58,7 @@
   const CPUContext* Context() const override;
   const MemorySnapshot* Stack() const override;
   uint64_t ThreadID() const override;
+  std::string ThreadName() const override;
   int SuspendCount() const override;
   int Priority() const override;
   uint64_t ThreadSpecificDataAddress() const override;
@@ -71,6 +74,7 @@
 #endif
   CPUContext context_;
   MemorySnapshotGeneric stack_;
+  std::string thread_name_;
   zx_koid_t thread_id_;
   zx_vaddr_t thread_specific_data_address_;
   InitializationStateDcheck initialized_;
diff --git a/third_party/crashpad/crashpad/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc b/third_party/crashpad/crashpad/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc
index 34c34f8..0a170e7 100644
--- a/third_party/crashpad/crashpad/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/ios/process_snapshot_ios_intermediate_dump_test.cc
@@ -392,6 +392,7 @@
               Key::kThreadContextMemoryRegionData, "string", 6));
         }
       }
+      EXPECT_TRUE(writer->AddPropertyBytes(Key::kThreadName, "ariadne", 7));
     }
   }
 
@@ -411,6 +412,7 @@
     uint64_t thread_id = 1;
     for (auto thread : threads) {
       EXPECT_EQ(thread->ThreadID(), thread_id);
+      EXPECT_EQ(thread->ThreadName(), "ariadne");
       EXPECT_EQ(thread->SuspendCount(), 666);
       EXPECT_EQ(thread->Priority(), 5);
       EXPECT_EQ(thread->ThreadSpecificDataAddress(), thread_id++);
diff --git a/third_party/crashpad/crashpad/snapshot/ios/thread_snapshot_ios_intermediate_dump.cc b/third_party/crashpad/crashpad/snapshot/ios/thread_snapshot_ios_intermediate_dump.cc
index ed7b28a..40387fa 100644
--- a/third_party/crashpad/crashpad/snapshot/ios/thread_snapshot_ios_intermediate_dump.cc
+++ b/third_party/crashpad/crashpad/snapshot/ios/thread_snapshot_ios_intermediate_dump.cc
@@ -75,6 +75,7 @@
 #endif
       context_(),
       stack_(),
+      thread_name_(),
       thread_id_(0),
       thread_specific_data_address_(0),
       suspend_count_(0),
@@ -100,6 +101,7 @@
   GetDataValueFromMap(thread_data, Key::kThreadID, &thread_id_);
   GetDataValueFromMap(
       thread_data, Key::kThreadDataAddress, &thread_specific_data_address_);
+  GetDataStringFromMap(thread_data, Key::kThreadName, &thread_name_);
 
 #if defined(ARCH_CPU_X86_64)
   typedef x86_thread_state64_t thread_state_type;
@@ -218,6 +220,11 @@
   return thread_id_;
 }
 
+std::string ThreadSnapshotIOSIntermediateDump::ThreadName() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return thread_name_;
+}
+
 int ThreadSnapshotIOSIntermediateDump::SuspendCount() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return suspend_count_;
diff --git a/third_party/crashpad/crashpad/snapshot/ios/thread_snapshot_ios_intermediate_dump.h b/third_party/crashpad/crashpad/snapshot/ios/thread_snapshot_ios_intermediate_dump.h
index cf9ccec..dafa455 100644
--- a/third_party/crashpad/crashpad/snapshot/ios/thread_snapshot_ios_intermediate_dump.h
+++ b/third_party/crashpad/crashpad/snapshot/ios/thread_snapshot_ios_intermediate_dump.h
@@ -15,6 +15,8 @@
 #ifndef CRASHPAD_SNAPSHOT_IOS_INTERMEDIATE_DUMP_THREAD_SNAPSHOT_IOS_INTERMEDIATEDUMP_H_
 #define CRASHPAD_SNAPSHOT_IOS_INTERMEDIATE_DUMP_THREAD_SNAPSHOT_IOS_INTERMEDIATEDUMP_H_
 
+#include <string>
+
 #include "build/build_config.h"
 #include "snapshot/cpu_context.h"
 #include "snapshot/ios/memory_snapshot_ios_intermediate_dump.h"
@@ -49,6 +51,7 @@
   const CPUContext* Context() const override;
   const MemorySnapshot* Stack() const override;
   uint64_t ThreadID() const override;
+  std::string ThreadName() const override;
   int SuspendCount() const override;
   int Priority() const override;
   uint64_t ThreadSpecificDataAddress() const override;
@@ -65,6 +68,7 @@
   CPUContext context_;
   std::vector<uint8_t> exception_stack_memory_;
   MemorySnapshotIOSIntermediateDump stack_;
+  std::string thread_name_;
   uint64_t thread_id_;
   uint64_t thread_specific_data_address_;
   int suspend_count_;
diff --git a/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux.cc b/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux.cc
index 5711f34..4b663bb 100644
--- a/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux.cc
+++ b/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux.cc
@@ -24,6 +24,7 @@
 #include <algorithm>
 
 #include "base/logging.h"
+#include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "snapshot/linux/debug_rendezvous.h"
 #include "util/linux/auxiliary_vector.h"
@@ -52,6 +53,7 @@
     : thread_info(),
       stack_region_address(0),
       stack_region_size(0),
+      name(),
       tid(-1),
       static_priority(-1),
       nice_value(-1) {}
@@ -64,6 +66,23 @@
     return false;
   }
 
+  // From man proc(5):
+  //
+  // /proc/[pid]/comm (since Linux 2.6.33)
+  //
+  // Different threads in the same process may have different comm values,
+  // accessible via /proc/[pid]/task/[tid]/comm.
+  const std::string path = base::StringPrintf(
+      "/proc/%d/task/%d/comm", connection->GetProcessID(), tid);
+  if (connection->ReadFileContents(base::FilePath(path), &name)) {
+    if (!name.empty() && name.back() == '\n') {
+      // Remove the final newline character.
+      name.pop_back();
+    }
+  } else {
+    // Continue on without the thread name.
+  }
+
   // TODO(jperaza): Collect scheduling priorities via the broker when they can't
   // be collected directly.
   have_priorities = false;
diff --git a/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux.h b/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux.h
index f44e15f..e8cf107 100644
--- a/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux.h
+++ b/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux.h
@@ -60,6 +60,7 @@
     ThreadInfo thread_info;
     LinuxVMAddress stack_region_address;
     LinuxVMSize stack_region_size;
+    std::string name;
     pid_t tid;
     int sched_policy;
     int static_priority;
diff --git a/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux_test.cc b/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux_test.cc
index 81b3d6e..e4179de 100644
--- a/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/linux/process_reader_linux_test.cc
@@ -43,6 +43,7 @@
 #include "test/linux/get_tls.h"
 #include "test/multiprocess.h"
 #include "test/scoped_module_handle.h"
+#include "test/scoped_set_thread_name.h"
 #include "test/test_paths.h"
 #include "util/file/file_io.h"
 #include "util/file/file_writer.h"
@@ -169,7 +170,9 @@
 
   void StartThreads(size_t thread_count, size_t stack_size = 0) {
     for (size_t thread_index = 0; thread_index < thread_count; ++thread_index) {
-      threads_.push_back(std::make_unique<Thread>());
+      const std::string thread_name =
+          base::StringPrintf("ThreadPool-%zu", thread_index);
+      threads_.push_back(std::make_unique<Thread>(thread_name));
       Thread* thread = threads_.back().get();
 
       pthread_attr_t attr;
@@ -211,22 +214,26 @@
   }
 
   pid_t GetThreadExpectation(size_t thread_index,
-                             ThreadExpectation* expectation) {
+                             ThreadExpectation* expectation,
+                             std::string* thread_name_expectation) {
     CHECK_LT(thread_index, threads_.size());
 
     const Thread* thread = threads_[thread_index].get();
     *expectation = thread->expectation;
+    *thread_name_expectation = thread->name;
     return thread->tid;
   }
 
  private:
   struct Thread {
-    Thread()
+    explicit Thread(const std::string& name)
         : pthread(),
           expectation(),
           ready_semaphore(0),
           exit_semaphore(0),
-          tid(-1) {}
+          tid(-1),
+          name(name) {
+    }
     ~Thread() {}
 
     pthread_t pthread;
@@ -235,10 +242,12 @@
     Semaphore ready_semaphore;
     Semaphore exit_semaphore;
     pid_t tid;
+    const std::string name;
   };
 
   static void* ThreadMain(void* argument) {
     Thread* thread = static_cast<Thread*>(argument);
+    const ScopedSetThreadName scoped_set_thread_name(thread->name);
 
     CHECK_EQ(setpriority(PRIO_PROCESS, 0, thread->expectation.nice_value), 0)
         << ErrnoMessage("setpriority");
@@ -260,20 +269,24 @@
 };
 
 using ThreadMap = std::map<pid_t, TestThreadPool::ThreadExpectation>;
+using ThreadNameMap = std::map<pid_t, std::string>;
 
 void ExpectThreads(const ThreadMap& thread_map,
+                   const ThreadNameMap& thread_name_map,
                    const std::vector<ProcessReaderLinux::Thread>& threads,
                    PtraceConnection* connection) {
   ASSERT_EQ(threads.size(), thread_map.size());
+  ASSERT_EQ(threads.size(), thread_name_map.size());
 
   MemoryMap memory_map;
   ASSERT_TRUE(memory_map.Initialize(connection));
 
   for (const auto& thread : threads) {
     SCOPED_TRACE(
-        base::StringPrintf("Thread id %d, tls 0x%" PRIx64
+        base::StringPrintf("Thread id %d, name %s, tls 0x%" PRIx64
                            ", stack addr 0x%" PRIx64 ", stack size 0x%" PRIx64,
                            thread.tid,
+                           thread.name.c_str(),
                            thread.thread_info.thread_specific_data_address,
                            thread.stack_region_address,
                            thread.stack_region_size));
@@ -306,6 +319,10 @@
     EXPECT_EQ(thread.sched_policy, iterator->second.sched_policy);
     EXPECT_EQ(thread.static_priority, iterator->second.static_priority);
     EXPECT_EQ(thread.nice_value, iterator->second.nice_value);
+
+    const auto& thread_name_iterator = thread_name_map.find(thread.tid);
+    ASSERT_NE(thread_name_iterator, thread_name_map.end());
+    EXPECT_EQ(thread.name, thread_name_iterator->second);
   }
 }
 
@@ -322,6 +339,7 @@
  private:
   void MultiprocessParent() override {
     ThreadMap thread_map;
+    ThreadNameMap thread_name_map;
     for (size_t thread_index = 0; thread_index < kThreadCount + 1;
          ++thread_index) {
       pid_t tid;
@@ -331,6 +349,14 @@
       CheckedReadFileExactly(
           ReadPipeHandle(), &expectation, sizeof(expectation));
       thread_map[tid] = expectation;
+
+      std::string::size_type thread_name_length;
+      CheckedReadFileExactly(
+          ReadPipeHandle(), &thread_name_length, sizeof(thread_name_length));
+      std::string thread_name(thread_name_length, '\0');
+      CheckedReadFileExactly(
+          ReadPipeHandle(), thread_name.data(), thread_name_length);
+      thread_name_map[tid] = thread_name;
     }
 
     DirectPtraceConnection connection;
@@ -340,19 +366,22 @@
     ASSERT_TRUE(process_reader.Initialize(&connection));
     const std::vector<ProcessReaderLinux::Thread>& threads =
         process_reader.Threads();
-    ExpectThreads(thread_map, threads, &connection);
+    ExpectThreads(thread_map, thread_name_map, threads, &connection);
   }
 
   void MultiprocessChild() override {
     TestThreadPool thread_pool;
     thread_pool.StartThreads(kThreadCount, stack_size_);
 
+    const std::string current_thread_name = "MultiprocChild";
+    const ScopedSetThreadName scoped_set_thread_name(current_thread_name);
+
     TestThreadPool::ThreadExpectation expectation;
 #if defined(MEMORY_SANITIZER)
     // memset() + re-initialization is required to zero padding bytes for MSan.
     memset(&expectation, 0, sizeof(expectation));
 #endif  // defined(MEMORY_SANITIZER)
-    expectation = {};
+    expectation = {0};
     expectation.tls = GetTLS();
     expectation.stack_address = reinterpret_cast<LinuxVMAddress>(&thread_pool);
 
@@ -373,11 +402,28 @@
 
     CheckedWriteFile(WritePipeHandle(), &tid, sizeof(tid));
     CheckedWriteFile(WritePipeHandle(), &expectation, sizeof(expectation));
+    const std::string::size_type current_thread_name_length =
+        current_thread_name.length();
+    CheckedWriteFile(WritePipeHandle(),
+                     &current_thread_name_length,
+                     sizeof(current_thread_name_length));
+    CheckedWriteFile(WritePipeHandle(),
+                     current_thread_name.data(),
+                     current_thread_name_length);
 
     for (size_t thread_index = 0; thread_index < kThreadCount; ++thread_index) {
-      tid = thread_pool.GetThreadExpectation(thread_index, &expectation);
+      std::string thread_name_expectation;
+      tid = thread_pool.GetThreadExpectation(
+          thread_index, &expectation, &thread_name_expectation);
       CheckedWriteFile(WritePipeHandle(), &tid, sizeof(tid));
       CheckedWriteFile(WritePipeHandle(), &expectation, sizeof(expectation));
+      const std::string::size_type thread_name_length =
+          thread_name_expectation.length();
+      CheckedWriteFile(
+          WritePipeHandle(), &thread_name_length, sizeof(thread_name_length));
+      CheckedWriteFile(WritePipeHandle(),
+                       thread_name_expectation.data(),
+                       thread_name_length);
     }
 
     CheckedReadFileAtEOF(ReadPipeHandle());
diff --git a/third_party/crashpad/crashpad/snapshot/linux/thread_snapshot_linux.cc b/third_party/crashpad/crashpad/snapshot/linux/thread_snapshot_linux.cc
index f279e0ad..04776de 100644
--- a/third_party/crashpad/crashpad/snapshot/linux/thread_snapshot_linux.cc
+++ b/third_party/crashpad/crashpad/snapshot/linux/thread_snapshot_linux.cc
@@ -133,6 +133,7 @@
       context_(),
       stack_(),
       thread_specific_data_address_(0),
+      thread_name_(),
       thread_id_(-1),
       priority_(-1),
       initialized_() {}
@@ -200,6 +201,7 @@
   thread_specific_data_address_ =
       thread.thread_info.thread_specific_data_address;
 
+  thread_name_ = thread.name;
   thread_id_ = thread.tid;
 
   priority_ =
@@ -234,6 +236,11 @@
   return thread_id_;
 }
 
+std::string ThreadSnapshotLinux::ThreadName() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return thread_name_;
+}
+
 int ThreadSnapshotLinux::SuspendCount() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return 0;
diff --git a/third_party/crashpad/crashpad/snapshot/linux/thread_snapshot_linux.h b/third_party/crashpad/crashpad/snapshot/linux/thread_snapshot_linux.h
index 40cd7e7..4d1f7a7 100644
--- a/third_party/crashpad/crashpad/snapshot/linux/thread_snapshot_linux.h
+++ b/third_party/crashpad/crashpad/snapshot/linux/thread_snapshot_linux.h
@@ -57,6 +57,7 @@
   const CPUContext* Context() const override;
   const MemorySnapshot* Stack() const override;
   uint64_t ThreadID() const override;
+  std::string ThreadName() const override;
   int SuspendCount() const override;
   int Priority() const override;
   uint64_t ThreadSpecificDataAddress() const override;
@@ -80,6 +81,7 @@
   CPUContext context_;
   MemorySnapshotGeneric stack_;
   LinuxVMAddress thread_specific_data_address_;
+  std::string thread_name_;
   pid_t thread_id_;
   int priority_;
   InitializationStateDcheck initialized_;
diff --git a/third_party/crashpad/crashpad/snapshot/mac/process_reader_mac.cc b/third_party/crashpad/crashpad/snapshot/mac/process_reader_mac.cc
index 9b2a235..5f9f8b7b 100644
--- a/third_party/crashpad/crashpad/snapshot/mac/process_reader_mac.cc
+++ b/third_party/crashpad/crashpad/snapshot/mac/process_reader_mac.cc
@@ -75,6 +75,7 @@
     : thread_context(),
       float_context(),
       debug_context(),
+      name(),
       id(0),
       stack_region_address(0),
       stack_region_size(0),
@@ -365,6 +366,20 @@
       thread.thread_specific_data_address = identifier_info.thread_handle;
     }
 
+    thread_extended_info extended_info;
+    count = THREAD_EXTENDED_INFO_COUNT;
+    kr = thread_info(thread.port,
+                     THREAD_EXTENDED_INFO,
+                     reinterpret_cast<thread_info_t>(&extended_info),
+                     &count);
+    if (kr != KERN_SUCCESS) {
+      MACH_LOG(WARNING, kr) << "thread_info(THREAD_EXTENDED_INFO)";
+    } else {
+      thread.name.assign(
+          extended_info.pth_name,
+          strnlen(extended_info.pth_name, sizeof(extended_info.pth_name)));
+    }
+
     thread_precedence_policy precedence;
     count = THREAD_PRECEDENCE_POLICY_COUNT;
     boolean_t get_default = FALSE;
diff --git a/third_party/crashpad/crashpad/snapshot/mac/process_reader_mac.h b/third_party/crashpad/crashpad/snapshot/mac/process_reader_mac.h
index 85cfe7c..4f7792f 100644
--- a/third_party/crashpad/crashpad/snapshot/mac/process_reader_mac.h
+++ b/third_party/crashpad/crashpad/snapshot/mac/process_reader_mac.h
@@ -75,6 +75,7 @@
     ThreadContext thread_context;
     FloatContext float_context;
     DebugContext debug_context;
+    std::string name;
     uint64_t id;
     mach_vm_address_t stack_region_address;
     mach_vm_size_t stack_region_size;
diff --git a/third_party/crashpad/crashpad/snapshot/mac/process_reader_mac_test.cc b/third_party/crashpad/crashpad/snapshot/mac/process_reader_mac_test.cc
index d5104e8..8d1ba2a 100644
--- a/third_party/crashpad/crashpad/snapshot/mac/process_reader_mac_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/mac/process_reader_mac_test.cc
@@ -42,6 +42,7 @@
 #include "test/mac/dyld.h"
 #include "test/mac/mach_errors.h"
 #include "test/mac/mach_multiprocess.h"
+#include "test/scoped_set_thread_name.h"
 #include "util/file/file_io.h"
 #include "util/mac/mac_util.h"
 #include "util/mach/mach_extensions.h"
@@ -139,6 +140,9 @@
 }
 
 TEST(ProcessReaderMac, SelfOneThread) {
+  const ScopedSetThreadName scoped_set_thread_name(
+      "ProcessReaderMac/SelfOneThread");
+
   ProcessReaderMac process_reader;
   ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
 
@@ -151,6 +155,7 @@
   ASSERT_GE(threads.size(), 1u);
 
   EXPECT_EQ(threads[0].id, PthreadToThreadID(pthread_self()));
+  EXPECT_EQ(threads[0].name, "ProcessReaderMac/SelfOneThread");
 
   thread_t thread_self = MachThreadSelf();
   EXPECT_EQ(threads[0].port, thread_self);
@@ -163,9 +168,11 @@
   struct ThreadExpectation {
     mach_vm_address_t stack_address;
     int suspend_count;
+    std::string thread_name;
   };
 
-  TestThreadPool() : thread_infos_() {}
+  TestThreadPool(const std::string& thread_name_prefix)
+      : thread_infos_(), thread_name_prefix_(thread_name_prefix) {}
 
   TestThreadPool(const TestThreadPool&) = delete;
   TestThreadPool& operator=(const TestThreadPool&) = delete;
@@ -199,7 +206,10 @@
     ASSERT_TRUE(thread_infos_.empty());
 
     for (size_t thread_index = 0; thread_index < thread_count; ++thread_index) {
-      thread_infos_.push_back(std::make_unique<ThreadInfo>());
+      std::string thread_name = base::StringPrintf(
+          "%s-%zu", thread_name_prefix_.c_str(), thread_index);
+      thread_infos_.push_back(
+          std::make_unique<ThreadInfo>(std::move(thread_name)));
       ThreadInfo* thread_info = thread_infos_.back().get();
 
       int rv = pthread_create(
@@ -235,18 +245,20 @@
     const auto& thread_info = thread_infos_[thread_index];
     expectation->stack_address = thread_info->stack_address;
     expectation->suspend_count = thread_info->suspend_count;
+    expectation->thread_name = thread_info->thread_name;
 
     return PthreadToThreadID(thread_info->pthread);
   }
 
  private:
   struct ThreadInfo {
-    ThreadInfo()
+    ThreadInfo(const std::string& thread_name)
         : pthread(nullptr),
           stack_address(0),
           ready_semaphore(0),
           exit_semaphore(0),
-          suspend_count(0) {}
+          suspend_count(0),
+          thread_name(thread_name) {}
 
     ~ThreadInfo() {}
 
@@ -270,10 +282,14 @@
 
     // The thread’s suspend count.
     int suspend_count;
+
+    // The thread's name.
+    const std::string thread_name;
   };
 
   static void* ThreadMain(void* argument) {
     ThreadInfo* thread_info = static_cast<ThreadInfo*>(argument);
+    const ScopedSetThreadName scoped_set_thread_name(thread_info->thread_name);
 
     thread_info->stack_address =
         FromPointerCast<mach_vm_address_t>(&thread_info);
@@ -293,6 +309,9 @@
   // This is a vector of pointers because the address of a ThreadInfo object is
   // passed to each thread’s ThreadMain(), so they cannot move around in memory.
   std::vector<std::unique_ptr<ThreadInfo>> thread_infos_;
+
+  // Prefix to use for each thread's name, suffixed with "-$threadindex".
+  const std::string thread_name_prefix_;
 };
 
 using ThreadMap = std::map<uint64_t, TestThreadPool::ThreadExpectation>;
@@ -328,6 +347,7 @@
       EXPECT_LT(iterator->second.stack_address, thread_stack_region_end);
 
       EXPECT_EQ(thread.suspend_count, iterator->second.suspend_count);
+      EXPECT_EQ(thread.name, iterator->second.thread_name);
 
       // Remove the thread from the expectation map since it’s already been
       // found. This makes it easy to check for duplicate thread IDs, and makes
@@ -375,7 +395,7 @@
   ProcessReaderMac process_reader;
   ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
 
-  TestThreadPool thread_pool;
+  TestThreadPool thread_pool("SelfSeveralThreads");
   constexpr size_t kChildThreads = 16;
   ASSERT_NO_FATAL_FAILURE(thread_pool.StartThreads(kChildThreads));
 
@@ -393,6 +413,8 @@
     // There can’t be any duplicate thread IDs.
     EXPECT_EQ(thread_map.count(thread_id), 0u);
 
+    expectation.thread_name =
+        base::StringPrintf("SelfSeveralThreads-%zu", thread_index);
     thread_map[thread_id] = expectation;
   }
 
@@ -432,8 +454,11 @@
 
 class ProcessReaderThreadedChild final : public MachMultiprocess {
  public:
-  explicit ProcessReaderThreadedChild(size_t thread_count)
-      : MachMultiprocess(), thread_count_(thread_count) {}
+  explicit ProcessReaderThreadedChild(const std::string thread_name_prefix,
+                                      size_t thread_count)
+      : MachMultiprocess(),
+        thread_name_prefix_(thread_name_prefix),
+        thread_count_(thread_count) {}
 
   ProcessReaderThreadedChild(const ProcessReaderThreadedChild&) = delete;
   ProcessReaderThreadedChild& operator=(const ProcessReaderThreadedChild&) =
@@ -464,6 +489,15 @@
       CheckedReadFileExactly(read_handle,
                              &expectation.suspend_count,
                              sizeof(expectation.suspend_count));
+      std::string::size_type expected_thread_name_length;
+      CheckedReadFileExactly(read_handle,
+                             &expected_thread_name_length,
+                             sizeof(expected_thread_name_length));
+      std::string expected_thread_name(expected_thread_name_length, '\0');
+      CheckedReadFileExactly(read_handle,
+                             expected_thread_name.data(),
+                             expected_thread_name_length);
+      expectation.thread_name = expected_thread_name;
 
       // There can’t be any duplicate thread IDs.
       EXPECT_EQ(thread_map.count(thread_id), 0u);
@@ -480,9 +514,13 @@
   }
 
   void MachMultiprocessChild() override {
-    TestThreadPool thread_pool;
+    TestThreadPool thread_pool(thread_name_prefix_);
     ASSERT_NO_FATAL_FAILURE(thread_pool.StartThreads(thread_count_));
 
+    const std::string current_thread_name(base::StringPrintf(
+        "%s-MachMultiprocessChild", thread_name_prefix_.c_str()));
+    const ScopedSetThreadName scoped_set_thread_name(current_thread_name);
+
     FileHandle write_handle = WritePipeHandle();
 
     // This thread isn’t part of the thread pool, but the parent will be able
@@ -501,6 +539,13 @@
     CheckedWriteFile(write_handle,
                      &expectation.suspend_count,
                      sizeof(expectation.suspend_count));
+    const std::string::size_type current_thread_name_length =
+        current_thread_name.length();
+    CheckedWriteFile(write_handle,
+                     &current_thread_name_length,
+                     sizeof(current_thread_name_length));
+    CheckedWriteFile(
+        write_handle, current_thread_name.data(), current_thread_name_length);
 
     // Write an entry for everything in the thread pool.
     for (size_t thread_index = 0; thread_index < thread_count_;
@@ -514,6 +559,16 @@
       CheckedWriteFile(write_handle,
                        &expectation.suspend_count,
                        sizeof(expectation.suspend_count));
+      const std::string thread_pool_thread_name = base::StringPrintf(
+          "%s-%zu", thread_name_prefix_.c_str(), thread_index);
+      const std::string::size_type thread_pool_thread_name_length =
+          thread_pool_thread_name.length();
+      CheckedWriteFile(write_handle,
+                       &thread_pool_thread_name_length,
+                       sizeof(thread_pool_thread_name_length));
+      CheckedWriteFile(write_handle,
+                       thread_pool_thread_name.data(),
+                       thread_pool_thread_name_length);
     }
 
     // Wait for the parent to signal that it’s OK to exit by closing its end of
@@ -521,6 +576,7 @@
     CheckedReadFileAtEOF(ReadPipeHandle());
   }
 
+  const std::string thread_name_prefix_;
   size_t thread_count_;
 };
 
@@ -528,14 +584,16 @@
 TEST(ProcessReaderMac, DISABLED_ChildOneThread) {
   // The main thread plus zero child threads equals one thread.
   constexpr size_t kChildThreads = 0;
-  ProcessReaderThreadedChild process_reader_threaded_child(kChildThreads);
+  ProcessReaderThreadedChild process_reader_threaded_child("ChildOneThread",
+                                                           kChildThreads);
   process_reader_threaded_child.Run();
 }
 
 // TODO(crbug.com/1319307): Test is failing on Mac. Re-enable it.
 TEST(ProcessReaderMac, DISABLED_ChildSeveralThreads) {
   constexpr size_t kChildThreads = 64;
-  ProcessReaderThreadedChild process_reader_threaded_child(kChildThreads);
+  ProcessReaderThreadedChild process_reader_threaded_child(
+      "ChildSeveralThreads", kChildThreads);
   process_reader_threaded_child.Run();
 }
 
diff --git a/third_party/crashpad/crashpad/snapshot/mac/thread_snapshot_mac.cc b/third_party/crashpad/crashpad/snapshot/mac/thread_snapshot_mac.cc
index d261f194..485ffc2 100644
--- a/third_party/crashpad/crashpad/snapshot/mac/thread_snapshot_mac.cc
+++ b/third_party/crashpad/crashpad/snapshot/mac/thread_snapshot_mac.cc
@@ -26,13 +26,13 @@
       context_union_(),
       context_(),
       stack_(),
+      thread_name_(),
       thread_id_(0),
       thread_specific_data_address_(0),
       thread_(MACH_PORT_NULL),
       suspend_count_(0),
       priority_(0),
-      initialized_() {
-}
+      initialized_() {}
 
 ThreadSnapshotMac::~ThreadSnapshotMac() {
 }
@@ -44,6 +44,7 @@
 
   thread_ = process_reader_thread.port;
   thread_id_ = process_reader_thread.id;
+  thread_name_ = process_reader_thread.name;
   suspend_count_ = process_reader_thread.suspend_count;
   priority_ = process_reader_thread.priority;
   thread_specific_data_address_ =
@@ -108,6 +109,11 @@
   return thread_id_;
 }
 
+std::string ThreadSnapshotMac::ThreadName() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return thread_name_;
+}
+
 int ThreadSnapshotMac::SuspendCount() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return suspend_count_;
diff --git a/third_party/crashpad/crashpad/snapshot/mac/thread_snapshot_mac.h b/third_party/crashpad/crashpad/snapshot/mac/thread_snapshot_mac.h
index 4d9cb3167..ebdb3ae 100644
--- a/third_party/crashpad/crashpad/snapshot/mac/thread_snapshot_mac.h
+++ b/third_party/crashpad/crashpad/snapshot/mac/thread_snapshot_mac.h
@@ -18,6 +18,8 @@
 #include <mach/mach.h>
 #include <stdint.h>
 
+#include <string>
+
 #include "build/build_config.h"
 #include "snapshot/cpu_context.h"
 #include "snapshot/mac/process_reader_mac.h"
@@ -60,6 +62,7 @@
   const CPUContext* Context() const override;
   const MemorySnapshot* Stack() const override;
   uint64_t ThreadID() const override;
+  std::string ThreadName() const override;
   int SuspendCount() const override;
   int Priority() const override;
   uint64_t ThreadSpecificDataAddress() const override;
@@ -78,6 +81,7 @@
   } context_union_;
   CPUContext context_;
   MemorySnapshotGeneric stack_;
+  std::string thread_name_;
   uint64_t thread_id_;
   uint64_t thread_specific_data_address_;
   thread_t thread_;
diff --git a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.cc b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.cc
index 8c870ee..2894244 100644
--- a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.cc
+++ b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.cc
@@ -23,6 +23,7 @@
 #include "minidump/minidump_extensions.h"
 #include "snapshot/memory_map_region_snapshot.h"
 #include "snapshot/minidump/minidump_simple_string_dictionary_reader.h"
+#include "snapshot/minidump/minidump_string_reader.h"
 #include "util/file/file_io.h"
 
 namespace crashpad {
@@ -576,12 +577,16 @@
     return false;
   }
 
+  if (!InitializeThreadNames()) {
+    return false;
+  }
+
   for (uint32_t thread_index = 0; thread_index < thread_count; ++thread_index) {
     const RVA thread_rva = stream_it->second->Rva + sizeof(thread_count) +
                            thread_index * sizeof(MINIDUMP_THREAD);
 
     auto thread = std::make_unique<internal::ThreadSnapshotMinidump>();
-    if (!thread->Initialize(file_reader_, thread_rva, arch_)) {
+    if (!thread->Initialize(file_reader_, thread_rva, arch_, thread_names_)) {
       return false;
     }
 
@@ -591,6 +596,59 @@
   return true;
 }
 
+bool ProcessSnapshotMinidump::InitializeThreadNames() {
+  const auto& stream_it = stream_map_.find(kMinidumpStreamTypeThreadNameList);
+  if (stream_it == stream_map_.end()) {
+    return true;
+  }
+
+  if (stream_it->second->DataSize < sizeof(MINIDUMP_THREAD_NAME_LIST)) {
+    LOG(ERROR) << "thread_name_list size mismatch";
+    return false;
+  }
+
+  if (!file_reader_->SeekSet(stream_it->second->Rva)) {
+    return false;
+  }
+
+  uint32_t thread_name_count;
+  if (!file_reader_->ReadExactly(&thread_name_count,
+                                 sizeof(thread_name_count))) {
+    return false;
+  }
+
+  if (sizeof(MINIDUMP_THREAD_NAME_LIST) +
+          thread_name_count * sizeof(MINIDUMP_THREAD_NAME) !=
+      stream_it->second->DataSize) {
+    LOG(ERROR) << "thread_name_list size mismatch";
+    return false;
+  }
+
+  for (uint32_t thread_name_index = 0; thread_name_index < thread_name_count;
+       ++thread_name_index) {
+    const RVA thread_name_rva =
+        stream_it->second->Rva + sizeof(thread_name_count) +
+        thread_name_index * sizeof(MINIDUMP_THREAD_NAME);
+    if (!file_reader_->SeekSet(thread_name_rva)) {
+      return false;
+    }
+    MINIDUMP_THREAD_NAME minidump_thread_name;
+    if (!file_reader_->ReadExactly(&minidump_thread_name,
+                                   sizeof(minidump_thread_name))) {
+      return false;
+    }
+    std::string name;
+    if (!internal::ReadMinidumpUTF16String(
+            file_reader_, minidump_thread_name.RvaOfThreadName, &name)) {
+      return false;
+    }
+
+    thread_names_.emplace(minidump_thread_name.ThreadId, std::move(name));
+  }
+
+  return true;
+}
+
 bool ProcessSnapshotMinidump::InitializeSystemSnapshot() {
   const auto& stream_it = stream_map_.find(kMinidumpStreamTypeSystemInfo);
   if (stream_it == stream_map_.end()) {
diff --git a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h
index 18fbc7c..351fc4e 100644
--- a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h
+++ b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h
@@ -112,6 +112,10 @@
   // Initialize().
   bool InitializeThreads();
 
+  // Initializes data carried in a MINIDUMP_THREAD_NAME_LIST stream on behalf of
+  // Initialize().
+  bool InitializeThreadNames();
+
   // Initializes data carried in a MINIDUMP_MEMORY_INFO_LIST stream on behalf of
   // Initialize().
   bool InitializeMemoryInfo();
@@ -147,6 +151,7 @@
   std::map<MinidumpStreamType, const MINIDUMP_LOCATION_DESCRIPTOR*> stream_map_;
   std::vector<std::unique_ptr<internal::ModuleSnapshotMinidump>> modules_;
   std::vector<std::unique_ptr<internal::ThreadSnapshotMinidump>> threads_;
+  std::map<uint32_t, std::string> thread_names_;
   std::vector<UnloadedModuleSnapshot> unloaded_modules_;
   std::vector<std::unique_ptr<internal::MemoryMapRegionSnapshotMinidump>>
       mem_regions_;
diff --git a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump_test.cc b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump_test.cc
index ded561b..7fb4388 100644
--- a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump_test.cc
@@ -728,6 +728,104 @@
   }
 }
 
+TEST(ProcessSnapshotMinidump, ThreadsWithNames) {
+  StringFile string_file;
+
+  MINIDUMP_HEADER header = {};
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  constexpr uint32_t kMinidumpThreadCount = 4;
+  constexpr uint32_t kBaseThreadId = 42;
+
+  const std::string thread_names[kMinidumpThreadCount] = {
+      "ariadne",
+      "theseus",
+      "pasiphae",
+      "minos",
+  };
+
+  RVA64 thread_name_rva64s[kMinidumpThreadCount];
+  for (uint32_t i = 0; i < kMinidumpThreadCount; i++) {
+    thread_name_rva64s[i] = static_cast<RVA64>(string_file.SeekGet());
+    auto name16 = base::UTF8ToUTF16(thread_names[i]);
+    uint32_t size =
+        base::checked_cast<uint32_t>(sizeof(name16[0]) * name16.size());
+    EXPECT_TRUE(string_file.Write(&size, sizeof(size)));
+    EXPECT_TRUE(string_file.Write(&name16[0], size));
+  }
+
+  MINIDUMP_DIRECTORY minidump_thread_list_directory = {};
+  minidump_thread_list_directory.StreamType = kMinidumpStreamTypeThreadList;
+  minidump_thread_list_directory.Location.DataSize =
+      sizeof(MINIDUMP_THREAD_LIST) +
+      kMinidumpThreadCount * sizeof(MINIDUMP_THREAD);
+  minidump_thread_list_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  // Fields in MINIDUMP_THREAD_LIST.
+  EXPECT_TRUE(
+      string_file.Write(&kMinidumpThreadCount, sizeof(kMinidumpThreadCount)));
+  for (uint32_t minidump_thread_index = 0;
+       minidump_thread_index < kMinidumpThreadCount;
+       ++minidump_thread_index) {
+    MINIDUMP_THREAD minidump_thread = {};
+    minidump_thread.ThreadId = kBaseThreadId + minidump_thread_index;
+    EXPECT_TRUE(string_file.Write(&minidump_thread, sizeof(minidump_thread)));
+  }
+
+  header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
+  EXPECT_TRUE(string_file.Write(&minidump_thread_list_directory,
+                                sizeof(minidump_thread_list_directory)));
+
+  MINIDUMP_DIRECTORY minidump_thread_name_list_directory = {};
+  minidump_thread_name_list_directory.StreamType =
+      kMinidumpStreamTypeThreadNameList;
+  minidump_thread_name_list_directory.Location.DataSize =
+      sizeof(MINIDUMP_THREAD_NAME_LIST) +
+      kMinidumpThreadCount * sizeof(MINIDUMP_THREAD_NAME);
+  minidump_thread_name_list_directory.Location.Rva =
+      static_cast<RVA>(string_file.SeekGet());
+
+  // Fields in MINIDUMP_THREAD_NAME_LIST.
+  EXPECT_TRUE(
+      string_file.Write(&kMinidumpThreadCount, sizeof(kMinidumpThreadCount)));
+  for (uint32_t minidump_thread_index = 0;
+       minidump_thread_index < kMinidumpThreadCount;
+       ++minidump_thread_index) {
+    MINIDUMP_THREAD_NAME minidump_thread_name = {0, 0};
+    minidump_thread_name.ThreadId = kBaseThreadId + minidump_thread_index;
+    minidump_thread_name.RvaOfThreadName =
+        thread_name_rva64s[minidump_thread_index];
+    EXPECT_TRUE(
+        string_file.Write(&minidump_thread_name, sizeof(minidump_thread_name)));
+  }
+
+  header.StreamDirectoryRva = static_cast<RVA>(string_file.SeekGet());
+  ASSERT_TRUE(string_file.Write(&minidump_thread_list_directory,
+                                sizeof(minidump_thread_list_directory)));
+  ASSERT_TRUE(string_file.Write(&minidump_thread_name_list_directory,
+                                sizeof(minidump_thread_name_list_directory)));
+
+  header.Signature = MINIDUMP_SIGNATURE;
+  header.Version = MINIDUMP_VERSION;
+  header.NumberOfStreams = 2;
+  EXPECT_TRUE(string_file.SeekSet(0));
+  EXPECT_TRUE(string_file.Write(&header, sizeof(header)));
+
+  ProcessSnapshotMinidump process_snapshot;
+  EXPECT_TRUE(process_snapshot.Initialize(&string_file));
+
+  std::vector<const ThreadSnapshot*> threads = process_snapshot.Threads();
+  ASSERT_EQ(threads.size(), kMinidumpThreadCount);
+
+  size_t idx = 0;
+  for (const auto& thread : threads) {
+    EXPECT_EQ(thread->ThreadID(), kBaseThreadId + idx);
+    EXPECT_EQ(thread->ThreadName(), thread_names[idx]);
+    idx++;
+  }
+}
+
 TEST(ProcessSnapshotMinidump, System) {
   const char* cpu_info = "GenuineIntel";
   const uint32_t* cpu_info_bytes = reinterpret_cast<const uint32_t*>(cpu_info);
diff --git a/third_party/crashpad/crashpad/snapshot/minidump/thread_snapshot_minidump.cc b/third_party/crashpad/crashpad/snapshot/minidump/thread_snapshot_minidump.cc
index f8f3673..26aabd7 100644
--- a/third_party/crashpad/crashpad/snapshot/minidump/thread_snapshot_minidump.cc
+++ b/third_party/crashpad/crashpad/snapshot/minidump/thread_snapshot_minidump.cc
@@ -26,15 +26,18 @@
 ThreadSnapshotMinidump::ThreadSnapshotMinidump()
     : ThreadSnapshot(),
       minidump_thread_(),
+      thread_name_(),
       context_(),
       stack_(),
       initialized_() {}
 
 ThreadSnapshotMinidump::~ThreadSnapshotMinidump() {}
 
-bool ThreadSnapshotMinidump::Initialize(FileReaderInterface* file_reader,
-                                        RVA minidump_thread_rva,
-                                        CPUArchitecture arch) {
+bool ThreadSnapshotMinidump::Initialize(
+    FileReaderInterface* file_reader,
+    RVA minidump_thread_rva,
+    CPUArchitecture arch,
+    const std::map<uint32_t, std::string>& thread_names) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
   std::vector<unsigned char> minidump_context;
 
@@ -67,6 +70,10 @@
   if (!stack_.Initialize(file_reader, stack_info_location)) {
     return false;
   }
+  const auto thread_name_iter = thread_names.find(minidump_thread_.ThreadId);
+  if (thread_name_iter != thread_names.end()) {
+    thread_name_ = thread_name_iter->second;
+  }
 
   INITIALIZATION_STATE_SET_VALID(initialized_);
   return true;
@@ -77,6 +84,11 @@
   return minidump_thread_.ThreadId;
 }
 
+std::string ThreadSnapshotMinidump::ThreadName() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return thread_name_;
+}
+
 int ThreadSnapshotMinidump::SuspendCount() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return minidump_thread_.SuspendCount;
diff --git a/third_party/crashpad/crashpad/snapshot/minidump/thread_snapshot_minidump.h b/third_party/crashpad/crashpad/snapshot/minidump/thread_snapshot_minidump.h
index 7efb18d..b0ef424 100644
--- a/third_party/crashpad/crashpad/snapshot/minidump/thread_snapshot_minidump.h
+++ b/third_party/crashpad/crashpad/snapshot/minidump/thread_snapshot_minidump.h
@@ -17,6 +17,8 @@
 
 #include <windows.h>
 
+#include <map>
+
 #include "minidump/minidump_extensions.h"
 #include "snapshot/cpu_context.h"
 #include "snapshot/minidump/memory_snapshot_minidump.h"
@@ -46,16 +48,20 @@
   //!     the thread’s MINIDUMP_THREAD structure is located.
   //! \param[in] arch The architecture of the system this thread is running on.
   //!     Used to decode CPU Context.
+  //! \param[in] thread_names Map from thread ID to thread name previously read
+  //!     from the minidump's MINIDUMP_THREAD_NAME_LIST.
   //!
   //! \return `true` if the snapshot could be created, `false` otherwise with
   //!     an appropriate message logged.
   bool Initialize(FileReaderInterface* file_reader,
                   RVA minidump_thread_rva,
-                  CPUArchitecture arch);
+                  CPUArchitecture arch,
+                  const std::map<uint32_t, std::string>& thread_names);
 
   const CPUContext* Context() const override;
   const MemorySnapshot* Stack() const override;
   uint64_t ThreadID() const override;
+  std::string ThreadName() const override;
   int SuspendCount() const override;
   int Priority() const override;
   uint64_t ThreadSpecificDataAddress() const override;
@@ -71,6 +77,7 @@
   bool InitializeContext(const std::vector<unsigned char>& minidump_context);
 
   MINIDUMP_THREAD minidump_thread_;
+  std::string thread_name_;
   MinidumpContextConverter context_;
   MemorySnapshotMinidump stack_;
   InitializationStateDcheck initialized_;
diff --git a/third_party/crashpad/crashpad/snapshot/sanitized/thread_snapshot_sanitized.cc b/third_party/crashpad/crashpad/snapshot/sanitized/thread_snapshot_sanitized.cc
index 186776e..4a1fb72 100644
--- a/third_party/crashpad/crashpad/snapshot/sanitized/thread_snapshot_sanitized.cc
+++ b/third_party/crashpad/crashpad/snapshot/sanitized/thread_snapshot_sanitized.cc
@@ -39,6 +39,10 @@
   return snapshot_->ThreadID();
 }
 
+std::string ThreadSnapshotSanitized::ThreadName() const {
+  return snapshot_->ThreadName();
+}
+
 int ThreadSnapshotSanitized::SuspendCount() const {
   return snapshot_->SuspendCount();
 }
diff --git a/third_party/crashpad/crashpad/snapshot/sanitized/thread_snapshot_sanitized.h b/third_party/crashpad/crashpad/snapshot/sanitized/thread_snapshot_sanitized.h
index dca0a0e..b520579 100644
--- a/third_party/crashpad/crashpad/snapshot/sanitized/thread_snapshot_sanitized.h
+++ b/third_party/crashpad/crashpad/snapshot/sanitized/thread_snapshot_sanitized.h
@@ -17,6 +17,8 @@
 
 #include "snapshot/thread_snapshot.h"
 
+#include <string>
+
 #include "snapshot/sanitized/memory_snapshot_sanitized.h"
 #include "util/misc/range_set.h"
 
@@ -44,6 +46,7 @@
   const CPUContext* Context() const override;
   const MemorySnapshot* Stack() const override;
   uint64_t ThreadID() const override;
+  std::string ThreadName() const override;
   int SuspendCount() const override;
   int Priority() const override;
   uint64_t ThreadSpecificDataAddress() const override;
diff --git a/third_party/crashpad/crashpad/snapshot/test/test_thread_snapshot.cc b/third_party/crashpad/crashpad/snapshot/test/test_thread_snapshot.cc
index ed6d9fd..3e73ecd 100644
--- a/third_party/crashpad/crashpad/snapshot/test/test_thread_snapshot.cc
+++ b/third_party/crashpad/crashpad/snapshot/test/test_thread_snapshot.cc
@@ -43,6 +43,10 @@
   return thread_id_;
 }
 
+std::string TestThreadSnapshot::ThreadName() const {
+  return thread_name_;
+}
+
 int TestThreadSnapshot::SuspendCount() const {
   return suspend_count_;
 }
diff --git a/third_party/crashpad/crashpad/snapshot/test/test_thread_snapshot.h b/third_party/crashpad/crashpad/snapshot/test/test_thread_snapshot.h
index f865bdfe..a1cef21 100644
--- a/third_party/crashpad/crashpad/snapshot/test/test_thread_snapshot.h
+++ b/third_party/crashpad/crashpad/snapshot/test/test_thread_snapshot.h
@@ -18,6 +18,7 @@
 #include <stdint.h>
 
 #include <memory>
+#include <string>
 #include <utility>
 #include <vector>
 
@@ -63,6 +64,9 @@
   }
 
   void SetThreadID(uint64_t thread_id) { thread_id_ = thread_id; }
+  void SetThreadName(const std::string& thread_name) {
+    thread_name_ = thread_name;
+  }
   void SetSuspendCount(int suspend_count) { suspend_count_ = suspend_count; }
   void SetPriority(int priority) { priority_ = priority; }
   void SetThreadSpecificDataAddress(uint64_t thread_specific_data_address) {
@@ -83,6 +87,7 @@
   const CPUContext* Context() const override;
   const MemorySnapshot* Stack() const override;
   uint64_t ThreadID() const override;
+  std::string ThreadName() const override;
   int SuspendCount() const override;
   int Priority() const override;
   uint64_t ThreadSpecificDataAddress() const override;
@@ -96,6 +101,7 @@
   CPUContext context_;
   std::unique_ptr<MemorySnapshot> stack_;
   uint64_t thread_id_;
+  std::string thread_name_;
   int suspend_count_;
   int priority_;
   uint64_t thread_specific_data_address_;
diff --git a/third_party/crashpad/crashpad/snapshot/thread_snapshot.h b/third_party/crashpad/crashpad/snapshot/thread_snapshot.h
index 4d73257..ade3ee6 100644
--- a/third_party/crashpad/crashpad/snapshot/thread_snapshot.h
+++ b/third_party/crashpad/crashpad/snapshot/thread_snapshot.h
@@ -17,6 +17,7 @@
 
 #include <stdint.h>
 
+#include <string>
 #include <vector>
 
 namespace crashpad {
@@ -51,6 +52,9 @@
   //! unique system-wide.
   virtual uint64_t ThreadID() const = 0;
 
+  //! \brief Returns the thread's name.
+  virtual std::string ThreadName() const = 0;
+
   //! \brief Returns the thread’s suspend count.
   //!
   //! A suspend count of `0` denotes a schedulable (not suspended) thread.
diff --git a/third_party/crashpad/crashpad/snapshot/win/process_reader_win.cc b/third_party/crashpad/crashpad/snapshot/win/process_reader_win.cc
index 2307380..45a932c0 100644
--- a/third_party/crashpad/crashpad/snapshot/win/process_reader_win.cc
+++ b/third_party/crashpad/crashpad/snapshot/win/process_reader_win.cc
@@ -21,13 +21,16 @@
 
 #include "base/notreached.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/strings/utf_string_conversions.h"
 #include "snapshot/win/cpu_context_win.h"
 #include "util/misc/capture_context.h"
 #include "util/misc/time.h"
+#include "util/win/get_function.h"
 #include "util/win/nt_internals.h"
 #include "util/win/ntstatus_logging.h"
 #include "util/win/process_structs.h"
 #include "util/win/scoped_handle.h"
+#include "util/win/scoped_local_alloc.h"
 
 namespace crashpad {
 
@@ -232,12 +235,9 @@
 bool ProcessReaderWin::ThreadContext::InitializeXState(
     HANDLE thread_handle,
     ULONG64 XStateCompactionMask) {
-  static auto initialize_context_2 = []() {
-    // InitializeContext2 needs Windows 10 build 20348.
-    HINSTANCE kernel32 = GetModuleHandle(L"Kernel32.dll");
-    return reinterpret_cast<decltype(InitializeContext2)*>(
-        GetProcAddress(kernel32, "InitializeContext2"));
-  }();
+  // InitializeContext2 needs Windows 10 build 20348.
+  static const auto initialize_context_2 =
+      GET_FUNCTION(L"kernel32.dll", ::InitializeContext2);
   if (!initialize_context_2)
     return false;
   // We want CET_U xstate to get the ssp, only possible when supported.
@@ -276,6 +276,7 @@
 
 ProcessReaderWin::Thread::Thread()
     : context(),
+      name(),
       id(0),
       teb_address(0),
       teb_size(0),
@@ -460,6 +461,21 @@
         thread.stack_region_size = base - limit;
       }
     }
+    // On Windows 10 build 1607 and later, read the thread name.
+    static const auto get_thread_description =
+        GET_FUNCTION(L"kernel32.dll", ::GetThreadDescription);
+    if (get_thread_description) {
+      wchar_t* thread_description;
+      HRESULT hr =
+          get_thread_description(thread_handle.get(), &thread_description);
+      if (SUCCEEDED(hr)) {
+        ScopedLocalAlloc thread_description_owner(thread_description);
+        thread.name = base::WideToUTF8(thread_description);
+      } else {
+        LOG(WARNING) << "GetThreadDescription: "
+                     << logging::SystemErrorCodeToString(hr);
+      }
+    }
     threads_.push_back(thread);
   }
 }
diff --git a/third_party/crashpad/crashpad/snapshot/win/process_reader_win.h b/third_party/crashpad/crashpad/snapshot/win/process_reader_win.h
index 5987c00..90ff1cb 100644
--- a/third_party/crashpad/crashpad/snapshot/win/process_reader_win.h
+++ b/third_party/crashpad/crashpad/snapshot/win/process_reader_win.h
@@ -18,6 +18,7 @@
 #include <windows.h>
 #include <sys/time.h>
 
+#include <string>
 #include <vector>
 
 #include "build/build_config.h"
@@ -77,6 +78,7 @@
     ~Thread() {}
 
     ThreadContext context;
+    std::string name;
     uint64_t id;
     WinVMAddress teb_address;
     WinVMSize teb_size;
diff --git a/third_party/crashpad/crashpad/snapshot/win/process_reader_win_test.cc b/third_party/crashpad/crashpad/snapshot/win/process_reader_win_test.cc
index 15a6e2b..2ff9f114 100644
--- a/third_party/crashpad/crashpad/snapshot/win/process_reader_win_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/win/process_reader_win_test.cc
@@ -17,9 +17,18 @@
 #include <windows.h>
 #include <string.h>
 
+#include <algorithm>
+#include <array>
 #include <iterator>
+#include <set>
+#include <string>
 
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "test/scoped_set_thread_name.h"
 #include "test/win/win_multiprocess.h"
 #include "util/misc/from_pointer_cast.h"
 #include "util/synchronization/semaphore.h"
@@ -31,6 +40,8 @@
 namespace test {
 namespace {
 
+using ::testing::IsSupersetOf;
+
 TEST(ProcessReaderWin, SelfBasic) {
   ProcessReaderWin process_reader;
   ASSERT_TRUE(process_reader.Initialize(GetCurrentProcess(),
@@ -98,6 +109,7 @@
 }
 
 TEST(ProcessReaderWin, SelfOneThread) {
+  const ScopedSetThreadName scoped_set_thread_name("SelfBasic");
   ProcessReaderWin process_reader;
   ASSERT_TRUE(process_reader.Initialize(GetCurrentProcess(),
                                         ProcessSuspensionState::kRunning));
@@ -111,6 +123,9 @@
   ASSERT_GE(threads.size(), 1u);
 
   EXPECT_EQ(threads[0].id, GetCurrentThreadId());
+  if (ScopedSetThreadName::IsSupported()) {
+    EXPECT_EQ(threads[0].name, "SelfBasic");
+  }
   EXPECT_NE(ProgramCounterFromCONTEXT(threads[0].context.context<CONTEXT>()),
             nullptr);
   EXPECT_EQ(threads[0].suspend_count, 0u);
@@ -132,18 +147,21 @@
 
   class SleepingThread : public Thread {
    public:
-    SleepingThread() : done_(nullptr) {}
+    explicit SleepingThread(const std::string& thread_name)
+        : done_(nullptr), thread_name_(thread_name) {}
 
     void SetHandle(Semaphore* done) {
       done_= done;
     }
 
     void ThreadMain() override {
+      const ScopedSetThreadName scoped_set_thread_name(thread_name_);
       done_->Wait();
     }
 
    private:
     Semaphore* done_;
+    const std::string thread_name_;
   };
 
   void WinMultiprocessParent() override {
@@ -158,8 +176,31 @@
 
       const auto& threads = process_reader.Threads();
       ASSERT_GE(threads.size(), kCreatedThreads + 1);
-      for (const auto& thread : threads)
+
+      for (const auto& thread : threads) {
         EXPECT_EQ(thread.suspend_count, 0u);
+      }
+
+      if (ScopedSetThreadName::IsSupported()) {
+        EXPECT_EQ(threads[0].name, "WinMultiprocessChild-Main");
+
+        const std::set<std::string> expected_thread_names = {
+            "WinMultiprocessChild-1",
+            "WinMultiprocessChild-2",
+            "WinMultiprocessChild-3",
+        };
+        // Windows can create threads besides the ones created in
+        // WinMultiprocessChild(), so keep track of the (non-main) thread names
+        // and make sure all the expected names are present.
+        std::set<std::string> thread_names;
+        for (size_t i = 1; i < threads.size(); i++) {
+          if (!threads[i].name.empty()) {
+            thread_names.emplace(threads[i].name);
+          }
+        }
+
+        EXPECT_THAT(thread_names, IsSupersetOf(expected_thread_names));
+      }
     }
 
     {
@@ -173,15 +214,23 @@
       // suspended.
       const auto& threads = process_reader.Threads();
       ASSERT_GE(threads.size(), kCreatedThreads + 1);
-      for (const auto& thread : threads)
+      for (const auto& thread : threads) {
         EXPECT_EQ(thread.suspend_count, 0u);
+      }
     }
   }
 
   void WinMultiprocessChild() override {
+    const ScopedSetThreadName scoped_set_thread_name(
+        "WinMultiprocessChild-Main");
+
     // Create three dummy threads so we can confirm we read successfully read
     // more than just the main thread.
-    SleepingThread threads[kCreatedThreads];
+    std::array<SleepingThread, kCreatedThreads> threads = {
+        SleepingThread(std::string("WinMultiprocessChild-1")),
+        SleepingThread(std::string("WinMultiprocessChild-2")),
+        SleepingThread(std::string("WinMultiprocessChild-3")),
+    };
     Semaphore done(0);
     for (auto& thread : threads)
       thread.SetHandle(&done);
diff --git a/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.cc b/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.cc
index f1a20b5..844986db 100644
--- a/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.cc
+++ b/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.cc
@@ -45,8 +45,7 @@
       annotations_simple_map_(),
       snapshot_time_(),
       options_(),
-      initialized_() {
-}
+      initialized_() {}
 
 ProcessSnapshotWin::~ProcessSnapshotWin() {
 }
diff --git a/third_party/crashpad/crashpad/snapshot/win/thread_snapshot_win.cc b/third_party/crashpad/crashpad/snapshot/win/thread_snapshot_win.cc
index 2c5569f8..222b2ab 100644
--- a/third_party/crashpad/crashpad/snapshot/win/thread_snapshot_win.cc
+++ b/third_party/crashpad/crashpad/snapshot/win/thread_snapshot_win.cc
@@ -165,6 +165,11 @@
   return thread_.id;
 }
 
+std::string ThreadSnapshotWin::ThreadName() const {
+  INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+  return thread_.name;
+}
+
 int ThreadSnapshotWin::SuspendCount() const {
   INITIALIZATION_STATE_DCHECK_VALID(initialized_);
   return thread_.suspend_count;
diff --git a/third_party/crashpad/crashpad/snapshot/win/thread_snapshot_win.h b/third_party/crashpad/crashpad/snapshot/win/thread_snapshot_win.h
index b9fafaa..af18a14 100644
--- a/third_party/crashpad/crashpad/snapshot/win/thread_snapshot_win.h
+++ b/third_party/crashpad/crashpad/snapshot/win/thread_snapshot_win.h
@@ -68,6 +68,7 @@
   const CPUContext* Context() const override;
   const MemorySnapshot* Stack() const override;
   uint64_t ThreadID() const override;
+  std::string ThreadName() const override;
   int SuspendCount() const override;
   int Priority() const override;
   uint64_t ThreadSpecificDataAddress() const override;
diff --git a/third_party/crashpad/crashpad/test/BUILD.gn b/third_party/crashpad/crashpad/test/BUILD.gn
index c2ad5b92..fef0fc7 100644
--- a/third_party/crashpad/crashpad/test/BUILD.gn
+++ b/third_party/crashpad/crashpad/test/BUILD.gn
@@ -37,6 +37,7 @@
     "scoped_guarded_page.h",
     "scoped_module_handle.cc",
     "scoped_module_handle.h",
+    "scoped_set_thread_name.h",
     "scoped_temp_dir.cc",
     "scoped_temp_dir.h",
     "test_paths.cc",
@@ -57,6 +58,14 @@
     }
   }
 
+  # TODO(crbug.com/812974): Remove !crashpad_is_fuchsia when Fuchsia is no
+  # longer treated as a posix platform.
+  if (crashpad_is_posix && !crashpad_is_fuchsia) {
+    sources += [
+      "scoped_set_thread_name_posix.cc",
+    ]
+  }
+
   if (crashpad_is_mac || crashpad_is_ios) {
     sources += [
       "mac/mach_errors.cc",
@@ -96,6 +105,7 @@
     sources += [
       "multiprocess_exec_win.cc",
       "scoped_guarded_page_win.cc",
+      "scoped_set_thread_name_win.cc",
       "scoped_temp_dir_win.cc",
       "win/child_launcher.cc",
       "win/child_launcher.h",
@@ -109,7 +119,10 @@
   }
 
   if (crashpad_is_fuchsia) {
-    sources += [ "multiprocess_exec_fuchsia.cc" ]
+    sources += [
+      "multiprocess_exec_fuchsia.cc",
+      "scoped_set_thread_name_fuchsia.cc",
+    ]
   }
 
   public_configs = [ "..:crashpad_config" ]
diff --git a/third_party/crashpad/crashpad/test/scoped_set_thread_name.h b/third_party/crashpad/crashpad/test/scoped_set_thread_name.h
new file mode 100644
index 0000000..dc149e4
--- /dev/null
+++ b/third_party/crashpad/crashpad/test/scoped_set_thread_name.h
@@ -0,0 +1,51 @@
+// Copyright 2022 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef CRASHPAD_TEST_SCOPED_SET_THREAD_NAME_H_
+#define CRASHPAD_TEST_SCOPED_SET_THREAD_NAME_H_
+
+#include <string>
+
+#include "build/build_config.h"
+
+namespace crashpad {
+namespace test {
+
+//! Sets the name of the current thread for the lifetime of this object.
+class ScopedSetThreadName final {
+ public:
+  explicit ScopedSetThreadName(const std::string& new_thread_name);
+
+  ScopedSetThreadName(const ScopedSetThreadName&) = delete;
+  ScopedSetThreadName& operator=(const ScopedSetThreadName&) = delete;
+
+  ~ScopedSetThreadName();
+
+#if BUILDFLAG(IS_WIN) || DOXYGEN
+  //! \brief Returns `true` if Windows supports setting and getting thread name.
+  static bool IsSupported();
+#endif
+
+ private:
+#if BUILDFLAG(IS_WIN)
+  std::wstring original_name_;
+#else
+  const std::string original_name_;
+#endif
+};
+
+}  // namespace test
+}  // namespace crashpad
+
+#endif  // CRASHPAD_TEST_SCOPED_SET_THREAD_NAME_H_
diff --git a/third_party/crashpad/crashpad/test/scoped_set_thread_name_fuchsia.cc b/third_party/crashpad/crashpad/test/scoped_set_thread_name_fuchsia.cc
new file mode 100644
index 0000000..7b672d15
--- /dev/null
+++ b/third_party/crashpad/crashpad/test/scoped_set_thread_name_fuchsia.cc
@@ -0,0 +1,61 @@
+// Copyright 2022 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "test/scoped_set_thread_name.h"
+
+#include <string>
+
+#include <lib/zx/thread.h>
+#include <zircon/syscalls/object.h>
+#include <zircon/types.h>
+
+#include "base/check_op.h"
+#include "base/fuchsia/fuchsia_logging.h"
+
+namespace crashpad {
+namespace test {
+
+namespace {
+
+std::string GetCurrentThreadName() {
+  std::string result(ZX_MAX_NAME_LEN, '\0');
+  const zx_status_t status = zx::thread::self()->get_property(
+      ZX_PROP_NAME, result.data(), result.length());
+  ZX_CHECK(status == ZX_OK, status) << "get_property(ZX_PROP_NAME)";
+  const auto result_nul_idx = result.find('\0');
+  CHECK_NE(result_nul_idx, std::string::npos)
+      << "get_property() did not NUL terminate";
+  result.resize(result_nul_idx);
+  return result;
+}
+
+}  // namespace
+
+ScopedSetThreadName::ScopedSetThreadName(const std::string& new_thread_name)
+    : original_name_(GetCurrentThreadName()) {
+  // Fuchsia silently truncates the thread name if it's too long.
+  CHECK_LT(new_thread_name.length(), ZX_MAX_NAME_LEN);
+  const zx_status_t status = zx::thread::self()->set_property(
+      ZX_PROP_NAME, new_thread_name.c_str(), new_thread_name.length());
+  ZX_CHECK(status == ZX_OK, status) << "set_property(ZX_PROP_NAME)";
+}
+
+ScopedSetThreadName::~ScopedSetThreadName() {
+  const zx_status_t status = zx::thread::self()->set_property(
+      ZX_PROP_NAME, original_name_.c_str(), original_name_.length());
+  ZX_CHECK(status == ZX_OK, status) << "set_property(ZX_PROP_NAME)";
+}
+
+}  // namespace test
+}  // namespace crashpad
diff --git a/third_party/crashpad/crashpad/test/scoped_set_thread_name_posix.cc b/third_party/crashpad/crashpad/test/scoped_set_thread_name_posix.cc
new file mode 100644
index 0000000..92c61bb
--- /dev/null
+++ b/third_party/crashpad/crashpad/test/scoped_set_thread_name_posix.cc
@@ -0,0 +1,92 @@
+// Copyright 2022 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "test/scoped_set_thread_name.h"
+
+#include <errno.h>
+#include <pthread.h>
+
+#include <ostream>
+#include <string>
+
+#include "base/check.h"
+#include "base/check_op.h"
+#include "build/build_config.h"
+
+#if BUILDFLAG(IS_APPLE)
+#include <mach/thread_info.h>
+#elif BUILDFLAG(IS_ANDROID)
+#include <sys/prctl.h>
+#endif
+
+namespace crashpad {
+namespace test {
+
+namespace {
+
+#if BUILDFLAG(IS_APPLE)
+constexpr size_t kPthreadNameMaxLen = MAXTHREADNAMESIZE;
+#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS)
+// The kernel headers define this in linux/sched.h as TASK_COMM_LEN, but the
+// userspace copy of that header does not define it.
+constexpr size_t kPthreadNameMaxLen = 16;
+#else
+#error Port to your platform
+#endif
+
+void SetCurrentThreadName(const std::string& thread_name) {
+#if BUILDFLAG(IS_APPLE)
+  // Apple's pthread_setname_np() sets errno instead of returning it.
+  PCHECK(pthread_setname_np(thread_name.c_str()) == 0) << "pthread_setname_np";
+#elif BUILDFLAG(IS_ANDROID) && __ANDROID_API__ < 24
+  // pthread_setname_np() requires Android API 24 or later.
+  CHECK_LT(thread_name.length(), kPthreadNameMaxLen);
+  PCHECK(prctl(PR_SET_NAME, thread_name.c_str()) == 0) << "prctl(PR_SET_NAME)";
+#else
+  PCHECK((errno = pthread_setname_np(pthread_self(), thread_name.c_str())) == 0)
+      << "pthread_setname_np";
+#endif
+}
+
+std::string GetCurrentThreadName() {
+  std::string result(kPthreadNameMaxLen, '\0');
+#if BUILDFLAG(IS_ANDROID) && __ANDROID_API__ < 24
+  static constexpr char kGetThreadNameFunctionName[] = "prctl";
+  PCHECK(prctl(PR_GET_NAME, result.data()) == 0) << "prctl(PR_GET_NAME)";
+#else
+  static constexpr char kGetThreadNameFunctionName[] = "pthread_getname_np";
+  PCHECK((errno = pthread_getname_np(
+              pthread_self(), result.data(), result.length())) == 0)
+      << "pthread_getname_np";
+#endif
+  const auto result_nul_idx = result.find('\0');
+  CHECK(result_nul_idx != std::string::npos)
+      << kGetThreadNameFunctionName << " did not NUL terminate";
+  result.resize(result_nul_idx);
+  return result;
+}
+
+}  // namespace
+
+ScopedSetThreadName::ScopedSetThreadName(const std::string& new_thread_name)
+    : original_name_(GetCurrentThreadName()) {
+  SetCurrentThreadName(new_thread_name);
+}
+
+ScopedSetThreadName::~ScopedSetThreadName() {
+  SetCurrentThreadName(original_name_);
+}
+
+}  // namespace test
+}  // namespace crashpad
diff --git a/third_party/crashpad/crashpad/test/scoped_set_thread_name_win.cc b/third_party/crashpad/crashpad/test/scoped_set_thread_name_win.cc
new file mode 100644
index 0000000..37c87ee8
--- /dev/null
+++ b/third_party/crashpad/crashpad/test/scoped_set_thread_name_win.cc
@@ -0,0 +1,84 @@
+// Copyright 2022 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "test/scoped_set_thread_name.h"
+
+#include <windows.h>
+
+#include "base/check.h"
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "util/win/get_function.h"
+#include "util/win/scoped_local_alloc.h"
+
+namespace crashpad {
+namespace test {
+
+namespace {
+
+auto GetThreadDescriptionFuncPtr() {
+  static const auto get_thread_description =
+      GET_FUNCTION(L"kernel32.dll", ::GetThreadDescription);
+  return get_thread_description;
+}
+
+auto SetThreadDescriptionFuncPtr() {
+  static const auto set_thread_description =
+      GET_FUNCTION(L"kernel32.dll", ::SetThreadDescription);
+  return set_thread_description;
+}
+
+std::wstring GetCurrentThreadName() {
+  wchar_t* thread_description;
+  const auto get_thread_description = GetThreadDescriptionFuncPtr();
+  DCHECK(get_thread_description);
+  HRESULT hr = get_thread_description(GetCurrentThread(), &thread_description);
+  CHECK(SUCCEEDED(hr)) << "GetThreadDescription: "
+                       << logging::SystemErrorCodeToString(hr);
+  ScopedLocalAlloc thread_description_owner(thread_description);
+  return std::wstring(thread_description);
+}
+
+void SetCurrentThreadName(const std::wstring& new_thread_name) {
+  const auto set_thread_description = SetThreadDescriptionFuncPtr();
+  DCHECK(set_thread_description);
+  HRESULT hr =
+      set_thread_description(GetCurrentThread(), new_thread_name.c_str());
+  CHECK(SUCCEEDED(hr)) << "SetThreadDescription: "
+                       << logging::SystemErrorCodeToString(hr);
+}
+
+}  // namespace
+
+ScopedSetThreadName::ScopedSetThreadName(const std::string& new_thread_name)
+    : original_name_() {
+  if (IsSupported()) {
+    original_name_.assign(GetCurrentThreadName());
+    SetCurrentThreadName(base::UTF8ToWide(new_thread_name));
+  }
+}
+
+ScopedSetThreadName::~ScopedSetThreadName() {
+  if (IsSupported()) {
+    SetCurrentThreadName(original_name_);
+  }
+}
+
+// static
+bool ScopedSetThreadName::IsSupported() {
+  return GetThreadDescriptionFuncPtr() && SetThreadDescriptionFuncPtr();
+}
+
+}  // namespace test
+}  // namespace crashpad
diff --git a/third_party/crashpad/crashpad/util/BUILD.gn b/third_party/crashpad/crashpad/util/BUILD.gn
index b8daef48b..6cec366 100644
--- a/third_party/crashpad/crashpad/util/BUILD.gn
+++ b/third_party/crashpad/crashpad/util/BUILD.gn
@@ -296,11 +296,11 @@
       sources += [
         "posix/close_multiple.cc",
         "posix/close_multiple.h",
-        "posix/double_fork_and_exec.cc",
-        "posix/double_fork_and_exec.h",
         "posix/drop_privileges.cc",
         "posix/drop_privileges.h",
         "posix/process_info.h",
+        "posix/spawn_subprocess.cc",
+        "posix/spawn_subprocess.h",
 
         # These map signals to and from strings. While Fuchsia defines some of
         # the common SIGx defines, signals are never raised on Fuchsia, so
diff --git a/third_party/crashpad/crashpad/util/ios/ios_intermediate_dump_format.h b/third_party/crashpad/crashpad/util/ios/ios_intermediate_dump_format.h
index 5e36862..ea206430dd 100644
--- a/third_party/crashpad/crashpad/util/ios/ios_intermediate_dump_format.h
+++ b/third_party/crashpad/crashpad/util/ios/ios_intermediate_dump_format.h
@@ -99,6 +99,7 @@
     TD(kThreadContextMemoryRegions, 6011) \
     TD(kThreadContextMemoryRegionAddress, 6012) \
     TD(kThreadContextMemoryRegionData, 6013) \
+    TD(kThreadName, 6014) \
   TD(kMaxValue, 65535) \
 // clang-format on
 
diff --git a/third_party/crashpad/crashpad/util/posix/double_fork_and_exec.cc b/third_party/crashpad/crashpad/util/posix/double_fork_and_exec.cc
deleted file mode 100644
index 1960430..0000000
--- a/third_party/crashpad/crashpad/util/posix/double_fork_and_exec.cc
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "util/posix/double_fork_and_exec.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include "base/check_op.h"
-#include "base/logging.h"
-#include "base/posix/eintr_wrapper.h"
-#include "base/strings/stringprintf.h"
-#include "util/posix/close_multiple.h"
-
-namespace crashpad {
-
-bool DoubleForkAndExec(const std::vector<std::string>& argv,
-                       const std::vector<std::string>* envp,
-                       int preserve_fd,
-                       bool use_path,
-                       void (*child_function)()) {
-  DCHECK(!envp || !use_path);
-
-  // argv_c contains const char* pointers and is terminated by nullptr. This is
-  // suitable for passing to execv(). Although argv_c is not used in the parent
-  // process, it must be built in the parent process because it’s unsafe to do
-  // so in the child or grandchild process.
-  std::vector<const char*> argv_c;
-  argv_c.reserve(argv.size() + 1);
-  for (const std::string& argument : argv) {
-    argv_c.push_back(argument.c_str());
-  }
-  argv_c.push_back(nullptr);
-
-  std::vector<const char*> envp_c;
-  if (envp) {
-    envp_c.reserve(envp->size() + 1);
-    for (const std::string& variable : *envp) {
-      envp_c.push_back(variable.c_str());
-    }
-    envp_c.push_back(nullptr);
-  }
-
-  // Double-fork(). The three processes involved are parent, child, and
-  // grandchild. The grandchild will call execv(). The child exits immediately
-  // after spawning the grandchild, so the grandchild becomes an orphan and its
-  // parent process ID becomes 1. This relieves the parent and child of the
-  // responsibility to reap the grandchild with waitpid() or similar. The
-  // grandchild is expected to outlive the parent process, so the parent
-  // shouldn’t be concerned with reaping it. This approach means that accidental
-  // early termination of the handler process will not result in a zombie
-  // process.
-  pid_t pid = fork();
-  if (pid < 0) {
-    PLOG(ERROR) << "fork";
-    return false;
-  }
-
-  if (pid == 0) {
-    // Child process.
-
-    if (child_function) {
-      child_function();
-    }
-
-    // Call setsid(), creating a new process group and a new session, both led
-    // by this process. The new process group has no controlling terminal. This
-    // disconnects it from signals generated by the parent process’ terminal.
-    //
-    // setsid() is done in the child instead of the grandchild so that the
-    // grandchild will not be a session leader. If it were a session leader, an
-    // accidental open() of a terminal device without O_NOCTTY would make that
-    // terminal the controlling terminal.
-    //
-    // It’s not desirable for the grandchild to have a controlling terminal. The
-    // grandchild manages its own lifetime, such as by monitoring clients on its
-    // own and exiting when it loses all clients and when it deems it
-    // appropraite to do so. It may serve clients in different process groups or
-    // sessions than its original client, and receiving signals intended for its
-    // original client’s process group could be harmful in that case.
-    PCHECK(setsid() != -1) << "setsid";
-
-    pid = fork();
-    if (pid < 0) {
-      PLOG(FATAL) << "fork";
-    }
-
-    if (pid > 0) {
-      // Child process.
-
-      // _exit() instead of exit(), because fork() was called.
-      _exit(EXIT_SUCCESS);
-    }
-
-    // Grandchild process.
-
-    CloseMultipleNowOrOnExec(STDERR_FILENO + 1, preserve_fd);
-
-    // &argv_c[0] is a pointer to a pointer to const char data, but because of
-    // how C (not C++) works, execvp() wants a pointer to a const pointer to
-    // char data. It modifies neither the data nor the pointers, so the
-    // const_cast is safe.
-    char* const* argv_for_execv = const_cast<char* const*>(&argv_c[0]);
-
-    if (envp) {
-      // This cast is safe for the same reason that the argv_for_execv cast is.
-      char* const* envp_for_execv = const_cast<char* const*>(&envp_c[0]);
-      execve(argv_for_execv[0], argv_for_execv, envp_for_execv);
-      PLOG(FATAL) << "execve " << argv_for_execv[0];
-    }
-
-    if (use_path) {
-      execvp(argv_for_execv[0], argv_for_execv);
-      PLOG(FATAL) << "execvp " << argv_for_execv[0];
-    }
-
-    execv(argv_for_execv[0], argv_for_execv);
-    PLOG(FATAL) << "execv " << argv_for_execv[0];
-  }
-
-  // waitpid() for the child, so that it does not become a zombie process. The
-  // child normally exits quickly.
-  //
-  // Failures from this point on may result in the accumulation of a zombie, but
-  // should not be considered fatal. Log only warnings, but don’t treat these
-  // failures as a failure of the function overall.
-  int status;
-  pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0));
-  if (wait_pid == -1) {
-    PLOG(WARNING) << "waitpid";
-    return true;
-  }
-  DCHECK_EQ(wait_pid, pid);
-
-  if (WIFSIGNALED(status)) {
-    int sig = WTERMSIG(status);
-    LOG(WARNING) << base::StringPrintf(
-        "intermediate process terminated by signal %d (%s)%s",
-        sig,
-        strsignal(sig),
-        WCOREDUMP(status) ? " (core dumped)" : "");
-  } else if (!WIFEXITED(status)) {
-    LOG(WARNING) << base::StringPrintf(
-        "intermediate process: unknown termination 0x%x", status);
-  } else if (WEXITSTATUS(status) != EXIT_SUCCESS) {
-    LOG(WARNING) << "intermediate process exited with code "
-                 << WEXITSTATUS(status);
-  }
-
-  return true;
-}
-
-}  // namespace crashpad
diff --git a/third_party/crashpad/crashpad/util/posix/double_fork_and_exec.h b/third_party/crashpad/crashpad/util/posix/double_fork_and_exec.h
deleted file mode 100644
index 02fc0f2..0000000
--- a/third_party/crashpad/crashpad/util/posix/double_fork_and_exec.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef CRASHPAD_UTIL_POSIX_DOUBLE_FORK_AND_EXEC_H_
-#define CRASHPAD_UTIL_POSIX_DOUBLE_FORK_AND_EXEC_H_
-
-#include <string>
-#include <vector>
-
-namespace crashpad {
-
-//! \brief Executes a (grand-)child process.
-//!
-//! The grandchild process will be started through the
-//! double-`fork()`-and-`execv()` pattern. This allows the grandchild to fully
-//! disassociate from the parent. The grandchild will not be a member of the
-//! parent’s process group or session and will not have a controlling terminal,
-//! providing isolation from signals not intended for it. The grandchild’s
-//! parent process, in terms of the process tree hierarchy, will be the process
-//! with process ID 1, relieving any other process of the responsibility to reap
-//! it via `waitpid()`. Aside from the three file descriptors associated with
-//! the standard input/output streams and any file descriptor passed in \a
-//! preserve_fd, the grandchild will not inherit any file descriptors from the
-//! parent process.
-//!
-//! \param[in] argv The argument vector to start the grandchild process with.
-//!     `argv[0]` is used as the path to the executable.
-//! \param[in] envp A vector of environment variables of the form `var=value` to
-//!     be passed to `execve()`. If this value is `nullptr`, the current
-//!     environment is used.
-//! \param[in] preserve_fd A file descriptor to be inherited by the grandchild
-//!     process. This file descriptor is inherited in addition to the three file
-//!     descriptors associated with the standard input/output streams. Use `-1`
-//!     if no additional file descriptors are to be inherited.
-//! \param[in] use_path Whether to consult the `PATH` environment variable when
-//!     requested to start an executable at a non-absolute path. If `false`,
-//!     `execv()`, which does not consult `PATH`, will be used. If `true`,
-//!     `execvp()`, which does consult `PATH`, will be used.
-//! \param[in] child_function If not `nullptr`, this function will be called in
-//!     the intermediate child process, prior to the second `fork()`. Take note
-//!     that this function will run in the context of a forked process, and must
-//!     be safe for that purpose.
-//!
-//! Setting both \a envp to a value other than `nullptr` and \a use_path to
-//! `true` is not currently supported.
-//!
-//! \return `true` on success, and `false` on failure with a message logged.
-//!     Only failures that occur in the parent process that indicate a definite
-//!     failure to start the the grandchild are reported in the return value.
-//!     Failures in the intermediate child or grandchild processes cannot be
-//!     reported in the return value, and are addressed by logging a message and
-//!     terminating. The caller assumes the responsibility for detecting such
-//!     failures, for example, by observing a failure to perform a successful
-//!     handshake with the grandchild process.
-bool DoubleForkAndExec(const std::vector<std::string>& argv,
-                       const std::vector<std::string>* envp,
-                       int preserve_fd,
-                       bool use_path,
-                       void (*child_function)());
-
-}  // namespace crashpad
-
-#endif  // CRASHPAD_UTIL_POSIX_DOUBLE_FORK_AND_EXEC_H_
diff --git a/third_party/crashpad/crashpad/util/posix/signals.cc b/third_party/crashpad/crashpad/util/posix/signals.cc
index f53ceb2..93874e5 100644
--- a/third_party/crashpad/crashpad/util/posix/signals.cc
+++ b/third_party/crashpad/crashpad/util/posix/signals.cc
@@ -147,6 +147,25 @@
     PLOG(ERROR) << "sigaction " << sig;
     return false;
   }
+
+// Sanitizers can prevent the installation of signal handlers, but sigaction
+// does not report this as failure. Attempt to detect this by checking the
+// currently installed signal handler.
+#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
+    defined(THREAD_SANITIZER) || defined(LEAK_SANITIZER) ||    \
+    defined(UNDEFINED_SANITIZER)
+  struct sigaction installed_handler;
+  CHECK_EQ(sigaction(sig, nullptr, &installed_handler), 0);
+  // If the installed handler does not point to the just installed handler, then
+  // the allow_user_segv_handler sanitizer flag is (probably) disabled.
+  if (installed_handler.sa_sigaction != handler) {
+    LOG(WARNING)
+        << "sanitizers are preventing signal handler installation (sig " << sig
+        << ")";
+    return false;
+  }
+#endif
+
   return true;
 }
 
diff --git a/third_party/crashpad/crashpad/util/posix/spawn_subprocess.cc b/third_party/crashpad/crashpad/util/posix/spawn_subprocess.cc
new file mode 100644
index 0000000..43fca18
--- /dev/null
+++ b/third_party/crashpad/crashpad/util/posix/spawn_subprocess.cc
@@ -0,0 +1,261 @@
+// Copyright 2017 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "util/posix/spawn_subprocess.h"
+
+#include <errno.h>
+#include <spawn.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "base/check.h"
+#include "base/check_op.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "util/posix/close_multiple.h"
+
+#if BUILDFLAG(IS_ANDROID)
+#include <android/api-level.h>
+#endif
+
+extern char** environ;
+
+namespace crashpad {
+
+namespace {
+
+#if BUILDFLAG(IS_APPLE)
+
+class PosixSpawnAttr {
+ public:
+  PosixSpawnAttr() {
+    PCHECK((errno = posix_spawnattr_init(&attr_)) == 0)
+        << "posix_spawnattr_init";
+  }
+
+  PosixSpawnAttr(const PosixSpawnAttr&) = delete;
+  PosixSpawnAttr& operator=(const PosixSpawnAttr&) = delete;
+
+  ~PosixSpawnAttr() {
+    PCHECK((errno = posix_spawnattr_destroy(&attr_)) == 0)
+        << "posix_spawnattr_destroy";
+  }
+
+  void SetFlags(short flags) {
+    PCHECK((errno = posix_spawnattr_setflags(&attr_, flags)) == 0)
+        << "posix_spawnattr_setflags";
+  }
+
+  const posix_spawnattr_t* Get() const { return &attr_; }
+
+ private:
+  posix_spawnattr_t attr_;
+};
+
+class PosixSpawnFileActions {
+ public:
+  PosixSpawnFileActions() {
+    PCHECK((errno = posix_spawn_file_actions_init(&file_actions_)) == 0)
+        << "posix_spawn_file_actions_init";
+  }
+
+  PosixSpawnFileActions(const PosixSpawnFileActions&) = delete;
+  PosixSpawnFileActions& operator=(const PosixSpawnFileActions&) = delete;
+
+  ~PosixSpawnFileActions() {
+    PCHECK((errno = posix_spawn_file_actions_destroy(&file_actions_)) == 0)
+        << "posix_spawn_file_actions_destroy";
+  }
+
+  void AddInheritedFileDescriptor(int fd) {
+    PCHECK((errno = posix_spawn_file_actions_addinherit_np(&file_actions_,
+                                                           fd)) == 0)
+        << "posix_spawn_file_actions_addinherit_np";
+  }
+
+  const posix_spawn_file_actions_t* Get() const { return &file_actions_; }
+
+ private:
+  posix_spawn_file_actions_t file_actions_;
+};
+
+#endif
+
+}  // namespace
+
+bool SpawnSubprocess(const std::vector<std::string>& argv,
+                     const std::vector<std::string>* envp,
+                     int preserve_fd,
+                     bool use_path,
+                     void (*child_function)()) {
+  // argv_c contains const char* pointers and is terminated by nullptr. This is
+  // suitable for passing to posix_spawn*() and execv*(). Although argv_c is not
+  // used in the parent process, it must be built in the parent process because
+  // it’s unsafe to do so in the child or grandchild process.
+  std::vector<const char*> argv_c;
+  argv_c.reserve(argv.size() + 1);
+  for (const std::string& argument : argv) {
+    argv_c.push_back(argument.c_str());
+  }
+  argv_c.push_back(nullptr);
+
+  std::vector<const char*> envp_c;
+  if (envp) {
+    envp_c.reserve(envp->size() + 1);
+    for (const std::string& variable : *envp) {
+      envp_c.push_back(variable.c_str());
+    }
+    envp_c.push_back(nullptr);
+  }
+
+  // The three processes involved are parent, child, and grandchild. The child
+  // exits immediately after spawning the grandchild, so the grandchild becomes
+  // an orphan and its parent process ID becomes 1. This relieves the parent and
+  // child of the responsibility to reap the grandchild with waitpid() or
+  // similar. The grandchild is expected to outlive the parent process, so the
+  // parent shouldn’t be concerned with reaping it. This approach means that
+  // accidental early termination of the handler process will not result in a
+  // zombie process.
+  pid_t pid = fork();
+  if (pid < 0) {
+    PLOG(ERROR) << "fork";
+    return false;
+  }
+
+  if (pid == 0) {
+    // Child process.
+
+    if (child_function) {
+      child_function();
+    }
+
+    // Call setsid(), creating a new process group and a new session, both led
+    // by this process. The new process group has no controlling terminal. This
+    // disconnects it from signals generated by the parent process’ terminal.
+    //
+    // setsid() is done in the child instead of the grandchild so that the
+    // grandchild will not be a session leader. If it were a session leader, an
+    // accidental open() of a terminal device without O_NOCTTY would make that
+    // terminal the controlling terminal.
+    //
+    // It’s not desirable for the grandchild to have a controlling terminal. The
+    // grandchild manages its own lifetime, such as by monitoring clients on its
+    // own and exiting when it loses all clients and when it deems it
+    // appropraite to do so. It may serve clients in different process groups or
+    // sessions than its original client, and receiving signals intended for its
+    // original client’s process group could be harmful in that case.
+    PCHECK(setsid() != -1) << "setsid";
+
+    // &argv_c[0] is a pointer to a pointer to const char data, but because of
+    // how C (not C++) works, posix_spawn*() and execv*() want a pointer to
+    // a const pointer to char data. They modify neither the data nor the
+    // pointers, so the const_cast is safe.
+    char* const* argv_for_spawn = const_cast<char* const*>(argv_c.data());
+
+    // This cast is safe for the same reason that the argv_for_spawn cast is.
+    char* const* envp_for_spawn =
+        envp ? const_cast<char* const*>(envp_c.data()) : environ;
+
+#if BUILDFLAG(IS_ANDROID) && __ANDROID_API__ < 28
+    pid = fork();
+    if (pid < 0) {
+      PLOG(FATAL) << "fork";
+    }
+
+    if (pid > 0) {
+      // Child process.
+
+      // _exit() instead of exit(), because fork() was called.
+      _exit(EXIT_SUCCESS);
+    }
+
+    // Grandchild process.
+
+    CloseMultipleNowOrOnExec(STDERR_FILENO + 1, preserve_fd);
+
+    auto execve_fp = use_path ? execvpe : execve;
+    execve_fp(argv_for_spawn[0], argv_for_spawn, envp_for_spawn);
+    PLOG(FATAL) << (use_path ? "execvpe" : "execve");
+#else
+#if BUILDFLAG(IS_APPLE)
+    PosixSpawnAttr attr;
+    attr.SetFlags(POSIX_SPAWN_CLOEXEC_DEFAULT);
+
+    PosixSpawnFileActions file_actions;
+    for (int fd = 0; fd <= STDERR_FILENO; ++fd) {
+      file_actions.AddInheritedFileDescriptor(fd);
+    }
+    file_actions.AddInheritedFileDescriptor(preserve_fd);
+
+    const posix_spawnattr_t* attr_p = attr.Get();
+    const posix_spawn_file_actions_t* file_actions_p = file_actions.Get();
+#else
+    CloseMultipleNowOrOnExec(STDERR_FILENO + 1, preserve_fd);
+
+    const posix_spawnattr_t* attr_p = nullptr;
+    const posix_spawn_file_actions_t* file_actions_p = nullptr;
+#endif
+
+    auto posix_spawn_fp = use_path ? posix_spawnp : posix_spawn;
+    if ((errno = posix_spawn_fp(nullptr,
+                                argv_for_spawn[0],
+                                file_actions_p,
+                                attr_p,
+                                argv_for_spawn,
+                                envp_for_spawn)) != 0) {
+      PLOG(FATAL) << (use_path ? "posix_spawnp" : "posix_spawn");
+    }
+
+    // _exit() instead of exit(), because fork() was called.
+    _exit(EXIT_SUCCESS);
+#endif
+  }
+
+  // waitpid() for the child, so that it does not become a zombie process. The
+  // child normally exits quickly.
+  //
+  // Failures from this point on may result in the accumulation of a zombie, but
+  // should not be considered fatal. Log only warnings, but don’t treat these
+  // failures as a failure of the function overall.
+  int status;
+  pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0));
+  if (wait_pid == -1) {
+    PLOG(WARNING) << "waitpid";
+    return true;
+  }
+  DCHECK_EQ(wait_pid, pid);
+
+  if (WIFSIGNALED(status)) {
+    int sig = WTERMSIG(status);
+    LOG(WARNING) << base::StringPrintf(
+        "intermediate process terminated by signal %d (%s)%s",
+        sig,
+        strsignal(sig),
+        WCOREDUMP(status) ? " (core dumped)" : "");
+  } else if (!WIFEXITED(status)) {
+    LOG(WARNING) << base::StringPrintf(
+        "intermediate process: unknown termination 0x%x", status);
+  } else if (WEXITSTATUS(status) != EXIT_SUCCESS) {
+    LOG(WARNING) << "intermediate process exited with code "
+                 << WEXITSTATUS(status);
+  }
+
+  return true;
+}
+
+}  // namespace crashpad
diff --git a/third_party/crashpad/crashpad/util/posix/spawn_subprocess.h b/third_party/crashpad/crashpad/util/posix/spawn_subprocess.h
new file mode 100644
index 0000000..23910810
--- /dev/null
+++ b/third_party/crashpad/crashpad/util/posix/spawn_subprocess.h
@@ -0,0 +1,69 @@
+// Copyright 2017 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef CRASHPAD_UTIL_POSIX_SPAWN_SUBPROCESS_H_
+#define CRASHPAD_UTIL_POSIX_SPAWN_SUBPROCESS_H_
+
+#include <string>
+#include <vector>
+
+namespace crashpad {
+
+//! \brief Spawns a subprocess.
+//!
+//! A grandchild process will be started through the
+//! `fork()`-and-`posix_spawn()` pattern where supported, and
+//! double-`fork()`-and-`execv()` pattern elsewhere. This allows the grandchild
+//! to fully disassociate from the parent. The grandchild will not be a member
+//! of the parent’s process group or session and will not have a controlling
+//! terminal, providing isolation from signals not intended for it. The
+//! grandchild’s parent process, in terms of the process tree hierarchy, will be
+//! the process with process ID 1, relieving any other process of the
+//! responsibility to reap it via `waitpid()`. Aside from the three file
+//! descriptors associated with the standard input/output streams and any file
+//! descriptor passed in \a preserve_fd, the grandchild will not inherit any
+//! file descriptors from the parent process.
+//!
+//! \param[in] argv The argument vector to start the grandchild process with.
+//!     `argv[0]` is used as the path to the executable.
+//! \param[in] envp A vector of environment variables of the form `var=value` to
+//!     be passed to the spawned process. If this value is `nullptr`, the
+//!     current environment is used.
+//! \param[in] preserve_fd A file descriptor to be inherited by the grandchild
+//!     process. This file descriptor is inherited in addition to the three file
+//!     descriptors associated with the standard input/output streams. Use `-1`
+//!     if no additional file descriptors are to be inherited.
+//! \param[in] use_path Whether to consult the `PATH` environment variable when
+//!     requested to start an executable at a non-absolute path.
+//! \param[in] child_function If not `nullptr`, this function will be called in
+//!     the intermediate child process. Take note that this function will run in
+//!     the context of a forked process, and must be safe for that purpose.
+//!
+//! \return `true` on success, and `false` on failure with a message logged.
+//!     Only failures that occur in the parent process that indicate a definite
+//!     failure to start the the grandchild are reported in the return value.
+//!     Failures in the intermediate child or grandchild processes cannot be
+//!     reported in the return value, and are addressed by logging a message and
+//!     terminating. The caller assumes the responsibility for detecting such
+//!     failures, for example, by observing a failure to perform a successful
+//!     handshake with the grandchild process.
+bool SpawnSubprocess(const std::vector<std::string>& argv,
+                     const std::vector<std::string>* envp,
+                     int preserve_fd,
+                     bool use_path,
+                     void (*child_function)());
+
+}  // namespace crashpad
+
+#endif  // CRASHPAD_UTIL_POSIX_SPAWN_SUBPROCESS_H_
diff --git a/third_party/lzma_sdk/7z.h b/third_party/lzma_sdk/7z.h
index 6c7886e..304f75ff 100644
--- a/third_party/lzma_sdk/7z.h
+++ b/third_party/lzma_sdk/7z.h
@@ -1,5 +1,5 @@
 /* 7z.h -- 7z interface
-2017-04-03 : Igor Pavlov : Public domain */
+2018-07-02 : Igor Pavlov : Public domain */
 
 #ifndef __7Z_H
 #define __7Z_H
@@ -91,6 +91,8 @@
   UInt64 *CoderUnpackSizes;       // for all coders in all folders
 
   Byte *CodersData;
+
+  UInt64 RangeLimit;
 } CSzAr;
 
 UInt64 SzAr_GetFolderUnpackSize(const CSzAr *p, UInt32 folderIndex);
diff --git a/third_party/lzma_sdk/7zArcIn.c b/third_party/lzma_sdk/7zArcIn.c
index f74d0fad..0d9dec4 100644
--- a/third_party/lzma_sdk/7zArcIn.c
+++ b/third_party/lzma_sdk/7zArcIn.c
@@ -1,5 +1,5 @@
 /* 7zArcIn.c -- 7z Input functions
-2018-12-31 : Igor Pavlov : Public domain */
+2021-02-09 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -75,7 +75,7 @@
   return SZ_OK;
 }
 
-void SzBitUi32s_Free(CSzBitUi32s *p, ISzAllocPtr alloc)
+static void SzBitUi32s_Free(CSzBitUi32s *p, ISzAllocPtr alloc)
 {
   ISzAlloc_Free(alloc, p->Defs); p->Defs = NULL;
   ISzAlloc_Free(alloc, p->Vals); p->Vals = NULL;
@@ -83,7 +83,7 @@
 
 #define SzBitUi64s_Init(p) { (p)->Defs = NULL; (p)->Vals = NULL; }
 
-void SzBitUi64s_Free(CSzBitUi64s *p, ISzAllocPtr alloc)
+static void SzBitUi64s_Free(CSzBitUi64s *p, ISzAllocPtr alloc)
 {
   ISzAlloc_Free(alloc, p->Defs); p->Defs = NULL;
   ISzAlloc_Free(alloc, p->Vals); p->Vals = NULL;
@@ -105,6 +105,8 @@
   p->CoderUnpackSizes = NULL;
 
   p->CodersData = NULL;
+
+  p->RangeLimit = 0;
 }
 
 static void SzAr_Free(CSzAr *p, ISzAllocPtr alloc)
@@ -502,7 +504,7 @@
         return SZ_ERROR_ARCHIVE;
       if (propsSize >= 0x80)
         return SZ_ERROR_UNSUPPORTED;
-      coder->PropsOffset = sd->Data - dataStart;
+      coder->PropsOffset = (size_t)(sd->Data - dataStart);
       coder->PropsSize = (Byte)propsSize;
       sd->Data += (size_t)propsSize;
       sd->Size -= (size_t)propsSize;
@@ -677,7 +679,7 @@
   {
     UInt32 numCoders, ci, numInStreams = 0;
     
-    p->FoCodersOffsets[fo] = sd.Data - startBufPtr;
+    p->FoCodersOffsets[fo] = (size_t)(sd.Data - startBufPtr);
     
     RINOK(SzReadNumber32(&sd, &numCoders));
     if (numCoders == 0 || numCoders > k_Scan_NumCoders_MAX)
@@ -797,7 +799,7 @@
   p->FoToCoderUnpackSizes[fo] = numCodersOutStreams;
   
   {
-    size_t dataSize = sd.Data - startBufPtr;
+    const size_t dataSize = (size_t)(sd.Data - startBufPtr);
     p->FoStartPackStreamIndex[fo] = packStreamIndex;
     p->FoCodersOffsets[fo] = dataSize;
     MY_ALLOC_ZE_AND_CPY(p->CodersData, dataSize, startBufPtr, alloc);
@@ -885,7 +887,7 @@
         if (numStreams != 1 || !SzBitWithVals_Check(&p->FolderCRCs, i))
           numSubDigests += numStreams;
       }
-      ssi->sdNumSubStreams.Size = sd->Data - ssi->sdNumSubStreams.Data;
+      ssi->sdNumSubStreams.Size = (size_t)(sd->Data - ssi->sdNumSubStreams.Data);
       continue;
     }
     if (type == k7zIdCRC || type == k7zIdSize || type == k7zIdEnd)
@@ -907,7 +909,7 @@
   {
     ssi->sdSizes.Data = sd->Data;
     RINOK(SkipNumbers(sd, numUnpackSizesInData));
-    ssi->sdSizes.Size = sd->Data - ssi->sdSizes.Data;
+    ssi->sdSizes.Size = (size_t)(sd->Data - ssi->sdSizes.Data);
     RINOK(ReadID(sd, &type));
   }
 
@@ -919,7 +921,7 @@
     {
       ssi->sdCRCs.Data = sd->Data;
       RINOK(SkipBitUi32s(sd, numSubDigests));
-      ssi->sdCRCs.Size = sd->Data - ssi->sdCRCs.Data;
+      ssi->sdCRCs.Size = (size_t)(sd->Data - ssi->sdCRCs.Data);
     }
     else
     {
@@ -947,7 +949,11 @@
   if (type == k7zIdPackInfo)
   {
     RINOK(ReadNumber(sd, dataOffset));
+    if (*dataOffset > p->RangeLimit)
+      return SZ_ERROR_ARCHIVE;
     RINOK(ReadPackInfo(p, sd, alloc));
+    if (p->PackPositions[p->NumPackStreams] > p->RangeLimit - *dataOffset)
+      return SZ_ERROR_ARCHIVE;
     RINOK(ReadID(sd, &type));
   }
   if (type == k7zIdUnpackInfo)
@@ -1028,12 +1034,12 @@
       return SZ_ERROR_ARCHIVE;
     for (p = data + pos;
       #ifdef _WIN32
-      *(const UInt16 *)p != 0
+      *(const UInt16 *)(const void *)p != 0
       #else
       p[0] != 0 || p[1] != 0
       #endif
       ; p += 2);
-    pos = p - data + 2;
+    pos = (size_t)(p - data) + 2;
     *offsets++ = (pos >> 1);
   }
   while (--numFiles);
@@ -1133,6 +1139,8 @@
     SRes res;
     
     SzAr_Init(&tempAr);
+    tempAr.RangeLimit = p->db.RangeLimit;
+
     res = SzReadAndDecodePackedStreams(inStream, sd, tempBufs, NUM_ADDITIONAL_STREAMS_MAX,
         p->startPosAfterHeader, &tempAr, allocTemp);
     *numTempBufs = tempAr.NumFolders;
@@ -1526,11 +1534,13 @@
   nextHeaderSize = GetUi64(header + 20);
   nextHeaderCRC = GetUi32(header + 28);
 
-  p->startPosAfterHeader = startArcPos + k7zStartHeaderSize;
+  p->startPosAfterHeader = (UInt64)startArcPos + k7zStartHeaderSize;
   
   if (CrcCalc(header + 12, 20) != GetUi32(header + 8))
     return SZ_ERROR_CRC;
 
+  p->db.RangeLimit = nextHeaderOffset;
+
   nextHeaderSizeT = (size_t)nextHeaderSize;
   if (nextHeaderSizeT != nextHeaderSize)
     return SZ_ERROR_MEM;
@@ -1543,13 +1553,13 @@
   {
     Int64 pos = 0;
     RINOK(ILookInStream_Seek(inStream, &pos, SZ_SEEK_END));
-    if ((UInt64)pos < startArcPos + nextHeaderOffset ||
-        (UInt64)pos < startArcPos + k7zStartHeaderSize + nextHeaderOffset ||
-        (UInt64)pos < startArcPos + k7zStartHeaderSize + nextHeaderOffset + nextHeaderSize)
+    if ((UInt64)pos < (UInt64)startArcPos + nextHeaderOffset ||
+        (UInt64)pos < (UInt64)startArcPos + k7zStartHeaderSize + nextHeaderOffset ||
+        (UInt64)pos < (UInt64)startArcPos + k7zStartHeaderSize + nextHeaderOffset + nextHeaderSize)
       return SZ_ERROR_INPUT_EOF;
   }
 
-  RINOK(LookInStream_SeekTo(inStream, startArcPos + k7zStartHeaderSize + nextHeaderOffset));
+  RINOK(LookInStream_SeekTo(inStream, (UInt64)startArcPos + k7zStartHeaderSize + nextHeaderOffset));
 
   if (!Buf_Create(&buf, nextHeaderSizeT, allocTemp))
     return SZ_ERROR_MEM;
@@ -1575,6 +1585,8 @@
         Buf_Init(&tempBuf);
         
         SzAr_Init(&tempAr);
+        tempAr.RangeLimit = p->db.RangeLimit;
+
         res = SzReadAndDecodePackedStreams(inStream, &sd, &tempBuf, 1, p->startPosAfterHeader, &tempAr, allocTemp);
         SzAr_Free(&tempAr, allocTemp);
        
diff --git a/third_party/lzma_sdk/7zCrc.c b/third_party/lzma_sdk/7zCrc.c
index b4d84f0..c0cc9bc 100644
--- a/third_party/lzma_sdk/7zCrc.c
+++ b/third_party/lzma_sdk/7zCrc.c
@@ -1,5 +1,5 @@
 /* 7zCrc.c -- CRC32 init
-2017-06-06 : Igor Pavlov : Public domain */
+2021-04-01 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -26,8 +26,20 @@
 
 typedef UInt32 (MY_FAST_CALL *CRC_FUNC)(UInt32 v, const void *data, size_t size, const UInt32 *table);
 
+extern
 CRC_FUNC g_CrcUpdateT4;
+CRC_FUNC g_CrcUpdateT4;
+extern
 CRC_FUNC g_CrcUpdateT8;
+CRC_FUNC g_CrcUpdateT8;
+extern
+CRC_FUNC g_CrcUpdateT0_32;
+CRC_FUNC g_CrcUpdateT0_32;
+extern
+CRC_FUNC g_CrcUpdateT0_64;
+CRC_FUNC g_CrcUpdateT0_64;
+extern
+CRC_FUNC g_CrcUpdate;
 CRC_FUNC g_CrcUpdate;
 
 UInt32 g_CrcTable[256 * CRC_NUM_TABLES];
@@ -44,6 +56,7 @@
 
 #define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
 
+UInt32 MY_FAST_CALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table);
 UInt32 MY_FAST_CALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table)
 {
   const Byte *p = (const Byte *)data;
@@ -53,6 +66,166 @@
   return v;
 }
 
+
+/* ---------- hardware CRC ---------- */
+
+#ifdef MY_CPU_LE
+
+#if defined(MY_CPU_ARM_OR_ARM64)
+
+// #pragma message("ARM*")
+
+  #if defined(_MSC_VER)
+    #if defined(MY_CPU_ARM64)
+    #if (_MSC_VER >= 1910)
+        // #define USE_ARM64_CRC
+    #endif
+    #endif
+  #elif (defined(__clang__) && (__clang_major__ >= 3)) \
+     || (defined(__GNUC__) && (__GNUC__ > 4))
+      #if !defined(__ARM_FEATURE_CRC32)
+        // #define __ARM_FEATURE_CRC32 1
+          #if (!defined(__clang__) || (__clang_major__ > 3)) // fix these numbers
+            // #define ATTRIB_CRC __attribute__((__target__("arch=armv8-a+crc")))
+          #endif
+      #endif
+      #if defined(__ARM_FEATURE_CRC32)
+        // #define USE_ARM64_CRC
+        // #include <arm_acle.h>
+      #endif
+  #endif
+
+#else
+
+// no hardware CRC
+
+// #define USE_CRC_EMU
+
+#ifdef USE_CRC_EMU
+
+#pragma message("ARM64 CRC emulation")
+
+MY_FORCE_INLINE
+UInt32 __crc32b(UInt32 v, UInt32 data)
+{
+  const UInt32 *table = g_CrcTable;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data);
+  return v;
+}
+
+MY_FORCE_INLINE
+UInt32 __crc32w(UInt32 v, UInt32 data)
+{
+  const UInt32 *table = g_CrcTable;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
+  return v;
+}
+
+MY_FORCE_INLINE
+UInt32 __crc32d(UInt32 v, UInt64 data)
+{
+  const UInt32 *table = g_CrcTable;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
+  v = CRC_UPDATE_BYTE_2(v, (Byte)data); data >>= 8;
+  return v;
+}
+
+#endif // USE_CRC_EMU
+
+#endif // defined(MY_CPU_ARM64) && defined(MY_CPU_LE)
+
+
+
+#if defined(USE_ARM64_CRC) || defined(USE_CRC_EMU)
+
+#define T0_32_UNROLL_BYTES (4 * 4)
+#define T0_64_UNROLL_BYTES (4 * 8)
+
+#ifndef ATTRIB_CRC
+#define ATTRIB_CRC
+#endif
+// #pragma message("USE ARM HW CRC")
+
+ATTRIB_CRC
+UInt32 MY_FAST_CALL CrcUpdateT0_32(UInt32 v, const void *data, size_t size, const UInt32 *table);
+ATTRIB_CRC
+UInt32 MY_FAST_CALL CrcUpdateT0_32(UInt32 v, const void *data, size_t size, const UInt32 *table)
+{
+  const Byte *p = (const Byte *)data;
+  UNUSED_VAR(table);
+
+  for (; size != 0 && ((unsigned)(ptrdiff_t)p & (T0_32_UNROLL_BYTES - 1)) != 0; size--)
+    v = __crc32b(v, *p++);
+
+  if (size >= T0_32_UNROLL_BYTES)
+  {
+    const Byte *lim = p + size;
+    size &= (T0_32_UNROLL_BYTES - 1);
+    lim -= size;
+    do
+    {
+      v = __crc32w(v, *(const UInt32 *)(const void *)(p));
+      v = __crc32w(v, *(const UInt32 *)(const void *)(p + 4)); p += 2 * 4;
+      v = __crc32w(v, *(const UInt32 *)(const void *)(p));
+      v = __crc32w(v, *(const UInt32 *)(const void *)(p + 4)); p += 2 * 4;
+    }
+    while (p != lim);
+  }
+  
+  for (; size != 0; size--)
+    v = __crc32b(v, *p++);
+
+  return v;
+}
+
+ATTRIB_CRC
+UInt32 MY_FAST_CALL CrcUpdateT0_64(UInt32 v, const void *data, size_t size, const UInt32 *table);
+ATTRIB_CRC
+UInt32 MY_FAST_CALL CrcUpdateT0_64(UInt32 v, const void *data, size_t size, const UInt32 *table)
+{
+  const Byte *p = (const Byte *)data;
+  UNUSED_VAR(table);
+
+  for (; size != 0 && ((unsigned)(ptrdiff_t)p & (T0_64_UNROLL_BYTES - 1)) != 0; size--)
+    v = __crc32b(v, *p++);
+
+  if (size >= T0_64_UNROLL_BYTES)
+  {
+    const Byte *lim = p + size;
+    size &= (T0_64_UNROLL_BYTES - 1);
+    lim -= size;
+    do
+    {
+      v = __crc32d(v, *(const UInt64 *)(const void *)(p));
+      v = __crc32d(v, *(const UInt64 *)(const void *)(p + 8)); p += 2 * 8;
+      v = __crc32d(v, *(const UInt64 *)(const void *)(p));
+      v = __crc32d(v, *(const UInt64 *)(const void *)(p + 8)); p += 2 * 8;
+    }
+    while (p != lim);
+  }
+  
+  for (; size != 0; size--)
+    v = __crc32b(v, *p++);
+
+  return v;
+}
+
+#endif // defined(USE_ARM64_CRC) || defined(USE_CRC_EMU)
+
+#endif // MY_CPU_LE
+
+
+
+
 void MY_FAST_CALL CrcGenerateTable()
 {
   UInt32 i;
@@ -123,6 +296,27 @@
     }
   }
   #endif
+  #endif
 
+  #ifdef MY_CPU_LE
+    #ifdef USE_ARM64_CRC
+      if (CPU_IsSupported_CRC32())
+      {
+        g_CrcUpdateT0_32 = CrcUpdateT0_32;
+        g_CrcUpdateT0_64 = CrcUpdateT0_64;
+        g_CrcUpdate =
+          #if defined(MY_CPU_ARM)
+            CrcUpdateT0_32;
+          #else
+            CrcUpdateT0_64;
+          #endif
+      }
+    #endif
+    
+    #ifdef USE_CRC_EMU
+      g_CrcUpdateT0_32 = CrcUpdateT0_32;
+      g_CrcUpdateT0_64 = CrcUpdateT0_64;
+      g_CrcUpdate = CrcUpdateT0_64;
+    #endif
   #endif
 }
diff --git a/third_party/lzma_sdk/7zCrcOpt.c b/third_party/lzma_sdk/7zCrcOpt.c
index 73beba2..69fad9c 100644
--- a/third_party/lzma_sdk/7zCrcOpt.c
+++ b/third_party/lzma_sdk/7zCrcOpt.c
@@ -1,5 +1,5 @@
 /* 7zCrcOpt.c -- CRC32 calculation
-2017-04-03 : Igor Pavlov : Public domain */
+2021-02-09 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -9,6 +9,7 @@
 
 #define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
 
+UInt32 MY_FAST_CALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table);
 UInt32 MY_FAST_CALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table)
 {
   const Byte *p = (const Byte *)data;
@@ -16,7 +17,7 @@
     v = CRC_UPDATE_BYTE_2(v, *p);
   for (; size >= 4; size -= 4, p += 4)
   {
-    v ^= *(const UInt32 *)p;
+    v ^= *(const UInt32 *)(const void *)p;
     v =
           (table + 0x300)[((v      ) & 0xFF)]
         ^ (table + 0x200)[((v >>  8) & 0xFF)]
@@ -28,6 +29,7 @@
   return v;
 }
 
+UInt32 MY_FAST_CALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table);
 UInt32 MY_FAST_CALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table)
 {
   const Byte *p = (const Byte *)data;
@@ -36,13 +38,13 @@
   for (; size >= 8; size -= 8, p += 8)
   {
     UInt32 d;
-    v ^= *(const UInt32 *)p;
+    v ^= *(const UInt32 *)(const void *)p;
     v =
           (table + 0x700)[((v      ) & 0xFF)]
         ^ (table + 0x600)[((v >>  8) & 0xFF)]
         ^ (table + 0x500)[((v >> 16) & 0xFF)]
         ^ (table + 0x400)[((v >> 24))];
-    d = *((const UInt32 *)p + 1);
+    d = *((const UInt32 *)(const void *)p + 1);
     v ^=
           (table + 0x300)[((d      ) & 0xFF)]
         ^ (table + 0x200)[((d >>  8) & 0xFF)]
@@ -72,7 +74,7 @@
     v = CRC_UPDATE_BYTE_2_BE(v, *p);
   for (; size >= 4; size -= 4, p += 4)
   {
-    v ^= *(const UInt32 *)p;
+    v ^= *(const UInt32 *)(const void *)p;
     v =
           (table + 0x000)[((v      ) & 0xFF)]
         ^ (table + 0x100)[((v >>  8) & 0xFF)]
@@ -94,13 +96,13 @@
   for (; size >= 8; size -= 8, p += 8)
   {
     UInt32 d;
-    v ^= *(const UInt32 *)p;
+    v ^= *(const UInt32 *)(const void *)p;
     v =
           (table + 0x400)[((v      ) & 0xFF)]
         ^ (table + 0x500)[((v >>  8) & 0xFF)]
         ^ (table + 0x600)[((v >> 16) & 0xFF)]
         ^ (table + 0x700)[((v >> 24))];
-    d = *((const UInt32 *)p + 1);
+    d = *((const UInt32 *)(const void *)p + 1);
     v ^=
           (table + 0x000)[((d      ) & 0xFF)]
         ^ (table + 0x100)[((d >>  8) & 0xFF)]
diff --git a/third_party/lzma_sdk/7zDec.c b/third_party/lzma_sdk/7zDec.c
index 7c46352..fbfd016 100644
--- a/third_party/lzma_sdk/7zDec.c
+++ b/third_party/lzma_sdk/7zDec.c
@@ -1,5 +1,5 @@
 /* 7zDec.c -- Decoding from 7z folder
-2019-02-02 : Igor Pavlov : Public domain */
+2021-02-09 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -21,17 +21,20 @@
 #endif
 
 #define k_Copy 0
-#define k_Delta 3
+#ifndef _7Z_NO_METHOD_LZMA2
 #define k_LZMA2 0x21
+#endif
 #define k_LZMA  0x30101
-#define k_BCJ   0x3030103
 #define k_BCJ2  0x303011B
+#ifndef _7Z_NO_METHODS_FILTERS
+#define k_Delta 3
+#define k_BCJ   0x3030103
 #define k_PPC   0x3030205
 #define k_IA64  0x3030401
 #define k_ARM   0x3030501
 #define k_ARMT  0x3030701
 #define k_SPARC 0x3030805
-
+#endif
 
 #ifdef _7ZIP_PPMD_SUPPPORT
 
@@ -56,7 +59,7 @@
     return *p->cur++;
   if (p->res == SZ_OK)
   {
-    size_t size = p->cur - p->begin;
+    size_t size = (size_t)(p->cur - p->begin);
     p->processed += size;
     p->res = ILookInStream_Skip(p->inStream, size);
     size = (1 << 25);
@@ -101,28 +104,32 @@
     Ppmd7_Init(&ppmd, order);
   }
   {
-    CPpmd7z_RangeDec rc;
-    Ppmd7z_RangeDec_CreateVTable(&rc);
-    rc.Stream = &s.vt;
-    if (!Ppmd7z_RangeDec_Init(&rc))
+    ppmd.rc.dec.Stream = &s.vt;
+    if (!Ppmd7z_RangeDec_Init(&ppmd.rc.dec))
       res = SZ_ERROR_DATA;
-    else if (s.extra)
-      res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA);
-    else
+    else if (!s.extra)
     {
-      SizeT i;
-      for (i = 0; i < outSize; i++)
+      Byte *buf = outBuffer;
+      const Byte *lim = buf + outSize;
+      for (; buf != lim; buf++)
       {
-        int sym = Ppmd7_DecodeSymbol(&ppmd, &rc.vt);
+        int sym = Ppmd7z_DecodeSymbol(&ppmd);
         if (s.extra || sym < 0)
           break;
-        outBuffer[i] = (Byte)sym;
+        *buf = (Byte)sym;
       }
-      if (i != outSize)
-        res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA);
-      else if (s.processed + (s.cur - s.begin) != inSize || !Ppmd7z_RangeDec_IsFinishedOK(&rc))
+      if (buf != lim)
         res = SZ_ERROR_DATA;
+      else if (!Ppmd7z_RangeDec_IsFinishedOK(&ppmd.rc.dec))
+      {
+        /* if (Ppmd7z_DecodeSymbol(&ppmd) != PPMD7_SYM_END || !Ppmd7z_RangeDec_IsFinishedOK(&ppmd.rc.dec)) */
+        res = SZ_ERROR_DATA;
+      }
     }
+    if (s.extra)
+      res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA);
+    else if (s.processed + (size_t)(s.cur - s.begin) != inSize)
+      res = SZ_ERROR_DATA;
   }
   Ppmd7_Free(&ppmd, allocMain);
   return res;
@@ -365,7 +372,9 @@
   return SZ_ERROR_UNSUPPORTED;
 }
 
+#ifndef _7Z_NO_METHODS_FILTERS
 #define CASE_BRA_CONV(isa) case k_ ## isa: isa ## _Convert(outBuffer, outSize, 0, 0); break;
+#endif
 
 static SRes SzFolder_Decode2(const CSzFolder *folder,
     const Byte *propsData,
diff --git a/third_party/lzma_sdk/7zFile.c b/third_party/lzma_sdk/7zFile.c
index 8992fb1c..13d2efa4 100644
--- a/third_party/lzma_sdk/7zFile.c
+++ b/third_party/lzma_sdk/7zFile.c
@@ -1,5 +1,5 @@
 /* 7zFile.c -- File IO
-2017-04-03 : Igor Pavlov : Public domain */
+2021-04-29 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -7,9 +7,19 @@
 
 #ifndef USE_WINDOWS_FILE
 
-#ifndef UNDER_CE
-#include <errno.h>
-#endif
+  #include <errno.h>
+
+  #ifndef USE_FOPEN
+    #include <stdio.h>
+    #include <fcntl.h>
+    #ifdef _WIN32
+      #include <io.h>
+      typedef int ssize_t;
+      typedef int off_t;
+    #else
+      #include <unistd.h>
+    #endif
+  #endif
 
 #else
 
@@ -23,30 +33,36 @@
    And message can be "Network connection was lost"
 */
 
-#define kChunkSizeMax (1 << 22)
-
 #endif
 
+#define kChunkSizeMax (1 << 22)
+
 void File_Construct(CSzFile *p)
 {
   #ifdef USE_WINDOWS_FILE
   p->handle = INVALID_HANDLE_VALUE;
-  #else
+  #elif defined(USE_FOPEN)
   p->file = NULL;
+  #else
+  p->fd = -1;
   #endif
 }
 
 #if !defined(UNDER_CE) || !defined(USE_WINDOWS_FILE)
+
 static WRes File_Open(CSzFile *p, const char *name, int writeMode)
 {
   #ifdef USE_WINDOWS_FILE
+  
   p->handle = CreateFileA(name,
       writeMode ? GENERIC_WRITE : GENERIC_READ,
       FILE_SHARE_READ, NULL,
       writeMode ? CREATE_ALWAYS : OPEN_EXISTING,
       FILE_ATTRIBUTE_NORMAL, NULL);
   return (p->handle != INVALID_HANDLE_VALUE) ? 0 : GetLastError();
-  #else
+  
+  #elif defined(USE_FOPEN)
+  
   p->file = fopen(name, writeMode ? "wb+" : "rb");
   return (p->file != 0) ? 0 :
     #ifdef UNDER_CE
@@ -54,13 +70,34 @@
     #else
     errno;
     #endif
+  
+  #else
+
+  int flags = (writeMode ? (O_CREAT | O_EXCL | O_WRONLY) : O_RDONLY);
+  #ifdef O_BINARY
+  flags |= O_BINARY;
+  #endif
+  p->fd = open(name, flags, 0666);
+  return (p->fd != -1) ? 0 : errno;
+
   #endif
 }
 
 WRes InFile_Open(CSzFile *p, const char *name) { return File_Open(p, name, 0); }
-WRes OutFile_Open(CSzFile *p, const char *name) { return File_Open(p, name, 1); }
+
+WRes OutFile_Open(CSzFile *p, const char *name)
+{
+  #if defined(USE_WINDOWS_FILE) || defined(USE_FOPEN)
+  return File_Open(p, name, 1);
+  #else
+  p->fd = creat(name, 0666);
+  return (p->fd != -1) ? 0 : errno;
+  #endif
+}
+
 #endif
 
+
 #ifdef USE_WINDOWS_FILE
 static WRes File_OpenW(CSzFile *p, const WCHAR *name, int writeMode)
 {
@@ -78,74 +115,124 @@
 WRes File_Close(CSzFile *p)
 {
   #ifdef USE_WINDOWS_FILE
+  
   if (p->handle != INVALID_HANDLE_VALUE)
   {
     if (!CloseHandle(p->handle))
       return GetLastError();
     p->handle = INVALID_HANDLE_VALUE;
   }
-  #else
+  
+  #elif defined(USE_FOPEN)
+
   if (p->file != NULL)
   {
     int res = fclose(p->file);
     if (res != 0)
+    {
+      if (res == EOF)
+        return errno;
       return res;
+    }
     p->file = NULL;
   }
+
+  #else
+
+  if (p->fd != -1)
+  {
+    if (close(p->fd) != 0)
+      return errno;
+    p->fd = -1;
+  }
+
   #endif
+
   return 0;
 }
 
+
 WRes File_Read(CSzFile *p, void *data, size_t *size)
 {
   size_t originalSize = *size;
+  *size = 0;
   if (originalSize == 0)
     return 0;
 
   #ifdef USE_WINDOWS_FILE
 
-  *size = 0;
   do
   {
-    DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize;
+    const DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize;
     DWORD processed = 0;
-    BOOL res = ReadFile(p->handle, data, curSize, &processed, NULL);
+    const BOOL res = ReadFile(p->handle, data, curSize, &processed, NULL);
     data = (void *)((Byte *)data + processed);
     originalSize -= processed;
     *size += processed;
     if (!res)
       return GetLastError();
+    // debug : we can break here for partial reading mode
     if (processed == 0)
       break;
   }
   while (originalSize > 0);
-  return 0;
+
+  #elif defined(USE_FOPEN)
+
+  do
+  {
+    const size_t curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : originalSize;
+    const size_t processed = fread(data, 1, curSize, p->file);
+    data = (void *)((Byte *)data + (size_t)processed);
+    originalSize -= processed;
+    *size += processed;
+    if (processed != curSize)
+      return ferror(p->file);
+    // debug : we can break here for partial reading mode
+    if (processed == 0)
+      break;
+  }
+  while (originalSize > 0);
 
   #else
-  
-  *size = fread(data, 1, originalSize, p->file);
-  if (*size == originalSize)
-    return 0;
-  return ferror(p->file);
-  
+
+  do
+  {
+    const size_t curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : originalSize;
+    const ssize_t processed = read(p->fd, data, curSize);
+    if (processed == -1)
+      return errno;
+    if (processed == 0)
+      break;
+    data = (void *)((Byte *)data + (size_t)processed);
+    originalSize -= (size_t)processed;
+    *size += (size_t)processed;
+    // debug : we can break here for partial reading mode
+    // break;
+  }
+  while (originalSize > 0);
+
   #endif
+
+  return 0;
 }
 
+
 WRes File_Write(CSzFile *p, const void *data, size_t *size)
 {
   size_t originalSize = *size;
+  *size = 0;
   if (originalSize == 0)
     return 0;
   
   #ifdef USE_WINDOWS_FILE
 
-  *size = 0;
   do
   {
-    DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize;
+    const DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize;
     DWORD processed = 0;
-    BOOL res = WriteFile(p->handle, data, curSize, &processed, NULL);
-    data = (void *)((Byte *)data + processed);
+    const BOOL res = WriteFile(p->handle, data, curSize, &processed, NULL);
+    data = (const void *)((const Byte *)data + processed);
     originalSize -= processed;
     *size += processed;
     if (!res)
@@ -154,26 +241,52 @@
       break;
   }
   while (originalSize > 0);
-  return 0;
+
+  #elif defined(USE_FOPEN)
+
+  do
+  {
+    const size_t curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : originalSize;
+    const size_t processed = fwrite(data, 1, curSize, p->file);
+    data = (void *)((Byte *)data + (size_t)processed);
+    originalSize -= processed;
+    *size += processed;
+    if (processed != curSize)
+      return ferror(p->file);
+    if (processed == 0)
+      break;
+  }
+  while (originalSize > 0);
 
   #else
 
-  *size = fwrite(data, 1, originalSize, p->file);
-  if (*size == originalSize)
-    return 0;
-  return ferror(p->file);
-  
+  do
+  {
+    const size_t curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : originalSize;
+    const ssize_t processed = write(p->fd, data, curSize);
+    if (processed == -1)
+      return errno;
+    if (processed == 0)
+      break;
+    data = (void *)((Byte *)data + (size_t)processed);
+    originalSize -= (size_t)processed;
+    *size += (size_t)processed;
+  }
+  while (originalSize > 0);
+
   #endif
+
+  return 0;
 }
 
+
 WRes File_Seek(CSzFile *p, Int64 *pos, ESzSeek origin)
 {
   #ifdef USE_WINDOWS_FILE
 
-  LARGE_INTEGER value;
   DWORD moveMethod;
-  value.LowPart = (DWORD)*pos;
-  value.HighPart = (LONG)((UInt64)*pos >> 16 >> 16); /* for case when UInt64 is 32-bit only */
+  UInt32 low = (UInt32)*pos;
+  LONG high = (LONG)((UInt64)*pos >> 16 >> 16); /* for case when UInt64 is 32-bit only */
   switch (origin)
   {
     case SZ_SEEK_SET: moveMethod = FILE_BEGIN; break;
@@ -181,34 +294,52 @@
     case SZ_SEEK_END: moveMethod = FILE_END; break;
     default: return ERROR_INVALID_PARAMETER;
   }
-  value.LowPart = SetFilePointer(p->handle, value.LowPart, &value.HighPart, moveMethod);
-  if (value.LowPart == 0xFFFFFFFF)
+  low = SetFilePointer(p->handle, (LONG)low, &high, moveMethod);
+  if (low == (UInt32)0xFFFFFFFF)
   {
     WRes res = GetLastError();
     if (res != NO_ERROR)
       return res;
   }
-  *pos = ((Int64)value.HighPart << 32) | value.LowPart;
+  *pos = ((Int64)high << 32) | low;
   return 0;
 
   #else
   
-  int moveMethod;
-  int res;
+  int moveMethod; // = origin;
+
   switch (origin)
   {
     case SZ_SEEK_SET: moveMethod = SEEK_SET; break;
     case SZ_SEEK_CUR: moveMethod = SEEK_CUR; break;
     case SZ_SEEK_END: moveMethod = SEEK_END; break;
-    default: return 1;
+    default: return EINVAL;
   }
-  res = fseek(p->file, (long)*pos, moveMethod);
-  *pos = ftell(p->file);
-  return res;
   
-  #endif
+  #if defined(USE_FOPEN)
+  {
+    int res = fseek(p->file, (long)*pos, moveMethod);
+    if (res == -1)
+      return errno;
+    *pos = ftell(p->file);
+    if (*pos == -1)
+      return errno;
+    return 0;
+  }
+  #else
+  {
+    off_t res = lseek(p->fd, (off_t)*pos, moveMethod);
+    if (res == -1)
+      return errno;
+    *pos = res;
+    return 0;
+  }
+  
+  #endif // USE_FOPEN
+  #endif // USE_WINDOWS_FILE
 }
 
+
 WRes File_GetLength(CSzFile *p, UInt64 *length)
 {
   #ifdef USE_WINDOWS_FILE
@@ -224,13 +355,31 @@
   *length = (((UInt64)sizeHigh) << 32) + sizeLow;
   return 0;
   
-  #else
+  #elif defined(USE_FOPEN)
   
   long pos = ftell(p->file);
   int res = fseek(p->file, 0, SEEK_END);
   *length = ftell(p->file);
   fseek(p->file, pos, SEEK_SET);
   return res;
+
+  #else
+
+  off_t pos;
+  *length = 0;
+  pos = lseek(p->fd, 0, SEEK_CUR);
+  if (pos != -1)
+  {
+    const off_t len2 = lseek(p->fd, 0, SEEK_END);
+    const off_t res2 = lseek(p->fd, pos, SEEK_SET);
+    if (len2 != -1)
+    {
+      *length = (UInt64)len2;
+      if (res2 != -1)
+        return 0;
+    }
+  }
+  return errno;
   
   #endif
 }
@@ -241,7 +390,9 @@
 static SRes FileSeqInStream_Read(const ISeqInStream *pp, void *buf, size_t *size)
 {
   CFileSeqInStream *p = CONTAINER_FROM_VTBL(pp, CFileSeqInStream, vt);
-  return File_Read(&p->file, buf, size) == 0 ? SZ_OK : SZ_ERROR_READ;
+  WRes wres = File_Read(&p->file, buf, size);
+  p->wres = wres;
+  return (wres == 0) ? SZ_OK : SZ_ERROR_READ;
 }
 
 void FileSeqInStream_CreateVTable(CFileSeqInStream *p)
@@ -255,13 +406,17 @@
 static SRes FileInStream_Read(const ISeekInStream *pp, void *buf, size_t *size)
 {
   CFileInStream *p = CONTAINER_FROM_VTBL(pp, CFileInStream, vt);
-  return (File_Read(&p->file, buf, size) == 0) ? SZ_OK : SZ_ERROR_READ;
+  WRes wres = File_Read(&p->file, buf, size);
+  p->wres = wres;
+  return (wres == 0) ? SZ_OK : SZ_ERROR_READ;
 }
 
 static SRes FileInStream_Seek(const ISeekInStream *pp, Int64 *pos, ESzSeek origin)
 {
   CFileInStream *p = CONTAINER_FROM_VTBL(pp, CFileInStream, vt);
-  return File_Seek(&p->file, pos, origin);
+  WRes wres = File_Seek(&p->file, pos, origin);
+  p->wres = wres;
+  return (wres == 0) ? SZ_OK : SZ_ERROR_READ;
 }
 
 void FileInStream_CreateVTable(CFileInStream *p)
@@ -276,7 +431,8 @@
 static size_t FileOutStream_Write(const ISeqOutStream *pp, const void *data, size_t size)
 {
   CFileOutStream *p = CONTAINER_FROM_VTBL(pp, CFileOutStream, vt);
-  File_Write(&p->file, data, &size);
+  WRes wres = File_Write(&p->file, data, &size);
+  p->wres = wres;
   return size;
 }
 
diff --git a/third_party/lzma_sdk/7zFile.h b/third_party/lzma_sdk/7zFile.h
index 0e79253..788abb6 100644
--- a/third_party/lzma_sdk/7zFile.h
+++ b/third_party/lzma_sdk/7zFile.h
@@ -1,17 +1,20 @@
 /* 7zFile.h -- File IO
-2017-04-03 : Igor Pavlov : Public domain */
+2021-02-15 : Igor Pavlov : Public domain */
 
 #ifndef __7Z_FILE_H
 #define __7Z_FILE_H
 
 #ifdef _WIN32
 #define USE_WINDOWS_FILE
+// #include <windows.h>
 #endif
 
 #ifdef USE_WINDOWS_FILE
 #include <windows.h>
 #else
-#include <stdio.h>
+// note: USE_FOPEN mode is limited to 32-bit file size
+// #define USE_FOPEN
+// #include <stdio.h>
 #endif
 
 #include "7zTypes.h"
@@ -24,8 +27,10 @@
 {
   #ifdef USE_WINDOWS_FILE
   HANDLE handle;
-  #else
+  #elif defined(USE_FOPEN)
   FILE *file;
+  #else
+  int fd;
   #endif
 } CSzFile;
 
@@ -56,6 +61,7 @@
 {
   ISeqInStream vt;
   CSzFile file;
+  WRes wres;
 } CFileSeqInStream;
 
 void FileSeqInStream_CreateVTable(CFileSeqInStream *p);
@@ -65,6 +71,7 @@
 {
   ISeekInStream vt;
   CSzFile file;
+  WRes wres;
 } CFileInStream;
 
 void FileInStream_CreateVTable(CFileInStream *p);
@@ -74,6 +81,7 @@
 {
   ISeqOutStream vt;
   CSzFile file;
+  WRes wres;
 } CFileOutStream;
 
 void FileOutStream_CreateVTable(CFileOutStream *p);
diff --git a/third_party/lzma_sdk/7zStream.c b/third_party/lzma_sdk/7zStream.c
index 6b5aa16..28a1460 100644
--- a/third_party/lzma_sdk/7zStream.c
+++ b/third_party/lzma_sdk/7zStream.c
@@ -1,5 +1,5 @@
 /* 7zStream.c -- 7z Stream functions
-2017-04-03 : Igor Pavlov : Public domain */
+2021-02-09 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -37,7 +37,7 @@
 
 SRes LookInStream_SeekTo(const ILookInStream *stream, UInt64 offset)
 {
-  Int64 t = offset;
+  Int64 t = (Int64)offset;
   return ILookInStream_Seek(stream, &t, SZ_SEEK_SET);
 }
 
diff --git a/third_party/lzma_sdk/7zTypes.h b/third_party/lzma_sdk/7zTypes.h
index 65b3af6..fe4fde3 100644
--- a/third_party/lzma_sdk/7zTypes.h
+++ b/third_party/lzma_sdk/7zTypes.h
@@ -1,11 +1,13 @@
 /* 7zTypes.h -- Basic types
-2018-08-04 : Igor Pavlov : Public domain */
+2021-12-25 : Igor Pavlov : Public domain */
 
 #ifndef __7Z_TYPES_H
 #define __7Z_TYPES_H
 
 #ifdef _WIN32
 /* #include <windows.h> */
+#else
+#include <errno.h>
 #endif
 
 #include <stddef.h>
@@ -43,18 +45,116 @@
 typedef int SRes;
 
 
+#ifdef _MSC_VER
+  #if _MSC_VER > 1200
+    #define MY_ALIGN(n) __declspec(align(n))
+  #else
+    #define MY_ALIGN(n)
+  #endif
+#else
+  #define MY_ALIGN(n) __attribute__ ((aligned(n)))
+#endif
+
+
 #ifdef _WIN32
 
 /* typedef DWORD WRes; */
 typedef unsigned WRes;
 #define MY_SRes_HRESULT_FROM_WRes(x) HRESULT_FROM_WIN32(x)
 
-#else
+// #define MY_HRES_ERROR__INTERNAL_ERROR  MY_SRes_HRESULT_FROM_WRes(ERROR_INTERNAL_ERROR)
 
+#else // _WIN32
+
+// #define ENV_HAVE_LSTAT
 typedef int WRes;
-#define MY__FACILITY_WIN32 7
-#define MY__FACILITY__WRes MY__FACILITY_WIN32
-#define MY_SRes_HRESULT_FROM_WRes(x) ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) : ((HRESULT) (((x) & 0x0000FFFF) | (MY__FACILITY__WRes << 16) | 0x80000000)))
+
+// (FACILITY_ERRNO = 0x800) is 7zip's FACILITY constant to represent (errno) errors in HRESULT
+#define MY__FACILITY_ERRNO  0x800
+#define MY__FACILITY_WIN32  7
+#define MY__FACILITY__WRes  MY__FACILITY_ERRNO
+
+#define MY_HRESULT_FROM_errno_CONST_ERROR(x) ((HRESULT)( \
+          ( (HRESULT)(x) & 0x0000FFFF) \
+          | (MY__FACILITY__WRes << 16)  \
+          | (HRESULT)0x80000000 ))
+
+#define MY_SRes_HRESULT_FROM_WRes(x) \
+  ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) : MY_HRESULT_FROM_errno_CONST_ERROR(x))
+
+// we call macro HRESULT_FROM_WIN32 for system errors (WRes) that are (errno)
+#define HRESULT_FROM_WIN32(x) MY_SRes_HRESULT_FROM_WRes(x)
+
+/*
+#define ERROR_FILE_NOT_FOUND             2L
+#define ERROR_ACCESS_DENIED              5L
+#define ERROR_NO_MORE_FILES              18L
+#define ERROR_LOCK_VIOLATION             33L
+#define ERROR_FILE_EXISTS                80L
+#define ERROR_DISK_FULL                  112L
+#define ERROR_NEGATIVE_SEEK              131L
+#define ERROR_ALREADY_EXISTS             183L
+#define ERROR_DIRECTORY                  267L
+#define ERROR_TOO_MANY_POSTS             298L
+
+#define ERROR_INTERNAL_ERROR             1359L
+#define ERROR_INVALID_REPARSE_DATA       4392L
+#define ERROR_REPARSE_TAG_INVALID        4393L
+#define ERROR_REPARSE_TAG_MISMATCH       4394L
+*/
+
+// we use errno equivalents for some WIN32 errors:
+
+#define ERROR_INVALID_PARAMETER     EINVAL
+#define ERROR_INVALID_FUNCTION      EINVAL
+#define ERROR_ALREADY_EXISTS        EEXIST
+#define ERROR_FILE_EXISTS           EEXIST
+#define ERROR_PATH_NOT_FOUND        ENOENT
+#define ERROR_FILE_NOT_FOUND        ENOENT
+#define ERROR_DISK_FULL             ENOSPC
+// #define ERROR_INVALID_HANDLE        EBADF
+
+// we use FACILITY_WIN32 for errors that has no errno equivalent
+// Too many posts were made to a semaphore.
+#define ERROR_TOO_MANY_POSTS        ((HRESULT)0x8007012AL)
+#define ERROR_INVALID_REPARSE_DATA  ((HRESULT)0x80071128L)
+#define ERROR_REPARSE_TAG_INVALID   ((HRESULT)0x80071129L)
+
+// if (MY__FACILITY__WRes != FACILITY_WIN32),
+// we use FACILITY_WIN32 for COM errors:
+#define E_OUTOFMEMORY               ((HRESULT)0x8007000EL)
+#define E_INVALIDARG                ((HRESULT)0x80070057L)
+#define MY__E_ERROR_NEGATIVE_SEEK   ((HRESULT)0x80070083L)
+
+/*
+// we can use FACILITY_ERRNO for some COM errors, that have errno equivalents:
+#define E_OUTOFMEMORY             MY_HRESULT_FROM_errno_CONST_ERROR(ENOMEM)
+#define E_INVALIDARG              MY_HRESULT_FROM_errno_CONST_ERROR(EINVAL)
+#define MY__E_ERROR_NEGATIVE_SEEK MY_HRESULT_FROM_errno_CONST_ERROR(EINVAL)
+*/
+
+// gcc / clang : (sizeof(long) == sizeof(void*)) in 32/64 bits
+typedef          long INT_PTR;
+typedef unsigned long UINT_PTR;
+
+#define TEXT(quote) quote
+
+#define FILE_ATTRIBUTE_READONLY       0x0001
+#define FILE_ATTRIBUTE_HIDDEN         0x0002
+#define FILE_ATTRIBUTE_SYSTEM         0x0004
+#define FILE_ATTRIBUTE_DIRECTORY      0x0010
+#define FILE_ATTRIBUTE_ARCHIVE        0x0020
+#define FILE_ATTRIBUTE_DEVICE         0x0040
+#define FILE_ATTRIBUTE_NORMAL         0x0080
+#define FILE_ATTRIBUTE_TEMPORARY      0x0100
+#define FILE_ATTRIBUTE_SPARSE_FILE    0x0200
+#define FILE_ATTRIBUTE_REPARSE_POINT  0x0400
+#define FILE_ATTRIBUTE_COMPRESSED     0x0800
+#define FILE_ATTRIBUTE_OFFLINE        0x1000
+#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x2000
+#define FILE_ATTRIBUTE_ENCRYPTED      0x4000
+
+#define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000   /* trick for Unix */
 
 #endif
 
@@ -63,6 +163,10 @@
 #define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; }
 #endif
 
+#ifndef RINOK_WRes
+#define RINOK_WRes(x) { WRes __result__ = (x); if (__result__ != 0) return __result__; }
+#endif
+
 typedef unsigned char Byte;
 typedef short Int16;
 typedef unsigned short UInt16;
@@ -75,6 +179,40 @@
 typedef unsigned int UInt32;
 #endif
 
+
+#ifndef _WIN32
+
+typedef int INT;
+typedef Int32 INT32;
+typedef unsigned int UINT;
+typedef UInt32 UINT32;
+typedef INT32 LONG;   // LONG, ULONG and DWORD must be 32-bit for _WIN32 compatibility
+typedef UINT32 ULONG;
+
+#undef DWORD
+typedef UINT32 DWORD;
+
+#define VOID void
+
+#define HRESULT LONG
+
+typedef void *LPVOID;
+// typedef void VOID;
+// typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR;
+// gcc / clang on Unix  : sizeof(long==sizeof(void*) in 32 or 64 bits)
+typedef          long  INT_PTR;
+typedef unsigned long  UINT_PTR;
+typedef          long  LONG_PTR;
+typedef unsigned long  DWORD_PTR;
+
+typedef size_t SIZE_T;
+
+#endif //  _WIN32
+
+
+#define MY_HRES_ERROR__INTERNAL_ERROR  ((HRESULT)0x8007054FL)
+
+
 #ifdef _SZ_NO_INT_64
 
 /* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers.
@@ -128,25 +266,37 @@
 #define MY_CDECL __cdecl
 #define MY_FAST_CALL __fastcall
 
-#else
+#else //  _MSC_VER
 
-#define MY_NO_INLINE
-#define MY_FORCE_INLINE
-#define MY_CDECL
-#define MY_FAST_CALL
-
-/* inline keyword : for C++ / C99 */
-
-/* GCC, clang: */
-/*
-#if defined (__GNUC__) && (__GNUC__ >= 4)
-#define MY_FORCE_INLINE __attribute__((always_inline))
+#if (defined(__GNUC__) && (__GNUC__ >= 4)) \
+    || (defined(__clang__) && (__clang_major__ >= 4)) \
+    || defined(__INTEL_COMPILER) \
+    || defined(__xlC__)
 #define MY_NO_INLINE __attribute__((noinline))
+// #define MY_FORCE_INLINE __attribute__((always_inline)) inline
+#else
+#define MY_NO_INLINE
 #endif
-*/
 
+#define MY_FORCE_INLINE
+
+
+#define MY_CDECL
+
+#if  defined(_M_IX86) \
+  || defined(__i386__)
+// #define MY_FAST_CALL __attribute__((fastcall))
+// #define MY_FAST_CALL __attribute__((cdecl))
+#define MY_FAST_CALL
+#elif defined(MY_CPU_AMD64)
+// #define MY_FAST_CALL __attribute__((ms_abi))
+#define MY_FAST_CALL
+#else
+#define MY_FAST_CALL
 #endif
 
+#endif //  _MSC_VER
+
 
 /* The following interfaces use first parameter as pointer to structure */
 
@@ -335,12 +485,11 @@
     GCC 4.8.1 : classes with non-public variable members"
 */
 
-#define MY_container_of(ptr, type, m) ((type *)((char *)(1 ? (ptr) : &((type *)0)->m) - MY_offsetof(type, m)))
-
+#define MY_container_of(ptr, type, m) ((type *)(void *)((char *)(void *)(1 ? (ptr) : &((type *)0)->m) - MY_offsetof(type, m)))
 
 #endif
 
-#define CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m) ((type *)(ptr))
+#define CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m) ((type *)(void *)(ptr))
 
 /*
 #define CONTAINER_FROM_VTBL(ptr, type, m) CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m)
@@ -353,6 +502,7 @@
 */
 
 
+#define MY_memset_0_ARRAY(a) memset((a), 0, sizeof(a))
 
 #ifdef _WIN32
 
diff --git a/third_party/lzma_sdk/7zVersion.h b/third_party/lzma_sdk/7zVersion.h
index c176823..e9363d3 100644
--- a/third_party/lzma_sdk/7zVersion.h
+++ b/third_party/lzma_sdk/7zVersion.h
@@ -1,7 +1,7 @@
-#define MY_VER_MAJOR 19
-#define MY_VER_MINOR 00
+#define MY_VER_MAJOR 21
+#define MY_VER_MINOR 07
 #define MY_VER_BUILD 0
-#define MY_VERSION_NUMBERS "19.00"
+#define MY_VERSION_NUMBERS "21.07"
 #define MY_VERSION MY_VERSION_NUMBERS
 
 #ifdef MY_CPU_NAME
@@ -10,12 +10,12 @@
   #define MY_VERSION_CPU MY_VERSION
 #endif
 
-#define MY_DATE "2019-02-21"
+#define MY_DATE "2021-12-26"
 #undef MY_COPYRIGHT
 #undef MY_VERSION_COPYRIGHT_DATE
 #define MY_AUTHOR_NAME "Igor Pavlov"
 #define MY_COPYRIGHT_PD "Igor Pavlov : Public domain"
-#define MY_COPYRIGHT_CR "Copyright (c) 1999-2018 Igor Pavlov"
+#define MY_COPYRIGHT_CR "Copyright (c) 1999-2021 Igor Pavlov"
 
 #ifdef USE_COPYRIGHT_CR
   #define MY_COPYRIGHT MY_COPYRIGHT_CR
diff --git a/third_party/lzma_sdk/7zr.exe b/third_party/lzma_sdk/7zr.exe
index bb1b30b..55b4def 100755
--- a/third_party/lzma_sdk/7zr.exe
+++ b/third_party/lzma_sdk/7zr.exe
Binary files differ
diff --git a/third_party/lzma_sdk/Alloc.c b/third_party/lzma_sdk/Alloc.c
index bcede4b..d1af76c 100644
--- a/third_party/lzma_sdk/Alloc.c
+++ b/third_party/lzma_sdk/Alloc.c
@@ -1,12 +1,12 @@
 /* Alloc.c -- Memory allocation functions
-2018-04-27 : Igor Pavlov : Public domain */
+2021-07-13 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
 #include <stdio.h>
 
 #ifdef _WIN32
-#include <windows.h>
+#include <Windows.h>
 #endif
 #include <stdlib.h>
 
@@ -122,7 +122,6 @@
 #define Print(s)
 #define PrintLn()
 #define PrintHex(v, align)
-#define PrintDec(v, align)
 #define PrintAddr(p)
 
 #endif
@@ -133,10 +132,11 @@
 {
   if (size == 0)
     return NULL;
+  PRINT_ALLOC("Alloc    ", g_allocCount, size, NULL);
   #ifdef _SZ_ALLOC_DEBUG
   {
     void *p = malloc(size);
-    PRINT_ALLOC("Alloc    ", g_allocCount, size, p);
+    // PRINT_ALLOC("Alloc    ", g_allocCount, size, p);
     return p;
   }
   #else
@@ -172,14 +172,20 @@
   VirtualFree(address, 0, MEM_RELEASE);
 }
 
-#ifndef MEM_LARGE_PAGES
-#undef _7ZIP_LARGE_PAGES
+#ifdef _7ZIP_LARGE_PAGES
+
+#ifdef MEM_LARGE_PAGES
+  #define MY__MEM_LARGE_PAGES  MEM_LARGE_PAGES
+#else
+  #define MY__MEM_LARGE_PAGES  0x20000000
 #endif
 
-#ifdef _7ZIP_LARGE_PAGES
+extern
+SIZE_T g_LargePageSize;
 SIZE_T g_LargePageSize = 0;
-typedef SIZE_T (WINAPI *GetLargePageMinimumP)();
-#endif
+typedef SIZE_T (WINAPI *GetLargePageMinimumP)(VOID);
+
+#endif // _7ZIP_LARGE_PAGES
 
 void SetLargePageSize()
 {
@@ -214,7 +220,7 @@
       size2 = (size + ps) & ~ps;
       if (size2 >= size)
       {
-        void *res = VirtualAlloc(NULL, size2, MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
+        void *res = VirtualAlloc(NULL, size2, MEM_COMMIT | MY__MEM_LARGE_PAGES, PAGE_READWRITE);
         if (res)
           return res;
       }
@@ -241,14 +247,14 @@
 static void SzFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p); MyFree(address); }
 const ISzAlloc g_Alloc = { SzAlloc, SzFree };
 
+#ifdef _WIN32
 static void *SzMidAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p); return MidAlloc(size); }
 static void SzMidFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p); MidFree(address); }
-const ISzAlloc g_MidAlloc = { SzMidAlloc, SzMidFree };
-
 static void *SzBigAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p); return BigAlloc(size); }
 static void SzBigFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p); BigFree(address); }
+const ISzAlloc g_MidAlloc = { SzMidAlloc, SzMidFree };
 const ISzAlloc g_BigAlloc = { SzBigAlloc, SzBigFree };
-
+#endif
 
 /*
   uintptr_t : <stdint.h> C99 (optional)
@@ -280,13 +286,15 @@
 */
 #define MY_ALIGN_PTR_DOWN(p, align) ((void *)((((UIntPtr)(p)) & ~((UIntPtr)(align) - 1))))
 
-#define MY_ALIGN_PTR_UP_PLUS(p, align) MY_ALIGN_PTR_DOWN(((char *)(p) + (align) + ADJUST_ALLOC_SIZE), align)
 
-
-#if (_POSIX_C_SOURCE >= 200112L) && !defined(_WIN32)
+#if !defined(_WIN32) && defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)
   #define USE_posix_memalign
 #endif
 
+#ifndef USE_posix_memalign
+#define MY_ALIGN_PTR_UP_PLUS(p, align) MY_ALIGN_PTR_DOWN(((char *)(p) + (align) + ADJUST_ALLOC_SIZE), align)
+#endif
+
 /*
   This posix_memalign() is for test purposes only.
   We also need special Free() function instead of free(),
diff --git a/third_party/lzma_sdk/Alloc.h b/third_party/lzma_sdk/Alloc.h
index 64823764..3be2041e 100644
--- a/third_party/lzma_sdk/Alloc.h
+++ b/third_party/lzma_sdk/Alloc.h
@@ -1,5 +1,5 @@
 /* Alloc.h -- Memory allocation functions
-2018-02-19 : Igor Pavlov : Public domain */
+2021-07-13 : Igor Pavlov : Public domain */
 
 #ifndef __COMMON_ALLOC_H
 #define __COMMON_ALLOC_H
@@ -13,7 +13,7 @@
 
 #ifdef _WIN32
 
-void SetLargePageSize();
+void SetLargePageSize(void);
 
 void *MidAlloc(size_t size);
 void MidFree(void *address);
@@ -30,8 +30,15 @@
 #endif
 
 extern const ISzAlloc g_Alloc;
+
+#ifdef _WIN32
 extern const ISzAlloc g_BigAlloc;
 extern const ISzAlloc g_MidAlloc;
+#else
+#define g_BigAlloc g_AlignedAlloc
+#define g_MidAlloc g_AlignedAlloc
+#endif
+
 extern const ISzAlloc g_AlignedAlloc;
 
 
diff --git a/third_party/lzma_sdk/BUILD.gn b/third_party/lzma_sdk/BUILD.gn
index 077bcde0..b276c727 100644
--- a/third_party/lzma_sdk/BUILD.gn
+++ b/third_party/lzma_sdk/BUILD.gn
@@ -2,25 +2,55 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/arm.gni")
+
+# TODO(richard.townsend@arm.com): Optimizations temporarily disabled for
+# Windows on Arm MSVC builds, see http://crbug.com/v8/10012.
+use_arm_neon_optimizations = (target_cpu == "arm" || target_cpu == "arm64") &&
+                             arm_use_neon && !(is_win && !is_clang)
+
 config("lzma_sdk_config") {
   include_dirs = [ "." ]
 }
 
-# Must be in a config because of how GN orders flags (otherwise -Wall will
-# appear after this, and turn it back on).
-config("clang_warnings") {
-  if (is_clang) {
-    # Upstream uses self-assignment to avoid warnings.
-    cflags = [ "-Wno-self-assign" ]
-  }
-}
-
-config("lzma_defines") {
+# Must be in a config for -Wno-self-assign because of how GN orders flags
+# (otherwise -Wall will appear after this, and turn it back on).
+config("lzma_build_config") {
   defines = [
     "_7ZIP_ST",
     "_7Z_NO_METHODS_FILTERS",
     "_LZMA_PROB32",
   ]
+
+  cflags = []
+  if (is_clang) {
+    # Upstream uses self-assignment to avoid warnings.
+    cflags += [ "-Wno-self-assign" ]
+  }
+
+  if (use_arm_neon_optimizations) {
+    if (is_fuchsia) {
+      defines += [ "ARMV8_OS_FUCHSIA" ]
+    }
+
+    if (target_cpu == "arm" && arm_version >= 8) {
+      if (is_clang) {
+        cflags += [
+          "-march=armv8-a",
+          "-Xclang",
+          "-target-feature",
+          "-Xclang",
+          "+crc",
+          "-Xclang",
+          "-target-feature",
+          "-Xclang",
+          "+crypto",
+        ]
+      } else {
+        cflags += [ "-march=armv8-a+crc+crypto" ]
+      }
+    }
+  }
 }
 
 static_library("lzma_sdk") {
@@ -72,8 +102,7 @@
     "//build/config/compiler:no_chromium_code",
 
     # Must be after no_chromium_code for warning flags to be ordered correctly.
-    ":clang_warnings",
-    ":lzma_defines",
+    ":lzma_build_config",
   ]
   public_configs = [ ":lzma_sdk_config" ]
 }
@@ -92,6 +121,11 @@
     "XzDec.c",
   ]
 
+  # TODO(crbug.com/1338627): Enable ARM optimizations
+  if (target_cpu == "x86" || target_cpu == "x64") {
+    sources += [ "Sha256Opt.c" ]
+  }
+
   deps = [ ":lzma_sdk" ]
 
   configs -= [ "//build/config/compiler:chromium_code" ]
@@ -99,8 +133,7 @@
     "//build/config/compiler:no_chromium_code",
 
     # Must be after no_chromium_code for warning flags to be ordered correctly.
-    ":clang_warnings",
-    ":lzma_defines",
+    ":lzma_build_config",
   ]
   public_configs = [ ":lzma_sdk_config" ]
 }
diff --git a/third_party/lzma_sdk/Bcj2.c b/third_party/lzma_sdk/Bcj2.c
index 9a0046a..c7b9567 100644
--- a/third_party/lzma_sdk/Bcj2.c
+++ b/third_party/lzma_sdk/Bcj2.c
@@ -1,5 +1,5 @@
 /* Bcj2.c -- BCJ2 Decoder (Converter for x86 code)
-2018-04-28 : Igor Pavlov : Public domain */
+2021-02-09 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -123,7 +123,7 @@
         const Byte *src = p->bufs[BCJ2_STREAM_MAIN];
         const Byte *srcLim;
         Byte *dest;
-        SizeT num = p->lims[BCJ2_STREAM_MAIN] - src;
+        SizeT num = (SizeT)(p->lims[BCJ2_STREAM_MAIN] - src);
         
         if (num == 0)
         {
@@ -134,7 +134,7 @@
         dest = p->dest;
         if (num > (SizeT)(p->destLim - dest))
         {
-          num = p->destLim - dest;
+          num = (SizeT)(p->destLim - dest);
           if (num == 0)
           {
             p->state = BCJ2_DEC_STATE_ORIG;
@@ -168,7 +168,7 @@
           break;
         }
         
-        num = src - p->bufs[BCJ2_STREAM_MAIN];
+        num = (SizeT)(src - p->bufs[BCJ2_STREAM_MAIN]);
         
         if (src == srcLim)
         {
@@ -228,7 +228,7 @@
       p->ip += 4;
       val -= p->ip;
       dest = p->dest;
-      rem = p->destLim - dest;
+      rem = (SizeT)(p->destLim - dest);
       
       if (rem < 4)
       {
diff --git a/third_party/lzma_sdk/Bra.c b/third_party/lzma_sdk/Bra.c
index aed17e3..3b854d9c 100644
--- a/third_party/lzma_sdk/Bra.c
+++ b/third_party/lzma_sdk/Bra.c
@@ -1,5 +1,5 @@
 /* Bra.c -- Converters for RISC code
-2017-04-04 : Igor Pavlov : Public domain */
+2021-02-09 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -22,7 +22,7 @@
     for (;;)
     {
       if (p >= lim)
-        return p - data;
+        return (SizeT)(p - data);
       p += 4;
       if (p[-1] == 0xEB)
         break;
@@ -43,7 +43,7 @@
     for (;;)
     {
       if (p >= lim)
-        return p - data;
+        return (SizeT)(p - data);
       p += 4;
       if (p[-1] == 0xEB)
         break;
@@ -78,7 +78,7 @@
     {
       UInt32 b3;
       if (p > lim)
-        return p - data;
+        return (SizeT)(p - data);
       b1 = p[1];
       b3 = p[3];
       p += 2;
@@ -113,7 +113,7 @@
     {
       UInt32 b3;
       if (p > lim)
-        return p - data;
+        return (SizeT)(p - data);
       b1 = p[1];
       b3 = p[3];
       p += 2;
@@ -162,7 +162,7 @@
     for (;;)
     {
       if (p >= lim)
-        return p - data;
+        return (SizeT)(p - data);
       p += 4;
       /* if ((v & 0xFC000003) == 0x48000001) */
       if ((p[-4] & 0xFC) == 0x48 && (p[-1] & 3) == 1)
@@ -196,7 +196,7 @@
     for (;;)
     {
       if (p >= lim)
-        return p - data;
+        return (SizeT)(p - data);
       /*
       v = GetBe32(p);
       p += 4;
diff --git a/third_party/lzma_sdk/Bra86.c b/third_party/lzma_sdk/Bra86.c
index 93ed4d76..10a0fbd1 100644
--- a/third_party/lzma_sdk/Bra86.c
+++ b/third_party/lzma_sdk/Bra86.c
@@ -1,5 +1,5 @@
 /* Bra86.c -- Converter for x86 code (BCJ)
-2017-04-03 : Igor Pavlov : Public domain */
+2021-02-09 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -25,7 +25,7 @@
         break;
 
     {
-      SizeT d = (SizeT)(p - data - pos);
+      SizeT d = (SizeT)(p - data) - pos;
       pos = (SizeT)(p - data);
       if (p >= limit)
       {
diff --git a/third_party/lzma_sdk/Compiler.h b/third_party/lzma_sdk/Compiler.h
index 0cc409d8..a9816fa 100644
--- a/third_party/lzma_sdk/Compiler.h
+++ b/third_party/lzma_sdk/Compiler.h
@@ -1,9 +1,13 @@
 /* Compiler.h
-2017-04-03 : Igor Pavlov : Public domain */
+2021-01-05 : Igor Pavlov : Public domain */
 
 #ifndef __7Z_COMPILER_H
 #define __7Z_COMPILER_H
 
+  #ifdef __clang__
+    #pragma clang diagnostic ignored "-Wunused-private-field"
+  #endif
+
 #ifdef _MSC_VER
 
   #ifdef UNDER_CE
@@ -25,6 +29,12 @@
     #pragma warning(disable : 4786) // identifier was truncated to '255' characters in the debug information
   #endif
 
+  #ifdef __clang__
+    #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+    #pragma clang diagnostic ignored "-Wmicrosoft-exception-spec"
+    // #pragma clang diagnostic ignored "-Wreserved-id-macro"
+  #endif
+
 #endif
 
 #define UNUSED_VAR(x) (void)x;
diff --git a/third_party/lzma_sdk/CpuArch.c b/third_party/lzma_sdk/CpuArch.c
index 02e482e..30451fba 100644
--- a/third_party/lzma_sdk/CpuArch.c
+++ b/third_party/lzma_sdk/CpuArch.c
@@ -1,5 +1,5 @@
 /* CpuArch.c -- CPU specific code
-2018-02-18: Igor Pavlov : Public domain */
+2021-07-13 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -55,6 +55,47 @@
 #define CHECK_CPUID_IS_SUPPORTED
 #endif
 
+#ifndef USE_ASM
+  #ifdef _MSC_VER
+    #if _MSC_VER >= 1600
+      #define MY__cpuidex  __cpuidex
+    #else
+
+/*
+ __cpuid (function == 4) requires subfunction number in ECX.
+  MSDN: The __cpuid intrinsic clears the ECX register before calling the cpuid instruction.
+   __cpuid() in new MSVC clears ECX.
+   __cpuid() in old MSVC (14.00) doesn't clear ECX
+ We still can use __cpuid for low (function) values that don't require ECX,
+ but __cpuid() in old MSVC will be incorrect for some function values: (function == 4).
+ So here we use the hack for old MSVC to send (subFunction) in ECX register to cpuid instruction,
+ where ECX value is first parameter for FAST_CALL / NO_INLINE function,
+ So the caller of MY__cpuidex_HACK() sets ECX as subFunction, and
+ old MSVC for __cpuid() doesn't change ECX and cpuid instruction gets (subFunction) value.
+ 
+ DON'T remove MY_NO_INLINE and MY_FAST_CALL for MY__cpuidex_HACK() !!!
+*/
+
+static
+MY_NO_INLINE
+void MY_FAST_CALL MY__cpuidex_HACK(UInt32 subFunction, int *CPUInfo, UInt32 function)
+{
+  UNUSED_VAR(subFunction);
+  __cpuid(CPUInfo, function);
+}
+
+      #define MY__cpuidex(info, func, func2)  MY__cpuidex_HACK(func2, info, func)
+      #pragma message("======== MY__cpuidex_HACK WAS USED ========")
+    #endif
+  #else
+     #define MY__cpuidex(info, func, func2)  __cpuid(info, func)
+     #pragma message("======== (INCORRECT ?) cpuid WAS USED ========")
+  #endif
+#endif
+
+
+
+
 void MyCPUID(UInt32 function, UInt32 *a, UInt32 *b, UInt32 *c, UInt32 *d)
 {
   #ifdef USE_ASM
@@ -99,18 +140,20 @@
   #endif
       "=c" (*c) ,
       "=d" (*d)
-    : "0" (function)) ;
+    : "0" (function), "c"(0) ) ;
 
   #endif
   
   #else
 
   int CPUInfo[4];
-  __cpuid(CPUInfo, function);
-  *a = CPUInfo[0];
-  *b = CPUInfo[1];
-  *c = CPUInfo[2];
-  *d = CPUInfo[3];
+
+  MY__cpuidex(CPUInfo, (int)function, 0);
+
+  *a = (UInt32)CPUInfo[0];
+  *b = (UInt32)CPUInfo[1];
+  *c = (UInt32)CPUInfo[2];
+  *d = (UInt32)CPUInfo[3];
 
   #endif
 }
@@ -174,7 +217,7 @@
 }
 
 #if !defined(MY_CPU_AMD64) && defined(_WIN32)
-#include <windows.h>
+#include <Windows.h>
 static BoolInt CPU_Sys_Is_SSE_Supported()
 {
   OSVERSIONINFO vi;
@@ -188,13 +231,101 @@
 #define CHECK_SYS_SSE_SUPPORT
 #endif
 
-BoolInt CPU_Is_Aes_Supported()
+
+static UInt32 X86_CPUID_ECX_Get_Flags()
+{
+  Cx86cpuid p;
+  CHECK_SYS_SSE_SUPPORT
+  if (!x86cpuid_CheckAndRead(&p))
+    return 0;
+  return p.c;
+}
+
+BoolInt CPU_IsSupported_AES()
+{
+  return (X86_CPUID_ECX_Get_Flags() >> 25) & 1;
+}
+
+BoolInt CPU_IsSupported_SSSE3()
+{
+  return (X86_CPUID_ECX_Get_Flags() >> 9) & 1;
+}
+
+BoolInt CPU_IsSupported_SSE41()
+{
+  return (X86_CPUID_ECX_Get_Flags() >> 19) & 1;
+}
+
+BoolInt CPU_IsSupported_SHA()
 {
   Cx86cpuid p;
   CHECK_SYS_SSE_SUPPORT
   if (!x86cpuid_CheckAndRead(&p))
     return False;
-  return (p.c >> 25) & 1;
+
+  if (p.maxFunc < 7)
+    return False;
+  {
+    UInt32 d[4] = { 0 };
+    MyCPUID(7, &d[0], &d[1], &d[2], &d[3]);
+    return (d[1] >> 29) & 1;
+  }
+}
+
+// #include <stdio.h>
+
+#ifdef _WIN32
+#include <Windows.h>
+#endif
+
+BoolInt CPU_IsSupported_AVX2()
+{
+  Cx86cpuid p;
+  CHECK_SYS_SSE_SUPPORT
+
+  #ifdef _WIN32
+  #define MY__PF_XSAVE_ENABLED  17
+  if (!IsProcessorFeaturePresent(MY__PF_XSAVE_ENABLED))
+    return False;
+  #endif
+
+  if (!x86cpuid_CheckAndRead(&p))
+    return False;
+  if (p.maxFunc < 7)
+    return False;
+  {
+    UInt32 d[4] = { 0 };
+    MyCPUID(7, &d[0], &d[1], &d[2], &d[3]);
+    // printf("\ncpuid(7): ebx=%8x ecx=%8x\n", d[1], d[2]);
+    return 1
+      & (d[1] >> 5); // avx2
+  }
+}
+
+BoolInt CPU_IsSupported_VAES_AVX2()
+{
+  Cx86cpuid p;
+  CHECK_SYS_SSE_SUPPORT
+
+  #ifdef _WIN32
+  #define MY__PF_XSAVE_ENABLED  17
+  if (!IsProcessorFeaturePresent(MY__PF_XSAVE_ENABLED))
+    return False;
+  #endif
+
+  if (!x86cpuid_CheckAndRead(&p))
+    return False;
+  if (p.maxFunc < 7)
+    return False;
+  {
+    UInt32 d[4] = { 0 };
+    MyCPUID(7, &d[0], &d[1], &d[2], &d[3]);
+    // printf("\ncpuid(7): ebx=%8x ecx=%8x\n", d[1], d[2]);
+    return 1
+      & (d[1] >> 5) // avx2
+      // & (d[1] >> 31) // avx512vl
+      & (d[2] >> 9); // vaes // VEX-256/EVEX
+  }
 }
 
 BoolInt CPU_IsSupported_PageGB()
@@ -215,4 +346,135 @@
   }
 }
 
+
+#elif defined(MY_CPU_ARM_OR_ARM64)
+
+#ifdef _WIN32
+
+#include <Windows.h>
+
+BoolInt CPU_IsSupported_CRC32()  { return IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE) ? 1 : 0; }
+BoolInt CPU_IsSupported_CRYPTO() { return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE) ? 1 : 0; }
+BoolInt CPU_IsSupported_NEON()   { return IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE) ? 1 : 0; }
+
+#else
+
+#if defined(__APPLE__)
+
+/*
+#include <stdio.h>
+#include <string.h>
+static void Print_sysctlbyname(const char *name)
+{
+  size_t bufSize = 256;
+  char buf[256];
+  int res = sysctlbyname(name, &buf, &bufSize, NULL, 0);
+  {
+    int i;
+    printf("\nres = %d : %s : '%s' : bufSize = %d, numeric", res, name, buf, (unsigned)bufSize);
+    for (i = 0; i < 20; i++)
+      printf(" %2x", (unsigned)(Byte)buf[i]);
+
+  }
+}
+*/
+
+static BoolInt My_sysctlbyname_Get_BoolInt(const char *name)
+{
+  UInt32 val = 0;
+  if (My_sysctlbyname_Get_UInt32(name, &val) == 0 && val == 1)
+    return 1;
+  return 0;
+}
+
+  /*
+  Print_sysctlbyname("hw.pagesize");
+  Print_sysctlbyname("machdep.cpu.brand_string");
+  */
+
+BoolInt CPU_IsSupported_CRC32(void)
+{
+  return My_sysctlbyname_Get_BoolInt("hw.optional.armv8_crc32");
+}
+
+BoolInt CPU_IsSupported_NEON(void)
+{
+  return My_sysctlbyname_Get_BoolInt("hw.optional.neon");
+}
+
+#ifdef MY_CPU_ARM64
+#define APPLE_CRYPTO_SUPPORT_VAL 1
+#else
+#define APPLE_CRYPTO_SUPPORT_VAL 0
+#endif
+
+BoolInt CPU_IsSupported_SHA1(void) { return APPLE_CRYPTO_SUPPORT_VAL; }
+BoolInt CPU_IsSupported_SHA2(void) { return APPLE_CRYPTO_SUPPORT_VAL; }
+BoolInt CPU_IsSupported_AES (void) { return APPLE_CRYPTO_SUPPORT_VAL; }
+
+
+#else // __APPLE__
+
+#include <sys/auxv.h>
+
+#if !defined(ARMV8_OS_FUCHSIA)
+#define USE_HWCAP
+#endif // !defined(ARMV8_OS_FUCHSIA)
+
+#ifdef USE_HWCAP
+
+#include <asm/hwcap.h>
+
+  #define MY_HWCAP_CHECK_FUNC_2(name1, name2) \
+  BoolInt CPU_IsSupported_ ## name1() { return (getauxval(AT_HWCAP)  & (HWCAP_  ## name2)) ? 1 : 0; }
+
+#ifdef MY_CPU_ARM64
+  #define MY_HWCAP_CHECK_FUNC(name) \
+  MY_HWCAP_CHECK_FUNC_2(name, name)
+  MY_HWCAP_CHECK_FUNC_2(NEON, ASIMD)
+// MY_HWCAP_CHECK_FUNC (ASIMD)
+#elif defined(MY_CPU_ARM)
+  #define MY_HWCAP_CHECK_FUNC(name) \
+  BoolInt CPU_IsSupported_ ## name() { return (getauxval(AT_HWCAP2) & (HWCAP2_ ## name)) ? 1 : 0; }
+  MY_HWCAP_CHECK_FUNC_2(NEON, NEON)
+#endif
+
+#else // USE_HWCAP
+
+  #define MY_HWCAP_CHECK_FUNC(name) \
+  BoolInt CPU_IsSupported_ ## name() { return 0; }
+  MY_HWCAP_CHECK_FUNC(NEON)
+
+#endif // USE_HWCAP
+
+MY_HWCAP_CHECK_FUNC (CRC32)
+MY_HWCAP_CHECK_FUNC (SHA1)
+MY_HWCAP_CHECK_FUNC (SHA2)
+MY_HWCAP_CHECK_FUNC (AES)
+
+#endif // __APPLE__
+#endif // _WIN32
+
+#endif // MY_CPU_ARM_OR_ARM64
+
+
+
+#ifdef __APPLE__
+
+#include <sys/sysctl.h>
+
+int My_sysctlbyname_Get(const char *name, void *buf, size_t *bufSize)
+{
+  return sysctlbyname(name, buf, bufSize, NULL, 0);
+}
+
+int My_sysctlbyname_Get_UInt32(const char *name, UInt32 *val)
+{
+  size_t bufSize = sizeof(*val);
+  int res = My_sysctlbyname_Get(name, val, &bufSize);
+  if (res == 0 && bufSize != sizeof(*val))
+    return EFAULT;
+  return res;
+}
+
 #endif
diff --git a/third_party/lzma_sdk/CpuArch.h b/third_party/lzma_sdk/CpuArch.h
index bd429388..529d3a5 100644
--- a/third_party/lzma_sdk/CpuArch.h
+++ b/third_party/lzma_sdk/CpuArch.h
@@ -1,5 +1,5 @@
 /* CpuArch.h -- CPU specific code
-2018-02-18 : Igor Pavlov : Public domain */
+2021-07-13 : Igor Pavlov : Public domain */
 
 #ifndef __CPU_ARCH_H
 #define __CPU_ARCH_H
@@ -14,6 +14,10 @@
 If MY_CPU_LE and MY_CPU_BE are not defined, we don't know about ENDIANNESS of platform.
 
 MY_CPU_LE_UNALIGN means that CPU is LITTLE ENDIAN and CPU supports unaligned memory accesses.
+
+MY_CPU_64BIT means that processor can work with 64-bit registers.
+  MY_CPU_64BIT can be used to select fast code branch
+  MY_CPU_64BIT doesn't mean that (sizeof(void *) == 8)
 */
 
 #if  defined(_M_X64) \
@@ -24,8 +28,10 @@
   #define MY_CPU_AMD64
   #ifdef __ILP32__
     #define MY_CPU_NAME "x32"
+    #define MY_CPU_SIZEOF_POINTER 4
   #else
     #define MY_CPU_NAME "x64"
+    #define MY_CPU_SIZEOF_POINTER 8
   #endif
   #define MY_CPU_64BIT
 #endif
@@ -35,7 +41,8 @@
   || defined(__i386__)
   #define MY_CPU_X86
   #define MY_CPU_NAME "x86"
-  #define MY_CPU_32BIT
+  /* #define MY_CPU_32BIT */
+  #define MY_CPU_SIZEOF_POINTER 4
 #endif
 
 
@@ -59,8 +66,14 @@
   || defined(__THUMBEL__) \
   || defined(__THUMBEB__)
   #define MY_CPU_ARM
-  #define MY_CPU_NAME "arm"
-  #define MY_CPU_32BIT
+
+  #if defined(__thumb__) || defined(__THUMBEL__) || defined(_M_ARMT)
+    #define MY_CPU_NAME "armt"
+  #else
+    #define MY_CPU_NAME "arm"
+  #endif
+  /* #define MY_CPU_32BIT */
+  #define MY_CPU_SIZEOF_POINTER 4
 #endif
 
 
@@ -84,17 +97,29 @@
 
 
 #if  defined(__ppc64__) \
-  || defined(__powerpc64__)
+  || defined(__powerpc64__) \
+  || defined(__ppc__) \
+  || defined(__powerpc__) \
+  || defined(__PPC__) \
+  || defined(_POWER)
+
+#if  defined(__ppc64__) \
+  || defined(__powerpc64__) \
+  || defined(_LP64) \
+  || defined(__64BIT__)
   #ifdef __ILP32__
     #define MY_CPU_NAME "ppc64-32"
+    #define MY_CPU_SIZEOF_POINTER 4
   #else
     #define MY_CPU_NAME "ppc64"
+    #define MY_CPU_SIZEOF_POINTER 8
   #endif
   #define MY_CPU_64BIT
-#elif defined(__ppc__) \
-  || defined(__powerpc__)
+#else
   #define MY_CPU_NAME "ppc"
-  #define MY_CPU_32BIT
+  #define MY_CPU_SIZEOF_POINTER 4
+  /* #define MY_CPU_32BIT */
+#endif
 #endif
 
 
@@ -111,6 +136,10 @@
 #define MY_CPU_X86_OR_AMD64
 #endif
 
+#if defined(MY_CPU_ARM) || defined(MY_CPU_ARM64)
+#define MY_CPU_ARM_OR_ARM64
+#endif
+
 
 #ifdef _WIN32
 
@@ -170,6 +199,40 @@
   #error Stop_Compiling_Bad_32_64_BIT
 #endif
 
+#ifdef __SIZEOF_POINTER__
+  #ifdef MY_CPU_SIZEOF_POINTER
+    #if MY_CPU_SIZEOF_POINTER != __SIZEOF_POINTER__
+      #error Stop_Compiling_Bad_MY_CPU_PTR_SIZE
+    #endif
+  #else
+    #define MY_CPU_SIZEOF_POINTER  __SIZEOF_POINTER__
+  #endif
+#endif
+
+#if defined(MY_CPU_SIZEOF_POINTER) && (MY_CPU_SIZEOF_POINTER == 4)
+#if defined (_LP64)
+      #error Stop_Compiling_Bad_MY_CPU_PTR_SIZE
+#endif
+#endif
+
+#ifdef _MSC_VER
+  #if _MSC_VER >= 1300
+    #define MY_CPU_pragma_pack_push_1   __pragma(pack(push, 1))
+    #define MY_CPU_pragma_pop           __pragma(pack(pop))
+  #else
+    #define MY_CPU_pragma_pack_push_1
+    #define MY_CPU_pragma_pop
+  #endif
+#else
+  #ifdef __xlC__
+    #define MY_CPU_pragma_pack_push_1   _Pragma("pack(1)")
+    #define MY_CPU_pragma_pop           _Pragma("pack()")
+  #else
+    #define MY_CPU_pragma_pack_push_1   _Pragma("pack(push, 1)")
+    #define MY_CPU_pragma_pop           _Pragma("pack(pop)")
+  #endif
+#endif
+
 
 #ifndef MY_CPU_NAME
   #ifdef MY_CPU_LE
@@ -189,8 +252,12 @@
 
 #ifdef MY_CPU_LE
   #if defined(MY_CPU_X86_OR_AMD64) \
-      || defined(MY_CPU_ARM64) \
-      || defined(__ARM_FEATURE_UNALIGNED)
+      || defined(MY_CPU_ARM64)
+    #define MY_CPU_LE_UNALIGN
+    #define MY_CPU_LE_UNALIGN_64
+  #elif defined(__ARM_FEATURE_UNALIGNED)
+    /* gcc9 for 32-bit arm can use LDRD instruction that requires 32-bit alignment.
+       So we can't use unaligned 64-bit operations. */
     #define MY_CPU_LE_UNALIGN
   #endif
 #endif
@@ -200,11 +267,15 @@
 
 #define GetUi16(p) (*(const UInt16 *)(const void *)(p))
 #define GetUi32(p) (*(const UInt32 *)(const void *)(p))
+#ifdef MY_CPU_LE_UNALIGN_64
 #define GetUi64(p) (*(const UInt64 *)(const void *)(p))
+#endif
 
-#define SetUi16(p, v) { *(UInt16 *)(p) = (v); }
-#define SetUi32(p, v) { *(UInt32 *)(p) = (v); }
-#define SetUi64(p, v) { *(UInt64 *)(p) = (v); }
+#define SetUi16(p, v) { *(UInt16 *)(void *)(p) = (v); }
+#define SetUi32(p, v) { *(UInt32 *)(void *)(p) = (v); }
+#ifdef MY_CPU_LE_UNALIGN_64
+#define SetUi64(p, v) { *(UInt64 *)(void *)(p) = (v); }
+#endif
 
 #else
 
@@ -218,8 +289,6 @@
     ((UInt32)((const Byte *)(p))[2] << 16) | \
     ((UInt32)((const Byte *)(p))[3] << 24))
 
-#define GetUi64(p) (GetUi32(p) | ((UInt64)GetUi32(((const Byte *)(p)) + 4) << 32))
-
 #define SetUi16(p, v) { Byte *_ppp_ = (Byte *)(p); UInt32 _vvv_ = (v); \
     _ppp_[0] = (Byte)_vvv_; \
     _ppp_[1] = (Byte)(_vvv_ >> 8); }
@@ -230,19 +299,29 @@
     _ppp_[2] = (Byte)(_vvv_ >> 16); \
     _ppp_[3] = (Byte)(_vvv_ >> 24); }
 
+#endif
+
+
+#ifndef MY_CPU_LE_UNALIGN_64
+
+#define GetUi64(p) (GetUi32(p) | ((UInt64)GetUi32(((const Byte *)(p)) + 4) << 32))
+
 #define SetUi64(p, v) { Byte *_ppp2_ = (Byte *)(p); UInt64 _vvv2_ = (v); \
     SetUi32(_ppp2_    , (UInt32)_vvv2_); \
     SetUi32(_ppp2_ + 4, (UInt32)(_vvv2_ >> 32)); }
 
 #endif
 
+
+
+
 #ifdef __has_builtin
   #define MY__has_builtin(x) __has_builtin(x)
 #else
   #define MY__has_builtin(x) 0
 #endif
 
-#if defined(MY_CPU_LE_UNALIGN) && /* defined(_WIN64) && */ (_MSC_VER >= 1300)
+#if defined(MY_CPU_LE_UNALIGN) && /* defined(_WIN64) && */ defined(_MSC_VER) && (_MSC_VER >= 1300)
 
 /* Note: we use bswap instruction, that is unsupported in 386 cpu */
 
@@ -253,8 +332,8 @@
 #pragma intrinsic(_byteswap_uint64)
 
 /* #define GetBe16(p) _byteswap_ushort(*(const UInt16 *)(const Byte *)(p)) */
-#define GetBe32(p) _byteswap_ulong(*(const UInt32 *)(const Byte *)(p))
-#define GetBe64(p) _byteswap_uint64(*(const UInt64 *)(const Byte *)(p))
+#define GetBe32(p) _byteswap_ulong (*(const UInt32 *)(const void *)(p))
+#define GetBe64(p) _byteswap_uint64(*(const UInt64 *)(const void *)(p))
 
 #define SetBe32(p, v) (*(UInt32 *)(void *)(p)) = _byteswap_ulong(v)
 
@@ -262,9 +341,9 @@
        (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))) \
     || (defined(__clang__) && MY__has_builtin(__builtin_bswap16)) )
 
-/* #define GetBe16(p) __builtin_bswap16(*(const UInt16 *)(const Byte *)(p)) */
-#define GetBe32(p) __builtin_bswap32(*(const UInt32 *)(const Byte *)(p))
-#define GetBe64(p) __builtin_bswap64(*(const UInt64 *)(const Byte *)(p))
+/* #define GetBe16(p) __builtin_bswap16(*(const UInt16 *)(const void *)(p)) */
+#define GetBe32(p) __builtin_bswap32(*(const UInt32 *)(const void *)(p))
+#define GetBe64(p) __builtin_bswap64(*(const UInt64 *)(const void *)(p))
 
 #define SetBe32(p, v) (*(UInt32 *)(void *)(p)) = __builtin_bswap32(v)
 
@@ -325,10 +404,37 @@
 #define x86cpuid_GetModel(ver)  (((ver >> 12) &  0xF0) | ((ver >> 4) & 0xF))
 #define x86cpuid_GetStepping(ver) (ver & 0xF)
 
-BoolInt CPU_Is_InOrder();
-BoolInt CPU_Is_Aes_Supported();
-BoolInt CPU_IsSupported_PageGB();
+BoolInt CPU_Is_InOrder(void);
 
+BoolInt CPU_IsSupported_AES(void);
+BoolInt CPU_IsSupported_AVX2(void);
+BoolInt CPU_IsSupported_VAES_AVX2(void);
+BoolInt CPU_IsSupported_SSSE3(void);
+BoolInt CPU_IsSupported_SSE41(void);
+BoolInt CPU_IsSupported_SHA(void);
+BoolInt CPU_IsSupported_PageGB(void);
+
+#elif defined(MY_CPU_ARM_OR_ARM64)
+
+BoolInt CPU_IsSupported_CRC32(void);
+BoolInt CPU_IsSupported_NEON(void);
+
+#if defined(_WIN32)
+BoolInt CPU_IsSupported_CRYPTO(void);
+#define CPU_IsSupported_SHA1  CPU_IsSupported_CRYPTO
+#define CPU_IsSupported_SHA2  CPU_IsSupported_CRYPTO
+#define CPU_IsSupported_AES   CPU_IsSupported_CRYPTO
+#else
+BoolInt CPU_IsSupported_SHA1(void);
+BoolInt CPU_IsSupported_SHA2(void);
+BoolInt CPU_IsSupported_AES(void);
+#endif
+
+#endif
+
+#if defined(__APPLE__)
+int My_sysctlbyname_Get(const char *name, void *buf, size_t *bufSize);
+int My_sysctlbyname_Get_UInt32(const char *name, UInt32 *val);
 #endif
 
 EXTERN_C_END
diff --git a/third_party/lzma_sdk/Delta.c b/third_party/lzma_sdk/Delta.c
index e3edd21e..c4a4499 100644
--- a/third_party/lzma_sdk/Delta.c
+++ b/third_party/lzma_sdk/Delta.c
@@ -1,5 +1,5 @@
 /* Delta.c -- Delta converter
-2009-05-26 : Igor Pavlov : Public domain */
+2021-02-09 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -12,53 +12,158 @@
     state[i] = 0;
 }
 
-static void MyMemCpy(Byte *dest, const Byte *src, unsigned size)
-{
-  unsigned i;
-  for (i = 0; i < size; i++)
-    dest[i] = src[i];
-}
 
 void Delta_Encode(Byte *state, unsigned delta, Byte *data, SizeT size)
 {
-  Byte buf[DELTA_STATE_SIZE];
-  unsigned j = 0;
-  MyMemCpy(buf, state, delta);
+  Byte temp[DELTA_STATE_SIZE];
+
+  if (size == 0)
+    return;
+
   {
-    SizeT i;
-    for (i = 0; i < size;)
+    unsigned i = 0;
+    do
+      temp[i] = state[i];
+    while (++i != delta);
+  }
+
+  if (size <= delta)
+  {
+    unsigned i = 0, k;
+    do
     {
-      for (j = 0; j < delta && i < size; i++, j++)
+      Byte b = *data;
+      *data++ = (Byte)(b - temp[i]);
+      temp[i] = b;
+    }
+    while (++i != size);
+    
+    k = 0;
+    
+    do
+    {
+      if (i == delta)
+        i = 0;
+      state[k] = temp[i++];
+    }
+    while (++k != delta);
+    
+    return;
+  }
+    
+  {
+    Byte *p = data + size - delta;
+    {
+      unsigned i = 0;
+      do
+        state[i] = *p++;
+      while (++i != delta);
+    }
+    {
+      const Byte *lim = data + delta;
+      ptrdiff_t dif = -(ptrdiff_t)delta;
+      
+      if (((ptrdiff_t)size + dif) & 1)
       {
-        Byte b = data[i];
-        data[i] = (Byte)(b - buf[j]);
-        buf[j] = b;
+        --p;  *p = (Byte)(*p - p[dif]);
       }
+
+      while (p != lim)
+      {
+        --p;  *p = (Byte)(*p - p[dif]);
+        --p;  *p = (Byte)(*p - p[dif]);
+      }
+      
+      dif = -dif;
+      
+      do
+      {
+        --p;  *p = (Byte)(*p - temp[--dif]);
+      }
+      while (dif != 0);
     }
   }
-  if (j == delta)
-    j = 0;
-  MyMemCpy(state, buf + j, delta - j);
-  MyMemCpy(state + delta - j, buf, j);
 }
 
+
 void Delta_Decode(Byte *state, unsigned delta, Byte *data, SizeT size)
 {
-  Byte buf[DELTA_STATE_SIZE];
-  unsigned j = 0;
-  MyMemCpy(buf, state, delta);
+  unsigned i;
+  const Byte *lim;
+
+  if (size == 0)
+    return;
+  
+  i = 0;
+  lim = data + size;
+  
+  if (size <= delta)
   {
-    SizeT i;
-    for (i = 0; i < size;)
+    do
+      *data = (Byte)(*data + state[i++]);
+    while (++data != lim);
+
+    for (; delta != i; state++, delta--)
+      *state = state[i];
+    data -= i;
+  }
+  else
+  {
+    /*
+    #define B(n) b ## n
+    #define I(n) Byte B(n) = state[n];
+    #define U(n) { B(n) = (Byte)((B(n)) + *data++); data[-1] = (B(n)); }
+    #define F(n) if (data != lim) { U(n) }
+
+    if (delta == 1)
     {
-      for (j = 0; j < delta && i < size; i++, j++)
+      I(0)
+      if ((lim - data) & 1) { U(0) }
+      while (data != lim) { U(0) U(0) }
+      data -= 1;
+    }
+    else if (delta == 2)
+    {
+      I(0) I(1)
+      lim -= 1; while (data < lim) { U(0) U(1) }
+      lim += 1; F(0)
+      data -= 2;
+    }
+    else if (delta == 3)
+    {
+      I(0) I(1) I(2)
+      lim -= 2; while (data < lim) { U(0) U(1) U(2) }
+      lim += 2; F(0) F(1)
+      data -= 3;
+    }
+    else if (delta == 4)
+    {
+      I(0) I(1) I(2) I(3)
+      lim -= 3; while (data < lim) { U(0) U(1) U(2) U(3) }
+      lim += 3; F(0) F(1) F(2)
+      data -= 4;
+    }
+    else
+    */
+    {
+      do
       {
-        buf[j] = data[i] = (Byte)(buf[j] + data[i]);
+        *data = (Byte)(*data + state[i++]);
+        data++;
+      }
+      while (i != delta);
+  
+      {
+        ptrdiff_t dif = -(ptrdiff_t)delta;
+        do
+          *data = (Byte)(*data + data[dif]);
+        while (++data != lim);
+        data += dif;
       }
     }
   }
-  if (j == delta)
-    j = 0;
-  MyMemCpy(state, buf + j, delta - j);
-  MyMemCpy(state + delta - j, buf, j);
+
+  do
+    *state++ = *data;
+  while (++data != lim);
 }
diff --git a/third_party/lzma_sdk/DllSecur.c b/third_party/lzma_sdk/DllSecur.c
index 5ea108a..d81508c 100644
--- a/third_party/lzma_sdk/DllSecur.c
+++ b/third_party/lzma_sdk/DllSecur.c
@@ -1,11 +1,11 @@
 /* DllSecur.c -- DLL loading security
-2018-02-21 : Igor Pavlov : Public domain */
+2021-12-25 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
 #ifdef _WIN32
 
-#include <windows.h>
+#include <Windows.h>
 
 #include "DllSecur.h"
 
@@ -33,17 +33,19 @@
 
 #endif
 
+// #define MY_CAST_FUNC  (void(*)())
+#define MY_CAST_FUNC  
+
 void My_SetDefaultDllDirectories()
 {
   #ifndef UNDER_CE
   
     OSVERSIONINFO vi;
     vi.dwOSVersionInfoSize = sizeof(vi);
-    GetVersionEx(&vi);
     if (!GetVersionEx(&vi) || vi.dwMajorVersion != 6 || vi.dwMinorVersion != 0)
     {
       Func_SetDefaultDllDirectories setDllDirs = (Func_SetDefaultDllDirectories)
-          GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "SetDefaultDllDirectories");
+          MY_CAST_FUNC GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "SetDefaultDllDirectories");
       if (setDllDirs)
         if (setDllDirs(MY_LOAD_LIBRARY_SEARCH_SYSTEM32 | MY_LOAD_LIBRARY_SEARCH_USER_DIRS))
           return;
@@ -66,7 +68,7 @@
     if (!GetVersionEx(&vi) || vi.dwMajorVersion != 6 || vi.dwMinorVersion != 0)
     {
       Func_SetDefaultDllDirectories setDllDirs = (Func_SetDefaultDllDirectories)
-          GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "SetDefaultDllDirectories");
+          MY_CAST_FUNC GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "SetDefaultDllDirectories");
       if (setDllDirs)
         if (setDllDirs(MY_LOAD_LIBRARY_SEARCH_SYSTEM32 | MY_LOAD_LIBRARY_SEARCH_USER_DIRS))
           return;
diff --git a/third_party/lzma_sdk/DllSecur.h b/third_party/lzma_sdk/DllSecur.h
index e2a049ad..64ff26c 100644
--- a/third_party/lzma_sdk/DllSecur.h
+++ b/third_party/lzma_sdk/DllSecur.h
@@ -10,8 +10,8 @@
 
 #ifdef _WIN32
 
-void My_SetDefaultDllDirectories();
-void LoadSecurityDlls();
+void My_SetDefaultDllDirectories(void);
+void LoadSecurityDlls(void);
 
 #endif
 
diff --git a/third_party/lzma_sdk/Executable/7za.exe b/third_party/lzma_sdk/Executable/7za.exe
index 2bdd57d2..9544a638 100755
--- a/third_party/lzma_sdk/Executable/7za.exe
+++ b/third_party/lzma_sdk/Executable/7za.exe
Binary files differ
diff --git a/third_party/lzma_sdk/LzFind.c b/third_party/lzma_sdk/LzFind.c
index df55e86..36f73309 100644
--- a/third_party/lzma_sdk/LzFind.c
+++ b/third_party/lzma_sdk/LzFind.c
@@ -1,20 +1,69 @@
 /* LzFind.c -- Match finder for LZ algorithms
-2018-07-08 : Igor Pavlov : Public domain */
+2021-11-29 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
 #include <string.h>
+// #include <stdio.h>
 
+#include "CpuArch.h"
 #include "LzFind.h"
 #include "LzHash.h"
 
-#define kEmptyHashValue 0
-#define kMaxValForNormalize ((UInt32)0xFFFFFFFF)
-#define kNormalizeStepMin (1 << 10) /* it must be power of 2 */
-#define kNormalizeMask (~(UInt32)(kNormalizeStepMin - 1))
-#define kMaxHistorySize ((UInt32)7 << 29)
+#define kBlockMoveAlign       (1 << 7)    // alignment for memmove()
+#define kBlockSizeAlign       (1 << 16)   // alignment for block allocation
+#define kBlockSizeReserveMin  (1 << 24)   // it's 1/256 from 4 GB dictinary
 
-#define kStartMaxLen 3
+#define kEmptyHashValue 0
+
+#define kMaxValForNormalize ((UInt32)0)
+// #define kMaxValForNormalize ((UInt32)(1 << 20) + 0xFFF) // for debug
+
+// #define kNormalizeAlign (1 << 7) // alignment for speculated accesses
+
+#define GET_AVAIL_BYTES(p) \
+  Inline_MatchFinder_GetNumAvailableBytes(p)
+
+
+// #define kFix5HashSize (kHash2Size + kHash3Size + kHash4Size)
+#define kFix5HashSize kFix4HashSize
+
+/*
+ HASH2_CALC:
+   if (hv) match, then cur[0] and cur[1] also match
+*/
+#define HASH2_CALC hv = GetUi16(cur);
+
+// (crc[0 ... 255] & 0xFF) provides one-to-one correspondence to [0 ... 255]
+
+/*
+ HASH3_CALC:
+   if (cur[0]) and (h2) match, then cur[1]            also match
+   if (cur[0]) and (hv) match, then cur[1] and cur[2] also match
+*/
+#define HASH3_CALC { \
+  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
+  h2 = temp & (kHash2Size - 1); \
+  hv = (temp ^ ((UInt32)cur[2] << 8)) & p->hashMask; }
+
+#define HASH4_CALC { \
+  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
+  h2 = temp & (kHash2Size - 1); \
+  temp ^= ((UInt32)cur[2] << 8); \
+  h3 = temp & (kHash3Size - 1); \
+  hv = (temp ^ (p->crc[cur[3]] << kLzHash_CrcShift_1)) & p->hashMask; }
+
+#define HASH5_CALC { \
+  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
+  h2 = temp & (kHash2Size - 1); \
+  temp ^= ((UInt32)cur[2] << 8); \
+  h3 = temp & (kHash3Size - 1); \
+  temp ^= (p->crc[cur[3]] << kLzHash_CrcShift_1); \
+  /* h4 = temp & p->hash4Mask; */ /* (kHash4Size - 1); */ \
+  hv = (temp ^ (p->crc[cur[4]] << kLzHash_CrcShift_2)) & p->hashMask; }
+
+#define HASH_ZIP_CALC hv = ((cur[2] | ((UInt32)cur[0] << 8)) ^ p->crc[cur[1]]) & 0xFFFF;
+
 
 static void LzInWindow_Free(CMatchFinder *p, ISzAllocPtr alloc)
 {
@@ -25,46 +74,57 @@
   }
 }
 
-/* keepSizeBefore + keepSizeAfter + keepSizeReserv must be < 4G) */
 
-static int LzInWindow_Create(CMatchFinder *p, UInt32 keepSizeReserv, ISzAllocPtr alloc)
+static int LzInWindow_Create2(CMatchFinder *p, UInt32 blockSize, ISzAllocPtr alloc)
 {
-  UInt32 blockSize = p->keepSizeBefore + p->keepSizeAfter + keepSizeReserv;
-  if (p->directInput)
-  {
-    p->blockSize = blockSize;
-    return 1;
-  }
+  if (blockSize == 0)
+    return 0;
   if (!p->bufferBase || p->blockSize != blockSize)
   {
+    // size_t blockSizeT;
     LzInWindow_Free(p, alloc);
     p->blockSize = blockSize;
-    p->bufferBase = (Byte *)ISzAlloc_Alloc(alloc, (size_t)blockSize);
+    // blockSizeT = blockSize;
+    
+    // printf("\nblockSize = 0x%x\n", blockSize);
+    /*
+    #if defined _WIN64
+    // we can allocate 4GiB, but still use UInt32 for (p->blockSize)
+    // we use UInt32 type for (p->blockSize), because
+    // we don't want to wrap over 4 GiB,
+    // when we use (p->streamPos - p->pos) that is UInt32.
+    if (blockSize >= (UInt32)0 - (UInt32)kBlockSizeAlign)
+    {
+      blockSizeT = ((size_t)1 << 32);
+      printf("\nchanged to blockSizeT = 4GiB\n");
+    }
+    #endif
+    */
+    
+    p->bufferBase = (Byte *)ISzAlloc_Alloc(alloc, blockSize);
+    // printf("\nbufferBase = %p\n", p->bufferBase);
+    // return 0; // for debug
   }
   return (p->bufferBase != NULL);
 }
 
-Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p) { return p->buffer; }
+static const Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p) { return p->buffer; }
 
-UInt32 MatchFinder_GetNumAvailableBytes(CMatchFinder *p) { return p->streamPos - p->pos; }
+static UInt32 MatchFinder_GetNumAvailableBytes(CMatchFinder *p) { return GET_AVAIL_BYTES(p); }
 
-void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue)
-{
-  p->posLimit -= subValue;
-  p->pos -= subValue;
-  p->streamPos -= subValue;
-}
 
+MY_NO_INLINE
 static void MatchFinder_ReadBlock(CMatchFinder *p)
 {
   if (p->streamEndWasReached || p->result != SZ_OK)
     return;
 
-  /* We use (p->streamPos - p->pos) value. (p->streamPos < p->pos) is allowed. */
+  /* We use (p->streamPos - p->pos) value.
+     (p->streamPos < p->pos) is allowed. */
 
   if (p->directInput)
   {
-    UInt32 curSize = 0xFFFFFFFF - (p->streamPos - p->pos);
+    UInt32 curSize = 0xFFFFFFFF - GET_AVAIL_BYTES(p);
     if (curSize > p->directInputRem)
       curSize = (UInt32)p->directInputRem;
     p->directInputRem -= curSize;
@@ -76,10 +136,22 @@
   
   for (;;)
   {
-    Byte *dest = p->buffer + (p->streamPos - p->pos);
-    size_t size = (p->bufferBase + p->blockSize - dest);
+    Byte *dest = p->buffer + GET_AVAIL_BYTES(p);
+    size_t size = (size_t)(p->bufferBase + p->blockSize - dest);
     if (size == 0)
+    {
+      /* we call ReadBlock() after NeedMove() and MoveBlock().
+         NeedMove() and MoveBlock() povide more than (keepSizeAfter)
+         to the end of (blockSize).
+         So we don't execute this branch in normal code flow.
+         We can go here, if we will call ReadBlock() before NeedMove(), MoveBlock().
+      */
+      // p->result = SZ_ERROR_FAIL; // we can show error here
       return;
+    }
+
+    // #define kRead 3
+    // if (size > kRead) size = kRead; // for debug
 
     p->result = ISeqInStream_Read(p->stream, dest, &size);
     if (p->result != SZ_OK)
@@ -90,41 +162,52 @@
       return;
     }
     p->streamPos += (UInt32)size;
-    if (p->streamPos - p->pos > p->keepSizeAfter)
+    if (GET_AVAIL_BYTES(p) > p->keepSizeAfter)
       return;
+    /* here and in another (p->keepSizeAfter) checks we keep on 1 byte more than was requested by Create() function
+         (GET_AVAIL_BYTES(p) >= p->keepSizeAfter) - minimal required size */
   }
+
+  // on exit: (p->result != SZ_OK || p->streamEndWasReached || GET_AVAIL_BYTES(p) > p->keepSizeAfter)
 }
 
+
+
+MY_NO_INLINE
 void MatchFinder_MoveBlock(CMatchFinder *p)
 {
+  const size_t offset = (size_t)(p->buffer - p->bufferBase) - p->keepSizeBefore;
+  const size_t keepBefore = (offset & (kBlockMoveAlign - 1)) + p->keepSizeBefore;
+  p->buffer = p->bufferBase + keepBefore;
   memmove(p->bufferBase,
-      p->buffer - p->keepSizeBefore,
-      (size_t)(p->streamPos - p->pos) + p->keepSizeBefore);
-  p->buffer = p->bufferBase + p->keepSizeBefore;
+      p->bufferBase + (offset & ~((size_t)kBlockMoveAlign - 1)),
+      keepBefore + (size_t)GET_AVAIL_BYTES(p));
 }
 
+/* We call MoveBlock() before ReadBlock().
+   So MoveBlock() can be wasteful operation, if the whole input data
+   can fit in current block even without calling MoveBlock().
+   in important case where (dataSize <= historySize)
+     condition (p->blockSize > dataSize + p->keepSizeAfter) is met
+     So there is no MoveBlock() in that case case.
+*/
+
 int MatchFinder_NeedMove(CMatchFinder *p)
 {
   if (p->directInput)
     return 0;
-  /* if (p->streamEndWasReached) return 0; */
+  if (p->streamEndWasReached || p->result != SZ_OK)
+    return 0;
   return ((size_t)(p->bufferBase + p->blockSize - p->buffer) <= p->keepSizeAfter);
 }
 
 void MatchFinder_ReadIfRequired(CMatchFinder *p)
 {
-  if (p->streamEndWasReached)
-    return;
-  if (p->keepSizeAfter >= p->streamPos - p->pos)
+  if (p->keepSizeAfter >= GET_AVAIL_BYTES(p))
     MatchFinder_ReadBlock(p);
 }
 
-static void MatchFinder_CheckAndMoveAndRead(CMatchFinder *p)
-{
-  if (MatchFinder_NeedMove(p))
-    MatchFinder_MoveBlock(p);
-  MatchFinder_ReadBlock(p);
-}
+
 
 static void MatchFinder_SetDefaultSettings(CMatchFinder *p)
 {
@@ -175,39 +258,74 @@
   return (CLzRef *)ISzAlloc_Alloc(alloc, sizeInBytes);
 }
 
+#if (kBlockSizeReserveMin < kBlockSizeAlign * 2)
+  #error Stop_Compiling_Bad_Reserve
+#endif
+
+
+
+static UInt32 GetBlockSize(CMatchFinder *p, UInt32 historySize)
+{
+  UInt32 blockSize = (p->keepSizeBefore + p->keepSizeAfter);
+  /*
+  if (historySize > kMaxHistorySize)
+    return 0;
+  */
+  // printf("\nhistorySize == 0x%x\n", historySize);
+  
+  if (p->keepSizeBefore < historySize || blockSize < p->keepSizeBefore)  // if 32-bit overflow
+    return 0;
+  
+  {
+    const UInt32 kBlockSizeMax = (UInt32)0 - (UInt32)kBlockSizeAlign;
+    const UInt32 rem = kBlockSizeMax - blockSize;
+    const UInt32 reserve = (blockSize >> (blockSize < ((UInt32)1 << 30) ? 1 : 2))
+        + (1 << 12) + kBlockMoveAlign + kBlockSizeAlign; // do not overflow 32-bit here
+    if (blockSize >= kBlockSizeMax
+        || rem < kBlockSizeReserveMin) // we reject settings that will be slow
+      return 0;
+    if (reserve >= rem)
+      blockSize = kBlockSizeMax;
+    else
+    {
+      blockSize += reserve;
+      blockSize &= ~(UInt32)(kBlockSizeAlign - 1);
+    }
+  }
+  // printf("\n LzFind_blockSize = %x\n", blockSize);
+  // printf("\n LzFind_blockSize = %d\n", blockSize >> 20);
+  return blockSize;
+}
+
+
 int MatchFinder_Create(CMatchFinder *p, UInt32 historySize,
     UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter,
     ISzAllocPtr alloc)
 {
-  UInt32 sizeReserv;
-  
-  if (historySize > kMaxHistorySize)
-  {
-    MatchFinder_Free(p, alloc);
-    return 0;
-  }
-  
-  sizeReserv = historySize >> 1;
-       if (historySize >= ((UInt32)3 << 30)) sizeReserv = historySize >> 3;
-  else if (historySize >= ((UInt32)2 << 30)) sizeReserv = historySize >> 2;
-  
-  sizeReserv += (keepAddBufferBefore + matchMaxLen + keepAddBufferAfter) / 2 + (1 << 19);
-
+  /* we need one additional byte in (p->keepSizeBefore),
+     since we use MoveBlock() after (p->pos++) and before dictionary using */
+  // keepAddBufferBefore = (UInt32)0xFFFFFFFF - (1 << 22); // for debug
   p->keepSizeBefore = historySize + keepAddBufferBefore + 1;
-  p->keepSizeAfter = matchMaxLen + keepAddBufferAfter;
-  
-  /* we need one additional byte, since we use MoveBlock after pos++ and before dictionary using */
-  
-  if (LzInWindow_Create(p, sizeReserv, alloc))
+
+  keepAddBufferAfter += matchMaxLen;
+  /* we need (p->keepSizeAfter >= p->numHashBytes) */
+  if (keepAddBufferAfter < p->numHashBytes)
+    keepAddBufferAfter = p->numHashBytes;
+  // keepAddBufferAfter -= 2; // for debug
+  p->keepSizeAfter = keepAddBufferAfter;
+
+  if (p->directInput)
+    p->blockSize = 0;
+  if (p->directInput || LzInWindow_Create2(p, GetBlockSize(p, historySize), alloc))
   {
-    UInt32 newCyclicBufferSize = historySize + 1;
+    const UInt32 newCyclicBufferSize = historySize + 1; // do not change it
     UInt32 hs;
     p->matchMaxLen = matchMaxLen;
     {
+      // UInt32 hs4;
       p->fixedHashSize = 0;
-      if (p->numHashBytes == 2)
-        hs = (1 << 16) - 1;
-      else
+      hs = (1 << 16) - 1;
+      if (p->numHashBytes != 2)
       {
         hs = historySize;
         if (hs > p->expectedDataSize)
@@ -218,9 +336,9 @@
         hs |= (hs >> 2);
         hs |= (hs >> 4);
         hs |= (hs >> 8);
+        // we propagated 16 bits in (hs). Low 16 bits must be set later
         hs >>= 1;
-        hs |= 0xFFFF; /* don't change it! It's required for Deflate */
-        if (hs > (1 << 24))
+        if (hs >= (1 << 24))
         {
           if (p->numHashBytes == 3)
             hs = (1 << 24) - 1;
@@ -228,12 +346,30 @@
             hs >>= 1;
           /* if (bigHash) mode, GetHeads4b() in LzFindMt.c needs (hs >= ((1 << 24) - 1))) */
         }
+        
+        // hs = ((UInt32)1 << 25) - 1; // for test
+        
+        // (hash_size >= (1 << 16)) : Required for (numHashBytes > 2)
+        hs |= (1 << 16) - 1; /* don't change it! */
+        
+        // bt5: we adjust the size with recommended minimum size
+        if (p->numHashBytes >= 5)
+          hs |= (256 << kLzHash_CrcShift_2) - 1;
       }
       p->hashMask = hs;
       hs++;
+
+      /*
+      hs4 = (1 << 20);
+      if (hs4 > hs)
+        hs4 = hs;
+      // hs4 = (1 << 16); // for test
+      p->hash4Mask = hs4 - 1;
+      */
+
       if (p->numHashBytes > 2) p->fixedHashSize += kHash2Size;
       if (p->numHashBytes > 3) p->fixedHashSize += kHash3Size;
-      if (p->numHashBytes > 4) p->fixedHashSize += kHash4Size;
+      // if (p->numHashBytes > 4) p->fixedHashSize += hs4; // kHash4Size;
       hs += p->fixedHashSize;
     }
 
@@ -242,13 +378,17 @@
       size_t numSons;
       p->historySize = historySize;
       p->hashSizeSum = hs;
-      p->cyclicBufferSize = newCyclicBufferSize;
+      p->cyclicBufferSize = newCyclicBufferSize; // it must be = (historySize + 1)
       
       numSons = newCyclicBufferSize;
       if (p->btMode)
         numSons <<= 1;
       newSize = hs + numSons;
 
+      // aligned size is not required here, but it can be better for some loops
+      #define NUM_REFS_ALIGN_MASK 0xF
+      newSize = (newSize + NUM_REFS_ALIGN_MASK) & ~(size_t)NUM_REFS_ALIGN_MASK;
+
       if (p->hash && p->numRefs == newSize)
         return 1;
       
@@ -268,33 +408,43 @@
   return 0;
 }
 
+
 static void MatchFinder_SetLimits(CMatchFinder *p)
 {
-  UInt32 limit = kMaxValForNormalize - p->pos;
-  UInt32 limit2 = p->cyclicBufferSize - p->cyclicBufferPos;
+  UInt32 k;
+  UInt32 n = kMaxValForNormalize - p->pos;
+  if (n == 0)
+    n = (UInt32)(Int32)-1;  // we allow (pos == 0) at start even with (kMaxValForNormalize == 0)
   
-  if (limit2 < limit)
-    limit = limit2;
-  limit2 = p->streamPos - p->pos;
-  
-  if (limit2 <= p->keepSizeAfter)
+  k = p->cyclicBufferSize - p->cyclicBufferPos;
+  if (k < n)
+    n = k;
+
+  k = GET_AVAIL_BYTES(p);
   {
-    if (limit2 > 0)
-      limit2 = 1;
+    const UInt32 ksa = p->keepSizeAfter;
+    UInt32 mm = p->matchMaxLen;
+    if (k > ksa)
+      k -= ksa; // we must limit exactly to keepSizeAfter for ReadBlock
+    else if (k >= mm)
+    {
+      // the limitation for (p->lenLimit) update
+      k -= mm;   // optimization : to reduce the number of checks
+      k++;
+      // k = 1; // non-optimized version : for debug
+    }
+    else
+    {
+      mm = k;
+      if (k != 0)
+        k = 1;
+    }
+    p->lenLimit = mm;
   }
-  else
-    limit2 -= p->keepSizeAfter;
+  if (k < n)
+    n = k;
   
-  if (limit2 < limit)
-    limit = limit2;
-  
-  {
-    UInt32 lenLimit = p->streamPos - p->pos;
-    if (lenLimit > p->matchMaxLen)
-      lenLimit = p->matchMaxLen;
-    p->lenLimit = lenLimit;
-  }
-  p->posLimit = p->pos + limit;
+  p->posLimit = p->pos + n;
 }
 
 
@@ -302,7 +452,7 @@
 {
   size_t i;
   CLzRef *items = p->hash;
-  size_t numItems = p->fixedHashSize;
+  const size_t numItems = p->fixedHashSize;
   for (i = 0; i < numItems; i++)
     items[i] = kEmptyHashValue;
 }
@@ -312,72 +462,323 @@
 {
   size_t i;
   CLzRef *items = p->hash + p->fixedHashSize;
-  size_t numItems = (size_t)p->hashMask + 1;
+  const size_t numItems = (size_t)p->hashMask + 1;
   for (i = 0; i < numItems; i++)
     items[i] = kEmptyHashValue;
 }
 
 
-void MatchFinder_Init_3(CMatchFinder *p, int readData)
+void MatchFinder_Init_4(CMatchFinder *p)
 {
-  p->cyclicBufferPos = 0;
   p->buffer = p->bufferBase;
-  p->pos =
-  p->streamPos = p->cyclicBufferSize;
+  {
+    /* kEmptyHashValue = 0 (Zero) is used in hash tables as NO-VALUE marker.
+       the code in CMatchFinderMt expects (pos = 1) */
+    p->pos =
+    p->streamPos =
+        1; // it's smallest optimal value. do not change it
+        // 0; // for debug
+  }
   p->result = SZ_OK;
   p->streamEndWasReached = 0;
-  
-  if (readData)
-    MatchFinder_ReadBlock(p);
-  
-  MatchFinder_SetLimits(p);
 }
 
 
+// (CYC_TO_POS_OFFSET == 0) is expected by some optimized code
+#define CYC_TO_POS_OFFSET 0
+// #define CYC_TO_POS_OFFSET 1 // for debug
+
 void MatchFinder_Init(CMatchFinder *p)
 {
   MatchFinder_Init_HighHash(p);
   MatchFinder_Init_LowHash(p);
-  MatchFinder_Init_3(p, True);
+  MatchFinder_Init_4(p);
+  // if (readData)
+  MatchFinder_ReadBlock(p);
+
+  /* if we init (cyclicBufferPos = pos), then we can use one variable
+     instead of both (cyclicBufferPos) and (pos) : only before (cyclicBufferPos) wrapping */
+  p->cyclicBufferPos = (p->pos - CYC_TO_POS_OFFSET); // init with relation to (pos)
+  // p->cyclicBufferPos = 0; // smallest value
+  // p->son[0] = p->son[1] = 0; // unused: we can init skipped record for speculated accesses.
+  MatchFinder_SetLimits(p);
 }
 
-  
-static UInt32 MatchFinder_GetSubValue(CMatchFinder *p)
+
+#if 0
+#ifdef MY_CPU_X86_OR_AMD64
+  #if defined(__clang__) && (__clang_major__ >= 8) \
+    || defined(__GNUC__) && (__GNUC__ >= 8) \
+    || defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1900)
+      #define USE_SATUR_SUB_128
+      #define USE_AVX2
+      #define ATTRIB_SSE41 __attribute__((__target__("sse4.1")))
+      #define ATTRIB_AVX2 __attribute__((__target__("avx2")))
+  #elif defined(_MSC_VER)
+    #if (_MSC_VER >= 1600)
+      #define USE_SATUR_SUB_128
+      #if (_MSC_VER >= 1900)
+        #define USE_AVX2
+        #include <immintrin.h> // avx
+      #endif
+    #endif
+  #endif
+
+// #elif defined(MY_CPU_ARM_OR_ARM64)
+#elif defined(MY_CPU_ARM64)
+
+  #if defined(__clang__) && (__clang_major__ >= 8) \
+    || defined(__GNUC__) && (__GNUC__ >= 8)
+      #define USE_SATUR_SUB_128
+    #ifdef MY_CPU_ARM64
+      // #define ATTRIB_SSE41 __attribute__((__target__("")))
+    #else
+      // #define ATTRIB_SSE41 __attribute__((__target__("fpu=crypto-neon-fp-armv8")))
+    #endif
+
+  #elif defined(_MSC_VER)
+    #if (_MSC_VER >= 1910)
+      #define USE_SATUR_SUB_128
+    #endif
+  #endif
+
+  #if defined(_MSC_VER) && defined(MY_CPU_ARM64)
+    #include <arm64_neon.h>
+  #else
+    #include <arm_neon.h>
+  #endif
+
+#endif
+#endif
+
+/*
+#ifndef ATTRIB_SSE41
+  #define ATTRIB_SSE41
+#endif
+#ifndef ATTRIB_AVX2
+  #define ATTRIB_AVX2
+#endif
+*/
+
+#ifdef USE_SATUR_SUB_128
+
+// #define _SHOW_HW_STATUS
+
+#ifdef _SHOW_HW_STATUS
+#include <stdio.h>
+#define _PRF(x) x
+_PRF(;)
+#else
+#define _PRF(x)
+#endif
+
+#ifdef MY_CPU_ARM_OR_ARM64
+
+#ifdef MY_CPU_ARM64
+// #define FORCE_SATUR_SUB_128
+#endif
+
+typedef uint32x4_t v128;
+#define SASUB_128(i) \
+   *(v128 *)(void *)(items + (i) * 4) = \
+  vsubq_u32(vmaxq_u32(*(const v128 *)(const void *)(items + (i) * 4), sub2), sub2);
+
+#else
+
+#include <smmintrin.h> // sse4.1
+
+typedef __m128i v128;
+#define SASUB_128(i) \
+  *(v128 *)(void *)(items + (i) * 4) = \
+  _mm_sub_epi32(_mm_max_epu32(*(const v128 *)(const void *)(items + (i) * 4), sub2), sub2); // SSE 4.1
+
+#endif
+
+
+
+MY_NO_INLINE
+static
+#ifdef ATTRIB_SSE41
+ATTRIB_SSE41
+#endif
+void
+MY_FAST_CALL
+LzFind_SaturSub_128(UInt32 subValue, CLzRef *items, const CLzRef *lim)
 {
-  return (p->pos - p->historySize - 1) & kNormalizeMask;
+  v128 sub2 =
+    #ifdef MY_CPU_ARM_OR_ARM64
+      vdupq_n_u32(subValue);
+    #else
+      _mm_set_epi32((Int32)subValue, (Int32)subValue, (Int32)subValue, (Int32)subValue);
+    #endif
+  do
+  {
+    SASUB_128(0)
+    SASUB_128(1)
+    SASUB_128(2)
+    SASUB_128(3)
+    items += 4 * 4;
+  }
+  while (items != lim);
 }
 
+
+
+#ifdef USE_AVX2
+
+#include <immintrin.h> // avx
+
+#define SASUB_256(i) *(__m256i *)(void *)(items + (i) * 8) = _mm256_sub_epi32(_mm256_max_epu32(*(const __m256i *)(const void *)(items + (i) * 8), sub2), sub2); // AVX2
+
+MY_NO_INLINE
+static
+#ifdef ATTRIB_AVX2
+ATTRIB_AVX2
+#endif
+void
+MY_FAST_CALL
+LzFind_SaturSub_256(UInt32 subValue, CLzRef *items, const CLzRef *lim)
+{
+  __m256i sub2 = _mm256_set_epi32(
+      (Int32)subValue, (Int32)subValue, (Int32)subValue, (Int32)subValue,
+      (Int32)subValue, (Int32)subValue, (Int32)subValue, (Int32)subValue);
+  do
+  {
+    SASUB_256(0)
+    SASUB_256(1)
+    items += 2 * 8;
+  }
+  while (items != lim);
+}
+#endif // USE_AVX2
+
+#ifndef FORCE_SATUR_SUB_128
+typedef void (MY_FAST_CALL *LZFIND_SATUR_SUB_CODE_FUNC)(
+    UInt32 subValue, CLzRef *items, const CLzRef *lim);
+static LZFIND_SATUR_SUB_CODE_FUNC g_LzFind_SaturSub;
+#endif // FORCE_SATUR_SUB_128
+
+#endif // USE_SATUR_SUB_128
+
+
+// kEmptyHashValue must be zero
+// #define SASUB_32(i) v = items[i];  m = v - subValue;  if (v < subValue) m = kEmptyHashValue;  items[i] = m;
+#define SASUB_32(i) v = items[i];  if (v < subValue) v = subValue; items[i] = v - subValue;
+
+#ifdef FORCE_SATUR_SUB_128
+
+#define DEFAULT_SaturSub LzFind_SaturSub_128
+
+#else
+
+#define DEFAULT_SaturSub LzFind_SaturSub_32
+
+MY_NO_INLINE
+static
+void
+MY_FAST_CALL
+LzFind_SaturSub_32(UInt32 subValue, CLzRef *items, const CLzRef *lim)
+{
+  do
+  {
+    UInt32 v;
+    SASUB_32(0)
+    SASUB_32(1)
+    SASUB_32(2)
+    SASUB_32(3)
+    SASUB_32(4)
+    SASUB_32(5)
+    SASUB_32(6)
+    SASUB_32(7)
+    items += 8;
+  }
+  while (items != lim);
+}
+
+#endif
+
+
+MY_NO_INLINE
 void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, size_t numItems)
 {
-  size_t i;
-  for (i = 0; i < numItems; i++)
+  #define K_NORM_ALIGN_BLOCK_SIZE (1 << 6)
+  
+  CLzRef *lim;
+
+  for (; numItems != 0 && ((unsigned)(ptrdiff_t)items & (K_NORM_ALIGN_BLOCK_SIZE - 1)) != 0; numItems--)
   {
-    UInt32 value = items[i];
-    if (value <= subValue)
-      value = kEmptyHashValue;
-    else
-      value -= subValue;
-    items[i] = value;
+    UInt32 v;
+    SASUB_32(0);
+    items++;
+  }
+
+  {
+    #define K_NORM_ALIGN_MASK (K_NORM_ALIGN_BLOCK_SIZE / 4 - 1)
+    lim = items + (numItems & ~(size_t)K_NORM_ALIGN_MASK);
+    numItems &= K_NORM_ALIGN_MASK;
+    if (items != lim)
+    {
+      #if defined(USE_SATUR_SUB_128) && !defined(FORCE_SATUR_SUB_128)
+        if (g_LzFind_SaturSub)
+          g_LzFind_SaturSub(subValue, items, lim);
+        else
+      #endif
+          DEFAULT_SaturSub(subValue, items, lim);
+    }
+    items = lim;
+  }
+
+
+  for (; numItems != 0; numItems--)
+  {
+    UInt32 v;
+    SASUB_32(0);
+    items++;
   }
 }
 
-static void MatchFinder_Normalize(CMatchFinder *p)
-{
-  UInt32 subValue = MatchFinder_GetSubValue(p);
-  MatchFinder_Normalize3(subValue, p->hash, p->numRefs);
-  MatchFinder_ReduceOffsets(p, subValue);
-}
 
 
+// call MatchFinder_CheckLimits() only after (p->pos++) update
+
 MY_NO_INLINE
 static void MatchFinder_CheckLimits(CMatchFinder *p)
 {
+  if (// !p->streamEndWasReached && p->result == SZ_OK &&
+      p->keepSizeAfter == GET_AVAIL_BYTES(p))
+  {
+    // we try to read only in exact state (p->keepSizeAfter == GET_AVAIL_BYTES(p))
+    if (MatchFinder_NeedMove(p))
+      MatchFinder_MoveBlock(p);
+    MatchFinder_ReadBlock(p);
+  }
+
   if (p->pos == kMaxValForNormalize)
-    MatchFinder_Normalize(p);
-  if (!p->streamEndWasReached && p->keepSizeAfter == p->streamPos - p->pos)
-    MatchFinder_CheckAndMoveAndRead(p);
+  if (GET_AVAIL_BYTES(p) >= p->numHashBytes) // optional optimization for last bytes of data.
+    /*
+       if we disable normalization for last bytes of data, and
+       if (data_size == 4 GiB), we don't call wastfull normalization,
+       but (pos) will be wrapped over Zero (0) in that case.
+       And we cannot resume later to normal operation
+    */
+  {
+    // MatchFinder_Normalize(p);
+    /* after normalization we need (p->pos >= p->historySize + 1); */
+    /* we can reduce subValue to aligned value, if want to keep alignment
+       of (p->pos) and (p->buffer) for speculated accesses. */
+    const UInt32 subValue = (p->pos - p->historySize - 1) /* & ~(UInt32)(kNormalizeAlign - 1) */;
+    // const UInt32 subValue = (1 << 15); // for debug
+    // printf("\nMatchFinder_Normalize() subValue == 0x%x\n", subValue);
+    size_t numSonRefs = p->cyclicBufferSize;
+    if (p->btMode)
+      numSonRefs <<= 1;
+    Inline_MatchFinder_ReduceOffsets(p, subValue);
+    MatchFinder_Normalize3(subValue, p->hash, (size_t)p->hashSizeSum + numSonRefs);
+  }
+
   if (p->cyclicBufferPos == p->cyclicBufferSize)
     p->cyclicBufferPos = 0;
+  
   MatchFinder_SetLimits(p);
 }
 
@@ -386,9 +787,9 @@
   (lenLimit > maxLen)
 */
 MY_FORCE_INLINE
-static UInt32 * Hc_GetMatchesSpec(unsigned lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
-    UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue,
-    UInt32 *distances, unsigned maxLen)
+static UInt32 * Hc_GetMatchesSpec(size_t lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
+    size_t _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue,
+    UInt32 *d, unsigned maxLen)
 {
   /*
   son[_cyclicBufferPos] = curMatch;
@@ -396,7 +797,7 @@
   {
     UInt32 delta = pos - curMatch;
     if (cutValue-- == 0 || delta >= _cyclicBufferSize)
-      return distances;
+      return d;
     {
       const Byte *pb = cur - delta;
       curMatch = son[_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)];
@@ -409,10 +810,10 @@
         if (maxLen < len)
         {
           maxLen = len;
-          *distances++ = len;
-          *distances++ = delta - 1;
+          *d++ = len;
+          *d++ = delta - 1;
           if (len == lenLimit)
-            return distances;
+            return d;
         }
       }
     }
@@ -421,35 +822,41 @@
 
   const Byte *lim = cur + lenLimit;
   son[_cyclicBufferPos] = curMatch;
+
   do
   {
-    UInt32 delta = pos - curMatch;
+    UInt32 delta;
+
+    if (curMatch == 0)
+      break;
+    // if (curMatch2 >= curMatch) return NULL;
+    delta = pos - curMatch;
     if (delta >= _cyclicBufferSize)
       break;
     {
       ptrdiff_t diff;
       curMatch = son[_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)];
-      diff = (ptrdiff_t)0 - delta;
-      if (cur[maxLen] == cur[maxLen + diff])
+      diff = (ptrdiff_t)0 - (ptrdiff_t)delta;
+      if (cur[maxLen] == cur[(ptrdiff_t)maxLen + diff])
       {
         const Byte *c = cur;
         while (*c == c[diff])
         {
           if (++c == lim)
           {
-            distances[0] = (UInt32)(lim - cur);
-            distances[1] = delta - 1;
-            return distances + 2;
+            d[0] = (UInt32)(lim - cur);
+            d[1] = delta - 1;
+            return d + 2;
           }
         }
         {
-          unsigned len = (unsigned)(c - cur);
+          const unsigned len = (unsigned)(c - cur);
           if (maxLen < len)
           {
             maxLen = len;
-            distances[0] = (UInt32)len;
-            distances[1] = delta - 1;
-            distances += 2;
+            d[0] = (UInt32)len;
+            d[1] = delta - 1;
+            d += 2;
           }
         }
       }
@@ -457,31 +864,36 @@
   }
   while (--cutValue);
   
-  return distances;
+  return d;
 }
 
 
 MY_FORCE_INLINE
 UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
-    UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue,
-    UInt32 *distances, UInt32 maxLen)
+    size_t _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue,
+    UInt32 *d, UInt32 maxLen)
 {
   CLzRef *ptr0 = son + ((size_t)_cyclicBufferPos << 1) + 1;
   CLzRef *ptr1 = son + ((size_t)_cyclicBufferPos << 1);
   unsigned len0 = 0, len1 = 0;
-  for (;;)
+
+  UInt32 cmCheck;
+
+  // if (curMatch >= pos) { *ptr0 = *ptr1 = kEmptyHashValue; return NULL; }
+
+  cmCheck = (UInt32)(pos - _cyclicBufferSize);
+  if ((UInt32)pos <= _cyclicBufferSize)
+    cmCheck = 0;
+
+  if (cmCheck < curMatch)
+  do
   {
-    UInt32 delta = pos - curMatch;
-    if (cutValue-- == 0 || delta >= _cyclicBufferSize)
-    {
-      *ptr0 = *ptr1 = kEmptyHashValue;
-      return distances;
-    }
+    const UInt32 delta = pos - curMatch;
     {
       CLzRef *pair = son + ((size_t)(_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1);
       const Byte *pb = cur - delta;
       unsigned len = (len0 < len1 ? len0 : len1);
-      UInt32 pair0 = pair[0];
+      const UInt32 pair0 = pair[0];
       if (pb[len] == cur[len])
       {
         if (++len != lenLimit && pb[len] == cur[len])
@@ -491,48 +903,60 @@
         if (maxLen < len)
         {
           maxLen = (UInt32)len;
-          *distances++ = (UInt32)len;
-          *distances++ = delta - 1;
+          *d++ = (UInt32)len;
+          *d++ = delta - 1;
           if (len == lenLimit)
           {
             *ptr1 = pair0;
             *ptr0 = pair[1];
-            return distances;
+            return d;
           }
         }
       }
       if (pb[len] < cur[len])
       {
         *ptr1 = curMatch;
+        // const UInt32 curMatch2 = pair[1];
+        // if (curMatch2 >= curMatch) { *ptr0 = *ptr1 = kEmptyHashValue;  return NULL; }
+        // curMatch = curMatch2;
+        curMatch = pair[1];
         ptr1 = pair + 1;
-        curMatch = *ptr1;
         len1 = len;
       }
       else
       {
         *ptr0 = curMatch;
+        curMatch = pair[0];
         ptr0 = pair;
-        curMatch = *ptr0;
         len0 = len;
       }
     }
   }
+  while(--cutValue && cmCheck < curMatch);
+
+  *ptr0 = *ptr1 = kEmptyHashValue;
+  return d;
 }
 
+
 static void SkipMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son,
-    UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue)
+    size_t _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue)
 {
   CLzRef *ptr0 = son + ((size_t)_cyclicBufferPos << 1) + 1;
   CLzRef *ptr1 = son + ((size_t)_cyclicBufferPos << 1);
   unsigned len0 = 0, len1 = 0;
-  for (;;)
+
+  UInt32 cmCheck;
+
+  cmCheck = (UInt32)(pos - _cyclicBufferSize);
+  if ((UInt32)pos <= _cyclicBufferSize)
+    cmCheck = 0;
+
+  if (// curMatch >= pos ||  // failure
+      cmCheck < curMatch)
+  do
   {
-    UInt32 delta = pos - curMatch;
-    if (cutValue-- == 0 || delta >= _cyclicBufferSize)
-    {
-      *ptr0 = *ptr1 = kEmptyHashValue;
-      return;
-    }
+    const UInt32 delta = pos - curMatch;
     {
       CLzRef *pair = son + ((size_t)(_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1);
       const Byte *pb = cur - delta;
@@ -554,80 +978,108 @@
       if (pb[len] < cur[len])
       {
         *ptr1 = curMatch;
+        curMatch = pair[1];
         ptr1 = pair + 1;
-        curMatch = *ptr1;
         len1 = len;
       }
       else
       {
         *ptr0 = curMatch;
+        curMatch = pair[0];
         ptr0 = pair;
-        curMatch = *ptr0;
         len0 = len;
       }
     }
   }
+  while(--cutValue && cmCheck < curMatch);
+  
+  *ptr0 = *ptr1 = kEmptyHashValue;
+  return;
 }
 
+
 #define MOVE_POS \
   ++p->cyclicBufferPos; \
   p->buffer++; \
-  if (++p->pos == p->posLimit) MatchFinder_CheckLimits(p);
+  { const UInt32 pos1 = p->pos + 1; p->pos = pos1; if (pos1 == p->posLimit) MatchFinder_CheckLimits(p); }
 
-#define MOVE_POS_RET MOVE_POS return (UInt32)offset;
+#define MOVE_POS_RET MOVE_POS return distances;
 
-static void MatchFinder_MovePos(CMatchFinder *p) { MOVE_POS; }
+MY_NO_INLINE
+static void MatchFinder_MovePos(CMatchFinder *p)
+{
+  /* we go here at the end of stream data, when (avail < num_hash_bytes)
+     We don't update sons[cyclicBufferPos << btMode].
+     So (sons) record will contain junk. And we cannot resume match searching
+     to normal operation, even if we will provide more input data in buffer.
+     p->sons[p->cyclicBufferPos << p->btMode] = 0;  // kEmptyHashValue
+     if (p->btMode)
+        p->sons[(p->cyclicBufferPos << p->btMode) + 1] = 0;  // kEmptyHashValue
+  */
+  MOVE_POS;
+}
 
 #define GET_MATCHES_HEADER2(minLen, ret_op) \
-  unsigned lenLimit; UInt32 hv; const Byte *cur; UInt32 curMatch; \
+  unsigned lenLimit; UInt32 hv; Byte *cur; UInt32 curMatch; \
   lenLimit = (unsigned)p->lenLimit; { if (lenLimit < minLen) { MatchFinder_MovePos(p); ret_op; }} \
   cur = p->buffer;
 
-#define GET_MATCHES_HEADER(minLen) GET_MATCHES_HEADER2(minLen, return 0)
-#define SKIP_HEADER(minLen)        GET_MATCHES_HEADER2(minLen, continue)
+#define GET_MATCHES_HEADER(minLen) GET_MATCHES_HEADER2(minLen, return distances)
+#define SKIP_HEADER(minLen)   do { GET_MATCHES_HEADER2(minLen, continue)
 
-#define MF_PARAMS(p) p->pos, p->buffer, p->son, p->cyclicBufferPos, p->cyclicBufferSize, p->cutValue
+#define MF_PARAMS(p)  lenLimit, curMatch, p->pos, p->buffer, p->son, p->cyclicBufferPos, p->cyclicBufferSize, p->cutValue
 
-#define GET_MATCHES_FOOTER(offset, maxLen) \
-  offset = (unsigned)(GetMatchesSpec1((UInt32)lenLimit, curMatch, MF_PARAMS(p), \
-  distances + offset, (UInt32)maxLen) - distances); MOVE_POS_RET;
+#define SKIP_FOOTER  SkipMatchesSpec(MF_PARAMS(p)); MOVE_POS; } while (--num);
 
-#define SKIP_FOOTER \
-  SkipMatchesSpec((UInt32)lenLimit, curMatch, MF_PARAMS(p)); MOVE_POS;
+#define GET_MATCHES_FOOTER_BASE(_maxLen_, func) \
+  distances = func(MF_PARAMS(p), \
+  distances, (UInt32)_maxLen_); MOVE_POS_RET;
+
+#define GET_MATCHES_FOOTER_BT(_maxLen_) \
+  GET_MATCHES_FOOTER_BASE(_maxLen_, GetMatchesSpec1)
+
+#define GET_MATCHES_FOOTER_HC(_maxLen_) \
+  GET_MATCHES_FOOTER_BASE(_maxLen_, Hc_GetMatchesSpec)
+
+
 
 #define UPDATE_maxLen { \
-    ptrdiff_t diff = (ptrdiff_t)0 - d2; \
+    const ptrdiff_t diff = (ptrdiff_t)0 - (ptrdiff_t)d2; \
     const Byte *c = cur + maxLen; \
     const Byte *lim = cur + lenLimit; \
     for (; c != lim; c++) if (*(c + diff) != *c) break; \
     maxLen = (unsigned)(c - cur); }
 
-static UInt32 Bt2_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+static UInt32* Bt2_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
 {
-  unsigned offset;
   GET_MATCHES_HEADER(2)
   HASH2_CALC;
   curMatch = p->hash[hv];
   p->hash[hv] = p->pos;
-  offset = 0;
-  GET_MATCHES_FOOTER(offset, 1)
+  GET_MATCHES_FOOTER_BT(1)
 }
 
-UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+UInt32* Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
 {
-  unsigned offset;
   GET_MATCHES_HEADER(3)
   HASH_ZIP_CALC;
   curMatch = p->hash[hv];
   p->hash[hv] = p->pos;
-  offset = 0;
-  GET_MATCHES_FOOTER(offset, 2)
+  GET_MATCHES_FOOTER_BT(2)
 }
 
-static UInt32 Bt3_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+
+#define SET_mmm  \
+  mmm = p->cyclicBufferSize; \
+  if (pos < mmm) \
+    mmm = pos;
+
+
+static UInt32* Bt3_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
 {
+  UInt32 mmm;
   UInt32 h2, d2, pos;
-  unsigned maxLen, offset;
+  unsigned maxLen;
   UInt32 *hash;
   GET_MATCHES_HEADER(3)
 
@@ -643,29 +1095,32 @@
   hash[h2] = pos;
   (hash + kFix3HashSize)[hv] = pos;
 
-  maxLen = 2;
-  offset = 0;
+  SET_mmm
 
-  if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur)
+  maxLen = 2;
+
+  if (d2 < mmm && *(cur - d2) == *cur)
   {
     UPDATE_maxLen
     distances[0] = (UInt32)maxLen;
     distances[1] = d2 - 1;
-    offset = 2;
+    distances += 2;
     if (maxLen == lenLimit)
     {
-      SkipMatchesSpec((UInt32)lenLimit, curMatch, MF_PARAMS(p));
+      SkipMatchesSpec(MF_PARAMS(p));
       MOVE_POS_RET;
     }
   }
   
-  GET_MATCHES_FOOTER(offset, maxLen)
+  GET_MATCHES_FOOTER_BT(maxLen)
 }
 
-static UInt32 Bt4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+
+static UInt32* Bt4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
 {
+  UInt32 mmm;
   UInt32 h2, h3, d2, d3, pos;
-  unsigned maxLen, offset;
+  unsigned maxLen;
   UInt32 *hash;
   GET_MATCHES_HEADER(4)
 
@@ -676,53 +1131,63 @@
 
   d2 = pos - hash                  [h2];
   d3 = pos - (hash + kFix3HashSize)[h3];
-
   curMatch = (hash + kFix4HashSize)[hv];
 
   hash                  [h2] = pos;
   (hash + kFix3HashSize)[h3] = pos;
   (hash + kFix4HashSize)[hv] = pos;
 
-  maxLen = 0;
-  offset = 0;
+  SET_mmm
+
+  maxLen = 3;
   
-  if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur)
+  for (;;)
   {
-    maxLen = 2;
-    distances[0] = 2;
-    distances[1] = d2 - 1;
-    offset = 2;
-  }
+    if (d2 < mmm && *(cur - d2) == *cur)
+    {
+      distances[0] = 2;
+      distances[1] = d2 - 1;
+      distances += 2;
+      if (*(cur - d2 + 2) == cur[2])
+      {
+        // distances[-2] = 3;
+      }
+      else if (d3 < mmm && *(cur - d3) == *cur)
+      {
+        d2 = d3;
+        distances[1] = d3 - 1;
+        distances += 2;
+      }
+      else
+        break;
+    }
+    else if (d3 < mmm && *(cur - d3) == *cur)
+    {
+      d2 = d3;
+      distances[1] = d3 - 1;
+      distances += 2;
+    }
+    else
+      break;
   
-  if (d2 != d3 && d3 < p->cyclicBufferSize && *(cur - d3) == *cur)
-  {
-    maxLen = 3;
-    distances[(size_t)offset + 1] = d3 - 1;
-    offset += 2;
-    d2 = d3;
-  }
-  
-  if (offset != 0)
-  {
     UPDATE_maxLen
-    distances[(size_t)offset - 2] = (UInt32)maxLen;
+    distances[-2] = (UInt32)maxLen;
     if (maxLen == lenLimit)
     {
-      SkipMatchesSpec((UInt32)lenLimit, curMatch, MF_PARAMS(p));
-      MOVE_POS_RET;
+      SkipMatchesSpec(MF_PARAMS(p));
+      MOVE_POS_RET
     }
+    break;
   }
   
-  if (maxLen < 3)
-    maxLen = 3;
-  
-  GET_MATCHES_FOOTER(offset, maxLen)
+  GET_MATCHES_FOOTER_BT(maxLen)
 }
 
-/*
-static UInt32 Bt5_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+
+static UInt32* Bt5_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
 {
-  UInt32 h2, h3, h4, d2, d3, d4, maxLen, offset, pos;
+  UInt32 mmm;
+  UInt32 h2, h3, d2, d3, maxLen, pos;
   UInt32 *hash;
   GET_MATCHES_HEADER(5)
 
@@ -733,73 +1198,69 @@
 
   d2 = pos - hash                  [h2];
   d3 = pos - (hash + kFix3HashSize)[h3];
-  d4 = pos - (hash + kFix4HashSize)[h4];
+  // d4 = pos - (hash + kFix4HashSize)[h4];
 
   curMatch = (hash + kFix5HashSize)[hv];
 
   hash                  [h2] = pos;
   (hash + kFix3HashSize)[h3] = pos;
-  (hash + kFix4HashSize)[h4] = pos;
+  // (hash + kFix4HashSize)[h4] = pos;
   (hash + kFix5HashSize)[hv] = pos;
 
-  maxLen = 0;
-  offset = 0;
+  SET_mmm
 
-  if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur)
+  maxLen = 4;
+
+  for (;;)
   {
-    distances[0] = maxLen = 2;
-    distances[1] = d2 - 1;
-    offset = 2;
-    if (*(cur - d2 + 2) == cur[2])
-      distances[0] = maxLen = 3;
-    else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur)
+    if (d2 < mmm && *(cur - d2) == *cur)
     {
-      distances[2] = maxLen = 3;
-      distances[3] = d3 - 1;
-      offset = 4;
+      distances[0] = 2;
+      distances[1] = d2 - 1;
+      distances += 2;
+      if (*(cur - d2 + 2) == cur[2])
+      {
+      }
+      else if (d3 < mmm && *(cur - d3) == *cur)
+      {
+        distances[1] = d3 - 1;
+        distances += 2;
+        d2 = d3;
+      }
+      else
+        break;
+    }
+    else if (d3 < mmm && *(cur - d3) == *cur)
+    {
+      distances[1] = d3 - 1;
+      distances += 2;
       d2 = d3;
     }
-  }
-  else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur)
-  {
-    distances[0] = maxLen = 3;
-    distances[1] = d3 - 1;
-    offset = 2;
-    d2 = d3;
-  }
-  
-  if (d2 != d4 && d4 < p->cyclicBufferSize
-      && *(cur - d4) == *cur
-      && *(cur - d4 + 3) == *(cur + 3))
-  {
-    maxLen = 4;
-    distances[(size_t)offset + 1] = d4 - 1;
-    offset += 2;
-    d2 = d4;
-  }
-  
-  if (offset != 0)
-  {
+    else
+      break;
+
+    distances[-2] = 3;
+    if (*(cur - d2 + 3) != cur[3])
+      break;
     UPDATE_maxLen
-    distances[(size_t)offset - 2] = maxLen;
+    distances[-2] = (UInt32)maxLen;
     if (maxLen == lenLimit)
     {
-      SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p));
+      SkipMatchesSpec(MF_PARAMS(p));
       MOVE_POS_RET;
     }
+    break;
   }
-
-  if (maxLen < 4)
-    maxLen = 4;
   
-  GET_MATCHES_FOOTER(offset, maxLen)
+  GET_MATCHES_FOOTER_BT(maxLen)
 }
-*/
 
-static UInt32 Hc4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+
+static UInt32* Hc4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
 {
+  UInt32 mmm;
   UInt32 h2, h3, d2, d3, pos;
-  unsigned maxLen, offset;
+  unsigned maxLen;
   UInt32 *hash;
   GET_MATCHES_HEADER(4)
 
@@ -816,48 +1277,57 @@
   (hash + kFix3HashSize)[h3] = pos;
   (hash + kFix4HashSize)[hv] = pos;
 
-  maxLen = 0;
-  offset = 0;
+  SET_mmm
 
-  if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur)
+  maxLen = 3;
+
+  for (;;)
   {
-    maxLen = 2;
-    distances[0] = 2;
-    distances[1] = d2 - 1;
-    offset = 2;
-  }
-  
-  if (d2 != d3 && d3 < p->cyclicBufferSize && *(cur - d3) == *cur)
-  {
-    maxLen = 3;
-    distances[(size_t)offset + 1] = d3 - 1;
-    offset += 2;
-    d2 = d3;
-  }
-  
-  if (offset != 0)
-  {
+    if (d2 < mmm && *(cur - d2) == *cur)
+    {
+      distances[0] = 2;
+      distances[1] = d2 - 1;
+      distances += 2;
+      if (*(cur - d2 + 2) == cur[2])
+      {
+        // distances[-2] = 3;
+      }
+      else if (d3 < mmm && *(cur - d3) == *cur)
+      {
+        d2 = d3;
+        distances[1] = d3 - 1;
+        distances += 2;
+      }
+      else
+        break;
+    }
+    else if (d3 < mmm && *(cur - d3) == *cur)
+    {
+      d2 = d3;
+      distances[1] = d3 - 1;
+      distances += 2;
+    }
+    else
+      break;
+
     UPDATE_maxLen
-    distances[(size_t)offset - 2] = (UInt32)maxLen;
+    distances[-2] = (UInt32)maxLen;
     if (maxLen == lenLimit)
     {
       p->son[p->cyclicBufferPos] = curMatch;
       MOVE_POS_RET;
     }
+    break;
   }
   
-  if (maxLen < 3)
-    maxLen = 3;
-
-  offset = (unsigned)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p),
-      distances + offset, maxLen) - (distances));
-  MOVE_POS_RET
+  GET_MATCHES_FOOTER_HC(maxLen);
 }
 
-/*
-static UInt32 Hc5_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+
+static UInt32 * Hc5_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
 {
-  UInt32 h2, h3, h4, d2, d3, d4, maxLen, offset, pos
+  UInt32 mmm;
+  UInt32 h2, h3, d2, d3, maxLen, pos;
   UInt32 *hash;
   GET_MATCHES_HEADER(5)
 
@@ -865,242 +1335,237 @@
 
   hash = p->hash;
   pos = p->pos;
-  
+
   d2 = pos - hash                  [h2];
   d3 = pos - (hash + kFix3HashSize)[h3];
-  d4 = pos - (hash + kFix4HashSize)[h4];
+  // d4 = pos - (hash + kFix4HashSize)[h4];
 
   curMatch = (hash + kFix5HashSize)[hv];
 
   hash                  [h2] = pos;
   (hash + kFix3HashSize)[h3] = pos;
-  (hash + kFix4HashSize)[h4] = pos;
+  // (hash + kFix4HashSize)[h4] = pos;
   (hash + kFix5HashSize)[hv] = pos;
 
-  maxLen = 0;
-  offset = 0;
+  SET_mmm
+  
+  maxLen = 4;
 
-  if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur)
+  for (;;)
   {
-    distances[0] = maxLen = 2;
-    distances[1] = d2 - 1;
-    offset = 2;
-    if (*(cur - d2 + 2) == cur[2])
-      distances[0] = maxLen = 3;
-    else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur)
+    if (d2 < mmm && *(cur - d2) == *cur)
     {
-      distances[2] = maxLen = 3;
-      distances[3] = d3 - 1;
-      offset = 4;
+      distances[0] = 2;
+      distances[1] = d2 - 1;
+      distances += 2;
+      if (*(cur - d2 + 2) == cur[2])
+      {
+      }
+      else if (d3 < mmm && *(cur - d3) == *cur)
+      {
+        distances[1] = d3 - 1;
+        distances += 2;
+        d2 = d3;
+      }
+      else
+        break;
+    }
+    else if (d3 < mmm && *(cur - d3) == *cur)
+    {
+      distances[1] = d3 - 1;
+      distances += 2;
       d2 = d3;
     }
-  }
-  else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur)
-  {
-    distances[0] = maxLen = 3;
-    distances[1] = d3 - 1;
-    offset = 2;
-    d2 = d3;
-  }
-  
-  if (d2 != d4 && d4 < p->cyclicBufferSize
-      && *(cur - d4) == *cur
-      && *(cur - d4 + 3) == *(cur + 3))
-  {
-    maxLen = 4;
-    distances[(size_t)offset + 1] = d4 - 1;
-    offset += 2;
-    d2 = d4;
-  }
-  
-  if (offset != 0)
-  {
+    else
+      break;
+
+    distances[-2] = 3;
+    if (*(cur - d2 + 3) != cur[3])
+      break;
     UPDATE_maxLen
-    distances[(size_t)offset - 2] = maxLen;
+    distances[-2] = maxLen;
     if (maxLen == lenLimit)
     {
       p->son[p->cyclicBufferPos] = curMatch;
       MOVE_POS_RET;
     }
+    break;
   }
   
-  if (maxLen < 4)
-    maxLen = 4;
-
-  offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p),
-      distances + offset, maxLen) - (distances));
-  MOVE_POS_RET
+  GET_MATCHES_FOOTER_HC(maxLen);
 }
-*/
 
-UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
+
+UInt32* Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances)
 {
-  unsigned offset;
   GET_MATCHES_HEADER(3)
   HASH_ZIP_CALC;
   curMatch = p->hash[hv];
   p->hash[hv] = p->pos;
-  offset = (unsigned)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p),
-      distances, 2) - (distances));
-  MOVE_POS_RET
+  GET_MATCHES_FOOTER_HC(2)
 }
 
+
 static void Bt2_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
 {
-  do
+  SKIP_HEADER(2)
   {
-    SKIP_HEADER(2)
     HASH2_CALC;
     curMatch = p->hash[hv];
     p->hash[hv] = p->pos;
-    SKIP_FOOTER
   }
-  while (--num != 0);
+  SKIP_FOOTER
 }
 
 void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
 {
-  do
+  SKIP_HEADER(3)
   {
-    SKIP_HEADER(3)
     HASH_ZIP_CALC;
     curMatch = p->hash[hv];
     p->hash[hv] = p->pos;
-    SKIP_FOOTER
   }
-  while (--num != 0);
+  SKIP_FOOTER
 }
 
 static void Bt3_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
 {
-  do
+  SKIP_HEADER(3)
   {
     UInt32 h2;
     UInt32 *hash;
-    SKIP_HEADER(3)
     HASH3_CALC;
     hash = p->hash;
     curMatch = (hash + kFix3HashSize)[hv];
     hash[h2] =
     (hash + kFix3HashSize)[hv] = p->pos;
-    SKIP_FOOTER
   }
-  while (--num != 0);
+  SKIP_FOOTER
 }
 
 static void Bt4_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
 {
-  do
+  SKIP_HEADER(4)
   {
     UInt32 h2, h3;
     UInt32 *hash;
-    SKIP_HEADER(4)
     HASH4_CALC;
     hash = p->hash;
     curMatch = (hash + kFix4HashSize)[hv];
     hash                  [h2] =
     (hash + kFix3HashSize)[h3] =
     (hash + kFix4HashSize)[hv] = p->pos;
-    SKIP_FOOTER
   }
-  while (--num != 0);
+  SKIP_FOOTER
 }
 
-/*
 static void Bt5_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
 {
-  do
+  SKIP_HEADER(5)
   {
-    UInt32 h2, h3, h4;
+    UInt32 h2, h3;
     UInt32 *hash;
-    SKIP_HEADER(5)
     HASH5_CALC;
     hash = p->hash;
     curMatch = (hash + kFix5HashSize)[hv];
     hash                  [h2] =
     (hash + kFix3HashSize)[h3] =
-    (hash + kFix4HashSize)[h4] =
+    // (hash + kFix4HashSize)[h4] =
     (hash + kFix5HashSize)[hv] = p->pos;
-    SKIP_FOOTER
   }
-  while (--num != 0);
+  SKIP_FOOTER
 }
-*/
+
+
+#define HC_SKIP_HEADER(minLen) \
+    do { if (p->lenLimit < minLen) { MatchFinder_MovePos(p); num--; continue; } { \
+    Byte *cur; \
+    UInt32 *hash; \
+    UInt32 *son; \
+    UInt32 pos = p->pos; \
+    UInt32 num2 = num; \
+    /* (p->pos == p->posLimit) is not allowed here !!! */ \
+    { const UInt32 rem = p->posLimit - pos; if (num2 > rem) num2 = rem; } \
+    num -= num2; \
+    { const UInt32 cycPos = p->cyclicBufferPos; \
+      son = p->son + cycPos; \
+      p->cyclicBufferPos = cycPos + num2; } \
+    cur = p->buffer; \
+    hash = p->hash; \
+    do { \
+    UInt32 curMatch; \
+    UInt32 hv;
+
+
+#define HC_SKIP_FOOTER \
+    cur++;  pos++;  *son++ = curMatch; \
+    } while (--num2); \
+    p->buffer = cur; \
+    p->pos = pos; \
+    if (pos == p->posLimit) MatchFinder_CheckLimits(p); \
+    }} while(num); \
+
 
 static void Hc4_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
 {
-  do
-  {
+  HC_SKIP_HEADER(4)
+
     UInt32 h2, h3;
-    UInt32 *hash;
-    SKIP_HEADER(4)
     HASH4_CALC;
-    hash = p->hash;
     curMatch = (hash + kFix4HashSize)[hv];
     hash                  [h2] =
     (hash + kFix3HashSize)[h3] =
-    (hash + kFix4HashSize)[hv] = p->pos;
-    p->son[p->cyclicBufferPos] = curMatch;
-    MOVE_POS
-  }
-  while (--num != 0);
+    (hash + kFix4HashSize)[hv] = pos;
+  
+  HC_SKIP_FOOTER
 }
 
-/*
+
 static void Hc5_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
 {
-  do
-  {
-    UInt32 h2, h3, h4;
-    UInt32 *hash;
-    SKIP_HEADER(5)
-    HASH5_CALC;
-    hash = p->hash;
-    curMatch = hash + kFix5HashSize)[hv];
+  HC_SKIP_HEADER(5)
+  
+    UInt32 h2, h3;
+    HASH5_CALC
+    curMatch = (hash + kFix5HashSize)[hv];
     hash                  [h2] =
     (hash + kFix3HashSize)[h3] =
-    (hash + kFix4HashSize)[h4] =
-    (hash + kFix5HashSize)[hv] = p->pos;
-    p->son[p->cyclicBufferPos] = curMatch;
-    MOVE_POS
-  }
-  while (--num != 0);
+    // (hash + kFix4HashSize)[h4] =
+    (hash + kFix5HashSize)[hv] = pos;
+  
+  HC_SKIP_FOOTER
 }
-*/
+
 
 void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num)
 {
-  do
-  {
-    SKIP_HEADER(3)
+  HC_SKIP_HEADER(3)
+
     HASH_ZIP_CALC;
-    curMatch = p->hash[hv];
-    p->hash[hv] = p->pos;
-    p->son[p->cyclicBufferPos] = curMatch;
-    MOVE_POS
-  }
-  while (--num != 0);
+    curMatch = hash[hv];
+    hash[hv] = pos;
+
+  HC_SKIP_FOOTER
 }
 
-void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable)
+
+void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder2 *vTable)
 {
   vTable->Init = (Mf_Init_Func)MatchFinder_Init;
   vTable->GetNumAvailableBytes = (Mf_GetNumAvailableBytes_Func)MatchFinder_GetNumAvailableBytes;
   vTable->GetPointerToCurrentPos = (Mf_GetPointerToCurrentPos_Func)MatchFinder_GetPointerToCurrentPos;
   if (!p->btMode)
   {
-    /* if (p->numHashBytes <= 4) */
+    if (p->numHashBytes <= 4)
     {
       vTable->GetMatches = (Mf_GetMatches_Func)Hc4_MatchFinder_GetMatches;
       vTable->Skip = (Mf_Skip_Func)Hc4_MatchFinder_Skip;
     }
-    /*
     else
     {
       vTable->GetMatches = (Mf_GetMatches_Func)Hc5_MatchFinder_GetMatches;
       vTable->Skip = (Mf_Skip_Func)Hc5_MatchFinder_Skip;
     }
-    */
   }
   else if (p->numHashBytes == 2)
   {
@@ -1112,16 +1577,53 @@
     vTable->GetMatches = (Mf_GetMatches_Func)Bt3_MatchFinder_GetMatches;
     vTable->Skip = (Mf_Skip_Func)Bt3_MatchFinder_Skip;
   }
-  else /* if (p->numHashBytes == 4) */
+  else if (p->numHashBytes == 4)
   {
     vTable->GetMatches = (Mf_GetMatches_Func)Bt4_MatchFinder_GetMatches;
     vTable->Skip = (Mf_Skip_Func)Bt4_MatchFinder_Skip;
   }
-  /*
   else
   {
     vTable->GetMatches = (Mf_GetMatches_Func)Bt5_MatchFinder_GetMatches;
     vTable->Skip = (Mf_Skip_Func)Bt5_MatchFinder_Skip;
   }
-  */
+}
+
+
+
+void LzFindPrepare()
+{
+  #ifndef FORCE_SATUR_SUB_128
+  #ifdef USE_SATUR_SUB_128
+  LZFIND_SATUR_SUB_CODE_FUNC f = NULL;
+  #ifdef MY_CPU_ARM_OR_ARM64
+  {
+    if (CPU_IsSupported_NEON())
+    {
+      // #pragma message ("=== LzFind NEON")
+      _PRF(printf("\n=== LzFind NEON\n"));
+      f = LzFind_SaturSub_128;
+    }
+    // f = 0; // for debug
+  }
+  #else // MY_CPU_ARM_OR_ARM64
+  if (CPU_IsSupported_SSE41())
+  {
+    // #pragma message ("=== LzFind SSE41")
+    _PRF(printf("\n=== LzFind SSE41\n"));
+    f = LzFind_SaturSub_128;
+
+    #ifdef USE_AVX2
+    if (CPU_IsSupported_AVX2())
+    {
+      // #pragma message ("=== LzFind AVX2")
+      _PRF(printf("\n=== LzFind AVX2\n"));
+      f = LzFind_SaturSub_256;
+    }
+    #endif
+  }
+  #endif // MY_CPU_ARM_OR_ARM64
+  g_LzFind_SaturSub = f;
+  #endif // USE_SATUR_SUB_128
+  #endif // FORCE_SATUR_SUB_128
 }
diff --git a/third_party/lzma_sdk/LzFind.h b/third_party/lzma_sdk/LzFind.h
index 42c13be1..eea873f 100644
--- a/third_party/lzma_sdk/LzFind.h
+++ b/third_party/lzma_sdk/LzFind.h
@@ -1,5 +1,5 @@
 /* LzFind.h -- Match finder for LZ algorithms
-2017-06-10 : Igor Pavlov : Public domain */
+2021-07-13 : Igor Pavlov : Public domain */
 
 #ifndef __LZ_FIND_H
 #define __LZ_FIND_H
@@ -15,7 +15,7 @@
   Byte *buffer;
   UInt32 pos;
   UInt32 posLimit;
-  UInt32 streamPos;
+  UInt32 streamPos;  /* wrap over Zero is allowed (streamPos < pos). Use (UInt32)(streamPos - pos) */
   UInt32 lenLimit;
 
   UInt32 cyclicBufferPos;
@@ -51,17 +51,19 @@
   UInt64 expectedDataSize;
 } CMatchFinder;
 
-#define Inline_MatchFinder_GetPointerToCurrentPos(p) ((p)->buffer)
+#define Inline_MatchFinder_GetPointerToCurrentPos(p) ((const Byte *)(p)->buffer)
 
-#define Inline_MatchFinder_GetNumAvailableBytes(p) ((p)->streamPos - (p)->pos)
+#define Inline_MatchFinder_GetNumAvailableBytes(p) ((UInt32)((p)->streamPos - (p)->pos))
 
+/*
 #define Inline_MatchFinder_IsFinishedOK(p) \
     ((p)->streamEndWasReached \
         && (p)->streamPos == (p)->pos \
         && (!(p)->directInput || (p)->directInputRem == 0))
+*/
       
 int MatchFinder_NeedMove(CMatchFinder *p);
-Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p);
+/* Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p); */
 void MatchFinder_MoveBlock(CMatchFinder *p);
 void MatchFinder_ReadIfRequired(CMatchFinder *p);
 
@@ -76,10 +78,21 @@
     ISzAllocPtr alloc);
 void MatchFinder_Free(CMatchFinder *p, ISzAllocPtr alloc);
 void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, size_t numItems);
-void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue);
+// void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue);
+
+/*
+#define Inline_MatchFinder_InitPos(p, val) \
+    (p)->pos = (val); \
+    (p)->streamPos = (val);
+*/
+
+#define Inline_MatchFinder_ReduceOffsets(p, subValue) \
+    (p)->pos -= (subValue); \
+    (p)->streamPos -= (subValue);
+
 
 UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *buffer, CLzRef *son,
-    UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue,
+    size_t _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue,
     UInt32 *distances, UInt32 maxLen);
 
 /*
@@ -91,7 +104,7 @@
 typedef void (*Mf_Init_Func)(void *object);
 typedef UInt32 (*Mf_GetNumAvailableBytes_Func)(void *object);
 typedef const Byte * (*Mf_GetPointerToCurrentPos_Func)(void *object);
-typedef UInt32 (*Mf_GetMatches_Func)(void *object, UInt32 *distances);
+typedef UInt32 * (*Mf_GetMatches_Func)(void *object, UInt32 *distances);
 typedef void (*Mf_Skip_Func)(void *object, UInt32);
 
 typedef struct _IMatchFinder
@@ -101,21 +114,23 @@
   Mf_GetPointerToCurrentPos_Func GetPointerToCurrentPos;
   Mf_GetMatches_Func GetMatches;
   Mf_Skip_Func Skip;
-} IMatchFinder;
+} IMatchFinder2;
 
-void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable);
+void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder2 *vTable);
 
 void MatchFinder_Init_LowHash(CMatchFinder *p);
 void MatchFinder_Init_HighHash(CMatchFinder *p);
-void MatchFinder_Init_3(CMatchFinder *p, int readData);
+void MatchFinder_Init_4(CMatchFinder *p);
 void MatchFinder_Init(CMatchFinder *p);
 
-UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances);
-UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances);
+UInt32* Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances);
+UInt32* Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances);
 
 void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num);
 void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num);
 
+void LzFindPrepare(void);
+
 EXTERN_C_END
 
 #endif
diff --git a/third_party/lzma_sdk/LzHash.h b/third_party/lzma_sdk/LzHash.h
index e7c94230..77b898c 100644
--- a/third_party/lzma_sdk/LzHash.h
+++ b/third_party/lzma_sdk/LzHash.h
@@ -1,57 +1,34 @@
 /* LzHash.h -- HASH functions for LZ algorithms
-2015-04-12 : Igor Pavlov : Public domain */
+2019-10-30 : Igor Pavlov : Public domain */
 
 #ifndef __LZ_HASH_H
 #define __LZ_HASH_H
 
+/*
+  (kHash2Size >= (1 <<  8)) : Required
+  (kHash3Size >= (1 << 16)) : Required
+*/
+
 #define kHash2Size (1 << 10)
 #define kHash3Size (1 << 16)
-#define kHash4Size (1 << 20)
+// #define kHash4Size (1 << 20)
 
 #define kFix3HashSize (kHash2Size)
 #define kFix4HashSize (kHash2Size + kHash3Size)
-#define kFix5HashSize (kHash2Size + kHash3Size + kHash4Size)
+// #define kFix5HashSize (kHash2Size + kHash3Size + kHash4Size)
 
-#define HASH2_CALC hv = cur[0] | ((UInt32)cur[1] << 8);
+/*
+  We use up to 3 crc values for hash:
+    crc0
+    crc1 << Shift_1
+    crc2 << Shift_2
+  (Shift_1 = 5) and (Shift_2 = 10) is good tradeoff.
+  Small values for Shift are not good for collision rate.
+  Big value for Shift_2 increases the minimum size
+  of hash table, that will be slow for small files.
+*/
 
-#define HASH3_CALC { \
-  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
-  h2 = temp & (kHash2Size - 1); \
-  hv = (temp ^ ((UInt32)cur[2] << 8)) & p->hashMask; }
-
-#define HASH4_CALC { \
-  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
-  h2 = temp & (kHash2Size - 1); \
-  temp ^= ((UInt32)cur[2] << 8); \
-  h3 = temp & (kHash3Size - 1); \
-  hv = (temp ^ (p->crc[cur[3]] << 5)) & p->hashMask; }
-
-#define HASH5_CALC { \
-  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
-  h2 = temp & (kHash2Size - 1); \
-  temp ^= ((UInt32)cur[2] << 8); \
-  h3 = temp & (kHash3Size - 1); \
-  temp ^= (p->crc[cur[3]] << 5); \
-  h4 = temp & (kHash4Size - 1); \
-  hv = (temp ^ (p->crc[cur[4]] << 3)) & p->hashMask; }
-
-/* #define HASH_ZIP_CALC hv = ((cur[0] | ((UInt32)cur[1] << 8)) ^ p->crc[cur[2]]) & 0xFFFF; */
-#define HASH_ZIP_CALC hv = ((cur[2] | ((UInt32)cur[0] << 8)) ^ p->crc[cur[1]]) & 0xFFFF;
-
-
-#define MT_HASH2_CALC \
-  h2 = (p->crc[cur[0]] ^ cur[1]) & (kHash2Size - 1);
-
-#define MT_HASH3_CALC { \
-  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
-  h2 = temp & (kHash2Size - 1); \
-  h3 = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); }
-
-#define MT_HASH4_CALC { \
-  UInt32 temp = p->crc[cur[0]] ^ cur[1]; \
-  h2 = temp & (kHash2Size - 1); \
-  temp ^= ((UInt32)cur[2] << 8); \
-  h3 = temp & (kHash3Size - 1); \
-  h4 = (temp ^ (p->crc[cur[3]] << 5)) & (kHash4Size - 1); }
+#define kLzHash_CrcShift_1 5
+#define kLzHash_CrcShift_2 10
 
 #endif
diff --git a/third_party/lzma_sdk/Lzma2Dec.c b/third_party/lzma_sdk/Lzma2Dec.c
index 4e138a4..ac970a84 100644
--- a/third_party/lzma_sdk/Lzma2Dec.c
+++ b/third_party/lzma_sdk/Lzma2Dec.c
@@ -1,5 +1,5 @@
 /* Lzma2Dec.c -- LZMA2 Decoder
-2019-02-02 : Igor Pavlov : Public domain */
+2021-02-09 : Igor Pavlov : Public domain */
 
 /* #define SHOW_DEBUG_INFO */
 
@@ -93,7 +93,8 @@
   LzmaDec_Init(&p->decoder);
 }
 
-static ELzma2State Lzma2Dec_UpdateState(CLzma2Dec *p, Byte b)
+// ELzma2State
+static unsigned Lzma2Dec_UpdateState(CLzma2Dec *p, Byte b)
 {
   switch (p->state)
   {
diff --git a/third_party/lzma_sdk/LzmaDec.c b/third_party/lzma_sdk/LzmaDec.c
index ba3e1dd..d6742e5a 100644
--- a/third_party/lzma_sdk/LzmaDec.c
+++ b/third_party/lzma_sdk/LzmaDec.c
@@ -1,5 +1,5 @@
 /* LzmaDec.c -- LZMA Decoder
-2018-07-04 : Igor Pavlov : Public domain */
+2021-04-01 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -13,10 +13,12 @@
 
 #define kNumBitModelTotalBits 11
 #define kBitModelTotal (1 << kNumBitModelTotalBits)
-#define kNumMoveBits 5
 
 #define RC_INIT_SIZE 5
 
+#ifndef _LZMA_DEC_OPT
+
+#define kNumMoveBits 5
 #define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); }
 
 #define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * (UInt32)ttt; if (code < bound)
@@ -62,9 +64,10 @@
   probLit = prob + (offs + bit + symbol); \
   GET_BIT2(probLit, symbol, offs ^= bit; , ;)
 
+#endif // _LZMA_DEC_OPT
 
 
-#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); }
+#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_INPUT_EOF; range <<= 8; code = (code << 8) | (*buf++); }
 
 #define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * (UInt32)ttt; if (code < bound)
 #define UPDATE_0_CHECK range = bound;
@@ -114,6 +117,9 @@
 #define kMatchMinLen 2
 #define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols * 2 + kLenNumHighSymbols)
 
+#define kMatchSpecLen_Error_Data (1 << 9)
+#define kMatchSpecLen_Error_Fail (kMatchSpecLen_Error_Data - 1)
+
 /* External ASM code needs same CLzmaProb array layout. So don't change it. */
 
 /* (probs_1664) is faster and better for code size at some platforms */
@@ -166,10 +172,12 @@
 
 /*
 p->remainLen : shows status of LZMA decoder:
-    < kMatchSpecLenStart : normal remain
-    = kMatchSpecLenStart : finished
-    = kMatchSpecLenStart + 1 : need init range coder
-    = kMatchSpecLenStart + 2 : need init range coder and state
+    < kMatchSpecLenStart  : the number of bytes to be copied with (p->rep0) offset
+    = kMatchSpecLenStart  : the LZMA stream was finished with end mark
+    = kMatchSpecLenStart + 1  : need init range coder
+    = kMatchSpecLenStart + 2  : need init range coder and state
+    = kMatchSpecLen_Error_Fail                : Internal Code Failure
+    = kMatchSpecLen_Error_Data + [0 ... 273]  : LZMA Data Error
 */
 
 /* ---------- LZMA_DECODE_REAL ---------- */
@@ -188,23 +196,31 @@
   {
     LzmaDec_TryDummy() was called before to exclude LITERAL and MATCH-REP cases.
     So first symbol can be only MATCH-NON-REP. And if that MATCH-NON-REP symbol
-    is not END_OF_PAYALOAD_MARKER, then function returns error code.
+    is not END_OF_PAYALOAD_MARKER, then the function doesn't write any byte to dictionary,
+    the function returns SZ_OK, and the caller can use (p->remainLen) and (p->reps[0]) later.
   }
 
 Processing:
-  first LZMA symbol will be decoded in any case
-  All checks for limits are at the end of main loop,
-  It will decode new LZMA-symbols while (p->buf < bufLimit && dicPos < limit),
+  The first LZMA symbol will be decoded in any case.
+  All main checks for limits are at the end of main loop,
+  It decodes additional LZMA-symbols while (p->buf < bufLimit && dicPos < limit),
   RangeCoder is still without last normalization when (p->buf < bufLimit) is being checked.
+  But if (p->buf < bufLimit), the caller provided at least (LZMA_REQUIRED_INPUT_MAX + 1) bytes for
+  next iteration  before limit (bufLimit + LZMA_REQUIRED_INPUT_MAX),
+  that is enough for worst case LZMA symbol with one additional RangeCoder normalization for one bit.
+  So that function never reads bufLimit [LZMA_REQUIRED_INPUT_MAX] byte.
 
 Out:
   RangeCoder is normalized
   Result:
     SZ_OK - OK
-    SZ_ERROR_DATA - Error
-  p->remainLen:
-    < kMatchSpecLenStart : normal remain
-    = kMatchSpecLenStart : finished
+      p->remainLen:
+        < kMatchSpecLenStart : the number of bytes to be copied with (p->reps[0]) offset
+        = kMatchSpecLenStart : the LZMA stream was finished with end mark
+
+    SZ_ERROR_DATA - error, when the MATCH-Symbol refers out of dictionary
+      p->remainLen : undefined
+      p->reps[*]    : undefined
 */
 
 
@@ -316,11 +332,6 @@
       else
       {
         UPDATE_1(prob);
-        /*
-        // that case was checked before with kBadRepCode
-        if (checkDicSize == 0 && processedPos == 0)
-          return SZ_ERROR_DATA;
-        */
         prob = probs + IsRepG0 + state;
         IF_BIT_0(prob)
         {
@@ -329,6 +340,13 @@
           IF_BIT_0(prob)
           {
             UPDATE_0(prob);
+  
+            // that case was checked before with kBadRepCode
+            // if (checkDicSize == 0 && processedPos == 0) { len = kMatchSpecLen_Error_Data + 1; break; }
+            // The caller doesn't allow (dicPos == limit) case here
+            // so we don't need the following check:
+            // if (dicPos == limit) { state = state < kNumLitStates ? 9 : 11; len = 1; break; }
+            
             dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)];
             dicPos++;
             processedPos++;
@@ -518,8 +536,10 @@
         state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3;
         if (distance >= (checkDicSize == 0 ? processedPos: checkDicSize))
         {
-          p->dicPos = dicPos;
-          return SZ_ERROR_DATA;
+          len += kMatchSpecLen_Error_Data + kMatchMinLen;
+          // len = kMatchSpecLen_Error_Data;
+          // len += kMatchMinLen;
+          break;
         }
       }
 
@@ -532,8 +552,13 @@
         
         if ((rem = limit - dicPos) == 0)
         {
-          p->dicPos = dicPos;
-          return SZ_ERROR_DATA;
+          /*
+          We stop decoding and return SZ_OK, and we can resume decoding later.
+          Any error conditions can be tested later in caller code.
+          For more strict mode we can stop decoding with error
+          // len += kMatchSpecLen_Error_Data;
+          */
+          break;
         }
         
         curLen = ((rem < len) ? (unsigned)rem : len);
@@ -572,7 +597,7 @@
   p->buf = buf;
   p->range = range;
   p->code = code;
-  p->remainLen = (UInt32)len;
+  p->remainLen = (UInt32)len; // & (kMatchSpecLen_Error_Data - 1); // we can write real length for error matches too.
   p->dicPos = dicPos;
   p->processedPos = processedPos;
   p->reps[0] = rep0;
@@ -580,40 +605,61 @@
   p->reps[2] = rep2;
   p->reps[3] = rep3;
   p->state = (UInt32)state;
-
+  if (len >= kMatchSpecLen_Error_Data)
+    return SZ_ERROR_DATA;
   return SZ_OK;
 }
 #endif
 
+
+
 static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit)
 {
-  if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart)
+  unsigned len = (unsigned)p->remainLen;
+  if (len == 0 /* || len >= kMatchSpecLenStart */)
+    return;
   {
-    Byte *dic = p->dic;
     SizeT dicPos = p->dicPos;
-    SizeT dicBufSize = p->dicBufSize;
-    unsigned len = (unsigned)p->remainLen;
-    SizeT rep0 = p->reps[0]; /* we use SizeT to avoid the BUG of VC14 for AMD64 */
-    SizeT rem = limit - dicPos;
-    if (rem < len)
-      len = (unsigned)(rem);
+    Byte *dic;
+    SizeT dicBufSize;
+    SizeT rep0;   /* we use SizeT to avoid the BUG of VC14 for AMD64 */
+    {
+      SizeT rem = limit - dicPos;
+      if (rem < len)
+      {
+        len = (unsigned)(rem);
+        if (len == 0)
+          return;
+      }
+    }
 
     if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len)
       p->checkDicSize = p->prop.dicSize;
 
     p->processedPos += (UInt32)len;
     p->remainLen -= (UInt32)len;
-    while (len != 0)
+    dic = p->dic;
+    rep0 = p->reps[0];
+    dicBufSize = p->dicBufSize;
+    do
     {
-      len--;
       dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)];
       dicPos++;
     }
+    while (--len);
     p->dicPos = dicPos;
   }
 }
 
 
+/*
+At staring of new stream we have one of the following symbols:
+  - Literal        - is allowed
+  - Non-Rep-Match  - is allowed only if it's end marker symbol
+  - Rep-Match      - is not allowed
+We use early check of (RangeCoder:Code) over kBadRepCode to simplify main decoding code
+*/
+
 #define kRange0 0xFFFFFFFF
 #define kBound0 ((kRange0 >> kNumBitModelTotalBits) << (kNumBitModelTotalBits - 1))
 #define kBadRepCode (kBound0 + (((kRange0 - kBound0) >> kNumBitModelTotalBits) << (kNumBitModelTotalBits - 1)))
@@ -621,69 +667,77 @@
   #error Stop_Compiling_Bad_LZMA_Check
 #endif
 
+
+/*
+LzmaDec_DecodeReal2():
+  It calls LZMA_DECODE_REAL() and it adjusts limit according (p->checkDicSize).
+
+We correct (p->checkDicSize) after LZMA_DECODE_REAL() and in LzmaDec_WriteRem(),
+and we support the following state of (p->checkDicSize):
+  if (total_processed < p->prop.dicSize) then
+  {
+    (total_processed == p->processedPos)
+    (p->checkDicSize == 0)
+  }
+  else
+    (p->checkDicSize == p->prop.dicSize)
+*/
+
 static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit)
 {
-  do
+  if (p->checkDicSize == 0)
   {
-    SizeT limit2 = limit;
-    if (p->checkDicSize == 0)
-    {
-      UInt32 rem = p->prop.dicSize - p->processedPos;
-      if (limit - p->dicPos > rem)
-        limit2 = p->dicPos + rem;
-
-      if (p->processedPos == 0)
-        if (p->code >= kBadRepCode)
-          return SZ_ERROR_DATA;
-    }
-
-    RINOK(LZMA_DECODE_REAL(p, limit2, bufLimit));
-    
+    UInt32 rem = p->prop.dicSize - p->processedPos;
+    if (limit - p->dicPos > rem)
+      limit = p->dicPos + rem;
+  }
+  {
+    int res = LZMA_DECODE_REAL(p, limit, bufLimit);
     if (p->checkDicSize == 0 && p->processedPos >= p->prop.dicSize)
       p->checkDicSize = p->prop.dicSize;
-    
-    LzmaDec_WriteRem(p, limit);
+    return res;
   }
-  while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart);
-
-  return 0;
 }
 
+
+
 typedef enum
 {
-  DUMMY_ERROR, /* unexpected end of input stream */
+  DUMMY_INPUT_EOF, /* need more input data */
   DUMMY_LIT,
   DUMMY_MATCH,
   DUMMY_REP
 } ELzmaDummy;
 
-static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize)
+
+#define IS_DUMMY_END_MARKER_POSSIBLE(dummyRes) ((dummyRes) == DUMMY_MATCH)
+
+static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, const Byte **bufOut)
 {
   UInt32 range = p->range;
   UInt32 code = p->code;
-  const Byte *bufLimit = buf + inSize;
+  const Byte *bufLimit = *bufOut;
   const CLzmaProb *probs = GET_PROBS;
   unsigned state = (unsigned)p->state;
   ELzmaDummy res;
 
+  for (;;)
   {
     const CLzmaProb *prob;
     UInt32 bound;
     unsigned ttt;
-    unsigned posState = CALC_POS_STATE(p->processedPos, (1 << p->prop.pb) - 1);
+    unsigned posState = CALC_POS_STATE(p->processedPos, ((unsigned)1 << p->prop.pb) - 1);
 
     prob = probs + IsMatch + COMBINED_PS_STATE;
     IF_BIT_0_CHECK(prob)
     {
       UPDATE_0_CHECK
 
-      /* if (bufLimit - buf >= 7) return DUMMY_LIT; */
-
       prob = probs + Literal;
       if (p->checkDicSize != 0 || p->processedPos != 0)
         prob += ((UInt32)LZMA_LIT_SIZE *
-            ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) +
-            (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc))));
+            ((((p->processedPos) & (((unsigned)1 << (p->prop.lp)) - 1)) << p->prop.lc) +
+            ((unsigned)p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc))));
 
       if (state < kNumLitStates)
       {
@@ -735,8 +789,7 @@
           IF_BIT_0_CHECK(prob)
           {
             UPDATE_0_CHECK;
-            NORMALIZE_CHECK;
-            return DUMMY_REP;
+            break;
           }
           else
           {
@@ -812,8 +865,6 @@
         {
           unsigned numDirectBits = ((posSlot >> 1) - 1);
 
-          /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */
-
           if (posSlot < kEndPosModelIndex)
           {
             prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits);
@@ -844,12 +895,15 @@
         }
       }
     }
+    break;
   }
   NORMALIZE_CHECK;
+
+  *bufOut = buf;
   return res;
 }
 
-
+void LzmaDec_InitDicAndState(CLzmaDec *p, BoolInt initDic, BoolInt initState);
 void LzmaDec_InitDicAndState(CLzmaDec *p, BoolInt initDic, BoolInt initState)
 {
   p->remainLen = kMatchSpecLenStart + 1;
@@ -872,16 +926,41 @@
 }
 
 
+/*
+LZMA supports optional end_marker.
+So the decoder can lookahead for one additional LZMA-Symbol to check end_marker.
+That additional LZMA-Symbol can require up to LZMA_REQUIRED_INPUT_MAX bytes in input stream.
+When the decoder reaches dicLimit, it looks (finishMode) parameter:
+  if (finishMode == LZMA_FINISH_ANY), the decoder doesn't lookahead
+  if (finishMode != LZMA_FINISH_ANY), the decoder lookahead, if end_marker is possible for current position
+
+When the decoder lookahead, and the lookahead symbol is not end_marker, we have two ways:
+  1) Strict mode (default) : the decoder returns SZ_ERROR_DATA.
+  2) The relaxed mode (alternative mode) : we could return SZ_OK, and the caller
+     must check (status) value. The caller can show the error,
+     if the end of stream is expected, and the (status) is noit
+     LZMA_STATUS_FINISHED_WITH_MARK or LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK.
+*/
+
+
+#define RETURN__NOT_FINISHED__FOR_FINISH \
+  *status = LZMA_STATUS_NOT_FINISHED; \
+  return SZ_ERROR_DATA; // for strict mode
+  // return SZ_OK; // for relaxed mode
+
+
 SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen,
     ELzmaFinishMode finishMode, ELzmaStatus *status)
 {
   SizeT inSize = *srcLen;
   (*srcLen) = 0;
-  
   *status = LZMA_STATUS_NOT_SPECIFIED;
 
   if (p->remainLen > kMatchSpecLenStart)
   {
+    if (p->remainLen > kMatchSpecLenStart + 2)
+      return p->remainLen == kMatchSpecLen_Error_Fail ? SZ_ERROR_FAIL : SZ_ERROR_DATA;
+
     for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--)
       p->tempBuf[p->tempBufSize++] = *src++;
     if (p->tempBufSize != 0 && p->tempBuf[0] != 0)
@@ -896,6 +975,12 @@
       | ((UInt32)p->tempBuf[2] << 16)
       | ((UInt32)p->tempBuf[3] << 8)
       | ((UInt32)p->tempBuf[4]);
+
+    if (p->checkDicSize == 0
+        && p->processedPos == 0
+        && p->code >= kBadRepCode)
+      return SZ_ERROR_DATA;
+
     p->range = 0xFFFFFFFF;
     p->tempBufSize = 0;
 
@@ -913,10 +998,21 @@
     p->remainLen = 0;
   }
 
-  LzmaDec_WriteRem(p, dicLimit);
-
-  while (p->remainLen != kMatchSpecLenStart)
+  for (;;)
   {
+    if (p->remainLen == kMatchSpecLenStart)
+    {
+      if (p->code != 0)
+        return SZ_ERROR_DATA;
+      *status = LZMA_STATUS_FINISHED_WITH_MARK;
+      return SZ_OK;
+    }
+
+    LzmaDec_WriteRem(p, dicLimit);
+
+    {
+      // (p->remainLen == 0 || p->dicPos == dicLimit)
+
       int checkEndMarkNow = 0;
 
       if (p->dicPos >= dicLimit)
@@ -933,92 +1029,174 @@
         }
         if (p->remainLen != 0)
         {
-          *status = LZMA_STATUS_NOT_FINISHED;
-          return SZ_ERROR_DATA;
+          RETURN__NOT_FINISHED__FOR_FINISH;
         }
         checkEndMarkNow = 1;
       }
 
+      // (p->remainLen == 0)
+
       if (p->tempBufSize == 0)
       {
-        SizeT processed;
         const Byte *bufLimit;
+        int dummyProcessed = -1;
+        
         if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
         {
-          int dummyRes = LzmaDec_TryDummy(p, src, inSize);
-          if (dummyRes == DUMMY_ERROR)
+          const Byte *bufOut = src + inSize;
+          
+          ELzmaDummy dummyRes = LzmaDec_TryDummy(p, src, &bufOut);
+          
+          if (dummyRes == DUMMY_INPUT_EOF)
           {
-            memcpy(p->tempBuf, src, inSize);
-            p->tempBufSize = (unsigned)inSize;
+            size_t i;
+            if (inSize >= LZMA_REQUIRED_INPUT_MAX)
+              break;
             (*srcLen) += inSize;
+            p->tempBufSize = (unsigned)inSize;
+            for (i = 0; i < inSize; i++)
+              p->tempBuf[i] = src[i];
             *status = LZMA_STATUS_NEEDS_MORE_INPUT;
             return SZ_OK;
           }
-          if (checkEndMarkNow && dummyRes != DUMMY_MATCH)
+ 
+          dummyProcessed = (int)(bufOut - src);
+          if ((unsigned)dummyProcessed > LZMA_REQUIRED_INPUT_MAX)
+            break;
+          
+          if (checkEndMarkNow && !IS_DUMMY_END_MARKER_POSSIBLE(dummyRes))
           {
-            *status = LZMA_STATUS_NOT_FINISHED;
-            return SZ_ERROR_DATA;
+            unsigned i;
+            (*srcLen) += (unsigned)dummyProcessed;
+            p->tempBufSize = (unsigned)dummyProcessed;
+            for (i = 0; i < (unsigned)dummyProcessed; i++)
+              p->tempBuf[i] = src[i];
+            // p->remainLen = kMatchSpecLen_Error_Data;
+            RETURN__NOT_FINISHED__FOR_FINISH;
           }
+          
           bufLimit = src;
+          // we will decode only one iteration
         }
         else
           bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX;
+
         p->buf = src;
-        if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0)
-          return SZ_ERROR_DATA;
-        processed = (SizeT)(p->buf - src);
-        (*srcLen) += processed;
-        src += processed;
-        inSize -= processed;
-      }
-      else
-      {
-        unsigned rem = p->tempBufSize, lookAhead = 0;
-        while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize)
-          p->tempBuf[rem++] = src[lookAhead++];
-        p->tempBufSize = rem;
-        if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
+        
         {
-          int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, (SizeT)rem);
-          if (dummyRes == DUMMY_ERROR)
+          int res = LzmaDec_DecodeReal2(p, dicLimit, bufLimit);
+          
+          SizeT processed = (SizeT)(p->buf - src);
+
+          if (dummyProcessed < 0)
           {
-            (*srcLen) += (SizeT)lookAhead;
-            *status = LZMA_STATUS_NEEDS_MORE_INPUT;
-            return SZ_OK;
+            if (processed > inSize)
+              break;
           }
-          if (checkEndMarkNow && dummyRes != DUMMY_MATCH)
+          else if ((unsigned)dummyProcessed != processed)
+            break;
+
+          src += processed;
+          inSize -= processed;
+          (*srcLen) += processed;
+
+          if (res != SZ_OK)
           {
-            *status = LZMA_STATUS_NOT_FINISHED;
+            p->remainLen = kMatchSpecLen_Error_Data;
             return SZ_ERROR_DATA;
           }
         }
+        continue;
+      }
+
+      {
+        // we have some data in (p->tempBuf)
+        // in strict mode: tempBufSize is not enough for one Symbol decoding.
+        // in relaxed mode: tempBufSize not larger than required for one Symbol decoding.
+
+        unsigned rem = p->tempBufSize;
+        unsigned ahead = 0;
+        int dummyProcessed = -1;
+        
+        while (rem < LZMA_REQUIRED_INPUT_MAX && ahead < inSize)
+          p->tempBuf[rem++] = src[ahead++];
+        
+        // ahead - the size of new data copied from (src) to (p->tempBuf)
+        // rem   - the size of temp buffer including new data from (src)
+        
+        if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow)
+        {
+          const Byte *bufOut = p->tempBuf + rem;
+        
+          ELzmaDummy dummyRes = LzmaDec_TryDummy(p, p->tempBuf, &bufOut);
+          
+          if (dummyRes == DUMMY_INPUT_EOF)
+          {
+            if (rem >= LZMA_REQUIRED_INPUT_MAX)
+              break;
+            p->tempBufSize = rem;
+            (*srcLen) += (SizeT)ahead;
+            *status = LZMA_STATUS_NEEDS_MORE_INPUT;
+            return SZ_OK;
+          }
+          
+          dummyProcessed = (int)(bufOut - p->tempBuf);
+
+          if ((unsigned)dummyProcessed < p->tempBufSize)
+            break;
+
+          if (checkEndMarkNow && !IS_DUMMY_END_MARKER_POSSIBLE(dummyRes))
+          {
+            (*srcLen) += (unsigned)dummyProcessed - p->tempBufSize;
+            p->tempBufSize = (unsigned)dummyProcessed;
+            // p->remainLen = kMatchSpecLen_Error_Data;
+            RETURN__NOT_FINISHED__FOR_FINISH;
+          }
+        }
+
         p->buf = p->tempBuf;
-        if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0)
-          return SZ_ERROR_DATA;
         
         {
-          unsigned kkk = (unsigned)(p->buf - p->tempBuf);
-          if (rem < kkk)
-            return SZ_ERROR_FAIL; /* some internal error */
-          rem -= kkk;
-          if (lookAhead < rem)
-            return SZ_ERROR_FAIL; /* some internal error */
-          lookAhead -= rem;
+          // we decode one symbol from (p->tempBuf) here, so the (bufLimit) is equal to (p->buf)
+          int res = LzmaDec_DecodeReal2(p, dicLimit, p->buf);
+
+          SizeT processed = (SizeT)(p->buf - p->tempBuf);
+          rem = p->tempBufSize;
+          
+          if (dummyProcessed < 0)
+          {
+            if (processed > LZMA_REQUIRED_INPUT_MAX)
+              break;
+            if (processed < rem)
+              break;
+          }
+          else if ((unsigned)dummyProcessed != processed)
+            break;
+          
+          processed -= rem;
+
+          src += processed;
+          inSize -= processed;
+          (*srcLen) += processed;
+          p->tempBufSize = 0;
+          
+          if (res != SZ_OK)
+          {
+            p->remainLen = kMatchSpecLen_Error_Data;
+            return SZ_ERROR_DATA;
+          }
         }
-        (*srcLen) += (SizeT)lookAhead;
-        src += lookAhead;
-        inSize -= (SizeT)lookAhead;
-        p->tempBufSize = 0;
       }
+    }
   }
-  
-  if (p->code != 0)
-    return SZ_ERROR_DATA;
-  *status = LZMA_STATUS_FINISHED_WITH_MARK;
-  return SZ_OK;
+
+  /*  Some unexpected error: internal error of code, memory corruption or hardware failure */
+  p->remainLen = kMatchSpecLen_Error_Fail;
+  return SZ_ERROR_FAIL;
 }
 
 
+
 SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status)
 {
   SizeT outSize = *destLen;
diff --git a/third_party/lzma_sdk/LzmaDec.h b/third_party/lzma_sdk/LzmaDec.h
index 1f0927a..6f12962 100644
--- a/third_party/lzma_sdk/LzmaDec.h
+++ b/third_party/lzma_sdk/LzmaDec.h
@@ -1,5 +1,5 @@
 /* LzmaDec.h -- LZMA Decoder
-2018-04-21 : Igor Pavlov : Public domain */
+2020-03-19 : Igor Pavlov : Public domain */
 
 #ifndef __LZMA_DEC_H
 #define __LZMA_DEC_H
@@ -181,6 +181,7 @@
       LZMA_STATUS_NEEDS_MORE_INPUT
       LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK
   SZ_ERROR_DATA - Data error
+  SZ_ERROR_FAIL - Some unexpected error: internal error of code, memory corruption or hardware failure
 */
 
 SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit,
@@ -223,6 +224,7 @@
   SZ_ERROR_MEM  - Memory allocation error
   SZ_ERROR_UNSUPPORTED - Unsupported properties
   SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src).
+  SZ_ERROR_FAIL - Some unexpected error: internal error of code, memory corruption or hardware failure
 */
 
 SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen,
diff --git a/third_party/lzma_sdk/LzmaEnc.c b/third_party/lzma_sdk/LzmaEnc.c
index 46a0db0..b04a7b7b 100644
--- a/third_party/lzma_sdk/LzmaEnc.c
+++ b/third_party/lzma_sdk/LzmaEnc.c
@@ -1,5 +1,5 @@
 /* LzmaEnc.c -- LZMA Encoder
-2019-01-10: Igor Pavlov : Public domain */
+2021-11-18: Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -12,6 +12,7 @@
 #include <stdio.h>
 #endif
 
+#include "CpuArch.h"
 #include "LzmaEnc.h"
 
 #include "LzFind.h"
@@ -19,12 +20,25 @@
 #include "LzFindMt.h"
 #endif
 
+/* the following LzmaEnc_* declarations is internal LZMA interface for LZMA2 encoder */
+
+SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle pp, ISeqInStream *inStream, UInt32 keepWindowSize,
+    ISzAllocPtr alloc, ISzAllocPtr allocBig);
+SRes LzmaEnc_MemPrepare(CLzmaEncHandle pp, const Byte *src, SizeT srcLen,
+    UInt32 keepWindowSize, ISzAllocPtr alloc, ISzAllocPtr allocBig);
+SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, BoolInt reInit,
+    Byte *dest, size_t *destLen, UInt32 desiredPackSize, UInt32 *unpackSize);
+const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle pp);
+void LzmaEnc_Finish(CLzmaEncHandle pp);
+void LzmaEnc_SaveState(CLzmaEncHandle pp);
+void LzmaEnc_RestoreState(CLzmaEncHandle pp);
+
 #ifdef SHOW_STAT
 static unsigned g_STAT_OFFSET = 0;
 #endif
 
-#define kLzmaMaxHistorySize ((UInt32)3 << 29)
-/* #define kLzmaMaxHistorySize ((UInt32)7 << 29) */
+/* for good normalization speed we still reserve 256 MB before 4 GB range */
+#define kLzmaMaxHistorySize ((UInt32)15 << 28)
 
 #define kNumTopBits 24
 #define kTopValue ((UInt32)1 << kNumTopBits)
@@ -36,7 +50,7 @@
 
 #define kNumMoveReducingBits 4
 #define kNumBitPriceShiftBits 4
-#define kBitPrice (1 << kNumBitPriceShiftBits)
+// #define kBitPrice (1 << kNumBitPriceShiftBits)
 
 #define REP_LEN_COUNT 64
 
@@ -47,6 +61,7 @@
   p->reduceSize = (UInt64)(Int64)-1;
   p->lc = p->lp = p->pb = p->algo = p->fb = p->btMode = p->numHashBytes = p->numThreads = -1;
   p->writeEndMark = 0;
+  p->affinity = 0;
 }
 
 void LzmaEncProps_Normalize(CLzmaEncProps *p)
@@ -55,16 +70,21 @@
   if (level < 0) level = 5;
   p->level = level;
   
-  if (p->dictSize == 0) p->dictSize = (level <= 5 ? (1 << (level * 2 + 14)) : (level <= 7 ? (1 << 25) : (1 << 26)));
+  if (p->dictSize == 0)
+    p->dictSize =
+      ( level <= 3 ? ((UInt32)1 << (level * 2 + 16)) :
+      ( level <= 6 ? ((UInt32)1 << (level + 19)) :
+      ( level <= 7 ? ((UInt32)1 << 25) : ((UInt32)1 << 26)
+      )));
+
   if (p->dictSize > p->reduceSize)
   {
-    unsigned i;
-    UInt32 reduceSize = (UInt32)p->reduceSize;
-    for (i = 11; i <= 30; i++)
-    {
-      if (reduceSize <= ((UInt32)2 << i)) { p->dictSize = ((UInt32)2 << i); break; }
-      if (reduceSize <= ((UInt32)3 << i)) { p->dictSize = ((UInt32)3 << i); break; }
-    }
+    UInt32 v = (UInt32)p->reduceSize;
+    const UInt32 kReduceMin = ((UInt32)1 << 12);
+    if (v < kReduceMin)
+      v = kReduceMin;
+    if (p->dictSize > v)
+      p->dictSize = v;
   }
 
   if (p->lc < 0) p->lc = 3;
@@ -74,8 +94,8 @@
   if (p->algo < 0) p->algo = (level < 5 ? 0 : 1);
   if (p->fb < 0) p->fb = (level < 7 ? 32 : 64);
   if (p->btMode < 0) p->btMode = (p->algo == 0 ? 0 : 1);
-  if (p->numHashBytes < 0) p->numHashBytes = 4;
-  if (p->mc == 0) p->mc = (16 + (p->fb >> 1)) >> (p->btMode ? 0 : 1);
+  if (p->numHashBytes < 0) p->numHashBytes = (p->btMode ? 4 : 5);
+  if (p->mc == 0) p->mc = (16 + ((unsigned)p->fb >> 1)) >> (p->btMode ? 0 : 1);
   
   if (p->numThreads < 0)
     p->numThreads =
@@ -93,18 +113,85 @@
   return props.dictSize;
 }
 
-#if (_MSC_VER >= 1400)
-/* BSR code is fast for some new CPUs */
-/* #define LZMA_LOG_BSR */
+
+/*
+x86/x64:
+
+BSR:
+  IF (SRC == 0) ZF = 1, DEST is undefined;
+                  AMD : DEST is unchanged;
+  IF (SRC != 0) ZF = 0; DEST is index of top non-zero bit
+  BSR is slow in some processors
+
+LZCNT:
+  IF (SRC  == 0) CF = 1, DEST is size_in_bits_of_register(src) (32 or 64)
+  IF (SRC  != 0) CF = 0, DEST = num_lead_zero_bits
+  IF (DEST == 0) ZF = 1;
+
+LZCNT works only in new processors starting from Haswell.
+if LZCNT is not supported by processor, then it's executed as BSR.
+LZCNT can be faster than BSR, if supported.
+*/
+
+// #define LZMA_LOG_BSR
+
+#if defined(MY_CPU_ARM_OR_ARM64) /* || defined(MY_CPU_X86_OR_AMD64) */
+
+  #if (defined(__clang__) && (__clang_major__ >= 6)) \
+      || (defined(__GNUC__) && (__GNUC__ >= 6))
+      #define LZMA_LOG_BSR
+  #elif defined(_MSC_VER) && (_MSC_VER >= 1300)
+    // #if defined(MY_CPU_ARM_OR_ARM64)
+      #define LZMA_LOG_BSR
+    // #endif
+  #endif
 #endif
 
+// #include <intrin.h>
+
 #ifdef LZMA_LOG_BSR
 
-#define kDicLogSizeMaxCompress 32
+#if defined(__clang__) \
+    || defined(__GNUC__)
 
-#define BSR2_RET(pos, res) { unsigned long zz; _BitScanReverse(&zz, (pos)); res = (zz + zz) + ((pos >> (zz - 1)) & 1); }
+/*
+  C code:                  : (30 - __builtin_clz(x))
+    gcc9/gcc10 for x64 /x86  : 30 - (bsr(x) xor 31)
+    clang10 for x64          : 31 + (bsr(x) xor -32)
+*/
 
-static unsigned GetPosSlot1(UInt32 pos)
+  #define MY_clz(x)  ((unsigned)__builtin_clz(x))
+  // __lzcnt32
+  // __builtin_ia32_lzcnt_u32
+
+#else  // #if defined(_MSC_VER)
+
+  #ifdef MY_CPU_ARM_OR_ARM64
+
+    #define MY_clz  _CountLeadingZeros
+
+  #else // if defined(MY_CPU_X86_OR_AMD64)
+
+    // #define MY_clz  __lzcnt  // we can use lzcnt (unsupported by old CPU)
+    // _BitScanReverse code is not optimal for some MSVC compilers
+    #define BSR2_RET(pos, res) { unsigned long zz; _BitScanReverse(&zz, (pos)); zz--; \
+      res = (zz + zz) + (pos >> zz); }
+
+  #endif // MY_CPU_X86_OR_AMD64
+
+#endif // _MSC_VER
+
+
+#ifndef BSR2_RET
+
+    #define BSR2_RET(pos, res) { unsigned zz = 30 - MY_clz(pos); \
+      res = (zz + zz) + (pos >> zz); }
+
+#endif
+
+
+unsigned GetPosSlot1(UInt32 pos);
+unsigned GetPosSlot1(UInt32 pos)
 {
   unsigned res;
   BSR2_RET(pos, res);
@@ -113,10 +200,10 @@
 #define GetPosSlot2(pos, res) { BSR2_RET(pos, res); }
 #define GetPosSlot(pos, res) { if (pos < 2) res = pos; else BSR2_RET(pos, res); }
 
-#else
 
-#define kNumLogBits (9 + sizeof(size_t) / 2)
-/* #define kNumLogBits (11 + sizeof(size_t) / 8 * 3) */
+#else // ! LZMA_LOG_BSR
+
+#define kNumLogBits (11 + sizeof(size_t) / 8 * 3)
 
 #define kDicLogSizeMaxCompress ((kNumLogBits - 1) * 2 + 7)
 
@@ -163,7 +250,7 @@
 #define GetPosSlot2(pos, res) { BSR2_RET(pos, res); }
 #define GetPosSlot(pos, res) { if (pos < kNumFullDistances) res = p->g_FastPos[pos & (kNumFullDistances - 1)]; else BSR2_RET(pos, res); }
 
-#endif
+#endif // LZMA_LOG_BSR
 
 
 #define LZMA_NUM_REPS 4
@@ -193,7 +280,7 @@
 
 #define kNumLenToPosStates 4
 #define kNumPosSlotBits 6
-#define kDicLogSizeMin 0
+// #define kDicLogSizeMin 0
 #define kDicLogSizeMax 32
 #define kDistTableSizeMax (kDicLogSizeMax * 2)
 
@@ -299,7 +386,7 @@
 typedef struct
 {
   void *matchFinderObj;
-  IMatchFinder matchFinder;
+  IMatchFinder2 matchFinder;
 
   unsigned optCur;
   unsigned optEnd;
@@ -344,10 +431,14 @@
   // begin of CMatchFinderMt is used in LZ thread
   CMatchFinderMt matchFinderMt;
   // end of CMatchFinderMt is used in BT and HASH threads
+  // #else
+  // CMatchFinder matchFinderBase;
   #endif
-
   CMatchFinder matchFinderBase;
 
+  
+  // we suppose that we have 8-bytes alignment after CMatchFinder
+ 
   #ifndef _7ZIP_ST
   Byte pad[128];
   #endif
@@ -355,8 +446,10 @@
   // LZ thread
   CProbPrice ProbPrices[kBitModelTotal >> kNumMoveReducingBits];
 
-  UInt32 matches[LZMA_MATCH_LEN_MAX * 2 + 2 + 1];
+  // we want {len , dist} pairs to be 8-bytes aligned in matches array
+  UInt32 matches[LZMA_MATCH_LEN_MAX * 2 + 2];
 
+  // we want 8-bytes alignment here
   UInt32 alignPrices[kAlignTableSize];
   UInt32 posSlotPrices[kNumLenToPosStates][kDistTableSizeMax];
   UInt32 distancesPrices[kNumLenToPosStates][kNumFullDistances];
@@ -385,12 +478,19 @@
 
   CSaveState saveState;
 
+  // BoolInt mf_Failure;
   #ifndef _7ZIP_ST
   Byte pad2[128];
   #endif
 } CLzmaEnc;
 
 
+#define MFB (p->matchFinderBase)
+/*
+#ifndef _7ZIP_ST
+#define MFB (p->matchFinderMt.MatchFinder)
+#endif
+*/
 
 #define COPY_ARR(dest, src, arr) memcpy(dest->arr, src->arr, sizeof(src->arr));
 
@@ -455,41 +555,51 @@
 
   if (props.lc > LZMA_LC_MAX
       || props.lp > LZMA_LP_MAX
-      || props.pb > LZMA_PB_MAX
-      || props.dictSize > ((UInt64)1 << kDicLogSizeMaxCompress)
-      || props.dictSize > kLzmaMaxHistorySize)
+      || props.pb > LZMA_PB_MAX)
     return SZ_ERROR_PARAM;
 
+
+  if (props.dictSize > kLzmaMaxHistorySize)
+    props.dictSize = kLzmaMaxHistorySize;
+
+  #ifndef LZMA_LOG_BSR
+  {
+    const UInt64 dict64 = props.dictSize;
+    if (dict64 > ((UInt64)1 << kDicLogSizeMaxCompress))
+      return SZ_ERROR_PARAM;
+  }
+  #endif
+
   p->dictSize = props.dictSize;
   {
-    unsigned fb = props.fb;
+    unsigned fb = (unsigned)props.fb;
     if (fb < 5)
       fb = 5;
     if (fb > LZMA_MATCH_LEN_MAX)
       fb = LZMA_MATCH_LEN_MAX;
     p->numFastBytes = fb;
   }
-  p->lc = props.lc;
-  p->lp = props.lp;
-  p->pb = props.pb;
+  p->lc = (unsigned)props.lc;
+  p->lp = (unsigned)props.lp;
+  p->pb = (unsigned)props.pb;
   p->fastMode = (props.algo == 0);
   // p->_maxMode = True;
-  p->matchFinderBase.btMode = (Byte)(props.btMode ? 1 : 0);
+  MFB.btMode = (Byte)(props.btMode ? 1 : 0);
   {
     unsigned numHashBytes = 4;
     if (props.btMode)
     {
-      if (props.numHashBytes < 2)
-        numHashBytes = 2;
-      else if (props.numHashBytes < 4)
-        numHashBytes = props.numHashBytes;
+           if (props.numHashBytes <  2) numHashBytes = 2;
+      else if (props.numHashBytes <  4) numHashBytes = (unsigned)props.numHashBytes;
     }
-    p->matchFinderBase.numHashBytes = numHashBytes;
+    if (props.numHashBytes >= 5) numHashBytes = 5;
+
+    MFB.numHashBytes = numHashBytes;
   }
 
-  p->matchFinderBase.cutValue = props.mc;
+  MFB.cutValue = props.mc;
 
-  p->writeEndMark = props.writeEndMark;
+  p->writeEndMark = (BoolInt)props.writeEndMark;
 
   #ifndef _7ZIP_ST
   /*
@@ -500,6 +610,8 @@
   }
   */
   p->multiThread = (props.numThreads > 1);
+  p->matchFinderMt.btSync.affinity =
+  p->matchFinderMt.hashSync.affinity = props.affinity;
   #endif
 
   return SZ_OK;
@@ -509,7 +621,7 @@
 void LzmaEnc_SetDataSize(CLzmaEncHandle pp, UInt64 expectedDataSiize)
 {
   CLzmaEnc *p = (CLzmaEnc *)pp;
-  p->matchFinderBase.expectedDataSize = expectedDataSiize;
+  MFB.expectedDataSize = expectedDataSiize;
 }
 
 
@@ -536,8 +648,8 @@
   p->bufBase = NULL;
 }
 
-#define RangeEnc_GetProcessed(p)       ((p)->processed + ((p)->buf - (p)->bufBase) + (p)->cacheSize)
-#define RangeEnc_GetProcessed_sizet(p) ((size_t)(p)->processed + ((p)->buf - (p)->bufBase) + (size_t)(p)->cacheSize)
+#define RangeEnc_GetProcessed(p)       (        (p)->processed + (size_t)((p)->buf - (p)->bufBase) +         (p)->cacheSize)
+#define RangeEnc_GetProcessed_sizet(p) ((size_t)(p)->processed + (size_t)((p)->buf - (p)->bufBase) + (size_t)(p)->cacheSize)
 
 #define RC_BUF_SIZE (1 << 16)
 
@@ -556,12 +668,11 @@
 static void RangeEnc_Free(CRangeEnc *p, ISzAllocPtr alloc)
 {
   ISzAlloc_Free(alloc, p->bufBase);
-  p->bufBase = 0;
+  p->bufBase = NULL;
 }
 
 static void RangeEnc_Init(CRangeEnc *p)
 {
-  /* Stream.Init(); */
   p->range = 0xFFFFFFFF;
   p->cache = 0;
   p->low = 0;
@@ -575,12 +686,12 @@
 
 MY_NO_INLINE static void RangeEnc_FlushStream(CRangeEnc *p)
 {
-  size_t num;
-  if (p->res != SZ_OK)
-    return;
-  num = p->buf - p->bufBase;
-  if (num != ISeqOutStream_Write(p->outStream, p->bufBase, num))
-    p->res = SZ_ERROR_WRITE;
+  const size_t num = (size_t)(p->buf - p->bufBase);
+  if (p->res == SZ_OK)
+  {
+    if (num != ISeqOutStream_Write(p->outStream, p->bufBase, num))
+      p->res = SZ_ERROR_WRITE;
+  }
   p->processed += num;
   p->buf = p->bufBase;
 }
@@ -656,7 +767,7 @@
   range += newBound & mask; \
   mask &= (kBitModelTotal - ((1 << kNumMoveBits) - 1)); \
   mask += ((1 << kNumMoveBits) - 1); \
-  ttt += (Int32)(mask - ttt) >> kNumMoveBits; \
+  ttt += (UInt32)((Int32)(mask - ttt) >> kNumMoveBits); \
   *(prob) = (CLzmaProb)ttt; \
   RC_NORM(p) \
   }
@@ -749,7 +860,7 @@
         bitCount++;
       }
     }
-    ProbPrices[i] = (CProbPrice)((kNumBitModelTotalBits << kCyclesBits) - 15 - bitCount);
+    ProbPrices[i] = (CProbPrice)(((unsigned)kNumBitModelTotalBits << kCyclesBits) - 15 - bitCount);
     // printf("\n%3d: %5d", i, ProbPrices[i]);
   }
 }
@@ -985,7 +1096,11 @@
   
   p->additionalOffset++;
   p->numAvail = p->matchFinder.GetNumAvailableBytes(p->matchFinderObj);
-  numPairs = p->matchFinder.GetMatches(p->matchFinderObj, p->matches);
+  {
+    const UInt32 *d = p->matchFinder.GetMatches(p->matchFinderObj, p->matches);
+    // if (!d) { p->mf_Failure = True; *numPairsRes = 0;  return 0; }
+    numPairs = (unsigned)(d - p->matches);
+  }
   *numPairsRes = numPairs;
   
   #ifdef SHOW_STAT
@@ -1001,7 +1116,7 @@
   if (numPairs == 0)
     return 0;
   {
-    unsigned len = p->matches[(size_t)numPairs - 2];
+    const unsigned len = p->matches[(size_t)numPairs - 2];
     if (len != p->numFastBytes)
       return len;
     {
@@ -1011,7 +1126,7 @@
       {
         const Byte *p1 = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1;
         const Byte *p2 = p1 + len;
-        ptrdiff_t dif = (ptrdiff_t)-1 - p->matches[(size_t)numPairs - 1];
+        const ptrdiff_t dif = (ptrdiff_t)-1 - (ptrdiff_t)p->matches[(size_t)numPairs - 1];
         const Byte *lim = p1 + numAvail;
         for (; p2 != lim && *p2 == p2[dif]; p2++)
         {}
@@ -1167,6 +1282,8 @@
       repLens[i] = len;
       if (len > repLens[repMaxIndex])
         repMaxIndex = i;
+      if (len == LZMA_MATCH_LEN_MAX) // 21.03 : optimization
+        break;
     }
     
     if (repLens[repMaxIndex] >= p->numFastBytes)
@@ -1179,10 +1296,12 @@
     }
     
     matches = p->matches;
+    #define MATCHES  matches
+    // #define MATCHES  p->matches
     
     if (mainLen >= p->numFastBytes)
     {
-      p->backRes = matches[(size_t)numPairs - 1] + LZMA_NUM_REPS;
+      p->backRes = MATCHES[(size_t)numPairs - 1] + LZMA_NUM_REPS;
       MOVE_POS(p, mainLen - 1)
       return mainLen;
     }
@@ -1276,13 +1395,13 @@
         if (len < 2)
           len = 2;
         else
-          while (len > matches[offs])
+          while (len > MATCHES[offs])
             offs += 2;
     
         for (; ; len++)
         {
           COptimal *opt;
-          UInt32 dist = matches[(size_t)offs + 1];
+          UInt32 dist = MATCHES[(size_t)offs + 1];
           UInt32 price = normalMatchPrice + GET_PRICE_LEN(&p->lenEnc, posState, len);
           unsigned lenToPosState = GetLenToPosState(len);
        
@@ -1306,7 +1425,7 @@
             opt->extra = 0;
           }
           
-          if (len == matches[offs])
+          if (len == MATCHES[offs])
           {
             offs += 2;
             if (offs == numPairs)
@@ -1727,8 +1846,8 @@
     if (newLen > numAvail)
     {
       newLen = numAvail;
-      for (numPairs = 0; newLen > matches[numPairs]; numPairs += 2);
-      matches[numPairs] = (UInt32)newLen;
+      for (numPairs = 0; newLen > MATCHES[numPairs]; numPairs += 2);
+      MATCHES[numPairs] = (UInt32)newLen;
       numPairs += 2;
     }
     
@@ -1747,9 +1866,9 @@
       }
 
       offs = 0;
-      while (startLen > matches[offs])
+      while (startLen > MATCHES[offs])
         offs += 2;
-      dist = matches[(size_t)offs + 1];
+      dist = MATCHES[(size_t)offs + 1];
       
       // if (dist >= kNumFullDistances)
       GetPosSlot2(dist, posSlot);
@@ -1776,7 +1895,7 @@
           }
         }
 
-        if (len == matches[offs])
+        if (len == MATCHES[offs])
         {
           // if (p->_maxMode) {
           // MATCH : LIT : REP_0
@@ -1841,7 +1960,7 @@
           offs += 2;
           if (offs == numPairs)
             break;
-          dist = matches[(size_t)offs + 1];
+          dist = MATCHES[(size_t)offs + 1];
           // if (dist >= kNumFullDistances)
             GetPosSlot2(dist, posSlot);
         }
@@ -2059,8 +2178,23 @@
     return p->result;
   if (p->rc.res != SZ_OK)
     p->result = SZ_ERROR_WRITE;
-  if (p->matchFinderBase.result != SZ_OK)
+
+  #ifndef _7ZIP_ST
+  if (
+      // p->mf_Failure ||
+        (p->mtMode &&
+          ( // p->matchFinderMt.failure_LZ_LZ ||
+            p->matchFinderMt.failure_LZ_BT))
+     )
+  {
+    p->result = MY_HRES_ERROR__INTERNAL_ERROR;
+    // printf("\nCheckErrors p->matchFinderMt.failureLZ\n");
+  }
+  #endif
+
+  if (MFB.result != SZ_OK)
     p->result = SZ_ERROR_READ;
+  
   if (p->result != SZ_OK)
     p->finished = True;
   return p->result;
@@ -2198,14 +2332,14 @@
 
 
 
-void LzmaEnc_Construct(CLzmaEnc *p)
+static void LzmaEnc_Construct(CLzmaEnc *p)
 {
   RangeEnc_Construct(&p->rc);
-  MatchFinder_Construct(&p->matchFinderBase);
+  MatchFinder_Construct(&MFB);
   
   #ifndef _7ZIP_ST
+  p->matchFinderMt.MatchFinder = &MFB;
   MatchFinderMt_Construct(&p->matchFinderMt);
-  p->matchFinderMt.MatchFinder = &p->matchFinderBase;
   #endif
 
   {
@@ -2221,7 +2355,6 @@
   LzmaEnc_InitPriceTables(p->ProbPrices);
   p->litProbs = NULL;
   p->saveState.litProbs = NULL;
-
 }
 
 CLzmaEncHandle LzmaEnc_Create(ISzAllocPtr alloc)
@@ -2233,7 +2366,7 @@
   return p;
 }
 
-void LzmaEnc_FreeLits(CLzmaEnc *p, ISzAllocPtr alloc)
+static void LzmaEnc_FreeLits(CLzmaEnc *p, ISzAllocPtr alloc)
 {
   ISzAlloc_Free(alloc, p->litProbs);
   ISzAlloc_Free(alloc, p->saveState.litProbs);
@@ -2241,13 +2374,13 @@
   p->saveState.litProbs = NULL;
 }
 
-void LzmaEnc_Destruct(CLzmaEnc *p, ISzAllocPtr alloc, ISzAllocPtr allocBig)
+static void LzmaEnc_Destruct(CLzmaEnc *p, ISzAllocPtr alloc, ISzAllocPtr allocBig)
 {
   #ifndef _7ZIP_ST
   MatchFinderMt_Destruct(&p->matchFinderMt, allocBig);
   #endif
   
-  MatchFinder_Free(&p->matchFinderBase, allocBig);
+  MatchFinder_Free(&MFB, allocBig);
   LzmaEnc_FreeLits(p, alloc);
   RangeEnc_Free(&p->rc, alloc);
 }
@@ -2259,11 +2392,18 @@
 }
 
 
+MY_NO_INLINE
 static SRes LzmaEnc_CodeOneBlock(CLzmaEnc *p, UInt32 maxPackSize, UInt32 maxUnpackSize)
 {
   UInt32 nowPos32, startPos32;
   if (p->needInit)
   {
+    #ifndef _7ZIP_ST
+    if (p->mtMode)
+    {
+      RINOK(MatchFinderMt_InitMt(&p->matchFinderMt));
+    }
+    #endif
     p->matchFinder.Init(p->matchFinderObj);
     p->needInit = 0;
   }
@@ -2521,12 +2661,12 @@
           // { int y; for (y = 0; y < 100; y++) {
           FillDistancesPrices(p);
           // }}
-          LenPriceEnc_UpdateTables(&p->lenEnc, 1 << p->pb, &p->lenProbs, p->ProbPrices);
+          LenPriceEnc_UpdateTables(&p->lenEnc, (unsigned)1 << p->pb, &p->lenProbs, p->ProbPrices);
         }
         if (p->repLenEncCounter <= 0)
         {
           p->repLenEncCounter = REP_LEN_COUNT;
-          LenPriceEnc_UpdateTables(&p->repLenEnc, 1 << p->pb, &p->repLenProbs, p->ProbPrices);
+          LenPriceEnc_UpdateTables(&p->repLenEnc, (unsigned)1 << p->pb, &p->repLenProbs, p->ProbPrices);
         }
       }
     
@@ -2559,11 +2699,13 @@
 static SRes LzmaEnc_Alloc(CLzmaEnc *p, UInt32 keepWindowSize, ISzAllocPtr alloc, ISzAllocPtr allocBig)
 {
   UInt32 beforeSize = kNumOpts;
+  UInt32 dictSize;
+
   if (!RangeEnc_Alloc(&p->rc, alloc))
     return SZ_ERROR_MEM;
 
   #ifndef _7ZIP_ST
-  p->mtMode = (p->multiThread && !p->fastMode && (p->matchFinderBase.btMode != 0));
+  p->mtMode = (p->multiThread && !p->fastMode && (MFB.btMode != 0));
   #endif
 
   {
@@ -2582,36 +2724,56 @@
     }
   }
 
-  p->matchFinderBase.bigHash = (Byte)(p->dictSize > kBigHashDicLimit ? 1 : 0);
+  MFB.bigHash = (Byte)(p->dictSize > kBigHashDicLimit ? 1 : 0);
 
-  if (beforeSize + p->dictSize < keepWindowSize)
-    beforeSize = keepWindowSize - p->dictSize;
+
+  dictSize = p->dictSize;
+  if (dictSize == ((UInt32)2 << 30) ||
+      dictSize == ((UInt32)3 << 30))
+  {
+    /* 21.03 : here we reduce the dictionary for 2 reasons:
+       1) we don't want 32-bit back_distance matches in decoder for 2 GB dictionary.
+       2) we want to elimate useless last MatchFinder_Normalize3() for corner cases,
+          where data size is aligned for 1 GB: 5/6/8 GB.
+          That reducing must be >= 1 for such corner cases. */
+    dictSize -= 1;
+  }
+
+  if (beforeSize + dictSize < keepWindowSize)
+    beforeSize = keepWindowSize - dictSize;
+
+  /* in worst case we can look ahead for
+        max(LZMA_MATCH_LEN_MAX, numFastBytes + 1 + numFastBytes) bytes.
+     we send larger value for (keepAfter) to MantchFinder_Create():
+        (numFastBytes + LZMA_MATCH_LEN_MAX + 1)
+  */
 
   #ifndef _7ZIP_ST
   if (p->mtMode)
   {
-    RINOK(MatchFinderMt_Create(&p->matchFinderMt, p->dictSize, beforeSize, p->numFastBytes,
-        LZMA_MATCH_LEN_MAX
-        + 1  /* 18.04 */
+    RINOK(MatchFinderMt_Create(&p->matchFinderMt, dictSize, beforeSize,
+        p->numFastBytes, LZMA_MATCH_LEN_MAX + 1 /* 18.04 */
         , allocBig));
     p->matchFinderObj = &p->matchFinderMt;
-    p->matchFinderBase.bigHash = (Byte)(
-        (p->dictSize > kBigHashDicLimit && p->matchFinderBase.hashMask >= 0xFFFFFF) ? 1 : 0);
+    MFB.bigHash = (Byte)(
+        (p->dictSize > kBigHashDicLimit && MFB.hashMask >= 0xFFFFFF) ? 1 : 0);
     MatchFinderMt_CreateVTable(&p->matchFinderMt, &p->matchFinder);
   }
   else
   #endif
   {
-    if (!MatchFinder_Create(&p->matchFinderBase, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig))
+    if (!MatchFinder_Create(&MFB, dictSize, beforeSize,
+        p->numFastBytes, LZMA_MATCH_LEN_MAX + 1 /* 21.03 */
+        , allocBig))
       return SZ_ERROR_MEM;
-    p->matchFinderObj = &p->matchFinderBase;
-    MatchFinder_CreateVTable(&p->matchFinderBase, &p->matchFinder);
+    p->matchFinderObj = &MFB;
+    MatchFinder_CreateVTable(&MFB, &p->matchFinder);
   }
   
   return SZ_OK;
 }
 
-void LzmaEnc_Init(CLzmaEnc *p)
+static void LzmaEnc_Init(CLzmaEnc *p)
 {
   unsigned i;
   p->state = 0;
@@ -2675,12 +2837,14 @@
 
   p->additionalOffset = 0;
 
-  p->pbMask = (1 << p->pb) - 1;
+  p->pbMask = ((unsigned)1 << p->pb) - 1;
   p->lpMask = ((UInt32)0x100 << p->lp) - ((unsigned)0x100 >> p->lc);
+
+  // p->mf_Failure = False;
 }
 
 
-void LzmaEnc_InitPrices(CLzmaEnc *p)
+static void LzmaEnc_InitPrices(CLzmaEnc *p)
 {
   if (!p->fastMode)
   {
@@ -2694,8 +2858,8 @@
 
   p->repLenEncCounter = REP_LEN_COUNT;
 
-  LenPriceEnc_UpdateTables(&p->lenEnc, 1 << p->pb, &p->lenProbs, p->ProbPrices);
-  LenPriceEnc_UpdateTables(&p->repLenEnc, 1 << p->pb, &p->repLenProbs, p->ProbPrices);
+  LenPriceEnc_UpdateTables(&p->lenEnc, (unsigned)1 << p->pb, &p->lenProbs, p->ProbPrices);
+  LenPriceEnc_UpdateTables(&p->repLenEnc, (unsigned)1 << p->pb, &p->repLenProbs, p->ProbPrices);
 }
 
 static SRes LzmaEnc_AllocAndInit(CLzmaEnc *p, UInt32 keepWindowSize, ISzAllocPtr alloc, ISzAllocPtr allocBig)
@@ -2719,7 +2883,7 @@
     ISzAllocPtr alloc, ISzAllocPtr allocBig)
 {
   CLzmaEnc *p = (CLzmaEnc *)pp;
-  p->matchFinderBase.stream = inStream;
+  MFB.stream = inStream;
   p->needInit = 1;
   p->rc.outStream = outStream;
   return LzmaEnc_AllocAndInit(p, 0, alloc, allocBig);
@@ -2730,16 +2894,16 @@
     ISzAllocPtr alloc, ISzAllocPtr allocBig)
 {
   CLzmaEnc *p = (CLzmaEnc *)pp;
-  p->matchFinderBase.stream = inStream;
+  MFB.stream = inStream;
   p->needInit = 1;
   return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig);
 }
 
 static void LzmaEnc_SetInputBuf(CLzmaEnc *p, const Byte *src, SizeT srcLen)
 {
-  p->matchFinderBase.directInput = 1;
-  p->matchFinderBase.bufferBase = (Byte *)src;
-  p->matchFinderBase.directInputRem = srcLen;
+  MFB.directInput = 1;
+  MFB.bufferBase = (Byte *)src;
+  MFB.directInputRem = srcLen;
 }
 
 SRes LzmaEnc_MemPrepare(CLzmaEncHandle pp, const Byte *src, SizeT srcLen,
@@ -2781,19 +2945,23 @@
     size = p->rem;
     p->overflow = True;
   }
-  memcpy(p->data, data, size);
-  p->rem -= size;
-  p->data += size;
+  if (size != 0)
+  {
+    memcpy(p->data, data, size);
+    p->rem -= size;
+    p->data += size;
+  }
   return size;
 }
 
 
+/*
 UInt32 LzmaEnc_GetNumAvailableBytes(CLzmaEncHandle pp)
 {
   const CLzmaEnc *p = (CLzmaEnc *)pp;
   return p->matchFinder.GetNumAvailableBytes(p->matchFinderObj);
 }
-
+*/
 
 const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle pp)
 {
@@ -2841,6 +3009,7 @@
 }
 
 
+MY_NO_INLINE
 static SRes LzmaEnc_Encode2(CLzmaEnc *p, ICompressProgress *progress)
 {
   SRes res = SZ_OK;
@@ -2870,7 +3039,7 @@
   LzmaEnc_Finish(p);
 
   /*
-  if (res == SZ_OK && !Inline_MatchFinder_IsFinishedOK(&p->matchFinderBase))
+  if (res == SZ_OK && !Inline_MatchFinder_IsFinishedOK(&MFB))
     res = SZ_ERROR_FAIL;
   }
   */
@@ -2889,35 +3058,43 @@
 
 SRes LzmaEnc_WriteProperties(CLzmaEncHandle pp, Byte *props, SizeT *size)
 {
-  CLzmaEnc *p = (CLzmaEnc *)pp;
-  unsigned i;
-  UInt32 dictSize = p->dictSize;
   if (*size < LZMA_PROPS_SIZE)
     return SZ_ERROR_PARAM;
   *size = LZMA_PROPS_SIZE;
-  props[0] = (Byte)((p->pb * 5 + p->lp) * 9 + p->lc);
-
-  if (dictSize >= ((UInt32)1 << 22))
   {
-    UInt32 kDictMask = ((UInt32)1 << 20) - 1;
-    if (dictSize < (UInt32)0xFFFFFFFF - kDictMask)
-      dictSize = (dictSize + kDictMask) & ~kDictMask;
-  }
-  else for (i = 11; i <= 30; i++)
-  {
-    if (dictSize <= ((UInt32)2 << i)) { dictSize = (2 << i); break; }
-    if (dictSize <= ((UInt32)3 << i)) { dictSize = (3 << i); break; }
-  }
+    const CLzmaEnc *p = (const CLzmaEnc *)pp;
+    const UInt32 dictSize = p->dictSize;
+    UInt32 v;
+    props[0] = (Byte)((p->pb * 5 + p->lp) * 9 + p->lc);
+    
+    // we write aligned dictionary value to properties for lzma decoder
+    if (dictSize >= ((UInt32)1 << 21))
+    {
+      const UInt32 kDictMask = ((UInt32)1 << 20) - 1;
+      v = (dictSize + kDictMask) & ~kDictMask;
+      if (v < dictSize)
+        v = dictSize;
+    }
+    else
+    {
+      unsigned i = 11 * 2;
+      do
+      {
+        v = (UInt32)(2 + (i & 1)) << (i >> 1);
+        i++;
+      }
+      while (v < dictSize);
+    }
 
-  for (i = 0; i < 4; i++)
-    props[1 + i] = (Byte)(dictSize >> (8 * i));
-  return SZ_OK;
+    SetUi32(props + 1, v);
+    return SZ_OK;
+  }
 }
 
 
 unsigned LzmaEnc_IsWriteEndMark(CLzmaEncHandle pp)
 {
-  return ((CLzmaEnc *)pp)->writeEndMark;
+  return (unsigned)((CLzmaEnc *)pp)->writeEndMark;
 }
 
 
@@ -2974,3 +3151,15 @@
   LzmaEnc_Destroy(p, alloc, allocBig);
   return res;
 }
+
+
+/*
+#ifndef _7ZIP_ST
+void LzmaEnc_GetLzThreads(CLzmaEncHandle pp, HANDLE lz_threads[2])
+{
+  const CLzmaEnc *p = (CLzmaEnc *)pp;
+  lz_threads[0] = p->matchFinderMt.hashSync.thread;
+  lz_threads[1] = p->matchFinderMt.btSync.thread;
+}
+#endif
+*/
diff --git a/third_party/lzma_sdk/LzmaEnc.h b/third_party/lzma_sdk/LzmaEnc.h
index 9194ee57..bc2ed5042 100644
--- a/third_party/lzma_sdk/LzmaEnc.h
+++ b/third_party/lzma_sdk/LzmaEnc.h
@@ -1,5 +1,5 @@
 /*  LzmaEnc.h -- LZMA Encoder
-2017-07-27 : Igor Pavlov : Public domain */
+2019-10-30 : Igor Pavlov : Public domain */
 
 #ifndef __LZMA_ENC_H
 #define __LZMA_ENC_H
@@ -29,6 +29,8 @@
 
   UInt64 reduceSize; /* estimated size of data that will be compressed. default = (UInt64)(Int64)-1.
                         Encoder uses this value to reduce dictionary size */
+
+  UInt64 affinity;
 } CLzmaEncProps;
 
 void LzmaEncProps_Init(CLzmaEncProps *p);
diff --git a/third_party/lzma_sdk/LzmaLib.h b/third_party/lzma_sdk/LzmaLib.h
index 88fa87d..c343a85 100644
--- a/third_party/lzma_sdk/LzmaLib.h
+++ b/third_party/lzma_sdk/LzmaLib.h
@@ -1,5 +1,5 @@
 /* LzmaLib.h -- LZMA library interface
-2013-01-18 : Igor Pavlov : Public domain */
+2021-04-03 : Igor Pavlov : Public domain */
 
 #ifndef __LZMA_LIB_H
 #define __LZMA_LIB_H
@@ -40,14 +40,16 @@
 level - compression level: 0 <= level <= 9;
 
   level dictSize algo  fb
-    0:    16 KB   0    32
-    1:    64 KB   0    32
-    2:   256 KB   0    32
-    3:     1 MB   0    32
-    4:     4 MB   0    32
+    0:    64 KB   0    32
+    1:   256 KB   0    32
+    2:     1 MB   0    32
+    3:     4 MB   0    32
+    4:    16 MB   0    32
     5:    16 MB   1    32
     6:    32 MB   1    32
-    7+:   64 MB   1    64
+    7:    32 MB   1    64
+    8:    64 MB   1    64
+    9:    64 MB   1    64
  
   The default value for "level" is 5.
 
@@ -83,6 +85,11 @@
 numThreads - The number of thereads. 1 or 2. The default value is 2.
      Fast mode (algo = 0) can use only 1 thread.
 
+In:
+  dest     - output data buffer
+  destLen  - output data buffer size
+  src      - input data
+  srcLen   - input data size
 Out:
   destLen  - processed output size
 Returns:
@@ -108,8 +115,8 @@
 LzmaUncompress
 --------------
 In:
-  dest     - output data
-  destLen  - output data size
+  dest     - output data buffer
+  destLen  - output data buffer size
   src      - input data
   srcLen   - input data size
 Out:
diff --git a/third_party/lzma_sdk/README.chromium b/third_party/lzma_sdk/README.chromium
index 1d99c640..a6d547d 100644
--- a/third_party/lzma_sdk/README.chromium
+++ b/third_party/lzma_sdk/README.chromium
@@ -1,17 +1,17 @@
 Name: LZMA SDK
 Short Name: lzma
 URL: http://www.7-zip.org/sdk.html
-Version: 19.00
-Date: 2019-02-21
+Version: 21.07
+Date: 2021-12-26
 License: Public Domain
 Security Critical: yes
 CPEPrefix: unknown
 
 Description:
-This contains a part of LZMA SDK 19.00.
+This contains a part of LZMA SDK 21.07.
 
 Local Modifications:
-The original code can be found at http://7-zip.org/a/lzma1900.7z.  Only parts
+The original code can be found at https://7-zip.org/a/lzma2107.7z.  Only parts
 of this archive are copied here.  More specifically:
 
 1/ C code required to open 7z archive files and uncompress LZMA
@@ -19,6 +19,18 @@
 3/ source code for SfxSetup, a utility for creating self extracting archives
 4/ C code required for xz decompression (split into its own static library)
 
+7za.exe and 7zr.exe are both standalone command-line utilities to archive and
+extract files. 7zr is "lightweight" and only handles 7zip extensions. 7za can
+handle a few more. 7za.exe comes from https://www.7-zip.org/a/7z2107-extra.7z.
+
+The patch in chromium.patch was applied to 7zCrc.c, CpuArch.c, LZFind.c and
+Sha256.c to disable some ARM code that was failing to build in Android and
+Fuschia as well as some of the AVX2 and SSE4 code for Windows. In Fuschia,
+`#include <asm/hwcap.h>` is not available. In Android builds, `armv8-a+crc` is
+not a known target architecture, even when the -march cflag is passed,
+specifying the CPU type to use. In Windows, Chromium still supports SSE3,
+so it is not be ready to transition to utilizing AVX2 and SSE4, yet.
+
 The patch in Util/SfxSetup/chromium.patch was applied so that:
 
 1/ Fix for includes file names, since the original code causes an include
diff --git a/third_party/lzma_sdk/Sha256.c b/third_party/lzma_sdk/Sha256.c
index 04b688c..2199684 100644
--- a/third_party/lzma_sdk/Sha256.c
+++ b/third_party/lzma_sdk/Sha256.c
@@ -1,5 +1,5 @@
-/* Crypto/Sha256.c -- SHA-256 Hash
-2017-04-03 : Igor Pavlov : Public domain
+/* Sha256.c -- SHA-256 Hash
+2021-04-01 : Igor Pavlov : Public domain
 This code is based on public domain code from Wei Dai's Crypto++ library. */
 
 #include "Precomp.h"
@@ -10,16 +10,108 @@
 #include "RotateDefs.h"
 #include "Sha256.h"
 
-/* define it for speed optimization */
-#ifndef _SFX
-#define _SHA256_UNROLL
-#define _SHA256_UNROLL2
+#if defined(_MSC_VER) && (_MSC_VER < 1900)
+// #define USE_MY_MM
 #endif
 
-/* #define _SHA256_UNROLL2 */
+#ifdef MY_CPU_X86_OR_AMD64
+  #ifdef _MSC_VER
+    #if _MSC_VER >= 1200
+      #define _SHA_SUPPORTED
+    #endif
+  #elif defined(__clang__)
+    #if (__clang_major__ >= 8) // fix that check
+      #define _SHA_SUPPORTED
+    #endif
+  #elif defined(__GNUC__)
+    #if (__GNUC__ >= 8) // fix that check
+      #define _SHA_SUPPORTED
+    #endif
+  #elif defined(__INTEL_COMPILER)
+    #if (__INTEL_COMPILER >= 1800) // fix that check
+      #define _SHA_SUPPORTED
+    #endif
+  #endif
+// TODO(crbug.com/1338627): Enable ARM optimizations
+#elif 0 // defined(MY_CPU_ARM_OR_ARM64)
+  #ifdef _MSC_VER
+    #if _MSC_VER >= 1910
+      #define _SHA_SUPPORTED
+    #endif
+  #elif defined(__clang__)
+    #if (__clang_major__ >= 8) // fix that check
+      #define _SHA_SUPPORTED
+    #endif
+  #elif defined(__GNUC__)
+    #if (__GNUC__ >= 6) // fix that check
+      #define _SHA_SUPPORTED
+    #endif
+  #endif
+#endif
 
-void Sha256_Init(CSha256 *p)
+void MY_FAST_CALL Sha256_UpdateBlocks(UInt32 state[8], const Byte *data, size_t numBlocks);
+
+#ifdef _SHA_SUPPORTED
+  void MY_FAST_CALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks);
+
+  static SHA256_FUNC_UPDATE_BLOCKS g_FUNC_UPDATE_BLOCKS = Sha256_UpdateBlocks;
+  static SHA256_FUNC_UPDATE_BLOCKS g_FUNC_UPDATE_BLOCKS_HW;
+
+  #define UPDATE_BLOCKS(p) p->func_UpdateBlocks
+#else
+  #define UPDATE_BLOCKS(p) Sha256_UpdateBlocks
+#endif
+
+
+BoolInt Sha256_SetFunction(CSha256 *p, unsigned algo)
 {
+  SHA256_FUNC_UPDATE_BLOCKS func = Sha256_UpdateBlocks;
+  
+  #ifdef _SHA_SUPPORTED
+    if (algo != SHA256_ALGO_SW)
+    {
+      if (algo == SHA256_ALGO_DEFAULT)
+        func = g_FUNC_UPDATE_BLOCKS;
+      else
+      {
+        if (algo != SHA256_ALGO_HW)
+          return False;
+        func = g_FUNC_UPDATE_BLOCKS_HW;
+        if (!func)
+          return False;
+      }
+    }
+  #else
+    if (algo > 1)
+      return False;
+  #endif
+
+  p->func_UpdateBlocks = func;
+  return True;
+}
+
+
+/* define it for speed optimization */
+
+#ifdef _SFX
+  #define STEP_PRE 1
+  #define STEP_MAIN 1
+#else
+  #define STEP_PRE 2
+  #define STEP_MAIN 4
+  // #define _SHA256_UNROLL
+#endif
+
+#if STEP_MAIN != 16
+  #define _SHA256_BIG_W
+#endif
+
+
+
+
+void Sha256_InitState(CSha256 *p)
+{
+  p->count = 0;
   p->state[0] = 0x6a09e667;
   p->state[1] = 0xbb67ae85;
   p->state[2] = 0x3c6ef372;
@@ -28,7 +120,17 @@
   p->state[5] = 0x9b05688c;
   p->state[6] = 0x1f83d9ab;
   p->state[7] = 0x5be0cd19;
-  p->count = 0;
+}
+
+void Sha256_Init(CSha256 *p)
+{
+  p->func_UpdateBlocks =
+  #ifdef _SHA_SUPPORTED
+      g_FUNC_UPDATE_BLOCKS;
+  #else
+      NULL;
+  #endif
+  Sha256_InitState(p);
 }
 
 #define S0(x) (rotrFixed(x, 2) ^ rotrFixed(x,13) ^ rotrFixed(x, 22))
@@ -36,61 +138,100 @@
 #define s0(x) (rotrFixed(x, 7) ^ rotrFixed(x,18) ^ (x >> 3))
 #define s1(x) (rotrFixed(x,17) ^ rotrFixed(x,19) ^ (x >> 10))
 
-#define blk0(i) (W[i])
-#define blk2(i) (W[i] += s1(W[((i)-2)&15]) + W[((i)-7)&15] + s0(W[((i)-15)&15]))
-
 #define Ch(x,y,z) (z^(x&(y^z)))
 #define Maj(x,y,z) ((x&y)|(z&(x|y)))
 
-#ifdef _SHA256_UNROLL2
 
-#define R(a,b,c,d,e,f,g,h, i) \
-    h += S1(e) + Ch(e,f,g) + K[(i)+(size_t)(j)] + (j ? blk2(i) : blk0(i)); \
+#define W_PRE(i) (W[(i) + (size_t)(j)] = GetBe32(data + ((size_t)(j) + i) * 4))
+
+#define blk2_main(j, i)  s1(w(j, (i)-2)) + w(j, (i)-7) + s0(w(j, (i)-15))
+
+#ifdef _SHA256_BIG_W
+    // we use +i instead of +(i) to change the order to solve CLANG compiler warning for signed/unsigned.
+    #define w(j, i)     W[(size_t)(j) + i]
+    #define blk2(j, i)  (w(j, i) = w(j, (i)-16) + blk2_main(j, i))
+#else
+    #if STEP_MAIN == 16
+        #define w(j, i)  W[(i) & 15]
+    #else
+        #define w(j, i)  W[((size_t)(j) + (i)) & 15]
+    #endif
+    #define blk2(j, i)  (w(j, i) += blk2_main(j, i))
+#endif
+
+#define W_MAIN(i)  blk2(j, i)
+
+
+#define T1(wx, i) \
+    tmp = h + S1(e) + Ch(e,f,g) + K[(i)+(size_t)(j)] + wx(i); \
+    h = g; \
+    g = f; \
+    f = e; \
+    e = d + tmp; \
+    tmp += S0(a) + Maj(a, b, c); \
+    d = c; \
+    c = b; \
+    b = a; \
+    a = tmp; \
+
+#define R1_PRE(i)  T1( W_PRE, i)
+#define R1_MAIN(i) T1( W_MAIN, i)
+
+#if (!defined(_SHA256_UNROLL) || STEP_MAIN < 8) && (STEP_MAIN >= 4)
+#define R2_MAIN(i) \
+    R1_MAIN(i) \
+    R1_MAIN(i + 1) \
+
+#endif
+
+
+
+#if defined(_SHA256_UNROLL) && STEP_MAIN >= 8
+
+#define T4( a,b,c,d,e,f,g,h, wx, i) \
+    h += S1(e) + Ch(e,f,g) + K[(i)+(size_t)(j)] + wx(i); \
+    tmp = h; \
+    h += d; \
+    d = tmp + S0(a) + Maj(a, b, c); \
+
+#define R4( wx, i) \
+    T4 ( a,b,c,d,e,f,g,h, wx, (i  )); \
+    T4 ( d,a,b,c,h,e,f,g, wx, (i+1)); \
+    T4 ( c,d,a,b,g,h,e,f, wx, (i+2)); \
+    T4 ( b,c,d,a,f,g,h,e, wx, (i+3)); \
+
+#define R4_PRE(i)  R4( W_PRE, i)
+#define R4_MAIN(i) R4( W_MAIN, i)
+
+
+#define T8( a,b,c,d,e,f,g,h, wx, i) \
+    h += S1(e) + Ch(e,f,g) + K[(i)+(size_t)(j)] + wx(i); \
     d += h; \
-    h += S0(a) + Maj(a, b, c)
+    h += S0(a) + Maj(a, b, c); \
 
-#define RX_8(i) \
-  R(a,b,c,d,e,f,g,h, i); \
-  R(h,a,b,c,d,e,f,g, i+1); \
-  R(g,h,a,b,c,d,e,f, i+2); \
-  R(f,g,h,a,b,c,d,e, i+3); \
-  R(e,f,g,h,a,b,c,d, i+4); \
-  R(d,e,f,g,h,a,b,c, i+5); \
-  R(c,d,e,f,g,h,a,b, i+6); \
-  R(b,c,d,e,f,g,h,a, i+7)
+#define R8( wx, i) \
+    T8 ( a,b,c,d,e,f,g,h, wx, i  ); \
+    T8 ( h,a,b,c,d,e,f,g, wx, i+1); \
+    T8 ( g,h,a,b,c,d,e,f, wx, i+2); \
+    T8 ( f,g,h,a,b,c,d,e, wx, i+3); \
+    T8 ( e,f,g,h,a,b,c,d, wx, i+4); \
+    T8 ( d,e,f,g,h,a,b,c, wx, i+5); \
+    T8 ( c,d,e,f,g,h,a,b, wx, i+6); \
+    T8 ( b,c,d,e,f,g,h,a, wx, i+7); \
 
-#define RX_16  RX_8(0); RX_8(8);
-
-#else
-
-#define a(i) T[(0-(i))&7]
-#define b(i) T[(1-(i))&7]
-#define c(i) T[(2-(i))&7]
-#define d(i) T[(3-(i))&7]
-#define e(i) T[(4-(i))&7]
-#define f(i) T[(5-(i))&7]
-#define g(i) T[(6-(i))&7]
-#define h(i) T[(7-(i))&7]
-
-#define R(i) \
-    h(i) += S1(e(i)) + Ch(e(i),f(i),g(i)) + K[(i)+(size_t)(j)] + (j ? blk2(i) : blk0(i)); \
-    d(i) += h(i); \
-    h(i) += S0(a(i)) + Maj(a(i), b(i), c(i)) \
-
-#ifdef _SHA256_UNROLL
-
-#define RX_8(i)  R(i+0); R(i+1); R(i+2); R(i+3); R(i+4); R(i+5); R(i+6); R(i+7);
-#define RX_16  RX_8(0); RX_8(8);
-
-#else
-
-#define RX_16  unsigned i; for (i = 0; i < 16; i++) { R(i); }
+#define R8_PRE(i)  R8( W_PRE, i)
+#define R8_MAIN(i) R8( W_MAIN, i)
 
 #endif
 
-#endif
+void MY_FAST_CALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks);
 
-static const UInt32 K[64] = {
+// static
+extern MY_ALIGN(64)
+const UInt32 SHA256_K_ARRAY[64];
+
+MY_ALIGN(64)
+const UInt32 SHA256_K_ARRAY[64] = {
   0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
   0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
   0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
@@ -109,30 +250,27 @@
   0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
 };
 
-static void Sha256_WriteByteBlock(CSha256 *p)
-{
-  UInt32 W[16];
-  unsigned j;
-  UInt32 *state;
+#define K SHA256_K_ARRAY
 
-  #ifdef _SHA256_UNROLL2
-  UInt32 a,b,c,d,e,f,g,h;
+
+MY_NO_INLINE
+void MY_FAST_CALL Sha256_UpdateBlocks(UInt32 state[8], const Byte *data, size_t numBlocks)
+{
+  UInt32 W
+  #ifdef _SHA256_BIG_W
+      [64];
   #else
-  UInt32 T[8];
+      [16];
   #endif
 
-  for (j = 0; j < 16; j += 4)
-  {
-    const Byte *ccc = p->buffer + j * 4;
-    W[j    ] = GetBe32(ccc);
-    W[j + 1] = GetBe32(ccc + 4);
-    W[j + 2] = GetBe32(ccc + 8);
-    W[j + 3] = GetBe32(ccc + 12);
-  }
+  unsigned j;
 
-  state = p->state;
+  UInt32 a,b,c,d,e,f,g,h;
 
-  #ifdef _SHA256_UNROLL2
+  #if !defined(_SHA256_UNROLL) || (STEP_MAIN <= 4) || (STEP_PRE <= 4)
+  UInt32 tmp;
+  #endif
+  
   a = state[0];
   b = state[1];
   c = state[2];
@@ -141,39 +279,96 @@
   f = state[5];
   g = state[6];
   h = state[7];
-  #else
-  for (j = 0; j < 8; j++)
-    T[j] = state[j];
-  #endif
 
-  for (j = 0; j < 64; j += 16)
+  while (numBlocks)
   {
-    RX_16
+
+  for (j = 0; j < 16; j += STEP_PRE)
+  {
+    #if STEP_PRE > 4
+
+      #if STEP_PRE < 8
+      R4_PRE(0);
+      #else
+      R8_PRE(0);
+      #if STEP_PRE == 16
+      R8_PRE(8);
+      #endif
+      #endif
+
+    #else
+
+      R1_PRE(0);
+      #if STEP_PRE >= 2
+      R1_PRE(1);
+      #if STEP_PRE >= 4
+      R1_PRE(2);
+      R1_PRE(3);
+      #endif
+      #endif
+    
+    #endif
   }
 
-  #ifdef _SHA256_UNROLL2
-  state[0] += a;
-  state[1] += b;
-  state[2] += c;
-  state[3] += d;
-  state[4] += e;
-  state[5] += f;
-  state[6] += g;
-  state[7] += h;
-  #else
-  for (j = 0; j < 8; j++)
-    state[j] += T[j];
-  #endif
-  
+  for (j = 16; j < 64; j += STEP_MAIN)
+  {
+    #if defined(_SHA256_UNROLL) && STEP_MAIN >= 8
+
+      #if STEP_MAIN < 8
+      R4_MAIN(0);
+      #else
+      R8_MAIN(0);
+      #if STEP_MAIN == 16
+      R8_MAIN(8);
+      #endif
+      #endif
+
+    #else
+      
+      R1_MAIN(0);
+      #if STEP_MAIN >= 2
+      R1_MAIN(1);
+      #if STEP_MAIN >= 4
+      R2_MAIN(2);
+      #if STEP_MAIN >= 8
+      R2_MAIN(4);
+      R2_MAIN(6);
+      #if STEP_MAIN >= 16
+      R2_MAIN(8);
+      R2_MAIN(10);
+      R2_MAIN(12);
+      R2_MAIN(14);
+      #endif
+      #endif
+      #endif
+      #endif
+    #endif
+  }
+
+  a += state[0]; state[0] = a;
+  b += state[1]; state[1] = b;
+  c += state[2]; state[2] = c;
+  d += state[3]; state[3] = d;
+  e += state[4]; state[4] = e;
+  f += state[5]; state[5] = f;
+  g += state[6]; state[6] = g;
+  h += state[7]; state[7] = h;
+
+  data += 64;
+  numBlocks--;
+  }
+
   /* Wipe variables */
   /* memset(W, 0, sizeof(W)); */
-  /* memset(T, 0, sizeof(T)); */
 }
 
 #undef S0
 #undef S1
 #undef s0
 #undef s1
+#undef K
+
+#define Sha256_UpdateBlock(p) UPDATE_BLOCKS(p)(p->state, p->buffer, 1)
 
 void Sha256_Update(CSha256 *p, const Byte *data, size_t size)
 {
@@ -193,25 +388,26 @@
       return;
     }
     
-    size -= num;
-    memcpy(p->buffer + pos, data, num);
-    data += num;
+    if (pos != 0)
+    {
+      size -= num;
+      memcpy(p->buffer + pos, data, num);
+      data += num;
+      Sha256_UpdateBlock(p);
+    }
   }
-
-  for (;;)
   {
-    Sha256_WriteByteBlock(p);
-    if (size < 64)
-      break;
-    size -= 64;
-    memcpy(p->buffer, data, 64);
-    data += 64;
-  }
-
-  if (size != 0)
+    size_t numBlocks = size >> 6;
+    UPDATE_BLOCKS(p)(p->state, data, numBlocks);
+    size &= 0x3F;
+    if (size == 0)
+      return;
+    data += (numBlocks << 6);
     memcpy(p->buffer, data, size);
+  }
 }
 
+
 void Sha256_Final(CSha256 *p, Byte *digest)
 {
   unsigned pos = (unsigned)p->count & 0x3F;
@@ -219,30 +415,73 @@
   
   p->buffer[pos++] = 0x80;
   
-  while (pos != (64 - 8))
+  if (pos > (64 - 8))
   {
-    pos &= 0x3F;
-    if (pos == 0)
-      Sha256_WriteByteBlock(p);
-    p->buffer[pos++] = 0;
+    while (pos != 64) { p->buffer[pos++] = 0; }
+    // memset(&p->buf.buffer[pos], 0, 64 - pos);
+    Sha256_UpdateBlock(p);
+    pos = 0;
   }
 
+  /*
+  if (pos & 3)
+  {
+    p->buffer[pos] = 0;
+    p->buffer[pos + 1] = 0;
+    p->buffer[pos + 2] = 0;
+    pos += 3;
+    pos &= ~3;
+  }
+  {
+    for (; pos < 64 - 8; pos += 4)
+      *(UInt32 *)(&p->buffer[pos]) = 0;
+  }
+  */
+
+  memset(&p->buffer[pos], 0, (64 - 8) - pos);
+
   {
     UInt64 numBits = (p->count << 3);
     SetBe32(p->buffer + 64 - 8, (UInt32)(numBits >> 32));
     SetBe32(p->buffer + 64 - 4, (UInt32)(numBits));
   }
   
-  Sha256_WriteByteBlock(p);
+  Sha256_UpdateBlock(p);
 
   for (i = 0; i < 8; i += 2)
   {
     UInt32 v0 = p->state[i];
-    UInt32 v1 = p->state[i + 1];
+    UInt32 v1 = p->state[(size_t)i + 1];
     SetBe32(digest    , v0);
     SetBe32(digest + 4, v1);
     digest += 8;
   }
   
-  Sha256_Init(p);
+  Sha256_InitState(p);
+}
+
+
+void Sha256Prepare()
+{
+  #ifdef _SHA_SUPPORTED
+  SHA256_FUNC_UPDATE_BLOCKS f, f_hw;
+  f = Sha256_UpdateBlocks;
+  f_hw = NULL;
+  #ifdef MY_CPU_X86_OR_AMD64
+  #ifndef USE_MY_MM
+  if (CPU_IsSupported_SHA()
+      && CPU_IsSupported_SSSE3()
+      // && CPU_IsSupported_SSE41()
+      )
+  #endif
+  #else
+  if (CPU_IsSupported_SHA2())
+  #endif
+  {
+    // printf("\n========== HW SHA256 ======== \n");
+    f = f_hw = Sha256_UpdateBlocks_HW;
+  }
+  g_FUNC_UPDATE_BLOCKS    = f;
+  g_FUNC_UPDATE_BLOCKS_HW = f_hw;
+  #endif
 }
diff --git a/third_party/lzma_sdk/Sha256.h b/third_party/lzma_sdk/Sha256.h
index 3f455db..aa38501 100644
--- a/third_party/lzma_sdk/Sha256.h
+++ b/third_party/lzma_sdk/Sha256.h
@@ -1,26 +1,76 @@
 /* Sha256.h -- SHA-256 Hash
-2013-01-18 : Igor Pavlov : Public domain */
+2021-01-01 : Igor Pavlov : Public domain */
 
-#ifndef __CRYPTO_SHA256_H
-#define __CRYPTO_SHA256_H
+#ifndef __7Z_SHA256_H
+#define __7Z_SHA256_H
 
 #include "7zTypes.h"
 
 EXTERN_C_BEGIN
 
-#define SHA256_DIGEST_SIZE 32
+#define SHA256_NUM_BLOCK_WORDS  16
+#define SHA256_NUM_DIGEST_WORDS  8
+
+#define SHA256_BLOCK_SIZE   (SHA256_NUM_BLOCK_WORDS * 4)
+#define SHA256_DIGEST_SIZE  (SHA256_NUM_DIGEST_WORDS * 4)
+
+typedef void (MY_FAST_CALL *SHA256_FUNC_UPDATE_BLOCKS)(UInt32 state[8], const Byte *data, size_t numBlocks);
+
+/*
+  if (the system supports different SHA256 code implementations)
+  {
+    (CSha256::func_UpdateBlocks) will be used
+    (CSha256::func_UpdateBlocks) can be set by
+       Sha256_Init()        - to default (fastest)
+       Sha256_SetFunction() - to any algo
+  }
+  else
+  {
+    (CSha256::func_UpdateBlocks) is ignored.
+  }
+*/
 
 typedef struct
 {
-  UInt32 state[8];
+  SHA256_FUNC_UPDATE_BLOCKS func_UpdateBlocks;
   UInt64 count;
-  Byte buffer[64];
+  UInt64 __pad_2[2];
+  UInt32 state[SHA256_NUM_DIGEST_WORDS];
+
+  Byte buffer[SHA256_BLOCK_SIZE];
 } CSha256;
 
+
+#define SHA256_ALGO_DEFAULT 0
+#define SHA256_ALGO_SW      1
+#define SHA256_ALGO_HW      2
+
+/*
+Sha256_SetFunction()
+return:
+  0 - (algo) value is not supported, and func_UpdateBlocks was not changed
+  1 - func_UpdateBlocks was set according (algo) value.
+*/
+
+BoolInt Sha256_SetFunction(CSha256 *p, unsigned algo);
+
+void Sha256_InitState(CSha256 *p);
 void Sha256_Init(CSha256 *p);
 void Sha256_Update(CSha256 *p, const Byte *data, size_t size);
 void Sha256_Final(CSha256 *p, Byte *digest);
 
+
+
+
+// void MY_FAST_CALL Sha256_UpdateBlocks(UInt32 state[8], const Byte *data, size_t numBlocks);
+
+/*
+call Sha256Prepare() once at program start.
+It prepares all supported implementations, and detects the fastest implementation.
+*/
+
+void Sha256Prepare(void);
+
 EXTERN_C_END
 
 #endif
diff --git a/third_party/lzma_sdk/Sha256Opt.c b/third_party/lzma_sdk/Sha256Opt.c
new file mode 100644
index 0000000..decc1382
--- /dev/null
+++ b/third_party/lzma_sdk/Sha256Opt.c
@@ -0,0 +1,373 @@
+/* Sha256Opt.c -- SHA-256 optimized code for SHA-256 hardware instructions
+2021-04-01 : Igor Pavlov : Public domain */
+
+#include "Precomp.h"
+
+#if defined(_MSC_VER)
+#if (_MSC_VER < 1900) && (_MSC_VER >= 1200)
+// #define USE_MY_MM
+#endif
+#endif
+
+#include "CpuArch.h"
+
+#ifdef MY_CPU_X86_OR_AMD64
+  #if defined(__clang__)
+    #if (__clang_major__ >= 8) // fix that check
+      #define USE_HW_SHA
+      #ifndef __SHA__
+        #define ATTRIB_SHA __attribute__((__target__("sha,ssse3")))
+        #if defined(_MSC_VER)
+          // SSSE3: for clang-cl:
+          #include <tmmintrin.h>
+          #define __SHA__
+        #endif
+      #endif
+
+    #endif
+  #elif defined(__GNUC__)
+    #if (__GNUC__ >= 8) // fix that check
+      #define USE_HW_SHA
+      #ifndef __SHA__
+        #define ATTRIB_SHA __attribute__((__target__("sha,ssse3")))
+        // #pragma GCC target("sha,ssse3")
+      #endif
+    #endif
+  #elif defined(__INTEL_COMPILER)
+    #if (__INTEL_COMPILER >= 1800) // fix that check
+      #define USE_HW_SHA
+    #endif
+  #elif defined(_MSC_VER)
+    #ifdef USE_MY_MM
+      #define USE_VER_MIN 1300
+    #else
+      #define USE_VER_MIN 1910
+    #endif
+    #if _MSC_VER >= USE_VER_MIN
+      #define USE_HW_SHA
+    #endif
+  #endif
+// #endif // MY_CPU_X86_OR_AMD64
+
+#ifdef USE_HW_SHA
+
+// #pragma message("Sha256 HW")
+// #include <wmmintrin.h>
+
+#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
+#include <immintrin.h>
+#else
+#include <emmintrin.h>
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1600)
+// #include <intrin.h>
+#endif
+
+#ifdef USE_MY_MM
+#include "My_mm.h"
+#endif
+
+#endif
+
+/*
+SHA256 uses:
+SSE2:
+  _mm_loadu_si128
+  _mm_storeu_si128
+  _mm_set_epi32
+  _mm_add_epi32
+  _mm_shuffle_epi32 / pshufd
+
+
+  
+SSSE3:
+  _mm_shuffle_epi8 / pshufb
+  _mm_alignr_epi8
+SHA:
+  _mm_sha256*
+*/
+
+// K array must be aligned for 16-bytes at least.
+// The compiler can look align attribute and selects
+//   movdqu - for code without align attribute
+//   movdqa - for code with    align attribute
+extern
+MY_ALIGN(64)
+const UInt32 SHA256_K_ARRAY[64];
+
+#define K SHA256_K_ARRAY
+
+
+#define ADD_EPI32(dest, src) dest = _mm_add_epi32(dest, src);
+#define SHA256_MSG1(dest, src) dest = _mm_sha256msg1_epu32(dest, src);
+#define SHA25G_MSG2(dest, src) dest = _mm_sha256msg2_epu32(dest, src);
+
+
+#define LOAD_SHUFFLE(m, k) \
+    m = _mm_loadu_si128((const __m128i *)(const void *)(data + (k) * 16)); \
+    m = _mm_shuffle_epi8(m, mask); \
+
+#define SM1(g0, g1, g2, g3) \
+    SHA256_MSG1(g3, g0); \
+
+#define SM2(g0, g1, g2, g3) \
+    tmp = _mm_alignr_epi8(g1, g0, 4); \
+    ADD_EPI32(g2, tmp); \
+    SHA25G_MSG2(g2, g1); \
+
+// #define LS0(k, g0, g1, g2, g3) LOAD_SHUFFLE(g0, k)
+// #define LS1(k, g0, g1, g2, g3) LOAD_SHUFFLE(g1, k+1)
+
+
+#define NNN(g0, g1, g2, g3)
+
+
+#define RND2(t0, t1) \
+    t0 = _mm_sha256rnds2_epu32(t0, t1, msg);
+
+#define RND2_0(m, k) \
+    msg = _mm_add_epi32(m, *(const __m128i *) (const void *) &K[(k) * 4]); \
+    RND2(state0, state1); \
+    msg = _mm_shuffle_epi32(msg, 0x0E); \
+
+
+#define RND2_1 \
+    RND2(state1, state0); \
+
+
+// We use scheme with 3 rounds ahead for SHA256_MSG1 / 2 rounds ahead for SHA256_MSG2
+
+#define R4(k, g0, g1, g2, g3, OP0, OP1) \
+    RND2_0(g0, k); \
+    OP0(g0, g1, g2, g3); \
+    RND2_1; \
+    OP1(g0, g1, g2, g3); \
+
+#define R16(k, OP0, OP1, OP2, OP3, OP4, OP5, OP6, OP7) \
+    R4 ( (k)*4+0, m0, m1, m2, m3, OP0, OP1 ) \
+    R4 ( (k)*4+1, m1, m2, m3, m0, OP2, OP3 ) \
+    R4 ( (k)*4+2, m2, m3, m0, m1, OP4, OP5 ) \
+    R4 ( (k)*4+3, m3, m0, m1, m2, OP6, OP7 ) \
+
+#define PREPARE_STATE \
+    tmp    = _mm_shuffle_epi32(state0, 0x1B); /* abcd */ \
+    state0 = _mm_shuffle_epi32(state1, 0x1B); /* efgh */ \
+    state1 = state0; \
+    state0 = _mm_unpacklo_epi64(state0, tmp); /* cdgh */ \
+    state1 = _mm_unpackhi_epi64(state1, tmp); /* abef */ \
+
+
+void MY_FAST_CALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks);
+#ifdef ATTRIB_SHA
+ATTRIB_SHA
+#endif
+void MY_FAST_CALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks)
+{
+  const __m128i mask = _mm_set_epi32(0x0c0d0e0f, 0x08090a0b, 0x04050607, 0x00010203);
+  __m128i tmp;
+  __m128i state0, state1;
+
+  if (numBlocks == 0)
+    return;
+
+  state0 = _mm_loadu_si128((const __m128i *) (const void *) &state[0]);
+  state1 = _mm_loadu_si128((const __m128i *) (const void *) &state[4]);
+  
+  PREPARE_STATE
+
+  do
+  {
+    __m128i state0_save, state1_save;
+    __m128i m0, m1, m2, m3;
+    __m128i msg;
+    // #define msg tmp
+
+    state0_save = state0;
+    state1_save = state1;
+    
+    LOAD_SHUFFLE (m0, 0)
+    LOAD_SHUFFLE (m1, 1)
+    LOAD_SHUFFLE (m2, 2)
+    LOAD_SHUFFLE (m3, 3)
+
+
+
+    R16 ( 0, NNN, NNN, SM1, NNN, SM1, SM2, SM1, SM2 );
+    R16 ( 1, SM1, SM2, SM1, SM2, SM1, SM2, SM1, SM2 );
+    R16 ( 2, SM1, SM2, SM1, SM2, SM1, SM2, SM1, SM2 );
+    R16 ( 3, SM1, SM2, NNN, SM2, NNN, NNN, NNN, NNN );
+    
+    ADD_EPI32(state0, state0_save);
+    ADD_EPI32(state1, state1_save);
+    
+    data += 64;
+  }
+  while (--numBlocks);
+
+  PREPARE_STATE
+
+  _mm_storeu_si128((__m128i *) (void *) &state[0], state0);
+  _mm_storeu_si128((__m128i *) (void *) &state[4], state1);
+}
+
+#endif // USE_HW_SHA
+
+#elif defined(MY_CPU_ARM_OR_ARM64)
+
+  #if defined(__clang__)
+    #if (__clang_major__ >= 8) // fix that check
+      #define USE_HW_SHA
+    #endif
+  #elif defined(__GNUC__)
+    #if (__GNUC__ >= 6) // fix that check
+      #define USE_HW_SHA
+    #endif
+  #elif defined(_MSC_VER)
+    #if _MSC_VER >= 1910
+      #define USE_HW_SHA
+    #endif
+  #endif
+
+#ifdef USE_HW_SHA
+
+// #pragma message("=== Sha256 HW === ")
+
+#if defined(__clang__) || defined(__GNUC__)
+  #ifdef MY_CPU_ARM64
+    #define ATTRIB_SHA __attribute__((__target__("+crypto")))
+  #else
+    #define ATTRIB_SHA __attribute__((__target__("fpu=crypto-neon-fp-armv8")))
+  #endif
+#else
+  // _MSC_VER
+  // for arm32
+  #define _ARM_USE_NEW_NEON_INTRINSICS
+#endif
+
+#if defined(_MSC_VER) && defined(MY_CPU_ARM64)
+#include <arm64_neon.h>
+#else
+#include <arm_neon.h>
+#endif
+
+typedef uint32x4_t v128;
+// typedef __n128 v128; // MSVC
+
+#ifdef MY_CPU_BE
+  #define MY_rev32_for_LE(x)
+#else
+  #define MY_rev32_for_LE(x) x = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(x)))
+#endif
+
+#define LOAD_128(_p)      (*(const v128 *)(const void *)(_p))
+#define STORE_128(_p, _v) *(v128 *)(void *)(_p) = (_v)
+
+#define LOAD_SHUFFLE(m, k) \
+    m = LOAD_128((data + (k) * 16)); \
+    MY_rev32_for_LE(m); \
+
+// K array must be aligned for 16-bytes at least.
+extern
+MY_ALIGN(64)
+const UInt32 SHA256_K_ARRAY[64];
+
+#define K SHA256_K_ARRAY
+
+
+#define SHA256_SU0(dest, src)        dest = vsha256su0q_u32(dest, src);
+#define SHA25G_SU1(dest, src2, src3) dest = vsha256su1q_u32(dest, src2, src3);
+
+#define SM1(g0, g1, g2, g3)  SHA256_SU0(g3, g0)
+#define SM2(g0, g1, g2, g3)  SHA25G_SU1(g2, g0, g1)
+#define NNN(g0, g1, g2, g3)
+
+
+#define R4(k, g0, g1, g2, g3, OP0, OP1) \
+    msg = vaddq_u32(g0, *(const v128 *) (const void *) &K[(k) * 4]); \
+    tmp = state0; \
+    state0 = vsha256hq_u32( state0, state1, msg ); \
+    state1 = vsha256h2q_u32( state1, tmp, msg ); \
+    OP0(g0, g1, g2, g3); \
+    OP1(g0, g1, g2, g3); \
+
+
+#define R16(k, OP0, OP1, OP2, OP3, OP4, OP5, OP6, OP7) \
+    R4 ( (k)*4+0, m0, m1, m2, m3, OP0, OP1 ) \
+    R4 ( (k)*4+1, m1, m2, m3, m0, OP2, OP3 ) \
+    R4 ( (k)*4+2, m2, m3, m0, m1, OP4, OP5 ) \
+    R4 ( (k)*4+3, m3, m0, m1, m2, OP6, OP7 ) \
+
+
+void MY_FAST_CALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks);
+#ifdef ATTRIB_SHA
+ATTRIB_SHA
+#endif
+void MY_FAST_CALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks)
+{
+  v128 state0, state1;
+
+  if (numBlocks == 0)
+    return;
+
+  state0 = LOAD_128(&state[0]);
+  state1 = LOAD_128(&state[4]);
+  
+  do
+  {
+    v128 state0_save, state1_save;
+    v128 m0, m1, m2, m3;
+    v128 msg, tmp;
+
+    state0_save = state0;
+    state1_save = state1;
+    
+    LOAD_SHUFFLE (m0, 0)
+    LOAD_SHUFFLE (m1, 1)
+    LOAD_SHUFFLE (m2, 2)
+    LOAD_SHUFFLE (m3, 3)
+
+    R16 ( 0, NNN, NNN, SM1, NNN, SM1, SM2, SM1, SM2 );
+    R16 ( 1, SM1, SM2, SM1, SM2, SM1, SM2, SM1, SM2 );
+    R16 ( 2, SM1, SM2, SM1, SM2, SM1, SM2, SM1, SM2 );
+    R16 ( 3, SM1, SM2, NNN, SM2, NNN, NNN, NNN, NNN );
+    
+    state0 = vaddq_u32(state0, state0_save);
+    state1 = vaddq_u32(state1, state1_save);
+    
+    data += 64;
+  }
+  while (--numBlocks);
+
+  STORE_128(&state[0], state0);
+  STORE_128(&state[4], state1);
+}
+
+#endif // USE_HW_SHA
+
+#endif // MY_CPU_ARM_OR_ARM64
+
+
+#ifndef USE_HW_SHA
+
+// #error Stop_Compiling_UNSUPPORTED_SHA
+// #include <stdlib.h>
+
+// #include "Sha256.h"
+void MY_FAST_CALL Sha256_UpdateBlocks(UInt32 state[8], const Byte *data, size_t numBlocks);
+
+#pragma message("Sha256 HW-SW stub was used")
+
+void MY_FAST_CALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks);
+void MY_FAST_CALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks)
+{
+  Sha256_UpdateBlocks(state, data, numBlocks);
+  /*
+  UNUSED_VAR(state);
+  UNUSED_VAR(data);
+  UNUSED_VAR(numBlocks);
+  exit(1);
+  return;
+  */
+}
+
+#endif
diff --git a/third_party/lzma_sdk/Xz.c b/third_party/lzma_sdk/Xz.c
index d9f83df..7c53b60 100644
--- a/third_party/lzma_sdk/Xz.c
+++ b/third_party/lzma_sdk/Xz.c
@@ -1,5 +1,5 @@
 /* Xz.c - Xz
-2017-05-12 : Igor Pavlov : Public domain */
+2021-02-09 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -41,7 +41,7 @@
 unsigned XzFlags_GetCheckSize(CXzStreamFlags f)
 {
   unsigned t = XzFlags_GetCheckType(f);
-  return (t == 0) ? 0 : (4 << ((t - 1) / 3));
+  return (t == 0) ? 0 : ((unsigned)4 << ((t - 1) / 3));
 }
 
 void XzCheck_Init(CXzCheck *p, unsigned mode)
diff --git a/third_party/lzma_sdk/Xz.h b/third_party/lzma_sdk/Xz.h
index 544ee18..849b944b 100644
--- a/third_party/lzma_sdk/Xz.h
+++ b/third_party/lzma_sdk/Xz.h
@@ -1,5 +1,5 @@
 /* Xz.h - Xz interface
-2018-07-04 : Igor Pavlov : Public domain */
+2021-04-01 : Igor Pavlov : Public domain */
 
 #ifndef __XZ_H
 #define __XZ_H
@@ -47,7 +47,7 @@
   CXzFilter filters[XZ_NUM_FILTERS_MAX];
 } CXzBlock;
 
-#define XzBlock_GetNumFilters(p) (((p)->flags & XZ_BF_NUM_FILTERS_MASK) + 1)
+#define XzBlock_GetNumFilters(p) (((unsigned)(p)->flags & XZ_BF_NUM_FILTERS_MASK) + 1)
 #define XzBlock_HasPackSize(p)   (((p)->flags & XZ_BF_PACK_SIZE) != 0)
 #define XzBlock_HasUnpackSize(p) (((p)->flags & XZ_BF_UNPACK_SIZE) != 0)
 #define XzBlock_HasUnsupportedFlags(p) (((p)->flags & ~(XZ_BF_NUM_FILTERS_MASK | XZ_BF_PACK_SIZE | XZ_BF_UNPACK_SIZE)) != 0)
@@ -277,7 +277,10 @@
     {
       XzUnpacker_Init()
       for()
+      {
         XzUnpacker_Code();
+      }
+      XzUnpacker_IsStreamWasFinished()
     }
     
   Interface-2 : Direct output buffer:
@@ -288,7 +291,10 @@
       XzUnpacker_Init()
       XzUnpacker_SetOutBufMode(); // to set output buffer and size
       for()
+      {
         XzUnpacker_Code(); // (dest = NULL) in XzUnpacker_Code()
+      }
+      XzUnpacker_IsStreamWasFinished()
     }
 
   Interface-3 : Direct output buffer : One call full decoding
@@ -296,6 +302,7 @@
     It uses Interface-2 internally.
     {
       XzUnpacker_CodeFull()
+      XzUnpacker_IsStreamWasFinished()
     }
 */
 
@@ -309,8 +316,12 @@
   SZ_OK
     status:
       CODER_STATUS_NOT_FINISHED,
-      CODER_STATUS_NEEDS_MORE_INPUT - maybe there are more xz streams,
-                                      call XzUnpacker_IsStreamWasFinished to check that current stream was finished
+      CODER_STATUS_NEEDS_MORE_INPUT - the decoder can return it in two cases:
+         1) it needs more input data to finish current xz stream
+         2) xz stream was finished successfully. But the decoder supports multiple
+            concatented xz streams. So it expects more input data for new xz streams.
+         Call XzUnpacker_IsStreamWasFinished() to check that latest xz stream was finished successfully.
+
   SZ_ERROR_MEM  - Memory allocation error
   SZ_ERROR_DATA - Data error
   SZ_ERROR_UNSUPPORTED - Unsupported method or method properties
@@ -335,12 +346,17 @@
     const Byte *src, SizeT *srcLen,
     ECoderFinishMode finishMode, ECoderStatus *status);
 
+/*
+If you decode full xz stream(s), then you can call XzUnpacker_IsStreamWasFinished()
+after successful XzUnpacker_CodeFull() or after last call of XzUnpacker_Code().
+*/
+
 BoolInt XzUnpacker_IsStreamWasFinished(const CXzUnpacker *p);
 
 /*
-XzUnpacker_GetExtraSize() returns then number of uncofirmed bytes,
+XzUnpacker_GetExtraSize() returns then number of unconfirmed bytes,
  if it's in (XZ_STATE_STREAM_HEADER) state or in (XZ_STATE_STREAM_PADDING) state.
-These bytes can be some bytes after xz archive, or
+These bytes can be some data after xz archive, or
 it can be start of new xz stream.
  
 Call XzUnpacker_GetExtraSize() after XzUnpacker_Code() function to detect real size of
@@ -371,19 +387,46 @@
 
 
 
-/* ---------- Multi Threading Decoding ---------- */
+
+
+
+/* ---- Single-Thread and Multi-Thread xz Decoding with Input/Output Streams ---- */
+
+/*
+  if (CXzDecMtProps::numThreads > 1), the decoder can try to use
+  Multi-Threading. The decoder analyses xz block header, and if
+  there are pack size and unpack size values stored in xz block header,
+  the decoder reads compressed data of block to internal buffers,
+  and then it can start parallel decoding, if there are another blocks.
+  The decoder can switch back to Single-Thread decoding after some conditions.
+
+  The sequence of calls for xz decoding with in/out Streams:
+  {
+    XzDecMt_Create()
+    XzDecMtProps_Init(XzDecMtProps) to set default values of properties
+    // then you can change some XzDecMtProps parameters with required values
+    // here you can set the number of threads and (memUseMax) - the maximum
+    Memory usage for multithreading decoding.
+    for()
+    {
+      XzDecMt_Decode() // one call per one file
+    }
+    XzDecMt_Destroy()
+  }
+*/
 
 
 typedef struct
 {
-  size_t inBufSize_ST;
-  size_t outStep_ST;
-  BoolInt ignoreErrors;
+  size_t inBufSize_ST;    // size of input buffer for Single-Thread decoding
+  size_t outStep_ST;      // size of output buffer for Single-Thread decoding
+  BoolInt ignoreErrors;   // if set to 1, the decoder can ignore some errors and it skips broken parts of data.
   
   #ifndef _7ZIP_ST
-  unsigned numThreads;
-  size_t inBufSize_MT;
-  size_t memUseMax;
+  unsigned numThreads;    // the number of threads for Multi-Thread decoding. if (umThreads == 1) it will use Single-thread decoding
+  size_t inBufSize_MT;    // size of small input data buffers for Multi-Thread decoding. Big number of such small buffers can be created
+  size_t memUseMax;       // the limit of total memory usage for Multi-Thread decoding.
+                          // it's recommended to set (memUseMax) manually to value that is smaller of total size of RAM in computer.
   #endif
 } CXzDecMtProps;
 
@@ -393,7 +436,7 @@
 typedef void * CXzDecMtHandle;
 
 /*
-  alloc    : XzDecMt uses CAlignOffsetAlloc for addresses allocated by (alloc).
+  alloc    : XzDecMt uses CAlignOffsetAlloc internally for addresses allocated by (alloc).
   allocMid : for big allocations, aligned allocation is better
 */
 
@@ -407,33 +450,46 @@
   Byte NumStreams_Defined;
   Byte NumBlocks_Defined;
 
-  Byte DataAfterEnd;
+  Byte DataAfterEnd;      // there are some additional data after good xz streams, and that data is not new xz stream.
   Byte DecodingTruncated; // Decoding was Truncated, we need only partial output data
 
-  UInt64 InSize;  // pack size processed
+  UInt64 InSize;          // pack size processed. That value doesn't include the data after
+                          // end of xz stream, if that data was not correct
   UInt64 OutSize;
 
   UInt64 NumStreams;
   UInt64 NumBlocks;
 
-  SRes DecodeRes;
-  SRes ReadRes;
-  SRes ProgressRes;
-  SRes CombinedRes;
-  SRes CombinedRes_Type;
+  SRes DecodeRes;         // the error code of xz streams data decoding
+  SRes ReadRes;           // error code from ISeqInStream:Read()
+  SRes ProgressRes;       // error code from ICompressProgress:Progress()
 
+  SRes CombinedRes;       // Combined result error code that shows main rusult
+                          // = S_OK, if there is no error.
+                          // but check also (DataAfterEnd) that can show additional minor errors.
+ 
+  SRes CombinedRes_Type;  // = SZ_ERROR_READ,     if error from ISeqInStream
+                          // = SZ_ERROR_PROGRESS, if error from ICompressProgress
+                          // = SZ_ERROR_WRITE,    if error from ISeqOutStream
+                          // = SZ_ERROR_* codes for decoding
 } CXzStatInfo;
 
 void XzStatInfo_Clear(CXzStatInfo *p);
 
 /*
+
 XzDecMt_Decode()
-SRes:
-  SZ_OK               - OK
+SRes: it's combined decoding result. It also is equal to stat->CombinedRes.
+
+  SZ_OK               - no error
+                        check also output value in (stat->DataAfterEnd)
+                        that can show additional possible error
+
   SZ_ERROR_MEM        - Memory allocation error
   SZ_ERROR_NO_ARCHIVE - is not xz archive
   SZ_ERROR_ARCHIVE    - Headers error
   SZ_ERROR_DATA       - Data Error
+  SZ_ERROR_UNSUPPORTED - Unsupported method or method properties
   SZ_ERROR_CRC        - CRC Error
   SZ_ERROR_INPUT_EOF  - it needs more input data
   SZ_ERROR_WRITE      - ISeqOutStream error
@@ -451,8 +507,9 @@
     // Byte *outBuf, size_t *outBufSize,
     ISeqInStream *inStream,
     // const Byte *inData, size_t inDataSize,
-    CXzStatInfo *stat,
-    int *isMT,                 // 0 means that ST (Single-Thread) version was used
+    CXzStatInfo *stat,         // out: decoding results and statistics
+    int *isMT,                 // out: 0 means that ST (Single-Thread) version was used
+                               //      1 means that MT (Multi-Thread) version was used
     ICompressProgress *progress);
 
 EXTERN_C_END
diff --git a/third_party/lzma_sdk/XzCrc64Opt.c b/third_party/lzma_sdk/XzCrc64Opt.c
index b2852de..93a9fff 100644
--- a/third_party/lzma_sdk/XzCrc64Opt.c
+++ b/third_party/lzma_sdk/XzCrc64Opt.c
@@ -1,5 +1,5 @@
 /* XzCrc64Opt.c -- CRC64 calculation
-2017-06-30 : Igor Pavlov : Public domain */
+2021-02-09 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -9,6 +9,7 @@
 
 #define CRC64_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
 
+UInt64 MY_FAST_CALL XzCrc64UpdateT4(UInt64 v, const void *data, size_t size, const UInt64 *table);
 UInt64 MY_FAST_CALL XzCrc64UpdateT4(UInt64 v, const void *data, size_t size, const UInt64 *table)
 {
   const Byte *p = (const Byte *)data;
@@ -16,7 +17,7 @@
     v = CRC64_UPDATE_BYTE_2(v, *p);
   for (; size >= 4; size -= 4, p += 4)
   {
-    UInt32 d = (UInt32)v ^ *(const UInt32 *)p;
+    UInt32 d = (UInt32)v ^ *(const UInt32 *)(const void *)p;
     v = (v >> 32)
         ^ (table + 0x300)[((d      ) & 0xFF)]
         ^ (table + 0x200)[((d >>  8) & 0xFF)]
@@ -45,6 +46,7 @@
 
 #define CRC64_UPDATE_BYTE_2_BE(crc, b) (table[(Byte)((crc) >> 56) ^ (b)] ^ ((crc) << 8))
 
+UInt64 MY_FAST_CALL XzCrc64UpdateT1_BeT4(UInt64 v, const void *data, size_t size, const UInt64 *table);
 UInt64 MY_FAST_CALL XzCrc64UpdateT1_BeT4(UInt64 v, const void *data, size_t size, const UInt64 *table)
 {
   const Byte *p = (const Byte *)data;
@@ -54,7 +56,7 @@
     v = CRC64_UPDATE_BYTE_2_BE(v, *p);
   for (; size >= 4; size -= 4, p += 4)
   {
-    UInt32 d = (UInt32)(v >> 32) ^ *(const UInt32 *)p;
+    UInt32 d = (UInt32)(v >> 32) ^ *(const UInt32 *)(const void *)p;
     v = (v << 32)
         ^ (table + 0x000)[((d      ) & 0xFF)]
         ^ (table + 0x100)[((d >>  8) & 0xFF)]
diff --git a/third_party/lzma_sdk/XzDec.c b/third_party/lzma_sdk/XzDec.c
index 395e83f..3f96a37 100644
--- a/third_party/lzma_sdk/XzDec.c
+++ b/third_party/lzma_sdk/XzDec.c
@@ -1,5 +1,5 @@
 /* XzDec.c -- Xz Decode
-2019-02-02 : Igor Pavlov : Public domain */
+2021-09-04 : Igor Pavlov : Public domain */
 
 #include "Precomp.h"
 
@@ -240,6 +240,7 @@
 }
 
 
+SRes BraState_SetFromMethod(IStateCoder *p, UInt64 id, int encodeMode, ISzAllocPtr alloc);
 SRes BraState_SetFromMethod(IStateCoder *p, UInt64 id, int encodeMode, ISzAllocPtr alloc)
 {
   CBraState *decoder;
@@ -772,7 +773,8 @@
 
 #define READ_VARINT_AND_CHECK(buf, pos, size, res) \
   { unsigned s = Xz_ReadVarInt(buf + pos, size - pos, res); \
-  if (s == 0) return SZ_ERROR_ARCHIVE; pos += s; }
+  if (s == 0) return SZ_ERROR_ARCHIVE; \
+  pos += s; }
 
 
 static BoolInt XzBlock_AreSupportedFilters(const CXzBlock *p)
@@ -1038,7 +1040,7 @@
             (p->outBuf ? NULL : dest), &destLen2, destFinish,
             src, &srcLen2, srcFinished2,
             finishMode2);
-        
+
         *status = p->decoder.status;
         XzCheck_Update(&p->check, (p->outBuf ? p->outBuf + p->outDataWritten : dest), destLen2);
         if (!p->outBuf)
@@ -1275,9 +1277,10 @@
         }
         else
         {
+          const Byte *ptr = p->buf;
           p->state = XZ_STATE_STREAM_FOOTER;
           p->pos = 0;
-          if (CRC_GET_DIGEST(p->crc) != GetUi32(p->buf))
+          if (CRC_GET_DIGEST(p->crc) != GetUi32(ptr))
             return SZ_ERROR_CRC;
         }
         break;
@@ -1456,7 +1459,6 @@
   ISeqInStream *inStream;
   ISeqOutStream *outStream;
   ICompressProgress *progress;
-  // CXzStatInfo *stat;
 
   BoolInt finishMode;
   BoolInt outSize_Defined;
@@ -1492,8 +1494,9 @@
   UInt64 numBlocks;
 
   // UInt64 numBadBlocks;
-  SRes mainErrorCode;
-
+  SRes mainErrorCode;  // it's set to error code, if the size Code() output doesn't patch the size from Parsing stage
+                       // it can be = SZ_ERROR_INPUT_EOF
+                       // it can be = SZ_ERROR_DATA, in some another cases
   BoolInt isBlockHeaderState_Parse;
   BoolInt isBlockHeaderState_Write;
   UInt64 outProcessed_Parse;
@@ -1877,7 +1880,7 @@
     {
       // if (res == SZ_ERROR_MEM) return res;
       if (me->props.ignoreErrors && res != SZ_ERROR_MEM)
-        return S_OK;
+        return SZ_OK;
       return res;
     }
   }
@@ -1898,15 +1901,18 @@
   *outCodePos = coder->outCodeSize;
   *stop = True;
 
+  if (srcSize > coder->inPreSize - coder->inCodeSize)
+    return SZ_ERROR_FAIL;
+  
   if (coder->inCodeSize < coder->inPreHeaderSize)
   {
-    UInt64 rem = coder->inPreHeaderSize - coder->inCodeSize;
-    size_t step = srcSize;
-    if (step > rem)
-      step = (size_t)rem;
+    size_t step = coder->inPreHeaderSize - coder->inCodeSize;
+    if (step > srcSize)
+      step = srcSize;
     src += step;
     srcSize -= step;
     coder->inCodeSize += step;
+    *inCodePos = coder->inCodeSize;
     if (coder->inCodeSize < coder->inPreHeaderSize)
     {
       *stop = False;
@@ -1956,7 +1962,7 @@
   {
     *inCodePos = coder->inPreSize;
     *outCodePos = coder->outPreSize;
-    return S_OK;
+    return SZ_OK;
   }
   return coder->codeRes;
 }
@@ -1966,7 +1972,7 @@
 
 static SRes XzDecMt_Callback_Write(void *pp, unsigned coderIndex,
     BoolInt needWriteToStream,
-    const Byte *src, size_t srcSize,
+    const Byte *src, size_t srcSize, BoolInt isCross,
     // int srcFinished,
     BoolInt *needContinue,
     BoolInt *canRecode)
@@ -1985,7 +1991,7 @@
   if (!coder->dec.headerParsedOk || !coder->outBuf)
   {
     if (me->finishedDecoderIndex < 0)
-      me->finishedDecoderIndex = coderIndex;
+      me->finishedDecoderIndex = (int)coderIndex;
     return SZ_OK;
   }
 
@@ -2077,7 +2083,7 @@
     if (coder->codeRes != SZ_OK)
       if (!me->props.ignoreErrors)
       {
-        me->finishedDecoderIndex = coderIndex;
+        me->finishedDecoderIndex = (int)coderIndex;
         return res;
       }
 
@@ -2086,7 +2092,7 @@
     if (coder->inPreSize != coder->inCodeSize
         || coder->blockPackTotal != coder->inCodeSize)
     {
-      me->finishedDecoderIndex = coderIndex;
+      me->finishedDecoderIndex = (int)coderIndex;
       return SZ_OK;
     }
 
@@ -2125,22 +2131,41 @@
         return SZ_OK;
       }
       
+      /*
+      We have processed all xz-blocks of stream,
+      And xz unpacker is at XZ_STATE_BLOCK_HEADER state, where
+      (src) is a pointer to xz-Index structure.
+      We finish reading of current xz-Stream, including Zero padding after xz-Stream.
+      We exit, if we reach extra byte (first byte of new-Stream or another data).
+      But we don't update input stream pointer for that new extra byte.
+      If extra byte is not correct first byte of xz-signature,
+      we have SZ_ERROR_NO_ARCHIVE error here.
+      */
+
       res = XzUnpacker_Code(dec,
           NULL, &outSizeCur,
           src, &srcProcessed,
           me->mtc.readWasFinished, // srcFinished
           CODER_FINISH_END, // CODER_FINISH_ANY,
           &status);
+
+      // res = SZ_ERROR_ARCHIVE; // for failure test
       
       me->status = status;
       me->codeRes = res;
 
+      if (isCross)
+        me->mtc.crossStart += srcProcessed;
+
       me->mtc.inProcessed += srcProcessed;
       me->mtc.mtProgress.totalInSize = me->mtc.inProcessed;
 
+      srcSize -= srcProcessed;
+      src += srcProcessed;
+
       if (res != SZ_OK)
       {
-        return S_OK;
+        return SZ_OK;
         // return res;
       }
       
@@ -2149,20 +2174,26 @@
         *needContinue = True;
         me->isBlockHeaderState_Parse = False;
         me->isBlockHeaderState_Write = False;
+
+        if (!isCross)
         {
           Byte *crossBuf = MtDec_GetCrossBuff(&me->mtc);
           if (!crossBuf)
             return SZ_ERROR_MEM;
-          memcpy(crossBuf, src + srcProcessed, srcSize - srcProcessed);
+          if (srcSize != 0)
+            memcpy(crossBuf, src, srcSize);
+          me->mtc.crossStart = 0;
+          me->mtc.crossEnd = srcSize;
         }
-        me->mtc.crossStart = 0;
-        me->mtc.crossEnd = srcSize - srcProcessed;
+
+        PRF_STR_INT("XZ_STATE_STREAM_HEADER crossEnd = ", (unsigned)me->mtc.crossEnd);
+
         return SZ_OK;
       }
       
-      if (status != CODER_STATUS_NEEDS_MORE_INPUT)
+      if (status != CODER_STATUS_NEEDS_MORE_INPUT || srcSize != 0)
       {
-        return E_FAIL;
+        return SZ_ERROR_FAIL;
       }
       
       if (me->mtc.readWasFinished)
@@ -2174,7 +2205,7 @@
     {
       size_t inPos;
       size_t inLim;
-      const Byte *inData;
+      // const Byte *inData;
       UInt64 inProgressPrev = me->mtc.inProcessed;
       
       // XzDecMt_Prepare_InBuf_ST(p);
@@ -2184,9 +2215,8 @@
       
       inPos = 0;
       inLim = 0;
-      // outProcessed = 0;
       
-      inData = crossBuf;
+      // inData = crossBuf;
       
       for (;;)
       {
@@ -2201,7 +2231,7 @@
           {
             inPos = 0;
             inLim = me->mtc.inBufSize;
-            me->mtc.readRes = ISeqInStream_Read(me->inStream, (void *)inData, &inLim);
+            me->mtc.readRes = ISeqInStream_Read(me->inStream, (void *)crossBuf, &inLim);
             me->mtc.readProcessed += inLim;
             if (inLim == 0 || me->mtc.readRes != SZ_OK)
               me->mtc.readWasFinished = True;
@@ -2213,7 +2243,7 @@
 
         res = XzUnpacker_Code(dec,
             NULL, &outProcessed,
-            inData + inPos, &inProcessed,
+            crossBuf + inPos, &inProcessed,
             (inProcessed == 0), // srcFinished
             CODER_FINISH_END, &status);
         
@@ -2225,7 +2255,7 @@
 
         if (res != SZ_OK)
         {
-          return S_OK;
+          return SZ_OK;
           // return res;
         }
 
@@ -2240,7 +2270,7 @@
         }
         
         if (status != CODER_STATUS_NEEDS_MORE_INPUT)
-          return E_FAIL;
+          return SZ_ERROR_FAIL;
         
         if (me->mtc.progress)
         {
@@ -2276,13 +2306,6 @@
   p->NumStreams_Defined = False;
   p->NumBlocks_Defined = False;
   
-  // p->IsArc = False;
-  // p->UnexpectedEnd = False;
-  // p->Unsupported = False;
-  // p->HeadersError = False;
-  // p->DataError = False;
-  // p->CrcError = False;
-
   p->DataAfterEnd = False;
   p->DecodingTruncated = False;
   
@@ -2296,6 +2319,16 @@
 
 
 
+/*
+  XzDecMt_Decode_ST() can return SZ_OK or the following errors
+     - SZ_ERROR_MEM for memory allocation error
+     - error from XzUnpacker_Code() function
+     - SZ_ERROR_WRITE for ISeqOutStream::Write(). stat->CombinedRes_Type = SZ_ERROR_WRITE in that case
+     - ICompressProgress::Progress() error,  stat->CombinedRes_Type = SZ_ERROR_PROGRESS.
+  But XzDecMt_Decode_ST() doesn't return ISeqInStream::Read() errors.
+  ISeqInStream::Read() result is set to p->readRes.
+  also it can set stat->CombinedRes_Type to SZ_ERROR_WRITE or SZ_ERROR_PROGRESS.
+*/
 
 static SRes XzDecMt_Decode_ST(CXzDecMt *p
     #ifndef _7ZIP_ST
@@ -2384,7 +2417,7 @@
         inPos = 0;
         inLim = p->inBufSize;
         inData = p->inBuf;
-        p->readRes = ISeqInStream_Read(p->inStream, (void *)inData, &inLim);
+        p->readRes = ISeqInStream_Read(p->inStream, (void *)p->inBuf, &inLim);
         p->readProcessed += inLim;
         if (inLim == 0 || p->readRes != SZ_OK)
           p->readWasFinished = True;
@@ -2426,8 +2459,8 @@
     if (finished || outProcessed >= outSize)
       if (outPos != 0)
       {
-        size_t written = ISeqOutStream_Write(p->outStream, p->outBuf, outPos);
-        p->outProcessed += written;
+        const size_t written = ISeqOutStream_Write(p->outStream, p->outBuf, outPos);
+        // p->outProcessed += written; // 21.01: BUG fixed
         if (written != outPos)
         {
           stat->CombinedRes_Type = SZ_ERROR_WRITE;
@@ -2438,9 +2471,8 @@
 
     if (p->progress && res == SZ_OK)
     {
-      UInt64 inDelta = p->inProcessed - inPrev;
-      UInt64 outDelta = p->outProcessed - outPrev;
-      if (inDelta >= (1 << 22) || outDelta >= (1 << 22))
+      if (p->inProcessed - inPrev >= (1 << 22) ||
+          p->outProcessed - outPrev >= (1 << 22))
       {
         res = ICompressProgress_Progress(p->progress, p->inProcessed, p->outProcessed);
         if (res != SZ_OK)
@@ -2455,14 +2487,31 @@
     }
 
     if (finished)
-      return res;
+    {
+      // p->codeRes is preliminary error from XzUnpacker_Code.
+      // and it can be corrected later as final result
+      // so we return SZ_OK here instead of (res);
+      return SZ_OK;
+      // return res;
+    }
   }
 }
 
-static SRes XzStatInfo_SetStat(const CXzUnpacker *dec,
+
+
+/*
+XzStatInfo_SetStat() transforms
+    CXzUnpacker return code and status to combined CXzStatInfo results.
+    it can convert SZ_OK to SZ_ERROR_INPUT_EOF
+    it can convert SZ_ERROR_NO_ARCHIVE to SZ_OK and (DataAfterEnd = 1)
+*/
+
+static void XzStatInfo_SetStat(const CXzUnpacker *dec,
     int finishMode,
-    UInt64 readProcessed, UInt64 inProcessed,
-    SRes res, ECoderStatus status,
+    // UInt64 readProcessed,
+    UInt64 inProcessed,
+    SRes res,                     // it's result from CXzUnpacker unpacker
+    ECoderStatus status,
     BoolInt decodingTruncated,
     CXzStatInfo *stat)
 {
@@ -2484,12 +2533,20 @@
     if (status == CODER_STATUS_NEEDS_MORE_INPUT)
     {
       // CODER_STATUS_NEEDS_MORE_INPUT is expected status for correct xz streams
+      // any extra data is part of correct data
       extraSize = 0;
+      // if xz stream was not finished, then we need more data
       if (!XzUnpacker_IsStreamWasFinished(dec))
         res = SZ_ERROR_INPUT_EOF;
     }
-    else if (!decodingTruncated || finishMode) // (status == CODER_STATUS_NOT_FINISHED)
-      res = SZ_ERROR_DATA;
+    else
+    {
+      // CODER_STATUS_FINISHED_WITH_MARK is not possible for multi stream xz decoding
+      // so he we have (status == CODER_STATUS_NOT_FINISHED)
+      // if (status != CODER_STATUS_FINISHED_WITH_MARK)
+      if (!decodingTruncated || finishMode)
+        res = SZ_ERROR_DATA;
+    }
   }
   else if (res == SZ_ERROR_NO_ARCHIVE)
   {
@@ -2497,24 +2554,29 @@
     SZ_ERROR_NO_ARCHIVE is possible for 2 states:
       XZ_STATE_STREAM_HEADER  - if bad signature or bad CRC
       XZ_STATE_STREAM_PADDING - if non-zero padding data
-    extraSize / inProcessed don't include "bad" byte
+    extraSize and inProcessed don't include "bad" byte
     */
-    if (inProcessed != extraSize) // if good streams before error
-      if (extraSize != 0 || readProcessed != inProcessed)
+    // if (inProcessed == extraSize), there was no any good xz stream header, and we keep error
+    if (inProcessed != extraSize) // if there were good xz streams before error
+    {
+      // if (extraSize != 0 || readProcessed != inProcessed)
       {
+        // he we suppose that all xz streams were finsihed OK, and we have
+        // some extra data after all streams
         stat->DataAfterEnd = True;
-        // there is some good xz stream before. So we set SZ_OK
         res = SZ_OK;
       }
+    }
   }
   
-  stat->DecodeRes = res;
+  if (stat->DecodeRes == SZ_OK)
+    stat->DecodeRes = res;
 
   stat->InSize -= extraSize;
-  return res;
 }
 
 
+
 SRes XzDecMt_Decode(CXzDecMtHandle pp,
     const CXzDecMtProps *props,
     const UInt64 *outDataSize, int finishMode,
@@ -2557,8 +2619,9 @@
   p->inProcessed = 0;
   p->readProcessed = 0;
   p->readWasFinished = False;
+  p->readRes = SZ_OK;
 
-  p->codeRes = 0;
+  p->codeRes = SZ_OK;
   p->status = CODER_STATUS_NOT_SPECIFIED;
 
   XzUnpacker_Init(&p->dec);
@@ -2589,8 +2652,9 @@
 
   if (p->props.numThreads > 1)
   {
-    IMtDecCallback vt;
-
+    IMtDecCallback2 vt;
+    BoolInt needContinue;
+    SRes res;
     // we just free ST buffers here
     // but we still keep state variables, that was set in XzUnpacker_Init()
     XzDecMt_FreeSt(p);
@@ -2628,45 +2692,45 @@
     vt.Code = XzDecMt_Callback_Code;
     vt.Write = XzDecMt_Callback_Write;
 
+
+    res = MtDec_Code(&p->mtc);
+
+
+    stat->InSize = p->mtc.inProcessed;
+    
+    p->inProcessed = p->mtc.inProcessed;
+    p->readRes = p->mtc.readRes;
+    p->readWasFinished = p->mtc.readWasFinished;
+    p->readProcessed = p->mtc.readProcessed;
+    
+    tMode = True;
+    needContinue = False;
+    
+    if (res == SZ_OK)
     {
-      BoolInt needContinue;
-      
-      SRes res = MtDec_Code(&p->mtc);
-
-      stat->InSize = p->mtc.inProcessed;
-
-      p->inProcessed = p->mtc.inProcessed;
-      p->readRes = p->mtc.readRes;
-      p->readWasFinished = p->mtc.readWasFinished;
-      p->readProcessed = p->mtc.readProcessed;
-
-      tMode = True;
-      needContinue = False;
-
-      if (res == SZ_OK)
+      if (p->mtc.mtProgress.res != SZ_OK)
       {
-        if (p->mtc.mtProgress.res != SZ_OK)
-        {
-          res = p->mtc.mtProgress.res;
-          stat->ProgressRes = res;
-          stat->CombinedRes_Type = SZ_ERROR_PROGRESS;
-        }
-        else
-          needContinue = p->mtc.needContinue;
+        res = p->mtc.mtProgress.res;
+        stat->ProgressRes = res;
+        stat->CombinedRes_Type = SZ_ERROR_PROGRESS;
       }
-
-      if (!needContinue)
+      else
+        needContinue = p->mtc.needContinue;
+    }
+    
+    if (!needContinue)
+    {
       {
         SRes codeRes;
         BoolInt truncated = False;
         ECoderStatus status;
-        CXzUnpacker *dec;
+        const CXzUnpacker *dec;
 
         stat->OutSize = p->outProcessed;
        
         if (p->finishedDecoderIndex >= 0)
         {
-          CXzDecMtThread *coder = &p->coders[(unsigned)p->finishedDecoderIndex];
+          const CXzDecMtThread *coder = &p->coders[(unsigned)p->finishedDecoderIndex];
           codeRes = coder->codeRes;
           dec = &coder->dec;
           status = coder->status;
@@ -2679,41 +2743,46 @@
           truncated = p->parsing_Truncated;
         }
         else
-          return E_FAIL;
+          return SZ_ERROR_FAIL;
+
+        if (p->mainErrorCode != SZ_OK)
+          stat->DecodeRes = p->mainErrorCode;
 
         XzStatInfo_SetStat(dec, p->finishMode,
-            p->mtc.readProcessed, p->mtc.inProcessed,
+            // p->mtc.readProcessed,
+            p->mtc.inProcessed,
             codeRes, status,
             truncated,
             stat);
-
-        if (res == SZ_OK)
-        {
-          if (p->writeRes != SZ_OK)
-          {
-            res = p->writeRes;
-            stat->CombinedRes_Type = SZ_ERROR_WRITE;
-          }
-          else if (p->mtc.readRes != SZ_OK && p->mtc.inProcessed == p->mtc.readProcessed)
-          {
-            res = p->mtc.readRes;
-            stat->ReadRes = res;
-            stat->CombinedRes_Type = SZ_ERROR_READ;
-          }
-          else if (p->mainErrorCode != SZ_OK)
-          {
-            res = p->mainErrorCode;
-          }
-        }
-
-        stat->CombinedRes = res;
-        if (stat->CombinedRes_Type == SZ_OK)
-          stat->CombinedRes_Type = res;
-        return res;
       }
 
-      PRF_STR("----- decoding ST -----");
+      if (res == SZ_OK)
+      {
+        stat->ReadRes = p->mtc.readRes;
+
+        if (p->writeRes != SZ_OK)
+        {
+          res = p->writeRes;
+          stat->CombinedRes_Type = SZ_ERROR_WRITE;
+        }
+        else if (p->mtc.readRes != SZ_OK
+            // && p->mtc.inProcessed == p->mtc.readProcessed
+            && stat->DecodeRes == SZ_ERROR_INPUT_EOF)
+        {
+          res = p->mtc.readRes;
+          stat->CombinedRes_Type = SZ_ERROR_READ;
+        }
+        else if (stat->DecodeRes != SZ_OK)
+          res = stat->DecodeRes;
+      }
+      
+      stat->CombinedRes = res;
+      if (stat->CombinedRes_Type == SZ_OK)
+        stat->CombinedRes_Type = res;
+      return res;
     }
+
+    PRF_STR("----- decoding ST -----");
   }
 
   #endif
@@ -2729,33 +2798,35 @@
         , stat
         );
 
+    #ifndef _7ZIP_ST
+    // we must set error code from MT decoding at first
+    if (p->mainErrorCode != SZ_OK)
+      stat->DecodeRes = p->mainErrorCode;
+    #endif
+
     XzStatInfo_SetStat(&p->dec,
         p->finishMode,
-        p->readProcessed, p->inProcessed,
+        // p->readProcessed,
+        p->inProcessed,
         p->codeRes, p->status,
         False, // truncated
         stat);
 
+    stat->ReadRes = p->readRes;
+
     if (res == SZ_OK)
     {
-      /*
-      if (p->writeRes != SZ_OK)
+      if (p->readRes != SZ_OK
+          // && p->inProcessed == p->readProcessed
+          && stat->DecodeRes == SZ_ERROR_INPUT_EOF)
       {
-        res = p->writeRes;
-        stat->CombinedRes_Type = SZ_ERROR_WRITE;
-      }
-      else
-      */
-      if (p->readRes != SZ_OK && p->inProcessed == p->readProcessed)
-      {
+        // we set read error as combined error, only if that error was the reason
+        // of decoding problem
         res = p->readRes;
-        stat->ReadRes = res;
         stat->CombinedRes_Type = SZ_ERROR_READ;
       }
-      #ifndef _7ZIP_ST
-      else if (p->mainErrorCode != SZ_OK)
-        res = p->mainErrorCode;
-      #endif
+      else if (stat->DecodeRes != SZ_OK)
+        res = stat->DecodeRes;
     }
 
     stat->CombinedRes = res;
diff --git a/third_party/lzma_sdk/chromium.patch b/third_party/lzma_sdk/chromium.patch
new file mode 100644
index 0000000..e2a614b
--- /dev/null
+++ b/third_party/lzma_sdk/chromium.patch
@@ -0,0 +1,79 @@
+diff --git "a/lzma2107\\C/7zCrc.c" "b/third_party\\lzma_sdk/7zCrc.c"
+index f186324ddc609..c0cc9bc7812e0 100644
+--- "a/lzma2107\\C/7zCrc.c"
++++ "b/third_party\\lzma_sdk/7zCrc.c"
+@@ -78,20 +78,20 @@ UInt32 MY_FAST_CALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const U
+   #if defined(_MSC_VER)
+     #if defined(MY_CPU_ARM64)
+     #if (_MSC_VER >= 1910)
+-        #define USE_ARM64_CRC
++        // #define USE_ARM64_CRC
+     #endif
+     #endif
+   #elif (defined(__clang__) && (__clang_major__ >= 3)) \
+      || (defined(__GNUC__) && (__GNUC__ > 4))
+       #if !defined(__ARM_FEATURE_CRC32)
+-        #define __ARM_FEATURE_CRC32 1
++        // #define __ARM_FEATURE_CRC32 1
+           #if (!defined(__clang__) || (__clang_major__ > 3)) // fix these numbers
+-            #define ATTRIB_CRC __attribute__((__target__("arch=armv8-a+crc")))
++            // #define ATTRIB_CRC __attribute__((__target__("arch=armv8-a+crc")))
+           #endif
+       #endif
+       #if defined(__ARM_FEATURE_CRC32)
+-        #define USE_ARM64_CRC
+-        #include <arm_acle.h>
++        // #define USE_ARM64_CRC
++        // #include <arm_acle.h>
+       #endif
+   #endif
+ 
+diff --git "a/lzma2107\\C/CpuArch.c" "b/third_party\\lzma_sdk/CpuArch.c"
+index fa9afe3970b3f..30451fba9b97b 100644
+--- "a/lzma2107\\C/CpuArch.c"
++++ "b/third_party\\lzma_sdk/CpuArch.c"
+@@ -417,7 +417,9 @@ BoolInt CPU_IsSupported_AES (void) { return APPLE_CRYPTO_SUPPORT_VAL; }
+ 
+ #include <sys/auxv.h>
+ 
++#if !defined(ARMV8_OS_FUCHSIA)
+ #define USE_HWCAP
++#endif // !defined(ARMV8_OS_FUCHSIA)
+ 
+ #ifdef USE_HWCAP
+ 
+diff --git "a/lzma2107\\C/LzFind.c" "b/third_party\\lzma_sdk/LzFind.c"
+index 1b73c28484ccf..36f7330911435 100644
+--- "a/lzma2107\\C/LzFind.c"
++++ "b/third_party\\lzma_sdk/LzFind.c"
+@@ -505,7 +505,7 @@ void MatchFinder_Init(CMatchFinder *p)
+ }
+ 
+ 
+-
++#if 0
+ #ifdef MY_CPU_X86_OR_AMD64
+   #if defined(__clang__) && (__clang_major__ >= 8) \
+     || defined(__GNUC__) && (__GNUC__ >= 8) \
+@@ -549,6 +549,7 @@ void MatchFinder_Init(CMatchFinder *p)
+   #endif
+ 
+ #endif
++#endif
+ 
+ /*
+ #ifndef ATTRIB_SSE41
+diff --git "a/lzma2107\\C/Sha256.c" "b/third_party\\lzma_sdk/Sha256.c"
+index 8b3983ea7323d..21996848c9156 100644
+--- "a/lzma2107\\C/Sha256.c"
++++ "b/third_party\\lzma_sdk/Sha256.c"
+@@ -32,7 +32,8 @@ This code is based on public domain code from Wei Dai's Crypto++ library. */
+       #define _SHA_SUPPORTED
+     #endif
+   #endif
+-#elif defined(MY_CPU_ARM_OR_ARM64)
++// TODO(crbug.com/1338627): Enable ARM optimizations
++#elif 0 // defined(MY_CPU_ARM_OR_ARM64)
+   #ifdef _MSC_VER
+     #if _MSC_VER >= 1910
+       #define _SHA_SUPPORTED
diff --git a/third_party/node/README.chromium b/third_party/node/README.chromium
index fefe2e7e..3758aaf 100644
--- a/third_party/node/README.chromium
+++ b/third_party/node/README.chromium
@@ -248,3 +248,19 @@
 
 Local Modifications:
 (none)
+
+Name: HTML Minifier
+Short Name: html-minifier
+URL: https://www.npmjs.com/package/html-minifier
+Version: 4.0.0
+License: MIT
+Security Critical: No, as html-minifier itself is only used during build time.
+It does affect the contents of shipped HTML files though.
+
+Description:
+HTMLMinifier is a highly configurable, well-tested, JavaScript-based HTML
+minifier.
+
+Local Modifications:
+Removed uglify-js dependency since we don't plan to use the --minify-js flag in
+Chromium.
diff --git a/third_party/node/html_minifier.patch b/third_party/node/html_minifier.patch
new file mode 100644
index 0000000..cce31233
--- /dev/null
+++ b/third_party/node/html_minifier.patch
@@ -0,0 +1,12 @@
+diff --git a/src/htmlminifier.js b/src/htmlminifier.js
+index d7efa99148a21..eb19dd6b33ff3 100644
+--- a/src/htmlminifier.js
++++ b/src/htmlminifier.js
+@@ -5,7 +5,6 @@ var decode = require('he').decode;
+ var HTMLParser = require('./htmlparser').HTMLParser;
+ var RelateUrl = require('relateurl');
+ var TokenChain = require('./tokenchain');
+-var UglifyJS = require('uglify-js');
+ var utils = require('./utils');
+ 
+ function trimWhitespace(str) {
diff --git a/third_party/node/node_modules.py b/third_party/node/node_modules.py
index d45f1c3..2229aff 100755
--- a/third_party/node/node_modules.py
+++ b/third_party/node/node_modules.py
@@ -26,6 +26,11 @@
   return _path_in_node_modules('eslint', 'bin', 'eslint')
 
 
+def PathToHtmlMinifier():
+  return _path_in_node_modules('html-minifier', 'cli.js')
+
+
+# TODO(crbug.com/1325169): Delete after migration to html-minifier is complete.
 def PathToPolymerCssBuild():
   return _path_in_node_modules('polymer-css-build', 'bin', 'polymer-css-build')
 
diff --git a/third_party/node/node_modules.tar.gz.sha1 b/third_party/node/node_modules.tar.gz.sha1
index bc95ff9..5758ae2 100644
--- a/third_party/node/node_modules.tar.gz.sha1
+++ b/third_party/node/node_modules.tar.gz.sha1
@@ -1 +1 @@
-6d04f91df50efaa847885c0296ec9ed440764cb7
+fb45b506e9a41671bb4666652b1cb1a5849dc9aa
diff --git a/third_party/node/npm_exclude.txt b/third_party/node/npm_exclude.txt
index d510100..bc68010 100644
--- a/third_party/node/npm_exclude.txt
+++ b/third_party/node/npm_exclude.txt
@@ -20,3 +20,7 @@
 *.map
 typescript/lib/*
 typescript/loc/*
+
+# This is pulled in from html-minifier, but there is no plan to use the
+# --minify-js which is the only entry to that codepath.
+uglify-js/*
diff --git a/third_party/node/package-lock.json b/third_party/node/package-lock.json
index 0d0285a..b69ec60 100644
--- a/third_party/node/package-lock.json
+++ b/third_party/node/package-lock.json
@@ -27,6 +27,7 @@
         "crisper": "2.1.1",
         "eslint": "7.11.0",
         "eslint-plugin-jsdoc": "37.5.1",
+        "html-minifier": "4.0.0",
         "polymer-bundler": "4.0.10",
         "polymer-css-build": "0.7.0",
         "rollup": "2.58.0",
@@ -1251,6 +1252,15 @@
         "node": ">=6"
       }
     },
+    "node_modules/camel-case": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
+      "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==",
+      "dependencies": {
+        "no-case": "^2.2.0",
+        "upper-case": "^1.1.1"
+      }
+    },
     "node_modules/cancel-token": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/cancel-token/-/cancel-token-0.1.1.tgz",
@@ -1272,6 +1282,25 @@
         "node": ">=4"
       }
     },
+    "node_modules/clean-css": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz",
+      "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==",
+      "dependencies": {
+        "source-map": "~0.6.0"
+      },
+      "engines": {
+        "node": ">= 4.0"
+      }
+    },
+    "node_modules/clean-css/node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/clone": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
@@ -2136,6 +2165,34 @@
         "node": ">=4"
       }
     },
+    "node_modules/he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+      "bin": {
+        "he": "bin/he"
+      }
+    },
+    "node_modules/html-minifier": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
+      "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
+      "dependencies": {
+        "camel-case": "^3.0.0",
+        "clean-css": "^4.2.1",
+        "commander": "^2.19.0",
+        "he": "^1.2.0",
+        "param-case": "^2.1.1",
+        "relateurl": "^0.2.7",
+        "uglify-js": "^3.5.1"
+      },
+      "bin": {
+        "html-minifier": "cli.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/ignore": {
       "version": "4.0.6",
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@@ -2361,6 +2418,11 @@
         "loose-envify": "cli.js"
       }
     },
+    "node_modules/lower-case": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
+      "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA=="
+    },
     "node_modules/lru-cache": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -2442,6 +2504,14 @@
       "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
       "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc="
     },
+    "node_modules/no-case": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
+      "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
+      "dependencies": {
+        "lower-case": "^1.1.1"
+      }
+    },
     "node_modules/nth-check": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz",
@@ -2477,6 +2547,14 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/param-case": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
+      "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==",
+      "dependencies": {
+        "no-case": "^2.2.0"
+      }
+    },
     "node_modules/parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -3155,6 +3233,14 @@
         "node": ">=0.1.14"
       }
     },
+    "node_modules/relateurl": {
+      "version": "0.2.7",
+      "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
+      "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
     "node_modules/repeating": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
@@ -3617,6 +3703,22 @@
       "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
       "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0="
     },
+    "node_modules/uglify-js": {
+      "version": "3.15.5",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.5.tgz",
+      "integrity": "sha512-hNM5q5GbBRB5xB+PMqVRcgYe4c8jbyZ1pzZhS6jbq54/4F2gFK869ZheiE5A8/t+W5jtTNpWef/5Q9zk639FNQ==",
+      "bin": {
+        "uglifyjs": "bin/uglifyjs"
+      },
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/upper-case": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
+      "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
+    },
     "node_modules/uri-js": {
       "version": "4.4.1",
       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -4704,6 +4806,15 @@
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
       "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
     },
+    "camel-case": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
+      "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==",
+      "requires": {
+        "no-case": "^2.2.0",
+        "upper-case": "^1.1.1"
+      }
+    },
     "cancel-token": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/cancel-token/-/cancel-token-0.1.1.tgz",
@@ -4722,6 +4833,21 @@
         "supports-color": "^5.3.0"
       }
     },
+    "clean-css": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz",
+      "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==",
+      "requires": {
+        "source-map": "~0.6.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+        }
+      }
+    },
     "clone": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
@@ -5357,6 +5483,25 @@
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
       "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
     },
+    "he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
+    },
+    "html-minifier": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
+      "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
+      "requires": {
+        "camel-case": "^3.0.0",
+        "clean-css": "^4.2.1",
+        "commander": "^2.19.0",
+        "he": "^1.2.0",
+        "param-case": "^2.1.1",
+        "relateurl": "^0.2.7",
+        "uglify-js": "^3.5.1"
+      }
+    },
     "ignore": {
       "version": "4.0.6",
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@@ -5525,6 +5670,11 @@
         "js-tokens": "^3.0.0 || ^4.0.0"
       }
     },
+    "lower-case": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
+      "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA=="
+    },
     "lru-cache": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -5591,6 +5741,14 @@
       "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
       "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc="
     },
+    "no-case": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
+      "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
+      "requires": {
+        "lower-case": "^1.1.1"
+      }
+    },
     "nth-check": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz",
@@ -5620,6 +5778,14 @@
         "word-wrap": "^1.2.3"
       }
     },
+    "param-case": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
+      "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==",
+      "requires": {
+        "no-case": "^2.2.0"
+      }
+    },
     "parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -6147,6 +6313,11 @@
       "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.8.0.tgz",
       "integrity": "sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ=="
     },
+    "relateurl": {
+      "version": "0.2.7",
+      "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
+      "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk="
+    },
     "repeating": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
@@ -6478,6 +6649,16 @@
       "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz",
       "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0="
     },
+    "uglify-js": {
+      "version": "3.15.5",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.5.tgz",
+      "integrity": "sha512-hNM5q5GbBRB5xB+PMqVRcgYe4c8jbyZ1pzZhS6jbq54/4F2gFK869ZheiE5A8/t+W5jtTNpWef/5Q9zk639FNQ=="
+    },
+    "upper-case": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
+      "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
+    },
     "uri-js": {
       "version": "4.4.1",
       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
diff --git a/third_party/node/package.json b/third_party/node/package.json
index 4e74aad..dbad99f 100644
--- a/third_party/node/package.json
+++ b/third_party/node/package.json
@@ -22,6 +22,7 @@
     "crisper": "2.1.1",
     "eslint": "7.11.0",
     "eslint-plugin-jsdoc": "37.5.1",
+    "html-minifier": "4.0.0",
     "polymer-bundler": "4.0.10",
     "polymer-css-build": "0.7.0",
     "rollup": "2.58.0",
diff --git a/third_party/node/update_npm_deps b/third_party/node/update_npm_deps
index d520f47b..4c6f756 100755
--- a/third_party/node/update_npm_deps
+++ b/third_party/node/update_npm_deps
@@ -24,6 +24,9 @@
 # Apply local patch to d3.
 patch -d node_modules/@types/d3/ -p1 < chromium_d3_types_index.patch
 
+# Apply local patch to html-minifier
+patch -d node_modules/html-minifier/ -p1 < html_minifier.patch
+
 rsync -c --delete -r -q --include-from="npm_include.txt" --exclude-from="npm_exclude.txt" \
       --prune-empty-dirs "node_modules/" "node_modules_filtered/"
 
diff --git a/tools/android/test_health/OWNERS b/tools/android/test_health/OWNERS
index d35eee9..005acb8 100644
--- a/tools/android/test_health/OWNERS
+++ b/tools/android/test_health/OWNERS
@@ -1,3 +1,2 @@
-andrewheard@chromium.org
 fredmello@chromium.org
 wnwen@chromium.org
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 464fa52..4c26347a 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -787,11 +787,14 @@
     'internal.chrome.fyi': {
       'chromeos-amd64-generic-lacros-internal-rel': 'chromeos_amd64-generic_lacros_rel',
       'linux-autofill-captured-sites-rel': 'release_bot',
+      'linux-finch-smoke-chrome': 'official_goma',
       'linux-password-manager-captured-sites-rel': 'release_bot',
       'lorenz-graph-dbg': 'android_debug_static_external_bot',
       'mac-autofill-captured-sites-rel': 'release_bot',
+      'mac-finch-smoke-chrome': 'official_goma_mac',
       'win-autofill-captured-sites-rel': 'release_bot',
       'win-celab-rel': 'official_celab_release_bot',
+      'win-finch-smoke-chrome': 'official_goma',
       'win-password-manager-captured-sites-rel': 'release_bot',
     },
 
@@ -867,13 +870,16 @@
       'linux-chrome-beta': 'official_goma',
       'linux-chrome-stable': 'official_goma',
       'linux-chromeos-chrome': 'official_goma_chromeos_include_unwind_tables',
+      'linux-finch-smoke-chrome': 'official_goma',
       'mac-chrome': 'official_goma_mac',
       'mac-chrome-beta': 'official_goma_mac',
       'mac-chrome-stable': 'official_goma_mac',
+      'mac-finch-smoke-chrome': 'official_goma_mac',
       'win-celab-try-rel': 'official_celab_release_bot',
       'win-chrome': 'official_goma_x86',
       'win-chrome-beta': 'official_goma_x86',
       'win-chrome-stable': 'official_goma_x86',
+      'win-finch-smoke-chrome': 'official_goma',
       'win64-chrome': 'official_goma_x64',
       'win64-chrome-beta': 'official_goma_x64',
       'win64-chrome-stable': 'official_goma_x64',
diff --git a/tools/mb/mb_config_expectations/internal.chrome.fyi.json b/tools/mb/mb_config_expectations/internal.chrome.fyi.json
index cc84dbf..6bd9cb3 100644
--- a/tools/mb/mb_config_expectations/internal.chrome.fyi.json
+++ b/tools/mb/mb_config_expectations/internal.chrome.fyi.json
@@ -19,6 +19,13 @@
       "use_goma": true
     }
   },
+  "linux-finch-smoke-chrome": {
+    "gn_args": {
+      "is_chrome_branded": true,
+      "is_official_build": true,
+      "use_goma": true
+    }
+  },
   "linux-password-manager-captured-sites-rel": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -47,6 +54,14 @@
       "use_goma": true
     }
   },
+  "mac-finch-smoke-chrome": {
+    "gn_args": {
+      "ignore_missing_widevine_signing_cert": true,
+      "is_chrome_branded": true,
+      "is_official_build": true,
+      "use_goma": true
+    }
+  },
   "win-autofill-captured-sites-rel": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -66,6 +81,13 @@
       "use_goma": true
     }
   },
+  "win-finch-smoke-chrome": {
+    "gn_args": {
+      "is_chrome_branded": true,
+      "is_official_build": true,
+      "use_goma": true
+    }
+  },
   "win-password-manager-captured-sites-rel": {
     "gn_args": {
       "dcheck_always_on": false,
diff --git a/tools/mb/mb_config_expectations/tryserver.chrome.json b/tools/mb/mb_config_expectations/tryserver.chrome.json
index 05d3191..f93d83f 100644
--- a/tools/mb/mb_config_expectations/tryserver.chrome.json
+++ b/tools/mb/mb_config_expectations/tryserver.chrome.json
@@ -315,6 +315,13 @@
       "use_goma": true
     }
   },
+  "linux-finch-smoke-chrome": {
+    "gn_args": {
+      "is_chrome_branded": true,
+      "is_official_build": true,
+      "use_goma": true
+    }
+  },
   "mac-chrome": {
     "gn_args": {
       "ignore_missing_widevine_signing_cert": true,
@@ -339,6 +346,14 @@
       "use_goma": true
     }
   },
+  "mac-finch-smoke-chrome": {
+    "gn_args": {
+      "ignore_missing_widevine_signing_cert": true,
+      "is_chrome_branded": true,
+      "is_official_build": true,
+      "use_goma": true
+    }
+  },
   "win-celab-try-rel": {
     "gn_args": {
       "dcheck_always_on": false,
@@ -374,6 +389,13 @@
       "use_goma": true
     }
   },
+  "win-finch-smoke-chrome": {
+    "gn_args": {
+      "is_chrome_branded": true,
+      "is_official_build": true,
+      "use_goma": true
+    }
+  },
   "win64-chrome": {
     "gn_args": {
       "is_chrome_branded": true,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 5249b4f..e082b06c 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -11231,6 +11231,7 @@
 <enum name="BluetoothStackName">
   <int value="0" label="BlueZ"/>
   <int value="1" label="Floss"/>
+  <int value="2" label="Unknown"/>
 </enum>
 
 <enum name="BluetoothStatus">
@@ -55508,6 +55509,7 @@
   <int value="-1835592983" label="WebAppEnableLaunchHandler:disabled"/>
   <int value="-1834967404" label="DesktopCaptureLacrosV2:enabled"/>
   <int value="-1834841895" label="CrostiniWebUIUpgrader:enabled"/>
+  <int value="-1834475132" label="IgnoreSyncEncryptionKeysLongMissing:enabled"/>
   <int value="-1834151268" label="OmniboxDynamicMaxAutocomplete:disabled"/>
   <int value="-1833149810" label="enable-accessibility-tab-switcher"/>
   <int value="-1832575380" label="show-saved-copy"/>
@@ -56369,6 +56371,7 @@
   <int value="-1285790931" label="FencedFrames:disabled"/>
   <int value="-1285021473" label="save-page-as-mhtml"/>
   <int value="-1284637134" label="pull-to-refresh"/>
+  <int value="-1283306503" label="LensSearchOptimizations:disabled"/>
   <int value="-1283164264" label="CroshSWA:disabled"/>
   <int value="-1282992935"
       label="AutofillLocalCardMigrationShowFeedback:disabled"/>
@@ -58577,6 +58580,7 @@
   <int value="157217034" label="enable-tab-for-desktop-share"/>
   <int value="157318016" label="AutomaticTabDiscarding:enabled"/>
   <int value="158490417" label="PhoneHubCameraRoll:enabled"/>
+  <int value="160429011" label="LensSearchOptimizations:enabled"/>
   <int value="160506687" label="ArcRtVcpuDualCore:disabled"/>
   <int value="160524775" label="PDFTwoUpView:disabled"/>
   <int value="160838658" label="SmartDimModelV3:enabled"/>
@@ -61070,6 +61074,7 @@
   <int value="1803465156" label="enable-zero-suggest-most-visited"/>
   <int value="1803470125" label="SyncUSSSessions:enabled"/>
   <int value="1803914892" label="TemporaryUnexpireFlagsM76:enabled"/>
+  <int value="1806220475" label="IgnoreSyncEncryptionKeysLongMissing:disabled"/>
   <int value="1806450300" label="WebUsbDeviceDetection:enabled"/>
   <int value="1807374811" label="CCTModuleCache:enabled"/>
   <int value="1808936313" label="AutofillEnableNameSurenameParsing:enabled"/>
@@ -87173,6 +87178,13 @@
   <int value="2" label="Dismiss"/>
 </enum>
 
+<enum name="SidePanelOpenTrigger">
+  <int value="0" label="Opened from toolbar button"/>
+  <int value="1" label="Opened from lens context menu"/>
+  <int value="2" label="Opened from side search page action button"/>
+  <int value="3" label="Opened from user notes context menu in page"/>
+</enum>
+
 <enum name="SideSearchAvailabilityChangeType">
   <int value="0" label="Become available"/>
   <int value="1" label="Become unavailable"/>
diff --git a/tools/metrics/histograms/metadata/bluetooth/histograms.xml b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
index 1afc702..b1a873b8c 100644
--- a/tools/metrics/histograms/metadata/bluetooth/histograms.xml
+++ b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
@@ -79,7 +79,7 @@
 </histogram>
 
 <histogram name="Bluetooth.ChromeOS.DeviceConnected.{ConnectionType}"
-    enum="BluetoothDeviceType" expires_after="2022-07-01">
+    enum="BluetoothDeviceType" expires_after="2023-06-01">
   <owner>khorimoto@chromium.org</owner>
   <owner>cros-connectivity@google.com</owner>
   <summary>
@@ -103,7 +103,7 @@
 
 <histogram
     name="Bluetooth.ChromeOS.DeviceSelectionDuration{DeviceSelectionUISurfaces}"
-    units="ms" expires_after="2022-07-01">
+    units="ms" expires_after="2023-06-01">
   <owner>khorimoto@chromium.org</owner>
   <owner>cros-connectivity@google.com</owner>
   <summary>
@@ -118,7 +118,7 @@
 
 <histogram
     name="Bluetooth.ChromeOS.DeviceSelectionDuration{DeviceSelectionUISurfaces}.NotPaired{BluetoothTransportTypes}"
-    units="ms" expires_after="2022-07-01">
+    units="ms" expires_after="2023-06-01">
   <owner>khorimoto@chromium.org</owner>
   <owner>cros-connectivity@google.com</owner>
   <summary>
@@ -132,7 +132,7 @@
 
 <histogram
     name="Bluetooth.ChromeOS.DeviceSelectionDuration{DeviceSelectionUISurfaces}{BluetoothPairedStates}"
-    units="ms" expires_after="2022-07-01">
+    units="ms" expires_after="2023-06-01">
   <owner>khorimoto@chromium.org</owner>
   <owner>cros-connectivity@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/browser/histograms.xml b/tools/metrics/histograms/metadata/browser/histograms.xml
index 729cc5c4..9637c0be 100644
--- a/tools/metrics/histograms/metadata/browser/histograms.xml
+++ b/tools/metrics/histograms/metadata/browser/histograms.xml
@@ -1112,6 +1112,27 @@
   </summary>
 </histogram>
 
+<histogram name="SidePanel.OpenDuration" units="ms" expires_after="2023-06-04">
+  <owner>corising@chromium.org</owner>
+  <owner>chrome-desktop-ui-sea@google.com</owner>
+  <summary>
+    Records how long the side panel was open for (capped at 1 hour) starting
+    when the side panel is triggered to be opened. Note this might be different
+    than when the side panel becomes visible due to delays for loading content.
+    Recorded when the side panel is closed.
+  </summary>
+</histogram>
+
+<histogram name="SidePanel.OpenTrigger" enum="SidePanelOpenTrigger"
+    expires_after="2023-06-04">
+  <owner>corising@chromium.org</owner>
+  <owner>chrome-desktop-ui-sea@google.com</owner>
+  <summary>
+    Logs the UI location from which the side panel is triggered to be opened.
+    Recorded when the side panel is shown and a trigger is provided.
+  </summary>
+</histogram>
+
 </histograms>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
index 33979a9..4300c286 100644
--- a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
+++ b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
@@ -304,6 +304,46 @@
   </summary>
 </histogram>
 
+<histogram
+    name="CustomTabs.RetainableSessions.TimeBetweenLaunch{IdentifierType}"
+    units="seconds" expires_after="2022-11-03">
+  <owner>wenyufu@chromium.org</owner>
+  <owner>chrome-connective-tissue@google.com</owner>
+  <summary>
+    When a CCT session is launched, if it is launch with the same Uri and same
+    taskId, record the duration between previous CCT closure and current CCT
+    launch. Recorded on CCT launch.
+
+    A CCT can be defined as retainable if: 1) it is launching with the same Uri
+    as the most recently closed CCT; 2) it is launched from the same embedded
+    app as the most recently closed CCT; 3) the most recently closed CCT had
+    user interactions. See &quot;CustomTabs.HadInteractionOnClose&quot; for
+    information on interactions.
+
+    The histogram suffix identifies whether the CCT is created with the same
+    intent Uri data and for the same embedded app, launching CCT with the same
+    taskId and package name. The package name identifier to use will depend on
+    whether the client app is using CustomTabService. If app is using the CCT
+    service, the package name will be read from CCT service; otherwise the
+    package name is read from the referrer of CCT activity.
+
+    This histogram is recorded for {IdentifierType}
+  </summary>
+  <token key="IdentifierType">
+    <variant name=".Different"
+        summary="App is not launched from the same embedded app as package
+                 name / referrer is different."/>
+    <variant name=".Mixed"
+        summary="Apps not consistently using CCT services. Referrer and
+                 package are used together."/>
+    <variant name=".PackageName"
+        summary="Apps using CCT services. Package name is used as identifier."/>
+    <variant name=".Referrer"
+        summary="Apps not connecting with CCT services. Activity referrer is
+                 used as identifier."/>
+  </token>
+</histogram>
+
 <histogram name="CustomTabs.ShareOptionLocation" enum="ShareOptionLocation"
     expires_after="M109">
   <owner>sophey@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/service/histograms.xml b/tools/metrics/histograms/metadata/service/histograms.xml
index 000d593..6550d7c 100644
--- a/tools/metrics/histograms/metadata/service/histograms.xml
+++ b/tools/metrics/histograms/metadata/service/histograms.xml
@@ -1067,7 +1067,7 @@
 </histogram>
 
 <histogram name="ServiceWorkerCache.Cache.Browser.Match.RelatedFetchEvent"
-    units="ms" expires_after="2022-08-01">
+    units="ms" expires_after="2023-06-24">
   <owner>wanderview@chromium.org</owner>
   <owner>chrome-owp-storage@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/web_core/histograms.xml b/tools/metrics/histograms/metadata/web_core/histograms.xml
index 3bdeb75..07b1d142e 100644
--- a/tools/metrics/histograms/metadata/web_core/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_core/histograms.xml
@@ -183,9 +183,8 @@
     expires_after="never">
 <!-- expires-never: indexeddb heartbeat metric; used for chirp alerts (go/chrome-indexeddb-heartbeat) -->
 
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Records actions that take place in IndexedDB. These stats are used for
     normalization in formulas. See go/chrome-indexeddb-heartbeat
@@ -193,10 +192,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.BackingStore.ConsistencyError"
-    enum="IDBLevelDBBackingStoreInternalErrorType" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="IDBLevelDBBackingStoreInternalErrorType" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Methods that encountered consistency errors. Such errors probably point to a
     bug in our code.
@@ -207,9 +205,8 @@
     enum="LevelDBStatus" expires_after="never">
 <!-- expires-never: indexeddb heartbeat metric; used for chirp alerts (go/chrome-indexeddb-heartbeat) -->
 
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Records the result of trying to delete an IndexedDB database, per the user
     using the IDBFactory::DeleteDatabase API.
@@ -220,9 +217,8 @@
     enum="LevelDBStatus" expires_after="never">
 <!-- expires-never: indexeddb heartbeat metric; used for chirp alerts (go/chrome-indexeddb-heartbeat) -->
 
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Records the result of the FIRST attempt at opening the backing store for
     IndexedDB (i.e. retries are ignored). This is recorded when an API call
@@ -233,10 +229,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.BackingStore.OpenFirstTrySuccessTime"
-    units="ms" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    units="ms" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Records the time that it takes to open IndexedDB's backing store. This is
     only recorded if the backing store was opened successfully on the first
@@ -250,9 +245,8 @@
 
 <!-- expires-never: indexeddb heartbeat metric; used for chirp alerts (go/chrome-indexeddb-heartbeat) -->
 
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Count of the different success and failure modes when opening an IndexedDB
     backing store - clean open, successful open with recovery, failed recovery,
@@ -264,9 +258,8 @@
     enum="IDBLevelDBBackingStoreOpenResult" expires_after="never">
 <!-- expires-never: core storage metric; consumed in separate dashboard (go/chrome-storage-dashboard) -->
 
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Count of the different success and failure modes when opening an IndexedDB
     backing store - clean open, successful open with recovery, failed recovery,
@@ -275,10 +268,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.BackingStore.OverlyLargeOriginLength"
-    units="characters" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    units="characters" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Length of leveldb directories that cause paths to not fit in the filesystem,
     either because the individual component is too long or the overall path is
@@ -287,37 +279,34 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.BackingStore.ReadError"
-    enum="IDBLevelDBBackingStoreInternalErrorType" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="IDBLevelDBBackingStoreInternalErrorType" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Methods that encountered leveldb errors while trying to read from disk.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.BackingStore.WriteError"
-    enum="IDBLevelDBBackingStoreInternalErrorType" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="IDBLevelDBBackingStoreInternalErrorType" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Methods that encountered leveldb errors while trying to write to disk.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.Context.ForceCloseReason"
-    enum="IDBContextForcedCloseReason" expires_after="2022-08-01">
-  <owner>cmumford@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="IDBContextForcedCloseReason" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>The reason that a forced-close of a backing store occurred.</summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.ErrorDuringForceCloseAborts"
-    enum="LevelDBStatus" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="LevelDBStatus" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Recorded when there is an error during the force close of IndexedDB for an
     origin. A force close can be triggered either from DevTools, or when there
@@ -328,10 +317,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.FoundBlobFileForValue" enum="Boolean"
-    expires_after="2022-12-04">
-  <owner>enne@chromium.org</owner>
-  <owner>mek@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Recorded when a blob is attempted to be read from an IndexedDB value. This
     is triggered for both implicit blob-wrapped large values and explicit blob
@@ -342,27 +330,27 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDB.CloseTime" units="ms"
-    expires_after="2022-08-01">
-  <owner>cmumford@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The time that it takes to close IndexedDB's LevelDB backing store.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDB.OpenTime" units="ms"
-    expires_after="2022-08-01">
-  <owner>cmumford@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The time that it takes to open IndexedDB's LevelDB backing store.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDB.WriteTime" units="ms"
-    expires_after="2022-08-01">
-  <owner>cmumford@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The time that it takes to write data to an IndexedDB's LevelDB backing
     store.
@@ -370,20 +358,18 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBOpenErrors" enum="LevelDBErrorTypes"
-    expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Error classes returned by LevelDB when it failed to open a database.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBOpenErrors.BFE"
-    enum="PlatformFileError" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="PlatformFileError" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Errors (base::File::Error) encountered by a single LevelDBEnv method when
     opening an IndexedDB instance.
@@ -391,40 +377,36 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBOpenErrors.Corruption"
-    enum="LevelDBCorruptionTypes" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="LevelDBCorruptionTypes" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Types of corruption that LevelDB encounters when opening a database.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBOpenErrors.EnvMethod"
-    enum="LevelDBIOErrorMethods" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="LevelDBIOErrorMethods" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     LevelDBEnv methods that generated IO errors when opening a database.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBReadErrors" enum="LevelDBErrorTypes"
-    expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Error classes returned by LevelDB when it failed to read a database.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBReadErrors.BFE"
-    enum="PlatformFileError" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="PlatformFileError" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Errors (base::File::Error) encountered by a single LevelDBEnv method when
     reading from an IndexedDB instance.
@@ -432,40 +414,36 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBReadErrors.Corruption"
-    enum="LevelDBCorruptionTypes" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="LevelDBCorruptionTypes" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Types of corruption that LevelDB encounters when reading a database.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBReadErrors.EnvMethod"
-    enum="LevelDBIOErrorMethods" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="LevelDBIOErrorMethods" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     LevelDBEnv methods that generated IO errors when reading a database.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBWriteErrors" enum="LevelDBErrorTypes"
-    expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Error classes returned by LevelDB when it failed to write to a database.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBWriteErrors.BFE"
-    enum="PlatformFileError" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="PlatformFileError" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Errors (base::File::Error) encountered by a single LevelDBEnv method when
     writing to an IndexedDB instance.
@@ -473,10 +451,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBWriteErrors.Corruption"
-    enum="LevelDBCorruptionTypes" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="LevelDBCorruptionTypes" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Types of corruption returned by LevelDB when it failed to write to a
     database.
@@ -484,20 +461,18 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.LevelDBWriteErrors.EnvMethod"
-    enum="LevelDBIOErrorMethods" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="LevelDBIOErrorMethods" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     LevelDBEnv methods that generated IO errors when writing to a database.
   </summary>
 </histogram>
 
 <histogram name="WebCore.IndexedDB.OpenTime.Cold" units="ms"
-    expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Records the full time it takes to open a database that isn't open yet (so
     files are loaded from disk, metadata checked, etc). The measurement happens
@@ -510,10 +485,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.OpenTime.Warm" units="ms"
-    expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Records the full time it takes to open an already-open database. The
     measurement happens when a website calls &quot;indexedDB.open&quot;, and
@@ -525,10 +499,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.PutBlobsCount" units="blobs"
-    expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The number of blobs being saved in an IndexedDB object store 'put'
     operation. Recorded for every 'put' operation.
@@ -536,10 +509,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.PutBlobsTotalSize" units="KB"
-    expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The total size of the blobs being saved in an IndexedDB object store 'put'
     operation. Recorded for every 'put' operation, except when there are no
@@ -548,10 +520,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.PutKeySize" units="KB"
-    expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The size of the IndexedDB key used in an IndexedDB object store 'put'
     operation. Recorded for every 'put' operation.
@@ -559,10 +530,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.PutValueSize2" units="KB"
-    expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The size of the IndexedDB value used in an IndexedDB object store 'put'
     operation. Recorded for every 'put' operation.
@@ -570,10 +540,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.TombstoneSweeper.DeletedTombstonesSize"
-    units="bytes" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    units="bytes" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Records the total size of tombstones deleted by the IndexedDB Tombstone
     Sweeper. Recorded on the browser side (back end) when the sweeper has
@@ -584,10 +553,9 @@
 
 <histogram
     name="WebCore.IndexedDB.TombstoneSweeper.DeletionCommitTime.Complete"
-    units="ms" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    units="ms" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Records the time it takes for the IndexedDB Tombstone Sweeper to commit
     tombstone deletions. Recorded on the browser side (back end) when the
@@ -597,10 +565,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.TombstoneSweeper.DeletionTotalTime.Complete"
-    units="ms" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    units="ms" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Records the time it takes for the IndexedDB Tombstone Sweeper to fully sweep
     the indexes. Recorded on the browser side (back end) when the sweeper has
@@ -610,10 +577,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.TombstoneSweeper.DeletionWriteError"
-    enum="LevelDBStatus" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="LevelDBStatus" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Records when an error occurs during deletion of index tombstones by the
     IndexedDB Tombstone Sweeper. Recorded on the browser side (back end) when
@@ -623,10 +589,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.TombstoneSweeper.IndexScanPercent"
-    units="%/5" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    units="%/5" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Recorded on the browser side (back end) when the IndexedDB Tombstone Sweeper
     has completed scanning. Records the percentage of the indexes the scanner
@@ -636,10 +601,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.TombstoneSweeper.NumDeletedTombstones"
-    units="Index Tombstones" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    units="Index Tombstones" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Records the number of tombstones deleted by the IndexedDB Tombstone Sweeper.
     Recorded on the browser side (back end) when the sweeper has completed
@@ -648,10 +612,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.TombstoneSweeper.SweepError"
-    enum="LevelDBStatus" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    enum="LevelDBStatus" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Recorded on the browser side (back end) when the IndexedDB Tombstone Sweeper
     encounters an error while sweeping. See https://goo.gl/coKwA7.
@@ -659,10 +622,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.Transaction.ReadOnly.SizeOnCommit2"
-    units="KB" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    units="KB" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The total temporary size of an IndexedDB ReadOnly Transaction. Since this is
     a readonly transaction, the size should only be &gt;0 when the transaction
@@ -671,10 +633,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.Transaction.ReadOnly.TimeActive" units="ms"
-    expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The time it takes for an IndexedDB ReadOnly Transaction to commit, starting
     from when it starts executing tasks (when it is scheduled). Recorded on
@@ -683,10 +644,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.Transaction.ReadWrite.SizeOnCommit2"
-    units="KB" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    units="KB" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The total temporary size of an IndexedDB ReadWrite Transaction. This is the
     memory that is temporarily stored before writing to disk. Recorded on
@@ -695,10 +655,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.Transaction.ReadWrite.TimeActive" units="ms"
-    expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The time it takes for an IndexedDB ReadWrite Transaction to commit, starting
     from when it starts executing tasks (when it is scheduled). Recorded on
@@ -707,10 +666,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.Transaction.VersionChange.SizeOnCommit2"
-    units="KB" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    units="KB" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The total temporary size of an IndexedDB VersionChange Transaction. This is
     the memory that is temporarily stored before writing to disk. Version change
@@ -720,10 +678,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.Transaction.VersionChange.TimeActive"
-    units="ms" expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    units="ms" expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     The time it takes for an IndexedDB VersionChange Transaction to commit,
     starting from when it starts executing tasks (when it is scheduled). Version
@@ -733,10 +690,9 @@
 </histogram>
 
 <histogram name="WebCore.IndexedDB.TransactionAbortReason" enum="IDBException"
-    expires_after="2022-08-01">
-  <owner>dmurph@chromium.org</owner>
-  <owner>pwnall@chromium.org</owner>
-  <owner>storage-dev@chromium.org</owner>
+    expires_after="2023-06-24">
+  <owner>ayui@chromium.org</owner>
+  <owner>chrome-owp-storage@google.com</owner>
   <summary>
     Recorded on the browser side (back end) when an IndexedDB transaction is
     aborted, specifically recording the reason for the abort. This can be
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index fe7decc..2129c70 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,7 +6,7 @@
         },
         "win": {
             "hash": "adcf9f60e35908138ad440207ded004627ccf43a",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/010e6c1867a689266316ad607725cfd8ed11d3f9/trace_processor_shell.exe"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/2a59c7427cfd780bcb98dfad01772b44468edc7a/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "58893933be305d3bfe0a72ebebcacde2ac3ca893",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v25.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "cd36bf5e5151252ac1b88bf8fc029c06ef2e8570",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/010e6c1867a689266316ad607725cfd8ed11d3f9/trace_processor_shell"
+            "hash": "25e9a9272f3b9bbacc96a3ae6b57f93ea0566549",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/048f619e850f327e1e6a9786680bebe582e4279a/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index ea0a214f..0749192 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -365,7 +365,7 @@
  <item id="help_content_provider" added_in_milestone="102" content_hash_code="0839311f" os_list="chromeos" file_path="ash/webui/os_feedback_ui/backend/help_content_provider.cc" />
  <item id="managed_acccount_signin_restrictions_secure_connect_chromeos" added_in_milestone="102" content_hash_code="068bc93d" os_list="chromeos" file_path="chrome/browser/ui/webui/signin/user_cloud_signin_restriction_policy_fetcher_chromeos.cc" />
  <item id="wallpaper_download_google_photo" added_in_milestone="102" content_hash_code="034be7b5" os_list="chromeos" file_path="ash/wallpaper/wallpaper_controller_impl.cc" />
- <item id="quick_answers_spellchecker" added_in_milestone="102" content_hash_code="06e81b7f" os_list="chromeos" file_path="chromeos/components/quick_answers/utils/spell_checker.cc" />
+ <item id="quick_answers_spellchecker" added_in_milestone="102" content_hash_code="06e81b7f" os_list="chromeos" file_path="chromeos/components/quick_answers/utils/spell_check_language.cc" />
  <item id="printing_oauth2_token_exchange_request" added_in_milestone="102" type="partial" second_id="printing_oauth2_http_exchange" content_hash_code="059df373" os_list="chromeos" semantics_fields="2,4" file_path="chrome/browser/ash/printing/oauth2/ipp_endpoint_token_fetcher.cc" />
  <item id="fedcm_account_profile_image_fetcher" added_in_milestone="102" content_hash_code="07d6a3e2" os_list="linux,windows,chromeos" file_path="chrome/browser/ui/views/webid/account_selection_bubble_view.cc" />
  <item id="device_activity_client_health_check" added_in_milestone="102" content_hash_code="0506113c" os_list="chromeos" file_path="ash/components/device_activity/device_activity_client.cc" />
diff --git a/tools/visual_debugger/frame.js b/tools/visual_debugger/frame.js
index 1894d12d..53bf1b2 100644
--- a/tools/visual_debugger/frame.js
+++ b/tools/visual_debugger/frame.js
@@ -59,19 +59,24 @@
       (this.submissionCount() - 1);
   }
 
-  updateCanvasOrientationAndSize(canvas, orientationDeg, scale) {
+  updateCanvasOrientation(canvas, orientationDeg) {
     // Swap canvas width/height for 90 or 270 deg rotations
     if (orientationDeg === 90 || orientationDeg === 270) {
-      canvas.width = this.size_.height * scale;
-      canvas.height = this.size_.width * scale;
+      canvas.width = this.size_.height;
+      canvas.height = this.size_.width;
     }
     // Restore original canvas width/height for 0 or 180 deg rotations
     else {
-      canvas.width = this.size_.width * scale;
-      canvas.height = this.size_.height * scale;
+      canvas.width = this.size_.width;
+      canvas.height = this.size_.height;
     }
   }
 
+  updateCanvasSize(canvas, scale) {
+    canvas.width *= scale;
+    canvas.height *= scale;
+  }
+
   getFilter(source_index) {
     const filters = Filter.enabledInstances();
     let filter = undefined;
@@ -266,11 +271,10 @@
   redrawCurrentFrame_() {
     const frame = this.getCurrentFrame();
     if (!frame) return;
-    frame.updateCanvasOrientationAndSize(this.canvas_,
-                this.viewOrientation, this.viewScale);
+    frame.updateCanvasOrientation(this.canvas_, this.viewOrientation);
+    frame.updateCanvasSize(this.canvas_, this.viewScale);
     this.updateTransformMatrix(this.viewOrientation);
     // this.drawContext_.translate(this.translationX, this.translationY);
-    frame.updateCanvasSize(this.canvas_, this.viewScale);
     frame.draw(this.canvas_, this.drawContext_,
                 this.viewScale, this.viewOrientation,
                 this.transformMatrix);
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn
index f5f8223..8cdcb5e 100644
--- a/ui/android/BUILD.gn
+++ b/ui/android/BUILD.gn
@@ -328,6 +328,7 @@
     "java/src/org/chromium/ui/resources/dynamics/BitmapDynamicResource.java",
     "java/src/org/chromium/ui/resources/dynamics/DynamicResource.java",
     "java/src/org/chromium/ui/resources/dynamics/DynamicResourceLoader.java",
+    "java/src/org/chromium/ui/resources/dynamics/DynamicResourceSnapshot.java",
     "java/src/org/chromium/ui/resources/dynamics/ViewResourceAdapter.java",
     "java/src/org/chromium/ui/resources/dynamics/ViewResourceInflater.java",
     "java/src/org/chromium/ui/resources/statics/NinePatchData.java",
@@ -405,6 +406,7 @@
 
 robolectric_library("ui_junit_test_support") {
   sources = [
+    "junit/src/org/chromium/ui/resources/dynamics/DynamicResourceTestUtils.java",
     "junit/src/org/chromium/ui/shadows/ShadowAnimatedStateListDrawable.java",
     "junit/src/org/chromium/ui/shadows/ShadowAppCompatResources.java",
     "junit/src/org/chromium/ui/shadows/ShadowAsyncLayoutInflater.java",
@@ -416,6 +418,7 @@
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
     "//third_party/androidx:androidx_asynclayoutinflater_asynclayoutinflater_java",
+    "//third_party/junit",
   ]
 }
 
@@ -504,6 +507,7 @@
     "junit/src/org/chromium/ui/widget/ViewRectProviderTest.java",
   ]
   deps = [
+    ":ui_android_jni_headers",
     ":ui_java",
     ":ui_java_test_support",
     ":ui_javatest_resources",
diff --git a/ui/android/java/src/org/chromium/ui/resources/ResourceFactory.java b/ui/android/java/src/org/chromium/ui/resources/ResourceFactory.java
index cd917996..ff0db447 100644
--- a/ui/android/java/src/org/chromium/ui/resources/ResourceFactory.java
+++ b/ui/android/java/src/org/chromium/ui/resources/ResourceFactory.java
@@ -28,7 +28,7 @@
     }
 
     @NativeMethods
-    interface Natives {
+    public interface Natives {
         long createBitmapResource();
         long createNinePatchBitmapResource(int paddingLeft, int paddingTop, int paddingRight,
                 int paddingBottom, int apertureLeft, int apertureTop, int apertureRight,
diff --git a/ui/android/java/src/org/chromium/ui/resources/ResourceManager.java b/ui/android/java/src/org/chromium/ui/resources/ResourceManager.java
index fa5f51a..a9cd903 100644
--- a/ui/android/java/src/org/chromium/ui/resources/ResourceManager.java
+++ b/ui/android/java/src/org/chromium/ui/resources/ResourceManager.java
@@ -17,7 +17,6 @@
 import org.chromium.ui.display.DisplayAndroid;
 import org.chromium.ui.resources.ResourceLoader.ResourceLoaderCallback;
 import org.chromium.ui.resources.dynamics.BitmapDynamicResource;
-import org.chromium.ui.resources.dynamics.DynamicResource;
 import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
 import org.chromium.ui.resources.statics.StaticResourceLoader;
 import org.chromium.ui.resources.system.SystemResourceLoader;
@@ -80,7 +79,7 @@
 
     /**
      * @return A reference to the {@link DynamicResourceLoader} that provides
-     *         {@link DynamicResource} objects to this class.
+     *         {@link Resource} objects to this class.
      */
     public DynamicResourceLoader getDynamicResourceLoader() {
         return (DynamicResourceLoader) mResourceLoaders.get(
diff --git a/ui/android/java/src/org/chromium/ui/resources/dynamics/BitmapDynamicResource.java b/ui/android/java/src/org/chromium/ui/resources/dynamics/BitmapDynamicResource.java
index 8decc17..527c43e 100644
--- a/ui/android/java/src/org/chromium/ui/resources/dynamics/BitmapDynamicResource.java
+++ b/ui/android/java/src/org/chromium/ui/resources/dynamics/BitmapDynamicResource.java
@@ -7,17 +7,22 @@
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 
+import androidx.annotation.Nullable;
+
+import org.chromium.base.Callback;
+import org.chromium.ui.resources.Resource;
 import org.chromium.ui.resources.ResourceFactory;
-import org.chromium.ui.resources.statics.NinePatchData;
 
 /**
  * A basic implementation of {@link DynamicResource} to handle updatable bitmaps.
  */
-public class BitmapDynamicResource extends DynamicResource {
+public class BitmapDynamicResource implements DynamicResource {
     private final int mResId;
     private Bitmap mBitmap;
     private final Rect mSize = new Rect();
-    private boolean mIsDirty = true;
+
+    @Nullable
+    private Callback<Resource> mOnResourceReady;
 
     public BitmapDynamicResource(int resourceId) {
         mResId = resourceId;
@@ -37,38 +42,22 @@
         // Not updating bitmap is still bad, but better than a crash. We will still crash if there
         // is no bitmap to start with. See http://crbug.com/471234 for more.
         if (bitmap == null) return;
-        mIsDirty = true;
         mBitmap = bitmap;
         mSize.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
     }
 
     @Override
-    public Bitmap getBitmap() {
-        super.getBitmap();
-        assert mBitmap != null: "setBitmap() should be called before calling getBitmap() again";
-        mIsDirty = false;
-        Bitmap bitmap = mBitmap;
-        mBitmap = null;
-        return bitmap;
+    public void onResourceRequested() {
+        if (mOnResourceReady != null && mBitmap != null) {
+            Resource resource = new DynamicResourceSnapshot(
+                    mBitmap, false, mSize, ResourceFactory.createBitmapResource(null));
+            mOnResourceReady.onResult(resource);
+            mBitmap = null;
+        }
     }
 
     @Override
-    public Rect getBitmapSize() {
-        return mSize;
-    }
-
-    @Override
-    public long createNativeResource() {
-        return ResourceFactory.createBitmapResource(null);
-    }
-
-    @Override
-    public NinePatchData getNinePatchData() {
-        return null;
-    }
-
-    @Override
-    public boolean isDirty() {
-        return mIsDirty;
+    public void setOnResourceReadyCallback(Callback<Resource> onResourceReady) {
+        mOnResourceReady = onResourceReady;
     }
 }
diff --git a/ui/android/java/src/org/chromium/ui/resources/dynamics/DynamicResource.java b/ui/android/java/src/org/chromium/ui/resources/dynamics/DynamicResource.java
index bcd9a62..4bd7f1a 100644
--- a/ui/android/java/src/org/chromium/ui/resources/dynamics/DynamicResource.java
+++ b/ui/android/java/src/org/chromium/ui/resources/dynamics/DynamicResource.java
@@ -4,46 +4,27 @@
 
 package org.chromium.ui.resources.dynamics;
 
-import android.graphics.Bitmap;
-
-import androidx.annotation.CallSuper;
-
+import org.chromium.base.Callback;
 import org.chromium.ui.resources.Resource;
-import org.chromium.ui.resources.ResourceLoader.ResourceLoaderCallback;
 
 /**
- * A representation of a dynamic resource.  The contents of the resource might change from frame to
- * frame.
+ * A representation of a dynamic resource. The contents may change from frame to frame. It should be
+ * be able to return a {@link Resource} version of itself asynchronously. The
+ * {@link DynamicResource} is in charge of tracking when it has changed and should actually be
+ * returning a copy of itself.
  */
-public abstract class DynamicResource implements Resource {
+public interface DynamicResource {
     /**
-     * {@link DynamicResourceLoader#loadResource(int)} only notifies {@link ResourceLoaderCallback}
-     * if the resource is dirty. Therefore, if the resource is not dirty, this should not be called.
-     * @return null. This method should be overridden, and ignore the return value here.
+     * Will be called every render frame to notify the resource. The expectation is that this call
+     * will happen a lot, but only needs to be responded to when the dynamic resource has had a
+     * change that would cause the resulting resource to be different in some way, typically a
+     * change in the return of {@link Resource#getBitmap()}.
      */
-    @Override
-    @CallSuper
-    public Bitmap getBitmap() {
-        assert isDirty() : "getBitmap() should not be called when not dirty";
-        return null;
-    }
-
-    @Override
-    public boolean shouldRemoveResourceOnNullBitmap() {
-        return false;
-    }
+    void onResourceRequested();
 
     /**
-     * Note that this is called for every access to the resource during a frame.  If a resource is
-     * dirty, it should not be dirty again during the same looper call.
-     * {@link DynamicResourceLoader#loadResource(int)} only notifies
-     * {@link ResourceLoaderCallback#onResourceLoaded} if the resource is dirty.
-     * Therefore, if the resource is not dirty, {@link #getBitmap()} doesn't get called.
-     *
-     * TODO(dtrainor): Add checks so that a dynamic resource **can't** be built more than once each
-     * frame.
-     *
-     * @return Whether or not this resource is dirty and the CC component should be rebuilt.
+     * Sets the way this dynamic resource will use to return the resource that is ready to be used
+     * and drawn.
      */
-    abstract boolean isDirty();
-}
+    void setOnResourceReadyCallback(Callback<Resource> onResourceReady);
+}
\ No newline at end of file
diff --git a/ui/android/java/src/org/chromium/ui/resources/dynamics/DynamicResourceLoader.java b/ui/android/java/src/org/chromium/ui/resources/dynamics/DynamicResourceLoader.java
index 4e72462..493d1bd1 100644
--- a/ui/android/java/src/org/chromium/ui/resources/dynamics/DynamicResourceLoader.java
+++ b/ui/android/java/src/org/chromium/ui/resources/dynamics/DynamicResourceLoader.java
@@ -9,8 +9,10 @@
 import org.chromium.ui.resources.ResourceLoader;
 
 /**
- * Handles managing dynamic resources.  This is basically a list of dynamic resources that checks
- * {@link DynamicResource#isDirty()} before notifying the {@link ResourceLoaderCallback}.
+ * Handles managing dynamic resources. Because {@link DynamicResource} decide when they are dirty
+ * and should return a loaded resource, this class mostly just passes through notifications when
+ * render frames are happening, and hands the captured {@link org.chromium.ui.resources.Resource}
+ * back in our {@link ResourceLoaderCallback}.
  */
 public class DynamicResourceLoader extends ResourceLoader {
     private final SparseArray<DynamicResource> mDynamicResources =
@@ -29,12 +31,14 @@
 
     /**
      * Registers a {@link DynamicResource} to be tracked and exposed by this class.
-     * @param resId    The Android id to use.  This should be an actual Android id (R.id.some_id).
-     * @param resource The {@link DynamicResource} to track and expose.
+     * @param resId The Android id to use.  This should be an actual Android id (R.id.some_id).
+     * @param asyncDynamicResource The {@link DynamicResource} to track and expose.
      */
-    public void registerResource(int resId, DynamicResource resource) {
+    public void registerResource(int resId, DynamicResource asyncDynamicResource) {
         assert mDynamicResources.get(resId) == null;
-        mDynamicResources.put(resId, resource);
+        mDynamicResources.put(resId, asyncDynamicResource);
+        asyncDynamicResource.setOnResourceReadyCallback(
+                (resource) -> notifyLoadFinished(resId, resource));
     }
 
     /**
@@ -42,7 +46,10 @@
      * @param resId The Android id representing the {@link DynamicResource}.
      */
     public void unregisterResource(int resId) {
+        DynamicResource dynamicResource = mDynamicResources.get(resId);
+        if (dynamicResource == null) return;
         mDynamicResources.remove(resId);
+        dynamicResource.setOnResourceReadyCallback(null);
         notifyResourceUnregistered(resId);
     }
 
@@ -53,10 +60,9 @@
      */
     @Override
     public void loadResource(int resId) {
-        DynamicResource resource = mDynamicResources.get(resId);
-        if (resource == null) return;
-
-        if (resource.isDirty()) notifyLoadFinished(resId, resource);
+        DynamicResource dynamicResource = mDynamicResources.get(resId);
+        if (dynamicResource == null) return;
+        dynamicResource.onResourceRequested();
     }
 
     /**
diff --git a/ui/android/java/src/org/chromium/ui/resources/dynamics/DynamicResourceSnapshot.java b/ui/android/java/src/org/chromium/ui/resources/dynamics/DynamicResourceSnapshot.java
new file mode 100644
index 0000000..7a08ac7
--- /dev/null
+++ b/ui/android/java/src/org/chromium/ui/resources/dynamics/DynamicResourceSnapshot.java
@@ -0,0 +1,52 @@
+// Copyright 2022 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.ui.resources.dynamics;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+
+import org.chromium.ui.resources.Resource;
+import org.chromium.ui.resources.statics.NinePatchData;
+
+/** The current state of a dynamic resource. */
+public class DynamicResourceSnapshot implements Resource {
+    private final Bitmap mBitmap;
+    private final boolean mShouldRemoveResourceOnNullBitmap;
+    private final Rect mBitmapSize;
+    private final long mNativeResourceId;
+
+    public DynamicResourceSnapshot(Bitmap bitmap, boolean shouldRemoveResourceOnNullBitmap,
+            Rect bitmapSize, long nativeResourceId) {
+        mBitmap = bitmap;
+        mShouldRemoveResourceOnNullBitmap = shouldRemoveResourceOnNullBitmap;
+        mBitmapSize = bitmapSize;
+        mNativeResourceId = nativeResourceId;
+    }
+
+    @Override
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
+    @Override
+    public boolean shouldRemoveResourceOnNullBitmap() {
+        return mShouldRemoveResourceOnNullBitmap;
+    }
+
+    @Override
+    public Rect getBitmapSize() {
+        return mBitmapSize;
+    }
+
+    @Override
+    public NinePatchData getNinePatchData() {
+        return null;
+    }
+
+    @Override
+    public long createNativeResource() {
+        return mNativeResourceId;
+    }
+}
diff --git a/ui/android/java/src/org/chromium/ui/resources/dynamics/ViewResourceAdapter.java b/ui/android/java/src/org/chromium/ui/resources/dynamics/ViewResourceAdapter.java
index 2479c4e..69de47f 100644
--- a/ui/android/java/src/org/chromium/ui/resources/dynamics/ViewResourceAdapter.java
+++ b/ui/android/java/src/org/chromium/ui/resources/dynamics/ViewResourceAdapter.java
@@ -21,8 +21,11 @@
 import android.view.View.OnLayoutChangeListener;
 import android.view.ViewGroup;
 
+import androidx.annotation.CallSuper;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
+import org.chromium.base.Callback;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.metrics.RecordHistogram;
@@ -31,18 +34,17 @@
 import org.chromium.base.task.TaskTraits;
 import org.chromium.ui.resources.Resource;
 import org.chromium.ui.resources.ResourceFactory;
-import org.chromium.ui.resources.statics.NinePatchData;
 
 import java.nio.ByteBuffer;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
- * An adapter that exposes a {@link View} as a {@link DynamicResource}. In order to properly use
- * this adapter {@link ViewResourceAdapter#invalidate(Rect)} must be called when parts of the
+ * An adapter that exposes a {@link View} as a {@link DynamicResourceSnapshot}. In order to properly
+ * use this adapter {@link ViewResourceAdapter#invalidate(Rect)} must be called when parts of the
  * {@link View} are invalidated.  For {@link ViewGroup}s the easiest way to do this is to override
  * {@link ViewGroup#invalidateChildInParent(int[], Rect)}.
  */
-public class ViewResourceAdapter extends DynamicResource implements OnLayoutChangeListener {
+public class ViewResourceAdapter implements DynamicResource, OnLayoutChangeListener {
     private final View mView;
     private final Rect mDirtyRect = new Rect();
 
@@ -68,6 +70,9 @@
     public enum ImageReaderStatus { NEW, INITIALIZING, UPDATED, RUNNING }
     private ThreadUtils.ThreadChecker mAdapterThreadChecker = new ThreadUtils.ThreadChecker();
 
+    @Nullable
+    private Callback<Resource> mOnResourceReady;
+
     // RenderNode was added in API level 29 (Android 10). So restrict AcceleratedImageReader as
     // well.
     @RequiresApi(Build.VERSION_CODES.Q)
@@ -368,17 +373,16 @@
     }
 
     /**
-     * If this resource is dirty ({@link #isDirty()} returned {@code true}), it will recapture a
-     * {@link Bitmap} of the {@link View}.
-     * @see DynamicResource#getBitmap()
+     * Typically called when ({@link #isDirty()} returned {@code true}), to return a new
+     * {@link Bitmap} and clear out the dirty rect, resulting in a non-dirty view. Depending on the
+     * draw mechanism, this may return a null bitmap. In such a case, on the next frame, isDirty()
+     * should still be used to decide whether to call this.
      * @return A {@link Bitmap} representing the {@link View}.
      */
-    @Override
     @SuppressWarnings("NewApi")
     public Bitmap getBitmap() {
         mAdapterThreadChecker.assertOnValidThread();
         TraceEvent.begin("ViewResourceAdapter:getBitmap");
-        super.getBitmap();
         boolean bitmapReady = false;
         if (mLastGetBitmapTimestamp > 0) {
             RecordHistogram.recordLongTimesHistogram("ViewResourceAdapter.GetBitmapInterval",
@@ -404,16 +408,6 @@
         return mBitmap;
     }
 
-    @Override
-    public boolean shouldRemoveResourceOnNullBitmap() {
-        return mUseHardwareBitmapDraw;
-    }
-
-    @Override
-    public Rect getBitmapSize() {
-        return mViewSize;
-    }
-
     /**
      * Set the downsampling scale. The rendered size is not affected.
      * @param scale The scale to use. <1 means the Bitmap is smaller than the View.
@@ -426,21 +420,28 @@
         mScale = scale;
     }
 
-    /**
-     * Override this method to create the native resource type for the generated bitmap.
-     */
-    @Override
+    /** {@see Resource#createNativeResource()}. */
     public long createNativeResource() {
         return ResourceFactory.createBitmapResource(null);
     }
 
     @Override
-    public final NinePatchData getNinePatchData() {
-        return null;
+    public void setOnResourceReadyCallback(Callback<Resource> onResourceReady) {
+        mOnResourceReady = onResourceReady;
     }
 
-    @Override
-    public boolean isDirty() {
+    /**
+     * Note that this is called for every access to the resource during a frame. If a resource is
+     * dirty, it should not be dirty again during the same looper call.
+     * {@link DynamicResourceLoader#loadResource(int)} only notifies
+     * {@link ResourceLoaderCallback#onResourceLoaded} if the resource is dirty.
+     * Therefore, if the resource is not dirty, {@link #onResourceRequested()} doesn't get called.
+     * TODO(dtrainor): Add checks so that a dynamic resource **can't** be built more than once each
+     * frame.
+     * @return Whether or not this resource is dirty and the CC component should be rebuilt.
+     */
+    @CallSuper
+    protected boolean isDirty() {
         // The bitmap is dirty if some part of it has changed, or in hardware mode we're waiting for
         // the results of a previous request (null mBitmap or RUNNING).
         return !mDirtyRect.isEmpty()
@@ -450,6 +451,20 @@
     }
 
     @Override
+    public void onResourceRequested() {
+        // TODO(skym): The hardware capture approach should be pushing bitmaps when they're ready,
+        // and avoid relying on isDirty and/or onResourceRequested signals. However this is an
+        // intermediate state during refactoring, and is intentionally keeping the old behavior for
+        // now.
+        if (mOnResourceReady != null && isDirty()) {
+            Bitmap bitmap = getBitmap();
+            Resource resource = new DynamicResourceSnapshot(
+                    bitmap, mUseHardwareBitmapDraw, mViewSize, createNativeResource());
+            mOnResourceReady.onResult(resource);
+        }
+    }
+
+    @Override
     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
             int oldTop, int oldRight, int oldBottom) {
         final int width = right - left;
diff --git a/ui/android/junit/src/org/chromium/ui/resources/dynamics/BitmapDynamicResourceTest.java b/ui/android/junit/src/org/chromium/ui/resources/dynamics/BitmapDynamicResourceTest.java
index 719def28..006f609 100644
--- a/ui/android/junit/src/org/chromium/ui/resources/dynamics/BitmapDynamicResourceTest.java
+++ b/ui/android/junit/src/org/chromium/ui/resources/dynamics/BitmapDynamicResourceTest.java
@@ -7,17 +7,23 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.MockitoAnnotations.initMocks;
 
 import static org.chromium.base.GarbageCollectionTestUtils.canBeGarbageCollected;
 
 import android.graphics.Bitmap;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.ui.resources.ResourceFactory;
+import org.chromium.ui.resources.ResourceFactoryJni;
 
 import java.lang.ref.WeakReference;
 
@@ -29,8 +35,15 @@
 public class BitmapDynamicResourceTest {
     private BitmapDynamicResource mResource;
 
+    @Rule
+    public JniMocker mJniMocker = new JniMocker();
+    @Mock
+    private ResourceFactory.Natives mResourceFactoryJni;
+
     @Before
     public void setup() {
+        initMocks(this);
+        mJniMocker.mock(ResourceFactoryJni.TEST_HOOKS, mResourceFactoryJni);
         mResource = new BitmapDynamicResource(1);
     }
 
@@ -38,7 +51,11 @@
     public void testGetBitmap() {
         Bitmap bitmap = Bitmap.createBitmap(1, 2, Bitmap.Config.ARGB_8888);
         mResource.setBitmap(bitmap);
-        assertEquals(bitmap, mResource.getBitmap());
+        assertEquals(bitmap, DynamicResourceTestUtils.getBitmapSync(mResource));
+
+        // Bitmap was already returned, next onResourceRequested should no-op.
+        mResource.setOnResourceReadyCallback((resource) -> { assert false; });
+        mResource.onResourceRequested();
     }
 
     @Test
@@ -62,7 +79,24 @@
         bitmap = null;
         assertFalse(canBeGarbageCollected(bitmapWeakReference));
 
-        mResource.getBitmap();
+        DynamicResourceTestUtils.getBitmapSync(mResource);
         assertTrue(canBeGarbageCollected(bitmapWeakReference));
     }
+
+    @Test
+    public void testOnResourceRequested_NotReady() {
+        Bitmap bitmap = Bitmap.createBitmap(1, 2, Bitmap.Config.ARGB_8888);
+
+        // No callback or bitmap, onResourceRequested should no-op.
+        mResource.onResourceRequested();
+
+        // No bitmap, onResourceRequested should no-op.
+        mResource.setOnResourceReadyCallback((resource) -> { assert false; });
+        mResource.onResourceRequested();
+
+        // No callback, onResourceRequested should no-op.
+        mResource.setOnResourceReadyCallback(null);
+        mResource.setBitmap(bitmap);
+        mResource.onResourceRequested();
+    }
 }
diff --git a/ui/android/junit/src/org/chromium/ui/resources/dynamics/DynamicResourceTestUtils.java b/ui/android/junit/src/org/chromium/ui/resources/dynamics/DynamicResourceTestUtils.java
new file mode 100644
index 0000000..8fa5e0c
--- /dev/null
+++ b/ui/android/junit/src/org/chromium/ui/resources/dynamics/DynamicResourceTestUtils.java
@@ -0,0 +1,45 @@
+// Copyright 2022 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.ui.resources.dynamics;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+
+import org.chromium.ui.resources.Resource;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Test utils class to share accessor patterns for working with {link @DynamicResoursce}. Primarily
+ * for old tests that were written and verify the behavior of synchronous resources.
+ */
+public final class DynamicResourceTestUtils {
+    /** Only works on {@link DynamicResource} that synchronously invoke their callback. */
+    public static Rect getBitmapSizeSync(DynamicResource dynamicResource) {
+        return getResourceSync(dynamicResource).getBitmapSize();
+    }
+
+    /** Only works on {@link DynamicResource} that synchronously invoke their callback. */
+    public static Bitmap getBitmapSync(DynamicResource dynamicResource) {
+        return getResourceSync(dynamicResource).getBitmap();
+    }
+
+    /** Only works on {@link DynamicResource} that synchronously invoke their callback. */
+    public static Resource getResourceSync(DynamicResource dynamicResource) {
+        AtomicReference<Resource> resourcePointer = new AtomicReference<>();
+        dynamicResource.setOnResourceReadyCallback((resource) -> resourcePointer.set(resource));
+        dynamicResource.onResourceRequested();
+        Resource temp = resourcePointer.get();
+        assertNotNull(temp);
+
+        // Clear out the callback which is owning the AtomicReference, which in turn keeps alive the
+        // Resource and/or Bitmap. Some tests will verify GC is able to reclaim these.
+        dynamicResource.setOnResourceReadyCallback(null);
+
+        return temp;
+    }
+}
\ No newline at end of file
diff --git a/ui/android/junit/src/org/chromium/ui/resources/dynamics/ViewResourceAdapterTest.java b/ui/android/junit/src/org/chromium/ui/resources/dynamics/ViewResourceAdapterTest.java
index da93e80..ad7a531 100644
--- a/ui/android/junit/src/org/chromium/ui/resources/dynamics/ViewResourceAdapterTest.java
+++ b/ui/android/junit/src/org/chromium/ui/resources/dynamics/ViewResourceAdapterTest.java
@@ -20,12 +20,16 @@
 import android.view.View;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.JniMocker;
+import org.chromium.ui.resources.ResourceFactory;
+import org.chromium.ui.resources.ResourceFactoryJni;
 
 import java.lang.ref.WeakReference;
 
@@ -37,6 +41,11 @@
 public class ViewResourceAdapterTest {
     private int mViewWidth;
     private int mViewHeight;
+
+    @Rule
+    public JniMocker mJniMocker = new JniMocker();
+    @Mock
+    private ResourceFactory.Natives mResourceFactoryJni;
     @Mock
     private View mView;
 
@@ -45,6 +54,7 @@
     @Before
     public void setup() {
         initMocks(this);
+        mJniMocker.mock(ResourceFactoryJni.TEST_HOOKS, mResourceFactoryJni);
 
         mViewWidth = 200;
         mViewHeight = 100;
@@ -62,7 +72,12 @@
                 return true;
             }
         };
+    }
 
+    private Rect getBitmapSize() {
+        // Need to mark dirty before requesting, otherwise it will no-op.
+        mAdapter.invalidate(null);
+        return DynamicResourceTestUtils.getBitmapSizeSync(mAdapter);
     }
 
     @Test
@@ -76,7 +91,7 @@
     @Test
     public void testGetBitmapSize() {
         Bitmap bitmap = mAdapter.getBitmap();
-        Rect rect = mAdapter.getBitmapSize();
+        Rect rect = getBitmapSize();
         assertEquals(bitmap.getWidth(), rect.width());
         assertEquals(bitmap.getHeight(), rect.height());
     }
@@ -89,7 +104,7 @@
         assertEquals(mViewWidth * scale, bitmap.getWidth(), 1);
         assertEquals(mViewHeight * scale, bitmap.getHeight(), 1);
 
-        Rect rect = mAdapter.getBitmapSize();
+        Rect rect = getBitmapSize();
         assertEquals(mViewWidth, rect.width());
         assertEquals(mViewHeight, rect.height());
     }
diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc
index 7b5519c..0360dbe 100644
--- a/ui/compositor/layer.cc
+++ b/ui/compositor/layer.cc
@@ -1600,7 +1600,7 @@
   return rounded_corner_radii();
 }
 
-gfx::LinearGradient Layer::GetGradientMaskForAnimation() const {
+const gfx::LinearGradient& Layer::GetGradientMaskForAnimation() const {
   return gradient_mask();
 }
 
diff --git a/ui/compositor/layer.h b/ui/compositor/layer.h
index c0cd707..3ea74c8 100644
--- a/ui/compositor/layer.h
+++ b/ui/compositor/layer.h
@@ -328,7 +328,7 @@
 
   // Gets/sets a gradient mask that is applied to the clip bounds on the layer
   void SetGradientMask(const gfx::LinearGradient& linear_gradient);
-  const gfx::LinearGradient gradient_mask() const {
+  const gfx::LinearGradient& gradient_mask() const {
     return cc_layer_->gradient_mask();
   }
 
@@ -601,7 +601,7 @@
   SkColor GetColorForAnimation() const override;
   gfx::Rect GetClipRectForAnimation() const override;
   gfx::RoundedCornersF GetRoundedCornersForAnimation() const override;
-  gfx::LinearGradient GetGradientMaskForAnimation() const override;
+  const gfx::LinearGradient& GetGradientMaskForAnimation() const override;
   float GetDeviceScaleFactor() const override;
   Layer* GetLayer() override;
   cc::Layer* GetCcLayer() const override;
diff --git a/ui/compositor/layer_animation_delegate.h b/ui/compositor/layer_animation_delegate.h
index 79088f3..1107a595 100644
--- a/ui/compositor/layer_animation_delegate.h
+++ b/ui/compositor/layer_animation_delegate.h
@@ -59,7 +59,7 @@
   virtual SkColor GetColorForAnimation() const = 0;
   virtual gfx::Rect GetClipRectForAnimation() const = 0;
   virtual gfx::RoundedCornersF GetRoundedCornersForAnimation() const = 0;
-  virtual gfx::LinearGradient GetGradientMaskForAnimation() const = 0;
+  virtual const gfx::LinearGradient& GetGradientMaskForAnimation() const = 0;
   virtual float GetDeviceScaleFactor() const = 0;
   virtual ui::Layer* GetLayer() = 0;
   virtual cc::Layer* GetCcLayer() const = 0;
diff --git a/ui/compositor/layer_animation_element.cc b/ui/compositor/layer_animation_element.cc
index 1b070f92..103a6e936 100644
--- a/ui/compositor/layer_animation_element.cc
+++ b/ui/compositor/layer_animation_element.cc
@@ -404,7 +404,7 @@
 
  private:
   gfx::LinearGradient start_;
-  gfx::LinearGradient target_;
+  const gfx::LinearGradient target_;
 };
 
 // ThreadedLayerAnimationElement -----------------------------------------------
diff --git a/ui/compositor/test/test_layer_animation_delegate.cc b/ui/compositor/test/test_layer_animation_delegate.cc
index b077818..697a563 100644
--- a/ui/compositor/test/test_layer_animation_delegate.cc
+++ b/ui/compositor/test/test_layer_animation_delegate.cc
@@ -175,8 +175,8 @@
   return rounded_corners_;
 }
 
-gfx::LinearGradient TestLayerAnimationDelegate::GetGradientMaskForAnimation()
-    const {
+const gfx::LinearGradient&
+TestLayerAnimationDelegate::GetGradientMaskForAnimation() const {
   return gradient_mask_;
 }
 
diff --git a/ui/compositor/test/test_layer_animation_delegate.h b/ui/compositor/test/test_layer_animation_delegate.h
index 642335e4d..f1af662 100644
--- a/ui/compositor/test/test_layer_animation_delegate.h
+++ b/ui/compositor/test/test_layer_animation_delegate.h
@@ -79,7 +79,7 @@
   SkColor GetColorForAnimation() const override;
   gfx::Rect GetClipRectForAnimation() const override;
   gfx::RoundedCornersF GetRoundedCornersForAnimation() const override;
-  gfx::LinearGradient GetGradientMaskForAnimation() const override;
+  const gfx::LinearGradient& GetGradientMaskForAnimation() const override;
   float GetDeviceScaleFactor() const override;
   LayerAnimatorCollection* GetLayerAnimatorCollection() override;
   ui::Layer* GetLayer() override;
diff --git a/ui/gl/generate_bindings.py b/ui/gl/generate_bindings.py
index 7839ed73..898e471 100755
--- a/ui/gl/generate_bindings.py
+++ b/ui/gl/generate_bindings.py
@@ -3124,9 +3124,6 @@
 class GLContext;
 """ % {'name': set_name.upper()})
 
-  if set_name == 'egl':
-    file.write("class GLDisplayEGL;\n")
-
   # Write typedefs for function pointer types. Always use the GL name for the
   # typedef.
   file.write('\n')
@@ -3161,10 +3158,10 @@
     file.write(
 """
 
-  void InitializeExtensionSettings(GLDisplayEGL* display);
-  void UpdateConditionalExtensionSettings(GLDisplayEGL* display);
+  void InitializeExtensionSettings(EGLDisplay display);
+  void UpdateConditionalExtensionSettings(EGLDisplay display);
 
-  static std::string GetPlatformExtensions(GLDisplayEGL* display);
+  static std::string GetPlatformExtensions(EGLDisplay display);
 """)
   file.write('};\n')
   file.write('\n')
@@ -3328,8 +3325,6 @@
                    'ui/gl/gl_implementation.h',
                    'ui/gl/gl_version_info.h',
                    set_header_name ]
-  if set_name == 'egl':
-    include_list.append('ui/gl/gl_display.h')
 
   includes_string = "\n".join(["#include \"{0}\"".format(h)
                                for h in sorted(include_list)])
@@ -3496,7 +3491,7 @@
     file.write("""\
 }
 
-void DisplayExtensionsEGL::InitializeExtensionSettings(GLDisplayEGL* display) {
+void DisplayExtensionsEGL::InitializeExtensionSettings(EGLDisplay display) {
   std::string platform_extensions(GetPlatformExtensions(display));
   [[maybe_unused]] gfx::ExtensionSet extensions(
       gfx::MakeExtensionSet(platform_extensions));
diff --git a/ui/gl/gl_bindings.cc b/ui/gl/gl_bindings.cc
index 8092fa1..42b1f6e 100644
--- a/ui/gl/gl_bindings.cc
+++ b/ui/gl/gl_bindings.cc
@@ -24,7 +24,7 @@
 
 #if defined(USE_EGL)
 void DisplayExtensionsEGL::UpdateConditionalExtensionSettings(
-    GLDisplayEGL* display) {
+    EGLDisplay display) {
   // For the moment, only two extensions can be conditionally disabled
   // through GPU driver bug workarounds mechanism:
   //   EGL_KHR_fence_sync
@@ -43,12 +43,10 @@
 }
 
 // static
-std::string DisplayExtensionsEGL::GetPlatformExtensions(GLDisplayEGL* display) {
-  DCHECK(display);
-  EGLDisplay egl_display = display->GetDisplay();
-  if (egl_display == EGL_NO_DISPLAY)
+std::string DisplayExtensionsEGL::GetPlatformExtensions(EGLDisplay display) {
+  if (display == EGL_NO_DISPLAY)
     return "";
-  const char* str = eglQueryString(egl_display, EGL_EXTENSIONS);
+  const char* str = eglQueryString(display, EGL_EXTENSIONS);
   return str ? std::string(str) : "";
 }
 
diff --git a/ui/gl/gl_bindings_autogen_egl.cc b/ui/gl/gl_bindings_autogen_egl.cc
index 7db07227..e203c35 100644
--- a/ui/gl/gl_bindings_autogen_egl.cc
+++ b/ui/gl/gl_bindings_autogen_egl.cc
@@ -13,7 +13,6 @@
 #include "base/trace_event/trace_event.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
-#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_egl_api_implementation.h"
 #include "ui/gl/gl_enums.h"
 #include "ui/gl/gl_implementation.h"
@@ -266,7 +265,7 @@
       gfx::HasExtension(extensions, "EGL_MESA_platform_surfaceless");
 }
 
-void DisplayExtensionsEGL::InitializeExtensionSettings(GLDisplayEGL* display) {
+void DisplayExtensionsEGL::InitializeExtensionSettings(EGLDisplay display) {
   std::string platform_extensions(GetPlatformExtensions(display));
   [[maybe_unused]] gfx::ExtensionSet extensions(
       gfx::MakeExtensionSet(platform_extensions));
diff --git a/ui/gl/gl_bindings_autogen_egl.h b/ui/gl/gl_bindings_autogen_egl.h
index 0c192dd3..2fdffb0 100644
--- a/ui/gl/gl_bindings_autogen_egl.h
+++ b/ui/gl/gl_bindings_autogen_egl.h
@@ -16,7 +16,6 @@
 namespace gl {
 
 class GLContext;
-class GLDisplayEGL;
 
 typedef EGLBoolean(GL_BINDING_CALL* eglBindAPIProc)(EGLenum api);
 typedef EGLBoolean(GL_BINDING_CALL* eglBindTexImageProc)(EGLDisplay dpy,
@@ -395,10 +394,10 @@
   bool b_GL_CHROMIUM_egl_android_native_fence_sync_hack;
   bool b_GL_CHROMIUM_egl_khr_fence_sync_hack;
 
-  void InitializeExtensionSettings(GLDisplayEGL* display);
-  void UpdateConditionalExtensionSettings(GLDisplayEGL* display);
+  void InitializeExtensionSettings(EGLDisplay display);
+  void UpdateConditionalExtensionSettings(EGLDisplay display);
 
-  static std::string GetPlatformExtensions(GLDisplayEGL* display);
+  static std::string GetPlatformExtensions(EGLDisplay display);
 };
 
 struct ProcsEGL {
diff --git a/ui/gl/gl_display.cc b/ui/gl/gl_display.cc
index e3a2466..9fc104a2 100644
--- a/ui/gl/gl_display.cc
+++ b/ui/gl/gl_display.cc
@@ -666,6 +666,17 @@
 GLDisplay::~GLDisplay() = default;
 
 #if defined(USE_EGL)
+GLDisplayEGL::EGLGpuSwitchingObserver::EGLGpuSwitchingObserver(
+    EGLDisplay display)
+    : display_(display) {
+  DCHECK(display != EGL_NO_DISPLAY);
+}
+
+void GLDisplayEGL::EGLGpuSwitchingObserver::OnGpuSwitched(
+    GpuPreference active_gpu_heuristic) {
+  eglHandleGPUSwitchANGLE(display_);
+}
+
 GLDisplayEGL::GLDisplayEGL(uint64_t system_device_id)
     : GLDisplay(system_device_id) {
   ext = std::make_unique<DisplayExtensionsEGL>();
@@ -678,6 +689,26 @@
   return display_;
 }
 
+void GLDisplayEGL::Shutdown() {
+  if (display_ == EGL_NO_DISPLAY)
+    return;
+
+  if (gpu_switching_observer_.get()) {
+    ui::GpuSwitchingManager::GetInstance()->RemoveObserver(
+        gpu_switching_observer_.get());
+    gpu_switching_observer_.reset();
+  }
+
+  angle::ResetPlatform(display_);
+  DCHECK(g_driver_egl.fn.eglTerminateFn);
+  eglTerminate(display_);
+
+  display_ = EGL_NO_DISPLAY;
+  egl_surfaceless_context_supported_ = false;
+  egl_context_priority_supported_ = false;
+  egl_android_native_fence_sync_supported_ = false;
+}
+
 void GLDisplayEGL::SetDisplay(EGLDisplay display) {
   display_ = display;
 }
@@ -712,6 +743,36 @@
   return this->ext->b_EGL_ANGLE_external_context_and_surface;
 }
 
+bool GLDisplayEGL::Initialize(EGLDisplayPlatform native_display) {
+  if (display_ != EGL_NO_DISPLAY)
+    return true;
+
+  if (!InitializeDisplay(native_display))
+    return false;
+  InitializeCommon();
+
+  if (ext->b_EGL_ANGLE_power_preference) {
+    gpu_switching_observer_ =
+        std::make_unique<EGLGpuSwitchingObserver>(display_);
+    ui::GpuSwitchingManager::GetInstance()->AddObserver(
+        gpu_switching_observer_.get());
+  }
+  return true;
+}
+
+void GLDisplayEGL::InitializeForTesting() {
+  display_ = eglGetCurrentDisplay();
+  ext->InitializeExtensionSettings(display_);
+  InitializeCommon();
+}
+
+bool GLDisplayEGL::InitializeExtensionSettings() {
+  if (display_ == EGL_NO_DISPLAY)
+    return false;
+  ext->UpdateConditionalExtensionSettings(display_);
+  return true;
+}
+
 // InitializeDisplay is necessary because the static binding code
 // needs a full Display init before it can query the Display extensions.
 bool GLDisplayEGL::InitializeDisplay(EGLDisplayPlatform native_display) {
@@ -837,7 +898,7 @@
                               DISPLAY_TYPE_MAX);
     display_ = display;
     display_type_ = display_type;
-    ext->InitializeExtensionSettings(this);
+    ext->InitializeExtensionSettings(display);
     return true;
   }
 
@@ -917,27 +978,6 @@
   }
 #endif
 }
-
-bool GLDisplayEGL::InitializeExtensionSettings() {
-  if (display_ == EGL_NO_DISPLAY)
-    return false;
-  ext->UpdateConditionalExtensionSettings(this);
-  return true;
-}
-
-void GLDisplayEGL::Shutdown() {
-  if (display_ == EGL_NO_DISPLAY)
-    return;
-
-  angle::ResetPlatform(display_);
-  DCHECK(g_driver_egl.fn.eglTerminateFn);
-  eglTerminate(display_);
-
-  display_ = EGL_NO_DISPLAY;
-  egl_surfaceless_context_supported_ = false;
-  egl_context_priority_supported_ = false;
-  egl_android_native_fence_sync_supported_ = false;
-}
 #endif  // defined(USE_EGL)
 
 #if defined(USE_GLX)
@@ -949,6 +989,8 @@
 void* GLDisplayX11::GetDisplay() {
   return x11::Connection::Get()->GetXlibDisplay();
 }
+
+void GLDisplayX11::Shutdown() {}
 #endif  // defined(USE_GLX)
 
 }  // namespace gl
diff --git a/ui/gl/gl_display.h b/ui/gl/gl_display.h
index a55edd5..8fa42a3 100644
--- a/ui/gl/gl_display.h
+++ b/ui/gl/gl_display.h
@@ -13,6 +13,8 @@
 
 #if defined(USE_EGL)
 #include <EGL/egl.h>
+
+#include "ui/gl/gpu_switching_manager.h"
 #endif  // defined(USE_EGL)
 
 namespace base {
@@ -89,6 +91,7 @@
   virtual ~GLDisplay();
 
   virtual void* GetDisplay() = 0;
+  virtual void Shutdown() = 0;
 
  protected:
   explicit GLDisplay(uint64_t system_device_id);
@@ -107,6 +110,8 @@
   static GLDisplayEGL* GetDisplayForCurrentContext();
 
   EGLDisplay GetDisplay() override;
+  void Shutdown() override;
+
   void SetDisplay(EGLDisplay display);
   EGLDisplayPlatform GetNativeDisplay() const;
   DisplayType GetDisplayType() const;
@@ -116,18 +121,30 @@
   bool IsAndroidNativeFenceSyncSupported();
   bool IsANGLEExternalContextAndSurfaceSupported();
 
-  bool InitializeDisplay(EGLDisplayPlatform native_display);
-  void InitializeCommon();
+  bool Initialize(EGLDisplayPlatform native_display);
+  void InitializeForTesting();
   bool InitializeExtensionSettings();
-  void Shutdown();
 
   std::unique_ptr<DisplayExtensionsEGL> ext;
 
  private:
   friend class GLDisplayManager<GLDisplayEGL>;
+  friend class EGLApiTest;
+
+  class EGLGpuSwitchingObserver final : public ui::GpuSwitchingObserver {
+   public:
+    explicit EGLGpuSwitchingObserver(EGLDisplay display);
+    void OnGpuSwitched(GpuPreference active_gpu_heuristic) override;
+
+   private:
+    EGLDisplay display_ = EGL_NO_DISPLAY;
+  };
 
   explicit GLDisplayEGL(uint64_t system_device_id);
 
+  bool InitializeDisplay(EGLDisplayPlatform native_display);
+  void InitializeCommon();
+
   EGLDisplay display_ = EGL_NO_DISPLAY;
   EGLDisplayPlatform native_display_ = EGLDisplayPlatform(EGL_DEFAULT_DISPLAY);
   DisplayType display_type_ = DisplayType::DEFAULT;
@@ -135,6 +152,8 @@
   bool egl_surfaceless_context_supported_ = false;
   bool egl_context_priority_supported_ = false;
   bool egl_android_native_fence_sync_supported_ = false;
+
+  std::unique_ptr<EGLGpuSwitchingObserver> gpu_switching_observer_;
 };
 #endif  // defined(USE_EGL)
 
@@ -147,6 +166,7 @@
   ~GLDisplayX11() override;
 
   void* GetDisplay() override;
+  void Shutdown() override;
 
  private:
   friend class GLDisplayManager<GLDisplayX11>;
diff --git a/ui/gl/gl_surface_egl.cc b/ui/gl/gl_surface_egl.cc
index b362ea0..9008a79d 100644
--- a/ui/gl/gl_surface_egl.cc
+++ b/ui/gl/gl_surface_egl.cc
@@ -31,7 +31,6 @@
 #include "ui/gl/gl_surface_presentation_helper.h"
 #include "ui/gl/gl_surface_stub.h"
 #include "ui/gl/gl_utils.h"
-#include "ui/gl/gpu_switching_manager.h"
 #include "ui/gl/scoped_make_current.h"
 #include "ui/gl/sync_control_vsync_provider.h"
 
@@ -85,10 +84,6 @@
 
 namespace {
 
-class EGLGpuSwitchingObserver;
-
-EGLGpuSwitchingObserver* g_egl_gpu_switching_observer = nullptr;
-
 constexpr const char kSwapEventTraceCategories[] = "gpu";
 
 constexpr size_t kMaxTimestampsSupportable = 9;
@@ -156,21 +151,6 @@
   raw_ptr<GLDisplayEGL> display_;
 };
 
-class EGLGpuSwitchingObserver final : public ui::GpuSwitchingObserver {
- public:
-  explicit EGLGpuSwitchingObserver(GLDisplayEGL* display) : display_(display) {
-    DCHECK(display_);
-  }
-
-  void OnGpuSwitched(gl::GpuPreference active_gpu_heuristic) override {
-    DCHECK(display_->ext->b_EGL_ANGLE_power_preference);
-    eglHandleGPUSwitchANGLE(display_->GetDisplay());
-  }
-
- private:
-  raw_ptr<GLDisplayEGL> display_ = nullptr;
-};
-
 bool ValidateEglConfig(EGLDisplay display,
                        const EGLint* config_attribs,
                        EGLint* num_configs) {
@@ -382,50 +362,6 @@
       GpuPreference::kDefault);
 }
 
-// static
-GLDisplayEGL* GLSurfaceEGL::InitializeOneOff(EGLDisplayPlatform native_display,
-                                             uint64_t system_device_id) {
-  GLDisplayEGL* display =
-      GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
-  if (display->GetDisplay() == EGL_NO_DISPLAY) {
-    if (!display->InitializeDisplay(native_display))
-      return nullptr;
-    display->InitializeCommon();
-    if (display->ext->b_EGL_ANGLE_power_preference) {
-      g_egl_gpu_switching_observer = new EGLGpuSwitchingObserver(display);
-      ui::GpuSwitchingManager::GetInstance()->AddObserver(
-          g_egl_gpu_switching_observer);
-    }
-  }
-  return display;
-}
-
-// static
-GLDisplayEGL* GLSurfaceEGL::InitializeOneOffForTesting() {
-  GLDisplayEGL* display =
-      GLDisplayManagerEGL::GetInstance()->GetDisplay(GpuPreference::kDefault);
-  display->SetDisplay(eglGetCurrentDisplay());
-  display->ext->InitializeExtensionSettings(display);
-  display->InitializeCommon();
-  return display;
-}
-
-// static
-void GLSurfaceEGL::ShutdownOneOff(GLDisplayEGL* display) {
-  if (!display || display->GetDisplay() == EGL_NO_DISPLAY) {
-    return;
-  }
-
-  if (g_egl_gpu_switching_observer) {
-    ui::GpuSwitchingManager::GetInstance()->RemoveObserver(
-        g_egl_gpu_switching_observer);
-    delete g_egl_gpu_switching_observer;
-    g_egl_gpu_switching_observer = nullptr;
-  }
-
-  display->Shutdown();
-}
-
 GLSurfaceEGL::~GLSurfaceEGL() = default;
 
 NativeViewGLSurfaceEGL::NativeViewGLSurfaceEGL(
diff --git a/ui/gl/gl_surface_egl.h b/ui/gl/gl_surface_egl.h
index ffb507c..31c9491 100644
--- a/ui/gl/gl_surface_egl.h
+++ b/ui/gl/gl_surface_egl.h
@@ -50,17 +50,6 @@
 
   static GLDisplayEGL* GetGLDisplayEGL();
 
-  // |system_device_id| specifies which GPU to use on a multi-GPU system.
-  // If its value is 0, use the default GPU of the system.
-  // Calling this functionm a second time on the same |system_device_id|
-  // is a no-op and returns the same GLDisplayEGL.
-  // TODO(https://crbug.com/1251724): This will be called once per display
-  // when Chrome begins to support multi-gpu rendering.
-  static GLDisplayEGL* InitializeOneOff(EGLDisplayPlatform native_display,
-                                        uint64_t system_device_id);
-  static GLDisplayEGL* InitializeOneOffForTesting();
-  static void ShutdownOneOff(GLDisplayEGL* display);
-
  protected:
   ~GLSurfaceEGL() override;
 
diff --git a/ui/gl/gl_utils.cc b/ui/gl/gl_utils.cc
index dffa2e5..c73fc65 100644
--- a/ui/gl/gl_utils.cc
+++ b/ui/gl/gl_utils.cc
@@ -208,6 +208,10 @@
   return GLDisplayManagerEGL::GetInstance()->GetDisplay(
       GpuPreference::kDefault);
 }
+
+GLDisplayEGL* GetDisplayEGL(uint64_t system_device_id) {
+  return GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
+}
 #endif  // USE_EGL
 
 #if defined(USE_GLX)
diff --git a/ui/gl/gl_utils.h b/ui/gl/gl_utils.h
index 1118348..89a01817 100644
--- a/ui/gl/gl_utils.h
+++ b/ui/gl/gl_utils.h
@@ -79,6 +79,9 @@
 
 // Query the default GLDisplayEGL.
 GL_EXPORT GLDisplayEGL* GetDefaultDisplayEGL();
+
+// Query the GLDisplayEGL by |system_device_id|.
+GL_EXPORT GLDisplayEGL* GetDisplayEGL(uint64_t system_device_id);
 #endif  // USE_EGL
 
 #if defined(USE_GLX)
diff --git a/ui/gl/init/gl_initializer_android.cc b/ui/gl/init/gl_initializer_android.cc
index 5abd2d9..792febb 100644
--- a/ui/gl/init/gl_initializer_android.cc
+++ b/ui/gl/init/gl_initializer_android.cc
@@ -10,11 +10,10 @@
 #include "base/logging.h"
 #include "base/native_library.h"
 #include "ui/gl/gl_bindings.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_display_manager.h"
 #include "ui/gl/gl_egl_api_implementation.h"
 #include "ui/gl/gl_gl_api_implementation.h"
-#include "ui/gl/gl_surface_egl.h"
-#include "ui/gl/init/gl_initializer.h"
 
 namespace gl {
 namespace init {
@@ -78,20 +77,20 @@
 }  // namespace
 
 GLDisplay* InitializeGLOneOffPlatform(uint64_t system_device_id) {
+  GLDisplayEGL* display =
+      GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
   switch (GetGLImplementation()) {
     case kGLImplementationEGLGLES2:
-    case kGLImplementationEGLANGLE: {
-      GLDisplay* display = GLSurfaceEGL::InitializeOneOff(
-          EGLDisplayPlatform(EGL_DEFAULT_DISPLAY), system_device_id);
-      if (!display) {
-        LOG(ERROR) << "GLSurfaceEGL::InitializeOneOff failed.";
+    case kGLImplementationEGLANGLE:
+      if (!display->Initialize(EGLDisplayPlatform(EGL_DEFAULT_DISPLAY))) {
+        LOG(ERROR) << "GLDisplayEGL::Initialize failed.";
         return nullptr;
       }
-      return display;
-    }
+      break;
     default:
-      return GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
+      break;
   }
+  return display;
 }
 
 bool InitializeStaticGLBindings(GLImplementationParts implementation) {
@@ -117,7 +116,8 @@
 }
 
 void ShutdownGLPlatform(GLDisplay* display) {
-  GLSurfaceEGL::ShutdownOneOff(static_cast<GLDisplayEGL*>(display));
+  if (display)
+    display->Shutdown();
   ClearBindingsEGL();
   ClearBindingsGL();
 }
diff --git a/ui/gl/init/gl_initializer_mac.cc b/ui/gl/init/gl_initializer_mac.cc
index edbbaed..4318ce1f 100644
--- a/ui/gl/init/gl_initializer_mac.cc
+++ b/ui/gl/init/gl_initializer_mac.cc
@@ -18,16 +18,15 @@
 #include "base/threading/thread_restrictions.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_display_manager.h"
 #include "ui/gl/gl_gl_api_implementation.h"
 #include "ui/gl/gl_implementation.h"
 #include "ui/gl/gl_surface.h"
 #include "ui/gl/gpu_switching_manager.h"
-#include "ui/gl/init/gl_initializer.h"
 
 #if defined(USE_EGL)
 #include "ui/gl/gl_egl_api_implementation.h"
-#include "ui/gl/gl_surface_egl.h"
 #endif  // defined(USE_EGL)
 
 namespace gl {
@@ -167,28 +166,28 @@
 }  // namespace
 
 GLDisplay* InitializeGLOneOffPlatform(uint64_t system_device_id) {
+  GLDisplayEGL* display =
+      GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
   switch (GetGLImplementation()) {
     case kGLImplementationDesktopGL:
     case kGLImplementationDesktopGLCoreProfile:
       if (!InitializeOneOffForSandbox()) {
         LOG(ERROR) << "GLSurfaceCGL::InitializeOneOff failed.";
       }
-      return GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
+      break;
 #if defined(USE_EGL)
     case kGLImplementationEGLGLES2:
-    case kGLImplementationEGLANGLE: {
-      GLDisplay* display = GLSurfaceEGL::InitializeOneOff(EGLDisplayPlatform(0),
-                                                          system_device_id);
-      if (!display) {
-        LOG(ERROR) << "GLSurfaceEGL::InitializeOneOff failed.";
+    case kGLImplementationEGLANGLE:
+      if (!display->Initialize(EGLDisplayPlatform(0))) {
+        LOG(ERROR) << "GLDisplayEGL::Initialize failed.";
         return nullptr;
       }
-      return display;
-    }
+      break;
 #endif  // defined(USE_EGL)
     default:
-      return GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
+      break;
   }
+  return display;
 }
 
 bool InitializeStaticGLBindings(GLImplementationParts implementation) {
@@ -227,7 +226,8 @@
 void ShutdownGLPlatform(GLDisplay* display) {
   ClearBindingsGL();
 #if defined(USE_EGL)
-  GLSurfaceEGL::ShutdownOneOff(static_cast<GLDisplayEGL*>(display));
+  if (display)
+    display->Shutdown();
   ClearBindingsEGL();
 #endif  // defined(USE_EGL)
 }
diff --git a/ui/gl/init/gl_initializer_win.cc b/ui/gl/init/gl_initializer_win.cc
index 960065c..a534d27 100644
--- a/ui/gl/init/gl_initializer_win.cc
+++ b/ui/gl/init/gl_initializer_win.cc
@@ -18,10 +18,10 @@
 #include "base/trace_event/trace_event.h"
 #include "base/win/windows_version.h"
 #include "ui/gl/gl_bindings.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_display_manager.h"
 #include "ui/gl/gl_egl_api_implementation.h"
 #include "ui/gl/gl_gl_api_implementation.h"
-#include "ui/gl/gl_surface_egl.h"
 #include "ui/gl/vsync_provider_win.h"
 
 namespace gl {
@@ -126,24 +126,23 @@
 GLDisplay* InitializeGLOneOffPlatform(uint64_t system_device_id) {
   VSyncProviderWin::InitializeOneOff();
 
+  GLDisplayEGL* display =
+      GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
   switch (GetGLImplementation()) {
-    case kGLImplementationEGLANGLE: {
-      GLDisplayEGL* display = GLSurfaceEGL::InitializeOneOff(
-          EGLDisplayPlatform(GetDC(nullptr)), system_device_id);
-      if (!display) {
-        LOG(ERROR) << "GLSurfaceEGL::InitializeOneOff failed.";
+    case kGLImplementationEGLANGLE:
+      if (!display->Initialize(EGLDisplayPlatform(GetDC(nullptr)))) {
+        LOG(ERROR) << "GLDisplayEGL::Initialize failed.";
         return nullptr;
       }
       DirectCompositionSurfaceWin::InitializeOneOff(display);
-      return display;
-    }
+      break;
     case kGLImplementationMockGL:
     case kGLImplementationStubGL:
       break;
     default:
       NOTREACHED();
   }
-  return GLDisplayManagerEGL::GetInstance()->GetDisplay(system_device_id);
+  return display;
 }
 
 bool InitializeStaticGLBindings(GLImplementationParts implementation) {
@@ -175,7 +174,8 @@
 
 void ShutdownGLPlatform(GLDisplay* display) {
   DirectCompositionSurfaceWin::ShutdownOneOff();
-  GLSurfaceEGL::ShutdownOneOff(static_cast<GLDisplayEGL*>(display));
+  if (display)
+    display->Shutdown();
   ClearBindingsEGL();
   ClearBindingsGL();
 }
diff --git a/ui/lottie/animation.cc b/ui/lottie/animation.cc
index eac05dd0..dff6b09 100644
--- a/ui/lottie/animation.cc
+++ b/ui/lottie/animation.cc
@@ -250,6 +250,7 @@
 void Animation::PaintFrame(gfx::Canvas* canvas,
                            float t,
                            const gfx::Size& size) {
+  TRACE_EVENT1("ui", "Animation::PaintFrame", "timestamp", t);
   DCHECK_GE(t, 0.f);
   DCHECK_LE(t, 1.f);
   // Not all of the image assets necessarily appear in the frame at time |t|. To
@@ -278,6 +279,7 @@
     float t,
     sk_sp<SkImage>&,
     SkSamplingOptions&) {
+  TRACE_EVENT0("ui", "Animation::LoadImageForAsset");
   cc::SkottieFrameDataProvider::ImageAsset& image_asset =
       *image_assets_.at(asset_id);
   all_frame_data.emplace(asset_id,
diff --git a/ui/ozone/common/gl_ozone_egl.cc b/ui/ozone/common/gl_ozone_egl.cc
index 7fd7fea..18d1195 100644
--- a/ui/ozone/common/gl_ozone_egl.cc
+++ b/ui/ozone/common/gl_ozone_egl.cc
@@ -7,21 +7,21 @@
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_context_egl.h"
+#include "ui/gl/gl_display.h"
 #include "ui/gl/gl_egl_api_implementation.h"
 #include "ui/gl/gl_gl_api_implementation.h"
 #include "ui/gl/gl_share_group.h"
 #include "ui/gl/gl_surface.h"
-#include "ui/gl/gl_surface_egl.h"
+#include "ui/gl/gl_utils.h"
 #include "ui/ozone/common/native_pixmap_egl_binding.h"
 
 namespace ui {
 
 gl::GLDisplay* GLOzoneEGL::InitializeGLOneOffPlatform(
     uint64_t system_device_id) {
-  gl::GLDisplay* display =
-      gl::GLSurfaceEGL::InitializeOneOff(GetNativeDisplay(), system_device_id);
-  if (!display) {
-    LOG(ERROR) << "GLSurfaceEGL::InitializeOneOff failed.";
+  gl::GLDisplayEGL* display = gl::GetDisplayEGL(system_device_id);
+  if (!display->Initialize(GetNativeDisplay())) {
+    LOG(ERROR) << "GLDisplayEGL::Initialize failed.";
     return nullptr;
   }
   return display;
@@ -51,7 +51,8 @@
 }
 
 void GLOzoneEGL::ShutdownGL(gl::GLDisplay* display) {
-  gl::GLSurfaceEGL::ShutdownOneOff(static_cast<gl::GLDisplayEGL*>(display));
+  if (display)
+    display->Shutdown();
   gl::ClearBindingsGL();
   gl::ClearBindingsEGL();
 }
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index 97043d5..c27fbc30 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -645,6 +645,7 @@
     "//ui/events:test_support",
     "//ui/events/types:headers",
     "//ui/gfx",
+    "//ui/ozone/platform/wayland",
     "//ui/platform_window/common",
   ]
 }
diff --git a/ui/ozone/platform/wayland/emulate/wayland_input_emulate.cc b/ui/ozone/platform/wayland/emulate/wayland_input_emulate.cc
index 1e64f8e..7a862b0 100644
--- a/ui/ozone/platform/wayland/emulate/wayland_input_emulate.cc
+++ b/ui/ozone/platform/wayland/emulate/wayland_input_emulate.cc
@@ -15,6 +15,7 @@
 #include "base/time/time.h"
 #include "ui/base/test/ui_controls.h"
 #include "ui/events/keycodes/dom/keycode_converter.h"
+#include "ui/ozone/platform/wayland/host/wayland_window.h"
 #include "ui/platform_window/common/platform_window_defaults.h"
 
 namespace wl {
@@ -107,7 +108,8 @@
 
 void WaylandInputEmulate::EmulatePointerMotion(
     gfx::AcceleratedWidget widget,
-    const gfx::Point& mouse_surface_loc) {
+    const gfx::Point& mouse_surface_loc,
+    const gfx::Point& mouse_screen_loc_in_px) {
   auto it = windows_.find(widget);
   DCHECK(it != windows_.end());
 
@@ -115,7 +117,8 @@
   if (!test_window->buffer_attached_and_configured) {
     auto pending_event =
         std::make_unique<PendingEvent>(ui::EventType::ET_MOUSE_MOVED, widget);
-    pending_event->location_in_px = mouse_surface_loc;
+    pending_event->pointer_surface_location = mouse_surface_loc;
+    pending_event->pointer_screen_location_in_px = mouse_screen_loc_in_px;
     test_window->pending_events.emplace_back(std::move(pending_event));
     return;
   }
@@ -127,18 +130,30 @@
 
   // If it's a toplevel window, activate it. This results in raising the the
   // parent window and its children windows.
+  // TODO(oshima): This is probably not right because a inactive window can
+  // still receive mouse wheel event, and you may want to move a mouse pointer
+  // w/o activating a window. A window will be activated when a mouse is
+  // clicked.
   auto window_type = wayland_proxy->GetWindowType(widget);
   if (window_type != ui::PlatformWindowType::kTooltip &&
       window_type != ui::PlatformWindowType::kMenu &&
       !wayland_proxy->WindowHasPointerFocus(widget)) {
     weston_test_activate_surface(weston_test_, wlsurface);
   }
+  bool screen_coordinates =
+      wayland_proxy->GetWaylandWindowForAcceleratedWidget(widget)
+          ->IsScreenCoordinatesEnabled();
 
+  auto* target_surface = screen_coordinates ? nullptr : wlsurface;
+  auto target_location =
+      screen_coordinates ? mouse_screen_loc_in_px : mouse_surface_loc;
+
+  // TODO(crbug.com/1306688): The coordinate should be in DIP.
   timespec ts = (base::TimeTicks::Now() - base::TimeTicks()).ToTimeSpec();
-  weston_test_move_pointer(weston_test_, wlsurface,
+  weston_test_move_pointer(weston_test_, target_surface,
                            static_cast<uint64_t>(ts.tv_sec) >> 32,
                            ts.tv_sec & 0xffffffff, ts.tv_nsec,
-                           mouse_surface_loc.x(), mouse_surface_loc.y());
+                           target_location.x(), target_location.y());
   wayland_proxy->ScheduleDisplayFlush();
 }
 
@@ -215,7 +230,7 @@
   auto* test_window = it->second.get();
   if (!test_window->buffer_attached_and_configured) {
     auto pending_event = std::make_unique<PendingEvent>(event_type, widget);
-    pending_event->location_in_px = touch_screen_loc;
+    pending_event->touch_screen_location = touch_screen_loc;
     pending_event->touch_id = id;
     test_window->pending_events.emplace_back(std::move(pending_event));
     return;
@@ -282,7 +297,9 @@
   //
   // This is needed as running some tests doesn't result in sending frames that
   // require buffers to be created.
-  auto buffer_size = wayland_proxy->GetWindowBounds(widget).size();
+  auto* wayland_window =
+      wayland_proxy->GetWaylandWindowForAcceleratedWidget(widget);
+  auto buffer_size = wayland_window->GetBoundsInPixels().size();
   // Adjust the buffer size in case if the window was created with empty size.
   if (buffer_size.IsEmpty())
     buffer_size.SetSize(1, 1);
@@ -348,8 +365,8 @@
                                                 wl_fixed_t x,
                                                 wl_fixed_t y) {
   WaylandInputEmulate* emulate = static_cast<WaylandInputEmulate*>(data);
-  auto mouse_position_on_screen_px =
-      gfx::Point(wl_fixed_to_int(x), wl_fixed_to_int(y));
+  gfx::Point mouse_position_on_screen_px(wl_fixed_to_int(x),
+                                         wl_fixed_to_int(y));
   for (WaylandInputEmulate::Observer& observer : emulate->observers_)
     observer.OnPointerMotionGlobal(mouse_position_on_screen_px);
 }
@@ -437,8 +454,9 @@
 
     switch (event->type) {
       case ui::EventType::ET_MOUSE_MOVED:
-        input_emulate->EmulatePointerMotion(window->widget,
-                                            event->location_in_px);
+        input_emulate->EmulatePointerMotion(
+            window->widget, event->pointer_surface_location,
+            event->pointer_screen_location_in_px);
         break;
       case ui::EventType::ET_MOUSE_PRESSED:
       case ui::EventType::ET_MOUSE_RELEASED:
@@ -454,7 +472,8 @@
       case ui::EventType::ET_TOUCH_MOVED:
       case ui::EventType::ET_TOUCH_RELEASED:
         input_emulate->EmulateTouch(window->widget, event->type,
-                                    event->touch_id, event->location_in_px);
+                                    event->touch_id,
+                                    event->touch_screen_location);
         break;
       default:
         NOTREACHED();
diff --git a/ui/ozone/platform/wayland/emulate/wayland_input_emulate.h b/ui/ozone/platform/wayland/emulate/wayland_input_emulate.h
index bb3e2c2..2f0075a9 100644
--- a/ui/ozone/platform/wayland/emulate/wayland_input_emulate.h
+++ b/ui/ozone/platform/wayland/emulate/wayland_input_emulate.h
@@ -58,7 +58,8 @@
   void AddObserver(Observer* obs);
   void RemoveObserver(Observer* obs);
   void EmulatePointerMotion(gfx::AcceleratedWidget widget,
-                            const gfx::Point& mouse_surface_loc);
+                            const gfx::Point& mouse_surface_loc,
+                            const gfx::Point& mouse_screen_loc_in_px);
   void EmulatePointerButton(gfx::AcceleratedWidget widget,
                             ui::EventType event_type,
                             uint32_t changed_button);
@@ -82,10 +83,13 @@
     ui::EventType type;
     gfx::AcceleratedWidget widget;
 
-    // Set for type == ET_MOUSE_MOVED || type == ET_TOUCH_*. Location is in
-    // surface coordinates for mouse events, and in root coordinates for touch
-    // events.
-    gfx::Point location_in_px;
+    // Set for type == ET_MOUSE_MOVED. Locations are
+    // in surface local, and pixel screen coordinates respectively.
+    gfx::Point pointer_surface_location;
+    gfx::Point pointer_screen_location_in_px;
+
+    // Set for type == ET_TOUCH_*. Location is in dip screen coordinates.
+    gfx::Point touch_screen_location;
 
     // Set for type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED.
     uint32_t mouse_button = 0;
diff --git a/ui/ozone/platform/wayland/host/proxy/wayland_proxy.h b/ui/ozone/platform/wayland/host/proxy/wayland_proxy.h
index 5f32460..82c056d 100644
--- a/ui/ozone/platform/wayland/host/proxy/wayland_proxy.h
+++ b/ui/ozone/platform/wayland/host/proxy/wayland_proxy.h
@@ -14,10 +14,13 @@
 struct wl_surface;
 
 namespace gfx {
-class Rect;
 class Size;
 }  // namespace gfx
 
+namespace ui {
+class WaylandWindow;
+}
+
 namespace wl {
 
 // A proxy interface to Ozone/Wayland that is used by input emulation. The
@@ -64,6 +67,9 @@
   // Returns wl_surface that backs the |widget|.
   virtual wl_surface* GetWlSurfaceForAcceleratedWidget(
       gfx::AcceleratedWidget widget) = 0;
+  // Returns WaylandWindow backed by |widget|.
+  virtual ui::WaylandWindow* GetWaylandWindowForAcceleratedWidget(
+      gfx::AcceleratedWidget widget) = 0;
 
   // Creates and returns a shm based wl_buffer with |buffer_size|. The shared
   // memory is hold until DestroyShmForWlBuffer is called.
@@ -79,9 +85,6 @@
   virtual ui::PlatformWindowType GetWindowType(
       gfx::AcceleratedWidget widget) = 0;
 
-  // Returns bounds in px of the window backed by |widget|.
-  virtual gfx::Rect GetWindowBounds(gfx::AcceleratedWidget widget) = 0;
-
   virtual bool WindowHasPointerFocus(gfx::AcceleratedWidget widget) = 0;
   virtual bool WindowHasKeyboardFocus(gfx::AcceleratedWidget widget) = 0;
 
diff --git a/ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.cc b/ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.cc
index 6117ed6..7942b4c 100644
--- a/ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.cc
+++ b/ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.cc
@@ -49,6 +49,13 @@
   return window->root_surface()->surface();
 }
 
+ui::WaylandWindow* WaylandProxyImpl::GetWaylandWindowForAcceleratedWidget(
+    gfx::AcceleratedWidget widget) {
+  auto* window = connection_->wayland_window_manager()->GetWindow(widget);
+  DCHECK(window);
+  return window;
+}
+
 wl_buffer* WaylandProxyImpl::CreateShmBasedWlBuffer(
     const gfx::Size& buffer_size) {
   ui::WaylandShmBuffer shm_buffer(connection_->wayland_buffer_factory(),
@@ -78,12 +85,6 @@
   return window->type();
 }
 
-gfx::Rect WaylandProxyImpl::GetWindowBounds(gfx::AcceleratedWidget widget) {
-  auto* window = connection_->wayland_window_manager()->GetWindow(widget);
-  DCHECK(window);
-  return window->GetBoundsInPixels();
-}
-
 bool WaylandProxyImpl::WindowHasPointerFocus(gfx::AcceleratedWidget widget) {
   auto* window = connection_->wayland_window_manager()->GetWindow(widget);
   DCHECK(window);
diff --git a/ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.h b/ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.h
index 4bb91c8..69a962a9 100644
--- a/ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.h
+++ b/ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.h
@@ -14,6 +14,7 @@
 namespace ui {
 class WaylandConnection;
 class WaylandShmBuffer;
+class WaylandWindow;
 }  // namespace ui
 
 namespace wl {
@@ -30,11 +31,12 @@
   void RoundTripQueue() override;
   wl_surface* GetWlSurfaceForAcceleratedWidget(
       gfx::AcceleratedWidget widget) override;
+  ui::WaylandWindow* GetWaylandWindowForAcceleratedWidget(
+      gfx::AcceleratedWidget widget) override;
   wl_buffer* CreateShmBasedWlBuffer(const gfx::Size& buffer_size) override;
   void DestroyShmForWlBuffer(wl_buffer* buffer) override;
   void ScheduleDisplayFlush() override;
   ui::PlatformWindowType GetWindowType(gfx::AcceleratedWidget widget) override;
-  gfx::Rect GetWindowBounds(gfx::AcceleratedWidget widget) override;
   bool WindowHasPointerFocus(gfx::AcceleratedWidget widget) override;
   bool WindowHasKeyboardFocus(gfx::AcceleratedWidget widget) override;
 
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
index 27b58bf..90900dc 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
@@ -329,6 +329,10 @@
   return window_shape_in_dips_;
 }
 
+bool WaylandToplevelWindow::IsScreenCoordinatesEnabled() const {
+  return screen_coordinates_enabled_;
+}
+
 // static
 void WaylandToplevelWindow::AllowSettingDecorationInsetsForTest(bool allow) {
   decorations_allowed_for_test_ = allow;
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.h b/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
index 50c2918..936c326 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
@@ -80,10 +80,7 @@
 
   // WaylandWindow overrides:
   absl::optional<std::vector<gfx::Rect>> GetWindowShape() const override;
-
-  bool screen_coordinates_enabled() const {
-    return screen_coordinates_enabled_;
-  }
+  bool IsScreenCoordinatesEnabled() const override;
 
   // Client-side decorations on Wayland take some portion of the window surface,
   // and when they are turned on or off, the window geometry is changed.  That
diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc
index 9c16d03..c2ba75c 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -709,6 +709,10 @@
   return nullptr;
 }
 
+bool WaylandWindow::IsScreenCoordinatesEnabled() const {
+  return false;
+}
+
 uint32_t WaylandWindow::DispatchEventToDelegate(
     const PlatformEvent& native_event) {
   bool handled = DispatchEventFromNativeUiEvent(
diff --git a/ui/ozone/platform/wayland/host/wayland_window.h b/ui/ozone/platform/wayland/host/wayland_window.h
index e189945..9acd02b 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_window.h
@@ -309,6 +309,9 @@
   // WaylandPopup, if |this| has type of WaylandPopup.
   virtual WaylandPopup* AsWaylandPopup();
 
+  // Returns true if the window's bounds is in screen coordinates.
+  virtual bool IsScreenCoordinatesEnabled() const;
+
   scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner() {
     return ui_task_runner_;
   }
diff --git a/ui/ozone/platform/wayland/test/wayland_ozone_ui_controls_test_helper.cc b/ui/ozone/platform/wayland/test/wayland_ozone_ui_controls_test_helper.cc
index 8567f42..5db8b47 100644
--- a/ui/ozone/platform/wayland/test/wayland_ozone_ui_controls_test_helper.cc
+++ b/ui/ozone/platform/wayland/test/wayland_ozone_ui_controls_test_helper.cc
@@ -167,13 +167,13 @@
 void WaylandOzoneUIControlsTestHelper::SendMouseMotionNotifyEvent(
     gfx::AcceleratedWidget widget,
     const gfx::Point& mouse_loc,
-    const gfx::Point& mouse_root_loc,
+    const gfx::Point& mouse_screen_loc_in_px,
     base::OnceClosure closure) {
   WaylandGlobalEventWaiter::Create(
       WaylandGlobalEventWaiter::WaylandEventType::kMotion, mouse_loc,
       std::move(closure), input_emulate_.get());
-
-  input_emulate_->EmulatePointerMotion(widget, mouse_loc);
+  input_emulate_->EmulatePointerMotion(widget, mouse_loc,
+                                       mouse_screen_loc_in_px);
 }
 
 void WaylandOzoneUIControlsTestHelper::SendMouseEvent(
@@ -182,7 +182,7 @@
     int button_state,
     int accelerator_state,
     const gfx::Point& mouse_loc,
-    const gfx::Point& mouse_root_loc,
+    const gfx::Point& mouse_screen_loc_in_px,
     base::OnceClosure closure) {
   uint32_t changed_button = 0;
   switch (type) {
@@ -208,7 +208,7 @@
                          accelerator_state & ui_controls::kCommand, {}, true);
   }
 
-  SendMouseMotionNotifyEvent(widget, mouse_loc, mouse_root_loc, {});
+  SendMouseMotionNotifyEvent(widget, mouse_loc, mouse_screen_loc_in_px, {});
 
   WaylandGlobalEventWaiter::Create(
       WaylandGlobalEventWaiter::WaylandEventType::kButton, changed_button,
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 07b123d8..f681d6de 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -133,6 +133,7 @@
     "controls/image_view_base.h",
     "controls/label.h",
     "controls/link.h",
+    "controls/link_fragment.h",
     "controls/menu/menu_config.h",
     "controls/menu/menu_controller.h",
     "controls/menu/menu_controller_delegate.h",
@@ -360,6 +361,7 @@
     "controls/image_view_base.cc",
     "controls/label.cc",
     "controls/link.cc",
+    "controls/link_fragment.cc",
     "controls/menu/menu_config.cc",
     "controls/menu/menu_controller.cc",
     "controls/menu/menu_delegate.cc",
@@ -1141,6 +1143,7 @@
     "controls/editable_combobox/editable_combobox_unittest.cc",
     "controls/image_view_unittest.cc",
     "controls/label_unittest.cc",
+    "controls/link_fragment_unittest.cc",
     "controls/link_unittest.cc",
     "controls/menu/menu_controller_unittest.cc",
     "controls/menu/menu_item_view_unittest.cc",
diff --git a/ui/views/controls/link.cc b/ui/views/controls/link.cc
index b8d7020..522d723 100644
--- a/ui/views/controls/link.cc
+++ b/ui/views/controls/link.cc
@@ -59,6 +59,10 @@
   RecalculateFont();
 }
 
+bool Link::GetForceUnderline() const {
+  return force_underline_;
+}
+
 ui::Cursor Link::GetCursor(const ui::MouseEvent& event) {
   if (!GetEnabled())
     return ui::Cursor();
@@ -231,6 +235,7 @@
 
 BEGIN_METADATA(Link, Label)
 ADD_READONLY_PROPERTY_METADATA(SkColor, Color, ui::metadata::SkColorConverter)
+ADD_PROPERTY_METADATA(bool, ForceUnderline)
 END_METADATA
 
 }  // namespace views
diff --git a/ui/views/controls/link.h b/ui/views/controls/link.h
index 405b605..115976c5 100644
--- a/ui/views/controls/link.h
+++ b/ui/views/controls/link.h
@@ -61,6 +61,7 @@
   SkColor GetColor() const;
 
   void SetForceUnderline(bool force_underline);
+  bool GetForceUnderline() const;
 
   // Label:
   ui::Cursor GetCursor(const ui::MouseEvent& event) override;
@@ -84,12 +85,12 @@
   bool IsSelectionSupported() const override;
 
  private:
+  virtual void RecalculateFont();
+
   void SetPressed(bool pressed);
 
   void OnClick(const ui::Event& event);
 
-  void RecalculateFont();
-
   void ConfigureFocus();
 
   ClickedCallback callback_;
diff --git a/ui/views/controls/link_fragment.cc b/ui/views/controls/link_fragment.cc
new file mode 100644
index 0000000..9f44db5
--- /dev/null
+++ b/ui/views/controls/link_fragment.cc
@@ -0,0 +1,88 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/controls/link_fragment.h"
+
+#include <string>
+
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/views/controls/link.h"
+#include "ui/views/style/typography.h"
+
+namespace views {
+
+LinkFragment::LinkFragment(const std::u16string& title,
+                           int text_context,
+                           int text_style,
+                           LinkFragment* other_fragment)
+    : Link(title, text_context, text_style),
+      prev_fragment_(this),
+      next_fragment_(this) {
+  // Connect to the previous fragment if it exists.
+  if (other_fragment)
+    Connect(other_fragment);
+}
+
+LinkFragment::~LinkFragment() {
+  Disconnect();
+}
+
+void LinkFragment::Connect(LinkFragment* other_fragment) {
+  DCHECK(prev_fragment_ == this);
+  DCHECK(next_fragment_ == this);
+  DCHECK(other_fragment);
+
+  next_fragment_ = other_fragment->next_fragment_;
+  other_fragment->next_fragment_->prev_fragment_ = this;
+  prev_fragment_ = other_fragment;
+  other_fragment->next_fragment_ = this;
+}
+
+void LinkFragment::Disconnect() {
+  DCHECK((prev_fragment_ != this) == (next_fragment_ != this));
+  if (prev_fragment_ != this) {
+    prev_fragment_->next_fragment_ = next_fragment_;
+    next_fragment_->prev_fragment_ = prev_fragment_;
+  }
+}
+
+bool LinkFragment::IsUnderlined() const {
+  return GetEnabled() &&
+         (HasFocus() || IsMouseHovered() || GetForceUnderline());
+}
+
+void LinkFragment::RecalculateFont() {
+  // Check whether any link fragment should be underlined.
+  bool should_be_underlined = IsUnderlined();
+  for (LinkFragment* current_fragment = next_fragment_;
+       !should_be_underlined && current_fragment != this;
+       current_fragment = current_fragment->next_fragment_) {
+    should_be_underlined = current_fragment->IsUnderlined();
+  }
+
+  // If the style differs from the current one, update.
+  if ((font_list().GetFontStyle() & gfx::Font::UNDERLINE) !=
+      should_be_underlined) {
+    auto MaybeUpdateStyle = [should_be_underlined](LinkFragment* fragment) {
+      const int style = fragment->font_list().GetFontStyle();
+      const int intended_style = should_be_underlined
+                                     ? (style | gfx::Font::UNDERLINE)
+                                     : (style & ~gfx::Font::UNDERLINE);
+      fragment->Label::SetFontList(
+          fragment->font_list().DeriveWithStyle(intended_style));
+      fragment->SchedulePaint();
+    };
+    MaybeUpdateStyle(this);
+    for (LinkFragment* current_fragment = next_fragment_;
+         current_fragment != this;
+         current_fragment = current_fragment->next_fragment_) {
+      MaybeUpdateStyle(current_fragment);
+    }
+  }
+}
+
+BEGIN_METADATA(LinkFragment, Link)
+END_METADATA
+
+}  // namespace views
diff --git a/ui/views/controls/link_fragment.h b/ui/views/controls/link_fragment.h
new file mode 100644
index 0000000..20a7b87
--- /dev/null
+++ b/ui/views/controls/link_fragment.h
@@ -0,0 +1,57 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_CONTROLS_LINK_FRAGMENT_H_
+#define UI_VIEWS_CONTROLS_LINK_FRAGMENT_H_
+
+#include "base/memory/raw_ptr.h"
+#include "ui/views/controls/link.h"
+#include "ui/views/metadata/view_factory.h"
+#include "ui/views/style/typography.h"
+
+namespace views {
+
+// A `LinkFragment` can be used to represent a logical link that spans across
+// multiple lines. Connected `LinkFragment`s adjust their style if any single
+// one of them is hovered over of focused.
+class VIEWS_EXPORT LinkFragment : public Link {
+ public:
+  METADATA_HEADER(LinkFragment);
+
+  explicit LinkFragment(const std::u16string& title = std::u16string(),
+                        int text_context = style::CONTEXT_LABEL,
+                        int text_style = style::STYLE_LINK,
+                        LinkFragment* other_fragment = nullptr);
+  ~LinkFragment() override;
+
+  LinkFragment(const LinkFragment&) = delete;
+  LinkFragment& operator=(const LinkFragment&) = delete;
+
+ private:
+  // Returns whether this fragment indicates that the entire link represented
+  // by it should be underlined.
+  bool IsUnderlined() const;
+
+  // Connects `this` to the `other_fragment`.
+  void Connect(LinkFragment* other_fragment);
+
+  // Disconnects `this` from any other fragments that it may be connected to.
+  void Disconnect();
+
+  // Recalculates the font style for this link fragment and, if it is changed,
+  // updates both this fragment and all other that are connected to it.
+  void RecalculateFont() override;
+
+  // Pointers to the previous and the next `LinkFragment` if the logical link
+  // represented by `this` consists of multiple such fragments (e.g. due to
+  // line breaks).
+  // If the logical link is just a single `LinkFragment` component, then these
+  // pointers point to `this`.
+  raw_ptr<LinkFragment> prev_fragment_;
+  raw_ptr<LinkFragment> next_fragment_;
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_CONTROLS_LINK_FRAGMENT_H_
diff --git a/ui/views/controls/link_fragment_unittest.cc b/ui/views/controls/link_fragment_unittest.cc
new file mode 100644
index 0000000..99ee662
--- /dev/null
+++ b/ui/views/controls/link_fragment_unittest.cc
@@ -0,0 +1,140 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/controls/link_fragment.h"
+
+#include <array>
+#include <memory>
+
+#include "base/memory/raw_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/test/event_generator.h"
+#include "ui/views/controls/base_control_test_widget.h"
+#include "ui/views/test/view_metadata_test_utils.h"
+#include "ui/views/widget/widget.h"
+
+namespace views {
+
+namespace {
+
+constexpr char16_t kLinkLabel[] = u"Test label";
+
+class LinkFragmentTest : public test::BaseControlTestWidget {
+ public:
+  LinkFragmentTest() {
+    for (auto& fragment : fragments_) {
+      fragment = nullptr;
+    }
+  }
+  ~LinkFragmentTest() override = default;
+
+  void SetUp() override {
+    test::BaseControlTestWidget::SetUp();
+
+    event_generator_ = std::make_unique<ui::test::EventGenerator>(
+        GetContext(), widget()->GetNativeWindow());
+  }
+
+ protected:
+  void CreateWidgetContent(View* container) override {
+    // Fragment 0 is stand-alone.
+    fragments_[0] =
+        container->AddChildView(std::make_unique<LinkFragment>(kLinkLabel));
+    gfx::Rect current_rect =
+        gfx::ScaleToEnclosedRect(container->GetLocalBounds(), 0.3f);
+    fragments_[0]->SetBoundsRect(current_rect);
+    int width = current_rect.width();
+
+    // Fragments 1 and 2 are connected.
+    current_rect.Offset(width, 0);
+    fragments_[1] =
+        container->AddChildView(std::make_unique<LinkFragment>(kLinkLabel));
+    fragments_[1]->SetBoundsRect(current_rect);
+
+    current_rect.Offset(width, 0);
+    fragments_[2] = container->AddChildView(std::make_unique<LinkFragment>(
+        kLinkLabel, style::CONTEXT_LABEL, style::STYLE_LINK, fragment(1)));
+    fragments_[2]->SetBoundsRect(current_rect);
+  }
+
+  LinkFragment* fragment(size_t index) {
+    DCHECK_LT(index, 3u);
+    return fragments_[index];
+  }
+  ui::test::EventGenerator* event_generator() { return event_generator_.get(); }
+
+  // Returns bounds of the fragment.
+  gfx::Rect GetBoundsForFragment(size_t index) {
+    return fragment(index)->GetBoundsInScreen();
+  }
+
+ private:
+  std::array<raw_ptr<LinkFragment>, 3> fragments_;
+  std::unique_ptr<ui::test::EventGenerator> event_generator_;
+};
+
+}  // namespace
+
+TEST_F(LinkFragmentTest, Metadata) {
+  for (size_t index = 0; index < 3; ++index) {
+    // Needed to avoid failing DCHECK when setting maximum width.
+    fragment(index)->SetMultiLine(true);
+    test::TestViewMetadata(fragment(index));
+  }
+}
+
+// Tests that hovering and unhovering a link adds and removes an underline
+// under all connected fragments.
+TEST_F(LinkFragmentTest, TestUnderlineOnHover) {
+  // A non-hovered link fragment should not be underlined.
+  const gfx::Point point_outside =
+      GetBoundsForFragment(2).bottom_right() + gfx::Vector2d(1, 1);
+  event_generator()->MoveMouseTo(point_outside);
+  EXPECT_FALSE(fragment(0)->IsMouseHovered());
+
+  const auto is_underlined = [this](size_t index) {
+    return !!(fragment(index)->font_list().GetFontStyle() &
+              gfx::Font::UNDERLINE);
+  };
+  EXPECT_FALSE(is_underlined(0));
+
+  // Hovering the first link fragment underlines it.
+  event_generator()->MoveMouseTo(GetBoundsForFragment(0).CenterPoint());
+  EXPECT_TRUE(fragment(0)->IsMouseHovered());
+  EXPECT_TRUE(is_underlined(0));
+  // The other link fragments stay non-hovered.
+  EXPECT_FALSE(is_underlined(1));
+  EXPECT_FALSE(is_underlined(2));
+
+  // Un-hovering the link removes the underline again.
+  event_generator()->MoveMouseTo(point_outside);
+  EXPECT_FALSE(fragment(0)->IsMouseHovered());
+  EXPECT_FALSE(is_underlined(0));
+  EXPECT_FALSE(is_underlined(1));
+  EXPECT_FALSE(is_underlined(2));
+
+  // Hovering the second link fragment underlines both the second and the
+  // third fragment.
+  event_generator()->MoveMouseTo(GetBoundsForFragment(1).CenterPoint());
+  EXPECT_TRUE(fragment(1)->IsMouseHovered());
+  EXPECT_FALSE(fragment(2)->IsMouseHovered());
+  EXPECT_FALSE(is_underlined(0));
+  EXPECT_TRUE(is_underlined(1));
+  EXPECT_TRUE(is_underlined(2));
+
+  // The same is true for hovering the third fragment.
+  event_generator()->MoveMouseTo(GetBoundsForFragment(2).CenterPoint());
+  EXPECT_TRUE(fragment(2)->IsMouseHovered());
+  EXPECT_FALSE(is_underlined(0));
+  EXPECT_TRUE(is_underlined(1));
+  EXPECT_TRUE(is_underlined(2));
+
+  // Moving outside removes the underline again.
+  event_generator()->MoveMouseTo(point_outside);
+  EXPECT_FALSE(is_underlined(0));
+  EXPECT_FALSE(is_underlined(1));
+  EXPECT_FALSE(is_underlined(2));
+}
+
+}  // namespace views
diff --git a/ui/views/controls/styled_label.cc b/ui/views/controls/styled_label.cc
index 8d03c42..283c51f 100644
--- a/ui/views/controls/styled_label.cc
+++ b/ui/views/controls/styled_label.cc
@@ -22,6 +22,8 @@
 #include "ui/gfx/text_elider.h"
 #include "ui/gfx/text_utils.h"
 #include "ui/views/controls/label.h"
+#include "ui/views/controls/link.h"
+#include "ui/views/controls/link_fragment.h"
 #include "ui/views/view_class_properties.h"
 
 namespace views {
@@ -332,8 +334,8 @@
 }
 
 views::Link* StyledLabel::GetFirstLinkForTesting() {
-  const auto it =
-      base::ranges::find(children(), Link::kViewClassName, &View::GetClassName);
+  const auto it = base::ranges::find(children(), LinkFragment::kViewClassName,
+                                     &View::GetClassName);
   DCHECK(it != children().cend());
   return static_cast<views::Link*>(*it);
 }
@@ -365,6 +367,11 @@
   // Try to preserve leading whitespace on the first line.
   bool can_trim_leading_whitespace = false;
   StyleRanges::const_iterator current_range = style_ranges_.begin();
+
+  // A pointer to the previous link fragment if a logical link consists of
+  // multiple `LinkFragment` elements.
+  LinkFragment* previous_link_fragment = nullptr;
+
   for (std::u16string remaining_string = text_;
        content_width > 0 && !remaining_string.empty();) {
     layout_size_info_.line_sizes.emplace_back(0, line_height);
@@ -475,18 +482,26 @@
         if (chunk.size() > range.end() - position)
           chunk = chunk.substr(0, range.end() - position);
 
-        if (!custom_view)
-          label = CreateLabel(chunk, style_info, range);
+        if (!custom_view) {
+          label =
+              CreateLabel(chunk, style_info, range, &previous_link_fragment);
+        } else {
+          previous_link_fragment = nullptr;
+        }
 
-        if (position + chunk.size() >= range.end())
+        if (position + chunk.size() >= range.end()) {
           ++current_range;
+          // Links do not connect across separate style ranges.
+          previous_link_fragment = nullptr;
+        }
       } else {
         chunk = substrings[0];
         if (position + chunk.size() > range.start())
           chunk = chunk.substr(0, range.start() - position);
 
         // This chunk is normal text.
-        label = CreateLabel(chunk, default_style, range);
+        label =
+            CreateLabel(chunk, default_style, range, &previous_link_fragment);
       }
 
       View* child_view = custom_view ? custom_view : label.get();
@@ -533,14 +548,17 @@
 std::unique_ptr<Label> StyledLabel::CreateLabel(
     const std::u16string& text,
     const RangeStyleInfo& style_info,
-    const gfx::Range& range) const {
+    const gfx::Range& range,
+    LinkFragment** previous_link_fragment) const {
   std::unique_ptr<Label> result;
   if (style_info.text_style == style::STYLE_LINK) {
     // Nothing should (and nothing does) use a custom font for links.
     DCHECK(!style_info.custom_font);
 
-    // Note this ignores |default_text_style_|, in favor of style::STYLE_LINK.
-    auto link = std::make_unique<Link>(text, text_context_);
+    // Note this ignores |default_text_style_|, in favor of `style::STYLE_LINK`.
+    auto link = std::make_unique<LinkFragment>(
+        text, text_context_, style::STYLE_LINK, *previous_link_fragment);
+    *previous_link_fragment = link.get();
     link->SetCallback(style_info.callback);
     if (!style_info.accessible_name.empty())
       link->SetAccessibleName(style_info.accessible_name);
@@ -576,7 +594,7 @@
       // TODO(kylixrd): Should updating the label background color even be
       // allowed if there are custom views?
       DCHECK((child->GetClassName() == Label::kViewClassName) ||
-             (child->GetClassName() == Link::kViewClassName));
+             (child->GetClassName() == LinkFragment::kViewClassName));
       static_cast<Label*>(child)->SetBackgroundColor(new_color);
     }
   }
diff --git a/ui/views/controls/styled_label.h b/ui/views/controls/styled_label.h
index 0693236..3b2bd66 100644
--- a/ui/views/controls/styled_label.h
+++ b/ui/views/controls/styled_label.h
@@ -30,6 +30,7 @@
 
 class Label;
 class Link;
+class LinkFragment;
 
 // A class which can apply mixed styles to a block of text. Currently, text is
 // always multiline. Trailing whitespace in the styled label text is not
@@ -215,9 +216,11 @@
   void CalculateLayout(int width) const;
 
   // Creates a Label for a given |text|, |style_info|, and |range|.
-  std::unique_ptr<Label> CreateLabel(const std::u16string& text,
-                                     const RangeStyleInfo& style_info,
-                                     const gfx::Range& range) const;
+  std::unique_ptr<Label> CreateLabel(
+      const std::u16string& text,
+      const RangeStyleInfo& style_info,
+      const gfx::Range& range,
+      LinkFragment** previous_link_component) const;
 
   // Update the label background color from the theme or
   // |displayed_on_background_color_| if set.
diff --git a/ui/views/controls/styled_label_unittest.cc b/ui/views/controls/styled_label_unittest.cc
index 2c4bdb0..699d1f2 100644
--- a/ui/views/controls/styled_label_unittest.cc
+++ b/ui/views/controls/styled_label_unittest.cc
@@ -23,6 +23,7 @@
 #include "ui/gfx/font_list.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/link.h"
+#include "ui/views/controls/link_fragment.h"
 #include "ui/views/style/typography.h"
 #include "ui/views/test/test_layout_provider.h"
 #include "ui/views/test/test_views.h"
@@ -385,8 +386,9 @@
 
   ASSERT_EQ(3u, styled()->children().size());
   EXPECT_EQ(SK_ColorBLUE, LabelAt(styled(), 0)->GetEnabledColor());
-  EXPECT_EQ(kDefaultLinkColor,
-            LabelAt(styled(), 1, Link::kViewClassName)->GetEnabledColor());
+  EXPECT_EQ(
+      kDefaultLinkColor,
+      LabelAt(styled(), 1, LinkFragment::kViewClassName)->GetEnabledColor());
   EXPECT_EQ(kDefaultTextColor, LabelAt(styled(), 2)->GetEnabledColor());
 
   // Test adjusted color readability.
diff --git a/ui/views/test/ui_controls_factory_desktop_aura_ozone.cc b/ui/views/test/ui_controls_factory_desktop_aura_ozone.cc
index 8205a81..a364780e 100644
--- a/ui/views/test/ui_controls_factory_desktop_aura_ozone.cc
+++ b/ui/views/test/ui_controls_factory_desktop_aura_ozone.cc
@@ -161,8 +161,8 @@
                                      int y,
                                      base::OnceClosure closure) override {
     gfx::Point screen_location(x, y);
-    gfx::Point root_location = screen_location;
     aura::Window* root_window;
+
     // Touch release events might not have coordinates that match any window, so
     // just use whichever window is on top.
     if (action & ui_controls::RELEASE)
@@ -170,19 +170,9 @@
     else
       root_window = RootWindowForPoint(screen_location);
 
-    aura::client::ScreenPositionClient* screen_position_client =
-        aura::client::GetScreenPositionClient(root_window);
-    if (screen_position_client) {
-      screen_position_client->ConvertPointFromScreen(root_window,
-                                                     &root_location);
-    }
-
-    aura::WindowTreeHost* host = root_window->GetHost();
-    gfx::Point screen_point(root_location);
-    host->ConvertDIPToScreenInPixels(&screen_point);
     ozone_ui_controls_test_helper_->SendTouchEvent(
         root_window->GetHost()->GetAcceleratedWidget(), action, id,
-        screen_point, std::move(closure));
+        screen_location, std::move(closure));
 
     return true;
   }
diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn
index b5d02bf..cc04430f 100644
--- a/ui/webui/resources/BUILD.gn
+++ b/ui/webui/resources/BUILD.gn
@@ -186,6 +186,7 @@
   "cr_elements/policy/cr_policy_pref_indicator.m.d.ts",
   "cr_elements/policy/cr_tooltip_icon.m.d.ts",
   "js/cr/ui/focus_row_behavior.m.d.ts",
+  "js/cr/ui/list.m.d.ts",
   "js/i18n_behavior.m.d.ts",
   "js/list_property_update_behavior.m.d.ts",
   "js/parse_html_subset.m.d.ts",
@@ -216,7 +217,6 @@
     "cr_components/chromeos/smb_shares/add_smb_share_dialog.d.ts",
     "cr_components/chromeos/smb_shares/smb_browser_proxy.d.ts",
     "js/cr/ui/grid.m.d.ts",
-    "js/cr/ui/list.m.d.ts",
   ]
 }
 
@@ -234,11 +234,16 @@
   "js/color_utils.js",
   "js/cr/event_target.m.js",
   "js/cr.m.js",
+  "js/cr/ui.m.js",
+  "js/cr/ui/array_data_model.m.js",
   "js/cr/ui/drag_wrapper.js",
   "js/cr/ui/focus_grid.js",
   "js/cr/ui/focus_outline_manager.m.js",
   "js/cr/ui/focus_row.m.js",
   "js/cr/ui/keyboard_shortcut_list.m.js",
+  "js/cr/ui/list_item.m.js",
+  "js/cr/ui/list_selection_controller.m.js",
+  "js/cr/ui/list_selection_model.m.js",
   "js/cr/ui/store.js",
   "js/event_tracker.m.js",
   "js/load_time_data.m.js",
@@ -252,18 +257,13 @@
   generate_definitions_js_files += [
     "js/cr/ui/menu.m.js",
     "js/cr/ui/command.m.js",
-    "js/cr/ui.m.js",
     "js/cr/ui/position_util.m.js",
   ]
 }
 
 if (is_chromeos_ash) {
   generate_definitions_js_files += [
-    "js/cr/ui/array_data_model.m.js",
-    "js/cr/ui/list_item.m.js",
-    "js/cr/ui/list_selection_model.m.js",
     "js/cr/ui/list_single_selection_model.m.js",
-    "js/cr/ui/list_selection_controller.m.js",
     "js/cr/ui/store_client.js",
   ]
 }
diff --git a/ui/webui/resources/js/list_property_update_behavior.m.d.ts b/ui/webui/resources/js/list_property_update_behavior.m.d.ts
index 9fc0420..bacfb0b 100644
--- a/ui/webui/resources/js/list_property_update_behavior.m.d.ts
+++ b/ui/webui/resources/js/list_property_update_behavior.m.d.ts
@@ -4,9 +4,8 @@
 
 export interface ListPropertyUpdateBehavior {
   updateList(
-      propertyPath: string,
-      identityGetter: ((arg0: any) => (any | string)),
-      updatedList: object[], identityBasedUpdate?: boolean): boolean;
+      propertyPath: string, identityGetter: ((arg0: any) => (any | string)),
+      updatedList: any[], identityBasedUpdate?: boolean): boolean;
 }
 
 declare const ListPropertyUpdateBehavior: object;
diff --git a/ui/webui/resources/js/static_types.d.ts b/ui/webui/resources/js/static_types.d.ts
index 3e605bd..75955698 100644
--- a/ui/webui/resources/js/static_types.d.ts
+++ b/ui/webui/resources/js/static_types.d.ts
@@ -4,5 +4,7 @@
 
 export function getTrustedHTML(literal: string[]|
                                TemplateStringsArray): TrustedHTML|string;
-export function getTrustedScript(literal: string[]): TrustedScript|string;
-export function getTrustedScriptURL(literal: string[]): TrustedScriptURL|string;
+export function getTrustedScript(literal: string[]|
+                                 TemplateStringsArray): TrustedScript|string;
+export function getTrustedScriptURL(literal: string[]|TemplateStringsArray):
+    TrustedScriptURL|string;
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java
index a316ed8..f688a20 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/TabCallbackTest.java
@@ -21,6 +21,7 @@
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisableIf;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
 import org.chromium.weblayer.ContextMenuParams;
@@ -384,6 +385,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "crbug.com/1339982")
     public void testScrollNotificationDirectionChange() throws TimeoutException {
         final String url = mActivityTestRule.getTestDataURL("tall_page.html");
         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(url);
diff --git a/weblayer/browser/feature_list_creator.cc b/weblayer/browser/feature_list_creator.cc
index 7030462..592ff38a 100644
--- a/weblayer/browser/feature_list_creator.cc
+++ b/weblayer/browser/feature_list_creator.cc
@@ -11,6 +11,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/variations/service/variations_service.h"
 #include "components/variations/variations_crash_keys.h"
+#include "components/variations/variations_switches.h"
 #include "content/public/browser/network_service_instance.h"
 #include "content/public/common/content_switch_dependent_feature_overrides.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -106,10 +107,13 @@
   std::vector<std::string> variation_ids;
   auto feature_list = std::make_unique<base::FeatureList>();
 
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
   variations_service_->SetUpFieldTrials(
       variation_ids,
-      content::GetSwitchDependentFeatureOverrides(
-          *base::CommandLine::ForCurrentProcess()),
+      command_line->GetSwitchValueASCII(
+          variations::switches::kForceVariationIds),
+      content::GetSwitchDependentFeatureOverrides(*command_line),
       std::move(feature_list), &weblayer_field_trials_);
   variations::InitCrashKeys();
 #else