diff --git a/.gitattributes b/.gitattributes
index d711b24..3393769 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -43,7 +43,7 @@
 .vpython   text eol=lf
 codereview.settings   text eol=lf
 DEPS   text eol=lf
-ENG_REVIEW_OWNERS   text eol=lf
+ATL_OWNERS   text eol=lf
 LICENSE text eol=lf
 LICENSE.* text eol=lf
 MAJOR_BRANCH_DATE   text eol=lf
diff --git a/ATL_OWNERS b/ATL_OWNERS
new file mode 100644
index 0000000..95b6ee3
--- /dev/null
+++ b/ATL_OWNERS
@@ -0,0 +1,10 @@
+# Current list of Chrome Area Tech Leads (ATLs) mostly for the purpose of
+# reviewing addings of top-level and third-party directories.
+
+# NOTE: keep this in sync with lsc-owners-override@chromium.org owners
+# by emailing lsc-policy@chromium.org when this list changes.
+jam@chromium.org
+rbyers@chromium.org
+thakis@chromium.org
+# NOTE: keep this in sync with lsc-owners-override@chromium.org owners
+# by emailing lsc-policy@chromium.org when this list changes.
diff --git a/DEPS b/DEPS
index ce78d40..09df101 100644
--- a/DEPS
+++ b/DEPS
@@ -251,7 +251,11 @@
   # Rust toolchain. Experimental. The corresponding GN arg
   # use_chromium_rust_toolchain directs the build to use this toolchain instead
   # of the Android toolchain.
-  'fetch_prebuilt_chromium_rust_toolchain': 'use_rust and host_os == "linux"',
+  #
+  # We avoid doing this on toolchain build bots (where
+  # `checkout_rust_toolchain_deps` is set) since they are building the Rust
+  # toolchain.
+  'fetch_prebuilt_chromium_rust_toolchain': 'use_rust and host_os == "linux" and not checkout_rust_toolchain_deps',
 
   # Build in-tree Rust toolchain. checkout_clang_libs must also be True. The
   # corresponding GN arg use_chromium_rust_toolchain directs the build to use
@@ -295,7 +299,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': '0c922b0e97a45929f8a13b7b4a316822c1f808a7',
+  'skia_revision': '8943ec85c41cc1dd976785f2d29f712952fc2e92',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -303,11 +307,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '1b5dfec1d3a107a4c1c62023297fa842614c121e',
+  'angle_revision': '2072aea9998c4170bff1811a5013605b99c66547',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '647d3d24c935c2adb4e7bc5f1ecfe14ab12725d2',
+  'swiftshader_revision': 'fbcd6095717437609d567fc7eaad5d8575677b19',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -322,7 +326,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:11.20221214.1.1',
+  'fuchsia_version': 'version:11.20221213.1.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -374,7 +378,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': 'e4774385293c8ea1b3ad849c3fa9e9b1e108edbf',
+  'devtools_frontend_revision': '8a5191d033269f671d9db2ebd1fb8168596eb848',
   # 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.
@@ -410,7 +414,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': '188ed1793a20602bba04792f4e7b426f329bff4a',
+  'dawn_revision': '0890380d10112b6071922dcb8af7166f8386bdd3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -569,6 +573,9 @@
     # TODO(https://crbug.com/1292038): gate this on use_rust as well as host_os.
     'condition': 'host_os == "linux"',
   },
+
+  # Rust sources are used to build the Rust standard library, and on toolchain
+  # build bots, to build the Rust toolchain.
   'src/third_party/rust_src/src': {
     'packages': [
       {
@@ -764,7 +771,7 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    'c845cf7f3e353eb0bd7a8526a56b6fe4cc27bd5d',
+    'd6e61b675e9d5f3064111c6ea37c8c58312e4d60',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -863,7 +870,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': '2Qa-4VYN4gTvNeC6SKF9vMGD7jCJqrxWw7b0wFYMzacC',
+          'version': 'L3VzUFocEuArHckM0_TOtSn1Dg-h7LmEkeXCJva1P-cC',
         },
       ],
       'dep_type': 'cipd',
@@ -874,7 +881,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/mac-amd64',
-          'version': 'N29oCTqJ7Uv24JlDVIfMZdAdXy5PoUTsYfzYVyUCPZIC',
+          'version': 'SLyNH9sWno4QIRYWxbXPKtQ5s3BbJXGSUq-aBuC2HAIC',
         },
       ],
       'dep_type': 'cipd',
@@ -885,7 +892,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/windows-amd64',
-          'version': '4LFXhe8B-cXCMACA99kCrdi6f8hHnM_lyTV5Erqfo7kC',
+          'version': 'b2yKrtEWySHc1NAGoZUkT1ttfzi_RctfT6614Z2lPqMC',
         },
       ],
       'dep_type': 'cipd',
@@ -953,7 +960,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'A9iQ0pmVMUDN9vNJclahJzs3Qzprf-p7va6LWZGw4rEC',
+          'version': 'S-GKQWky-riwXwzGHQxWZQEag1uM0Mo4blc5VIfl7TcC',
       },
     ],
     'condition': 'checkout_android',
@@ -1170,7 +1177,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' + '@' + '53e7a328603e09ebc0d22680b3d8e0b006ff26af',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '0e94c059f4731259ff1a8b2d70209f84899dbc5d',
       'condition': 'checkout_chromeos',
   },
 
@@ -1204,7 +1211,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + '857803dd5b6617f417d27cd3086c128f4e250dd6',
+      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + '58d1bf52043ab247699937a8f262a1c7663313d9',
     'condition': 'checkout_src_internal',
   },
 
@@ -1633,7 +1640,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '8d5d2a94b712354ef35ea0519c8d18531720b86f',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '165c172f3a6e30aa98495eaef67d5042cb4133a8',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1778,7 +1785,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@a7b8bd76ed0c833a79132475105eb9ef37d6beff',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@65f11fade2bd2b9f23d9c7c1e76087caac3490b7',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1818,7 +1825,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'b99b81d6c5f615447fbdb469fa720808faf998b1',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'c0d44d9d6369968bcb2371640ad65b4589d20b33',
+    Var('webrtc_git') + '/src.git' + '@' + '597a2ba41aaa0b7ce09c8e0db94060cd6af7a52d',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -1888,7 +1895,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@4998ecfddf7dd423ca5ed445901bc6628548497c',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@5232f9de253ba0a63dfdd8c7965d59fb4cb2b315',
     'condition': 'checkout_src_internal',
   },
 
@@ -1929,7 +1936,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'A9YQwSWojk63JsFUpkGR8ts2H7YsH41oWDkanOzxcO4C',
+        'version': 'OEBt9gPj59-k8q24vYKZksVSx7x0ium6Vv_5VqjOqcQC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3774,15 +3781,6 @@
       'dep_type': 'cipd',
   },
 
-  'src/tools/bazel/windows-amd64': {
-    'packages': [{
-       'package': 'infra/3pp/tools/bazel_bootstrap/windows-amd64',
-       'version': 'version:2@5.3.2',
-    }],
-    'dep_type': 'cipd',
-    'condition': 'checkout_win and checkout_bazel',
-  },
-
   'src/tools/bazel/linux-amd64': {
     'packages': [{
        'package': 'infra/3pp/tools/bazel_bootstrap/linux-amd64',
@@ -3791,6 +3789,33 @@
     'dep_type': 'cipd',
     'condition': 'checkout_linux and checkout_bazel',
   },
+
+  'src/tools/bazel/mac-amd64': {
+    'packages': [{
+       'package': 'infra/3pp/tools/bazel_bootstrap/mac-amd64',
+       'version': 'version:2@5.3.2.1',
+    }],
+    'dep_type': 'cipd',
+    'condition': 'checkout_mac and checkout_bazel',
+  },
+
+  'src/tools/bazel/mac-arm64': {
+    'packages': [{
+       'package': 'infra/3pp/tools/bazel_bootstrap/mac-arm64',
+       'version': 'version:2@5.3.2.1',
+    }],
+    'dep_type': 'cipd',
+    'condition': 'checkout_mac and checkout_bazel',
+  },
+
+  'src/tools/bazel/windows-amd64': {
+    'packages': [{
+       'package': 'infra/3pp/tools/bazel_bootstrap/windows-amd64',
+       'version': 'version:2@5.3.2',
+    }],
+    'dep_type': 'cipd',
+    'condition': 'checkout_win and checkout_bazel',
+  },
 }
 
 
diff --git a/ENG_REVIEW_OWNERS b/ENG_REVIEW_OWNERS
deleted file mode 100644
index b103f4d4..0000000
--- a/ENG_REVIEW_OWNERS
+++ /dev/null
@@ -1,14 +0,0 @@
-# Current list of eng reviewers mostly for the purpose of reviewing
-# addings of top-level and third-party directories.
-
-# NOTE: keep this in sync with lsc-owners-override@chromium.org owners
-# by emailing lsc-policy@chromium.org when this list changes.
-creis@chromium.org
-estaab@chromium.org
-haraken@chromium.org
-jam@chromium.org
-pinkerton@chromium.org
-sky@chromium.org
-yfriedman@chromium.org
-# NOTE: keep this in sync with lsc-owners-override@chromium.org owners
-# by emailing lsc-policy@chromium.org when this list changes.
diff --git a/OWNERS b/OWNERS
index 00de2c5..987f0e4 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,9 +1,9 @@
 # OWNERS_STATUS = build/OWNERS.status
 
-# Eng reviewer. Please reach out before adding new top-level directories.
+# Chrome ATLs. Please reach out before adding new top-level directories.
 # Note: this list is not for rubber-stamping mechanical changes that span the
 # code base. Please reach out to owners of top-level directories instead.
-file://ENG_REVIEW_OWNERS
+file://ATL_OWNERS
 
 per-file .clang-format=thakis@chromium.org
 per-file .clang-tidy=file://styleguide/c++/OWNERS
diff --git a/WATCHLISTS b/WATCHLISTS
index af13a47..a20c7ba 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -2229,7 +2229,8 @@
                      'jimmyxgong+watch-accelerators@chromium.org',
                      'cambickel+watch-accelerators@google.com',
                      'longbowei+watch-accelerators@google.com',
-                     'zhangwenyu+watch-accelerators@google.com'],
+                     'zhangwenyu+watch-accelerators@google.com',
+                     'michaelcheco+watch-accelerators@google.com'],
     'accessibility': ['abigailbklein+watch@google.com',
                       'akihiroota@chromium.org',
                       'dtseng+watch@chromium.org',
diff --git a/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc b/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc
index 6083757..4d216a6 100644
--- a/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc
+++ b/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc
@@ -334,6 +334,13 @@
   base::UmaHistogramBoolean(
       "Android.WebView.RequestedWithHeader.OriginTrialEnabled",
       result_args->xrw_origin_trial_enabled);
+
+  if (result_args->xrw_origin_trial_enabled &&
+      (request_url.SchemeIsHTTPOrHTTPS() || request_url.SchemeIsWSOrWSS())) {
+    base::UmaHistogramBoolean(
+        "Android.WebView.RequestedWithHeader.PageSchemeIsCryptographic",
+        request_url.SchemeIsCryptographic());
+  }
 }
 
 // Post a call to the UI thread to check if the XRW deprecation trial is enabled
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 20abe14..3809647 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -337,6 +337,8 @@
     "capture_mode/key_combo_view.h",
     "capture_mode/key_item_view.cc",
     "capture_mode/key_item_view.h",
+    "capture_mode/pointer_highlight_layer.cc",
+    "capture_mode/pointer_highlight_layer.h",
     "capture_mode/recording_overlay_controller.cc",
     "capture_mode/recording_overlay_controller.h",
     "capture_mode/recording_type_menu_view.cc",
@@ -2832,6 +2834,7 @@
     "drag_drop/drag_image_view_unittest.cc",
     "drag_drop/tab_drag_drop_delegate_unittest.cc",
     "events/accessibility_event_rewriter_unittest.cc",
+    "events/keyboard_capability_unittest.cc",
     "events/keyboard_driven_event_rewriter_unittest.cc",
     "events/select_to_speak_event_handler_unittest.cc",
     "extended_desktop_unittest.cc",
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 37caebc..bc477ea 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -480,6 +480,9 @@
       <message name="IDS_ASH_STATUS_TRAY_CAST_SHORT" desc="The label used for Cast button in system tray bubble. [CHAR_LIMIT=14]">
         Cast
       </message>
+      <message name="IDS_ASH_STATUS_TRAY_CAST_DEVICES_AVAILABLE" desc="Subtitle under the Cast button in the system tray bubble when Chromecast devices are available for casting">
+        Devices available
+      </message>
       <message name="IDS_ASH_STATUS_TRAY_CAST_TOOLTIP" desc="The tooltip text used for Cast button in system tray bubble.">
         Show cast devices
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_DEVICES_AVAILABLE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_DEVICES_AVAILABLE.png.sha1
new file mode 100644
index 0000000..b73f3745
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_STATUS_TRAY_CAST_DEVICES_AVAILABLE.png.sha1
@@ -0,0 +1 @@
+c35a1d6ab1a9cee8df1789a5720288b62ac31c8a
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_demo_tools_controller.cc b/ash/capture_mode/capture_mode_demo_tools_controller.cc
index 4a6ee1d..ffa80cc 100644
--- a/ash/capture_mode/capture_mode_demo_tools_controller.cc
+++ b/ash/capture_mode/capture_mode_demo_tools_controller.cc
@@ -7,16 +7,22 @@
 #include <memory>
 
 #include "ash/capture_mode/capture_mode_constants.h"
+#include "ash/capture_mode/capture_mode_util.h"
 #include "ash/capture_mode/key_combo_view.h"
+#include "ash/capture_mode/pointer_highlight_layer.h"
 #include "ash/capture_mode/video_recording_watcher.h"
 #include "base/check_op.h"
 #include "base/containers/contains.h"
+#include "base/containers/cxx20_erase.h"
+#include "base/containers/unique_ptr_adapters.h"
 #include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animator.h"
 #include "ui/events/event.h"
 #include "ui/events/event_constants.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 #include "ui/events/types/event_type.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/views/animation/animation_builder.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
@@ -25,6 +31,11 @@
 
 constexpr int kDistanceFromBottom = 24;
 
+constexpr float kHighlightLayerFinalOpacity = 0.f;
+constexpr float kHighlightLayerInitialScale = 0.1f;
+constexpr float kHighlightLayerFinalScale = 1.0f;
+constexpr base::TimeDelta kScaleUpDuration = base::Milliseconds(1500);
+
 int GetModifierFlagForKeyCode(ui::KeyboardCode key_code) {
   switch (key_code) {
     case ui::VKEY_COMMAND:
@@ -103,6 +114,38 @@
   OnKeyDownEvent(event);
 }
 
+void CaptureModeDemoToolsController::PerformMousePressAnimation(
+    const gfx::PointF& event_location_in_window) {
+  std::unique_ptr<PointerHighlightLayer> mouse_highlight_layer =
+      std::make_unique<PointerHighlightLayer>(
+          event_location_in_window,
+          video_recording_watcher_->GetOnCaptureSurfaceWidgetParentWindow()
+              ->layer());
+  PointerHighlightLayer* mouse_highlight_layer_ptr =
+      mouse_highlight_layer.get();
+  mouse_highlight_layers_.push_back(std::move(mouse_highlight_layer));
+
+  ui::Layer* highlight_layer = mouse_highlight_layer_ptr->layer();
+  highlight_layer->SetTransform(capture_mode_util::GetScaleTransformAboutCenter(
+      highlight_layer, kHighlightLayerInitialScale));
+  const gfx::Transform scale_up_transform =
+      capture_mode_util::GetScaleTransformAboutCenter(
+          highlight_layer, kHighlightLayerFinalScale);
+
+  views::AnimationBuilder()
+      .OnEnded(base::BindOnce(
+          &CaptureModeDemoToolsController::OnMouseHighlightAnimationEnded,
+          weak_ptr_factory_.GetWeakPtr(), mouse_highlight_layer_ptr))
+      .SetPreemptionStrategy(
+          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
+      .Once()
+      .SetDuration(kScaleUpDuration)
+      .SetTransform(highlight_layer, scale_up_transform,
+                    gfx::Tween::ACCEL_0_40_DECEL_100)
+      .SetOpacity(highlight_layer, kHighlightLayerFinalOpacity,
+                  gfx::Tween::ACCEL_0_80_DECEL_80);
+}
+
 void CaptureModeDemoToolsController::OnKeyUpEvent(ui::KeyEvent* event) {
   const ui::KeyboardCode key_code = event->key_code();
   const int modifier_flag = GetModifierFlagForKeyCode(key_code);
@@ -188,4 +231,13 @@
   key_combo_view_ = nullptr;
 }
 
+void CaptureModeDemoToolsController::OnMouseHighlightAnimationEnded(
+    PointerHighlightLayer* pointer_highlight_layer_ptr) {
+  base::EraseIf(mouse_highlight_layers_,
+                base::MatchesUniquePtr(pointer_highlight_layer_ptr));
+
+  if (on_mouse_highlight_animation_ended_callback_for_test_)
+    std::move(on_mouse_highlight_animation_ended_callback_for_test_).Run();
+}
+
 }  // namespace ash
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_demo_tools_controller.h b/ash/capture_mode/capture_mode_demo_tools_controller.h
index 3060b58..cbf0e1d 100644
--- a/ash/capture_mode/capture_mode_demo_tools_controller.h
+++ b/ash/capture_mode/capture_mode_demo_tools_controller.h
@@ -5,6 +5,8 @@
 #ifndef ASH_CAPTURE_MODE_CAPTURE_MODE_DEMO_TOOLS_CONTROLLER_H_
 #define ASH_CAPTURE_MODE_CAPTURE_MODE_DEMO_TOOLS_CONTROLLER_H_
 
+#include "base/functional/callback_forward.h"
+#include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 #include "ui/views/widget/unique_widget_ptr.h"
@@ -15,9 +17,13 @@
 
 namespace ash {
 
+class PointerHighlightLayer;
 class KeyComboView;
 class VideoRecordingWatcher;
 
+using MouseHighlightLayers =
+    std::vector<std::unique_ptr<PointerHighlightLayer>>;
+
 // Observes and decides whether to show a helper widget representing the
 // currently pressed key combination or not. The key combination will be used to
 // construct or modify the `KeyComboViewer`. The
@@ -36,6 +42,14 @@
   // Decides whether to show a helper widget for the `event` or not.
   void OnKeyEvent(ui::KeyEvent* event);
 
+  // Creates a new highlight layer each time it gets called and performs the
+  // grow-and-fade-out animation on it.
+  void PerformMousePressAnimation(const gfx::PointF& event_location_in_window);
+
+  const MouseHighlightLayers& mouse_highlight_layers_for_testing() const {
+    return mouse_highlight_layers_;
+  }
+
  private:
   friend class CaptureModeDemoToolsTestApi;
 
@@ -51,6 +65,11 @@
   // Resets the `demo_tools_widget_` when the `hide_timer_` expires.
   void AnimateToResetTheWidget();
 
+  // Called when the mouse highlight animation ends to remove the corresponding
+  // pointer highlight from the `mouse_highlight_layers_`.
+  void OnMouseHighlightAnimationEnded(
+      PointerHighlightLayer* pointer_highlight_layer_ptr);
+
   VideoRecordingWatcher* const video_recording_watcher_;
   views::UniqueWidgetPtr demo_tools_widget_;
   KeyComboView* key_combo_view_ = nullptr;
@@ -64,6 +83,14 @@
   // Starts on key up of the last non-modifier key and the `key_combo_view_`
   // will disappear when it expires.
   base::OneShotTimer hide_timer_;
+
+  // Contains all the mouse highlight layers that are being animated.
+  MouseHighlightLayers mouse_highlight_layers_;
+
+  // If set, it will be called when the mouse highlight animation is completed.
+  base::OnceClosure on_mouse_highlight_animation_ended_callback_for_test_;
+
+  base::WeakPtrFactory<CaptureModeDemoToolsController> weak_ptr_factory_{this};
 };
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_demo_tools_test_api.cc b/ash/capture_mode/capture_mode_demo_tools_test_api.cc
index 55aac6a..766340b 100644
--- a/ash/capture_mode/capture_mode_demo_tools_test_api.cc
+++ b/ash/capture_mode/capture_mode_demo_tools_test_api.cc
@@ -68,4 +68,12 @@
   return key_combo_view->non_modifier_view_->icon();
 }
 
+void CaptureModeDemoToolsTestApi::SetOnMouseHighlightAnimationEndedCallback(
+    base::OnceClosure callback) {
+  DCHECK(demo_tools_controller_);
+  demo_tools_controller_
+      ->on_mouse_highlight_animation_ended_callback_for_test_ =
+      std::move(callback);
+}
+
 }  // namespace ash
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_demo_tools_test_api.h b/ash/capture_mode/capture_mode_demo_tools_test_api.h
index c698894..0864bb0 100644
--- a/ash/capture_mode/capture_mode_demo_tools_test_api.h
+++ b/ash/capture_mode/capture_mode_demo_tools_test_api.h
@@ -7,6 +7,7 @@
 
 #include <vector>
 
+#include "base/functional/callback_forward.h"
 #include "base/timer/timer.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 
@@ -54,6 +55,10 @@
   // Returns the `icon_` of the non-modifier component of the key combo.
   views::ImageView* GetNonModifierKeyItemIcon();
 
+  // Sets a callback that will be triggered once the mouse highlight animation
+  // ends.
+  void SetOnMouseHighlightAnimationEndedCallback(base::OnceClosure callback);
+
  private:
   CaptureModeDemoToolsController* const demo_tools_controller_;
 };
diff --git a/ash/capture_mode/capture_mode_demo_tools_unittests.cc b/ash/capture_mode/capture_mode_demo_tools_unittests.cc
index a01de1ea..a6daac8 100644
--- a/ash/capture_mode/capture_mode_demo_tools_unittests.cc
+++ b/ash/capture_mode/capture_mode_demo_tools_unittests.cc
@@ -17,17 +17,21 @@
 #include "ash/capture_mode/capture_mode_test_util.h"
 #include "ash/capture_mode/capture_mode_types.h"
 #include "ash/capture_mode/key_combo_view.h"
+#include "ash/capture_mode/pointer_highlight_layer.h"
+#include "ash/capture_mode/video_recording_watcher.h"
 #include "ash/constants/ash_features.h"
-#include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/style/icon_button.h"
 #include "ash/test/ash_test_base.h"
+#include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
-#include "base/timer/timer.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/events/event_constants.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
+#include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/controls/button/toggle_button.h"
 #include "ui/views/controls/image_view.h"
+#include "ui/wm/core/coordinate_conversion.h"
 
 namespace ash {
 
@@ -88,6 +92,18 @@
     return recording_watcher->demo_tools_controller_for_testing();
   }
 
+  void WaitForMouseHighlightAnimationCompleted() {
+    base::RunLoop run_loop;
+    CaptureModeDemoToolsController* demo_tools_controller =
+        GetCaptureModeDemoToolsController();
+    DCHECK(demo_tools_controller);
+    CaptureModeDemoToolsTestApi capture_mode_demo_tools_test_api(
+        demo_tools_controller);
+    capture_mode_demo_tools_test_api.SetOnMouseHighlightAnimationEndedCallback(
+        run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<aura::Window> window_;
@@ -407,6 +423,16 @@
     EXPECT_TRUE(controller->is_recording_in_progress());
     return controller;
   }
+
+  gfx::Rect GetDemoToolsConfinedBoundsInScreenCoordinates() {
+    auto* recording_watcher =
+        CaptureModeController::Get()->video_recording_watcher_for_testing();
+    gfx::Rect confined_bounds_in_screen =
+        recording_watcher->GetCaptureSurfaceConfineBounds();
+    wm::ConvertRectToScreen(recording_watcher->window_being_recorded(),
+                            &confined_bounds_in_screen);
+    return confined_bounds_in_screen;
+  }
 };
 
 // Tests that the key combo viewer widget should be centered within its confined
@@ -417,17 +443,8 @@
   auto* demo_tools_controller = GetCaptureModeDemoToolsController();
   EXPECT_TRUE(demo_tools_controller);
 
-  auto* recording_watcher =
-      CaptureModeController::Get()->video_recording_watcher_for_testing();
   gfx::Rect confined_bounds_in_screen =
-      recording_watcher->GetCaptureSurfaceConfineBounds();
-
-  // Converts the bounds if it is in the window's coordinate to screen
-  // coordinate.
-  if (GetParam() == CaptureModeSource::kWindow) {
-    auto window_bounds = window()->GetBoundsInScreen();
-    confined_bounds_in_screen.Offset(window_bounds.x(), window_bounds.y());
-  }
+      GetDemoToolsConfinedBoundsInScreenCoordinates();
 
   // Verifies that the `demo_tools_widget` is positioned in the middle
   // horizontally within the confined bounds.
@@ -458,6 +475,70 @@
   EXPECT_FALSE(controller->IsActive());
 }
 
+// Tests that the mouse highlight layer will be created on mouse down and
+// will disappear after the animation.
+TEST_P(CaptureModeDemoToolsTestWithAllSources, MouseHighlightTest) {
+  ui::ScopedAnimationDurationScaleMode normal_animation(
+      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+  StartDemoToolsEnabledVideoRecordingWithParam();
+  auto* demo_tools_controller = GetCaptureModeDemoToolsController();
+  EXPECT_TRUE(demo_tools_controller);
+
+  gfx::Rect confined_bounds_in_screen =
+      GetDemoToolsConfinedBoundsInScreenCoordinates();
+  auto* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(confined_bounds_in_screen.CenterPoint());
+  event_generator->PressLeftButton();
+  event_generator->ReleaseLeftButton();
+  EXPECT_FALSE(
+      demo_tools_controller->mouse_highlight_layers_for_testing().empty());
+  EXPECT_EQ(demo_tools_controller->mouse_highlight_layers_for_testing().size(),
+            1u);
+  WaitForMouseHighlightAnimationCompleted();
+  EXPECT_TRUE(
+      demo_tools_controller->mouse_highlight_layers_for_testing().empty());
+}
+
+// Tests that multiple mouse highlight layers will be visible on consecutive
+// mouse press events when the whole duration are within the expiration of the
+// first animation expiration. It also tests that each mouse highlight layer
+// will be centered on its mouse event location.
+TEST_P(CaptureModeDemoToolsTestWithAllSources,
+       MouseHighlightShouldBeCenteredWithMouseClick) {
+  ui::ScopedAnimationDurationScaleMode normal_animation(
+      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+  StartDemoToolsEnabledVideoRecordingWithParam();
+  auto* recording_watcher =
+      CaptureModeController::Get()->video_recording_watcher_for_testing();
+  auto* window_being_recorded = recording_watcher->window_being_recorded();
+  auto* demo_tools_controller = GetCaptureModeDemoToolsController();
+  EXPECT_TRUE(demo_tools_controller);
+
+  gfx::Rect inner_rect = GetDemoToolsConfinedBoundsInScreenCoordinates();
+  inner_rect.Inset(5);
+
+  auto& layers_vector =
+      demo_tools_controller->mouse_highlight_layers_for_testing();
+  auto* event_generator = GetEventGenerator();
+
+  for (auto point : {inner_rect.CenterPoint(), inner_rect.origin(),
+                     inner_rect.bottom_right()}) {
+    event_generator->MoveMouseTo(point);
+    event_generator->PressLeftButton();
+    event_generator->ReleaseLeftButton();
+    auto* highlight_layer = layers_vector.back().get();
+    auto highlight_center_point =
+        highlight_layer->layer()->bounds().CenterPoint();
+
+    // Convert the highlight layer center pointer to screen coordinates.
+    wm::ConvertPointToScreen(window_being_recorded, &highlight_center_point);
+
+    EXPECT_EQ(highlight_center_point, point);
+  }
+
+  EXPECT_EQ(layers_vector.size(), 3u);
+}
+
 INSTANTIATE_TEST_SUITE_P(All,
                          CaptureModeDemoToolsTestWithAllSources,
                          testing::Values(CaptureModeSource::kFullscreen,
diff --git a/ash/capture_mode/capture_mode_session.cc b/ash/capture_mode/capture_mode_session.cc
index 84d98d5..ae969dc 100644
--- a/ash/capture_mode/capture_mode_session.cc
+++ b/ash/capture_mode/capture_mode_session.cc
@@ -14,7 +14,6 @@
 #include "ash/capture_mode/capture_mode_camera_preview_view.h"
 #include "ash/capture_mode/capture_mode_constants.h"
 #include "ash/capture_mode/capture_mode_controller.h"
-#include "ash/capture_mode/capture_mode_menu_group.h"
 #include "ash/capture_mode/capture_mode_session_focus_cycler.h"
 #include "ash/capture_mode/capture_mode_settings_view.h"
 #include "ash/capture_mode/capture_mode_type_view.h"
@@ -23,7 +22,6 @@
 #include "ash/capture_mode/folder_selection_dialog_controller.h"
 #include "ash/capture_mode/recording_type_menu_view.h"
 #include "ash/capture_mode/user_nudge_controller.h"
-#include "ash/constants/ash_features.h"
 #include "ash/display/mouse_cursor_event_filter.h"
 #include "ash/display/screen_orientation_controller.h"
 #include "ash/display/window_tree_host_manager.h"
@@ -31,12 +29,10 @@
 #include "ash/projector/projector_controller_impl.h"
 #include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
 #include "ash/public/cpp/shell_window_ids.h"
-#include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
-#include "ash/style/color_util.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/window_dimmer.h"
@@ -69,13 +65,11 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/transform_util.h"
-#include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/scoped_canvas.h"
 #include "ui/gfx/shadow_value.h"
 #include "ui/gfx/skia_paint_util.h"
 #include "ui/views/animation/animation_builder.h"
 #include "ui/views/background.h"
-#include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/widget/widget.h"
 #include "ui/wm/core/coordinate_conversion.h"
@@ -369,13 +363,6 @@
              widget->GetWindowBoundsInScreen());
 }
 
-// Returns the color provider for the native theme.
-ui::ColorProvider* GetColorProvider() {
-  auto* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
-  return ui::ColorProviderManager::Get().GetColorProviderFor(
-      native_theme->GetColorProviderKey(nullptr));
-}
-
 }  // namespace
 
 // -----------------------------------------------------------------------------
@@ -1786,7 +1773,8 @@
   const float dsf = canvas->UndoDeviceScaleFactor();
   region = gfx::ScaleToEnclosingRect(region, dsf);
 
-  const auto* color_provider = GetColorProvider();
+  const auto* color_provider =
+      capture_mode_util::GetColorProviderForNativeTheme();
 
   if (!adjustable_region) {
     canvas->FillRect(region, SK_ColorTRANSPARENT, SkBlendMode::kClear);
diff --git a/ash/capture_mode/capture_mode_util.cc b/ash/capture_mode/capture_mode_util.cc
index 3032702..de748cb 100644
--- a/ash/capture_mode/capture_mode_util.cc
+++ b/ash/capture_mode/capture_mode_util.cc
@@ -78,11 +78,6 @@
              ->CalculateCameraPreviewTargetVisibility();
 }
 
-// Returns the local center point of the given `layer`.
-gfx::Point GetLocalCenterPoint(ui::Layer* layer) {
-  return gfx::Rect(layer->GetTargetBounds().size()).CenterPoint();
-}
-
 void FadeInWidget(views::Widget* widget,
                   const AnimationParams& animation_params) {
   DCHECK(widget);
@@ -355,6 +350,10 @@
   return play_view;
 }
 
+gfx::Point GetLocalCenterPoint(ui::Layer* layer) {
+  return gfx::Rect(layer->GetTargetBounds().size()).CenterPoint();
+}
+
 gfx::Transform GetScaleTransformAboutCenter(ui::Layer* layer, float scale) {
   return gfx::GetScaleTransform(GetLocalCenterPoint(layer), scale);
 }
@@ -513,6 +512,12 @@
   }
 }
 
+ui::ColorProvider* GetColorProviderForNativeTheme() {
+  auto* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
+  return ui::ColorProviderManager::Get().GetColorProviderFor(
+      native_theme->GetColorProviderKey(nullptr));
+}
+
 bool IsEventTargetedOnWidget(const ui::LocatedEvent& event,
                              views::Widget* widget) {
   auto* target = static_cast<aura::Window*>(event.target());
diff --git a/ash/capture_mode/capture_mode_util.h b/ash/capture_mode/capture_mode_util.h
index 4a58176..1a5e3abe 100644
--- a/ash/capture_mode/capture_mode_util.h
+++ b/ash/capture_mode/capture_mode_util.h
@@ -26,6 +26,7 @@
 }  // namespace gfx
 
 namespace ui {
+class ColorProvider;
 class Layer;
 class LocatedEvent;
 }  // namespace ui
@@ -109,6 +110,9 @@
 // notification.
 std::unique_ptr<views::View> CreatePlayIconView();
 
+// Returns the local center point of the given `layer`.
+gfx::Point GetLocalCenterPoint(ui::Layer* layer);
+
 // Returns a transform that scales the given `layer` by the given `scale` factor
 // in both X and Y around its local center point.
 gfx::Transform GetScaleTransformAboutCenter(ui::Layer* layer, float scale);
@@ -190,6 +194,8 @@
 void MaybeUpdateCameraPrivacyIndicator(bool camera_on);
 void MaybeUpdateMicrophonePrivacyIndicator(bool mic_on);
 
+ui::ColorProvider* GetColorProviderForNativeTheme();
+
 // Returns true if the given located `event` is targeted on a window that is a
 // descendant of the given `widget`. Note that `widget` can be provided as null
 // if it no longer exists, in this case this function returns false.
diff --git a/ash/capture_mode/pointer_highlight_layer.cc b/ash/capture_mode/pointer_highlight_layer.cc
new file mode 100644
index 0000000..66673b2
--- /dev/null
+++ b/ash/capture_mode/pointer_highlight_layer.cc
@@ -0,0 +1,95 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/capture_mode/pointer_highlight_layer.h"
+
+#include "ash/capture_mode/capture_mode_util.h"
+#include "ash/style/dark_light_mode_controller_impl.h"
+#include "base/check.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
+#include "ui/color/color_provider.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_type.h"
+#include "ui/compositor/paint_recorder.h"
+#include "ui/events/event.h"
+#include "ui/gfx/geometry/dip_util.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/scoped_canvas.h"
+
+namespace ash {
+
+namespace {
+
+constexpr int kHighlightLayerRadius = 36;
+constexpr float kHighlightLayerInitialOpacity = 1.f;
+constexpr float kLightModeBorderOpacityScaleFactor = 0.8f;
+const int kHighlightStrokeWidth = 2;
+constexpr int kFillsRadius = kHighlightLayerRadius - kHighlightStrokeWidth;
+
+// Calculates the layer bounds based on the event location in the coordinates of
+// the window being recorded.
+gfx::Rect CalculateHighlightLayerBounds(
+    const gfx::PointF& event_location_in_window) {
+  return gfx::Rect(event_location_in_window.x() - kHighlightLayerRadius,
+                   event_location_in_window.y() - kHighlightLayerRadius,
+                   kHighlightLayerRadius * 2, kHighlightLayerRadius * 2);
+}
+
+// Returns the color used for the highlight layer affordance and border.
+SkColor GetColor() {
+  return capture_mode_util::GetColorProviderForNativeTheme()->GetColor(
+      cros_tokens::kCrosSysOnSurface);
+}
+
+}  // namespace
+
+PointerHighlightLayer::PointerHighlightLayer(
+    const gfx::PointF& event_location_in_window,
+    ui::Layer* parent_layer) {
+  SetLayer(std::make_unique<ui::Layer>(ui::LAYER_TEXTURED));
+  layer()->SetFillsBoundsOpaquely(false);
+  layer()->SetBounds(CalculateHighlightLayerBounds(event_location_in_window));
+  layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(kHighlightLayerRadius));
+  layer()->SetOpacity(kHighlightLayerInitialOpacity);
+  layer()->set_delegate(this);
+  layer()->SetName("PointerHighlightLayer");
+
+  DCHECK(parent_layer);
+  parent_layer->Add(layer());
+  parent_layer->StackAtTop(layer());
+}
+
+PointerHighlightLayer::~PointerHighlightLayer() = default;
+
+void PointerHighlightLayer::OnPaintLayer(const ui::PaintContext& context) {
+  ui::PaintRecorder recorder(context, layer()->size());
+  gfx::ScopedCanvas scoped_canvas(recorder.canvas());
+  const float dsf = recorder.canvas()->UndoDeviceScaleFactor();
+  const float scaled_highlight_radius = dsf * kHighlightLayerRadius;
+  const float scaled_fills_radius = dsf * kFillsRadius;
+  const gfx::PointF scaled_highlight_center = gfx::ConvertPointToPixels(
+      capture_mode_util::GetLocalCenterPoint(layer()), dsf);
+
+  cc::PaintFlags flags;
+  const SkColor color = GetColor();
+
+  // 50% opacity.
+  flags.setColor(SkColorSetA(color, 128));
+  flags.setAntiAlias(true);
+  flags.setStyle(cc::PaintFlags::kFill_Style);
+  recorder.canvas()->DrawCircle(scaled_highlight_center,
+                                scaled_highlight_radius, flags);
+
+  flags.setColor(
+      SkColorSetA(color, DarkLightModeControllerImpl::Get()->IsDarkModeEnabled()
+                             ? 255
+                             : 255 * kLightModeBorderOpacityScaleFactor));
+  flags.setStyle(cc::PaintFlags::kStroke_Style);
+  flags.setStrokeWidth(kHighlightStrokeWidth);
+  recorder.canvas()->DrawCircle(scaled_highlight_center, scaled_fills_radius,
+                                flags);
+}
+
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/capture_mode/pointer_highlight_layer.h b/ash/capture_mode/pointer_highlight_layer.h
new file mode 100644
index 0000000..082c4b4
--- /dev/null
+++ b/ash/capture_mode/pointer_highlight_layer.h
@@ -0,0 +1,43 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_CAPTURE_MODE_POINTER_HIGHLIGHT_LAYER_H_
+#define ASH_CAPTURE_MODE_POINTER_HIGHLIGHT_LAYER_H_
+
+#include "ui/aura/window.h"
+#include "ui/compositor/layer_delegate.h"
+#include "ui/compositor/layer_owner.h"
+#include "ui/compositor/paint_context.h"
+#include "ui/events/event.h"
+
+namespace gfx {
+class PointF;
+}  // namespace gfx
+
+namespace ash {
+
+// `PointerHighlightLayer` is a `LayerOwner` that owns a texture layer that is
+// added as a descendant of the window being recorded and on top of it (z-order)
+// such that it can be captured with it. This layer is used to highlight the
+// mouse or touch press events by painting a ring centered at the event
+// location. `PointerHighlightLayer` is owned by
+// `CaptureModeDemoToolsController` which will be created when animation starts
+// and destroyed when animation ends.
+class PointerHighlightLayer : public ui::LayerOwner, public ui::LayerDelegate {
+ public:
+  PointerHighlightLayer(const gfx::PointF& event_location_in_window,
+                        ui::Layer* parent_layer);
+  PointerHighlightLayer(const PointerHighlightLayer&) = delete;
+  PointerHighlightLayer& operator=(const PointerHighlightLayer&) = delete;
+  ~PointerHighlightLayer() override;
+
+  // ui::LayerDelegate:
+  void OnPaintLayer(const ui::PaintContext& context) override;
+  void OnDeviceScaleFactorChanged(float old_device_scale_factor,
+                                  float new_device_scale_factor) override {}
+};
+
+}  // namespace ash
+
+#endif  // ASH_CAPTURE_MODE_POINTER_HIGHLIGHT_LAYER_H_
\ No newline at end of file
diff --git a/ash/capture_mode/video_recording_watcher.cc b/ash/capture_mode/video_recording_watcher.cc
index 7c3633d..4833f42 100644
--- a/ash/capture_mode/video_recording_watcher.cc
+++ b/ash/capture_mode/video_recording_watcher.cc
@@ -18,7 +18,6 @@
 #include "ash/projector/projector_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/style/ash_color_id.h"
-#include "ash/style/ash_color_provider.h"
 #include "ash/wm/desks/desks_util.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
@@ -29,12 +28,12 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/aura/client/cursor_shape_client.h"
-#include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/base/cursor/cursor.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/paint_recorder.h"
 #include "ui/display/screen.h"
+#include "ui/events/types/event_type.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/geometry/dip_util.h"
 #include "ui/gfx/geometry/point.h"
@@ -527,6 +526,8 @@
 }
 
 void VideoRecordingWatcher::OnMouseEvent(ui::MouseEvent* event) {
+  const gfx::PointF location_in_window =
+      GetEventLocationInWindow(window_being_recorded_, *event);
   switch (event->type()) {
     case ui::ET_MOUSEWHEEL:
     case ui::ET_MOUSE_CAPTURE_CHANGED:
@@ -536,17 +537,18 @@
       auto* camera_preview_view = GetCameraPreviewView();
       if (camera_preview_view)
         camera_preview_view->MaybeBlurFocus(*event);
+
+      if (demo_tools_controller_)
+        demo_tools_controller_->PerformMousePressAnimation(location_in_window);
     }
       [[fallthrough]];
     case ui::ET_MOUSE_RELEASED:
       // Pressed/released events are important, so we handle them immediately.
-      UpdateCursorOverlayNow(
-          GetEventLocationInWindow(window_being_recorded_, *event));
+      UpdateCursorOverlayNow(location_in_window);
       return;
 
     default:
-      UpdateOrThrottleCursorOverlay(
-          GetEventLocationInWindow(window_being_recorded_, *event));
+      UpdateOrThrottleCursorOverlay(location_in_window);
   }
 }
 
diff --git a/ash/capture_mode/video_recording_watcher.h b/ash/capture_mode/video_recording_watcher.h
index 2160b8f..4589ab0 100644
--- a/ash/capture_mode/video_recording_watcher.h
+++ b/ash/capture_mode/video_recording_watcher.h
@@ -149,7 +149,7 @@
 
   void SendThrottledWindowSizeChangedNowForTesting();
 
-  CaptureModeDemoToolsController* demo_tools_controller_for_testing() {
+  CaptureModeDemoToolsController* demo_tools_controller_for_testing() const {
     return demo_tools_controller_.get();
   }
 
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index fbd0c70..809d9bc 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -825,9 +825,7 @@
              base::FEATURE_DISABLED_BY_DEFAULT);
 
 // If enabled, allows user to request to view PPD for a printer.
-BASE_FEATURE(kEnableViewPpd,
-             "EnableViewPpd",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kEnableViewPpd, "EnableViewPpd", base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enforces Ash extension keep-list. Only the extensions/Chrome apps in the
 // keep-list are enabled in Ash.
diff --git a/ash/events/OWNERS b/ash/events/OWNERS
index 6b572ab..385efa91 100644
--- a/ash/events/OWNERS
+++ b/ash/events/OWNERS
@@ -1,2 +1,3 @@
 per-file accessibility_event_rewriter*=file://ash/accessibility/OWNERS
 per-file select_to_speak_event_handler*=file://ash/accessibility/OWNERS
+per-file keyboard_capability*=zentaro@chromium.org
diff --git a/ash/events/keyboard_capability_unittest.cc b/ash/events/keyboard_capability_unittest.cc
new file mode 100644
index 0000000..8eced23
--- /dev/null
+++ b/ash/events/keyboard_capability_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/chromeos/events/keyboard_capability.h"
+
+#include "ash/test/ash_test_base.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+
+namespace ash {
+
+constexpr ui::KeyboardCode kSixPackKeyList[] = {
+    ui::KeyboardCode::VKEY_DELETE, ui::KeyboardCode::VKEY_HOME,
+    ui::KeyboardCode::VKEY_UP,     ui::KeyboardCode::VKEY_END,
+    ui::KeyboardCode::VKEY_NEXT,   ui::KeyboardCode::VKEY_INSERT,
+};
+
+constexpr ui::KeyboardCode kLegacyLayoutTwoTopRowKeyList[] = {
+    ui::KeyboardCode::VKEY_BROWSER_BACK,
+    ui::KeyboardCode::VKEY_BROWSER_REFRESH,
+    ui::KeyboardCode::VKEY_ZOOM,
+    ui::KeyboardCode::VKEY_MEDIA_LAUNCH_APP1,
+    ui::KeyboardCode::VKEY_BRIGHTNESS_DOWN,
+    ui::KeyboardCode::VKEY_BRIGHTNESS_UP,
+    ui::KeyboardCode::VKEY_MEDIA_PLAY_PAUSE,
+    ui::KeyboardCode::VKEY_VOLUME_MUTE,
+    ui::KeyboardCode::VKEY_VOLUME_DOWN,
+    ui::KeyboardCode::VKEY_VOLUME_UP,
+};
+
+class KeyboardCapabilityTest : public AshTestBase {
+ public:
+  KeyboardCapabilityTest() = default;
+  ~KeyboardCapabilityTest() override = default;
+
+  void SetUp() override {
+    keyboard_capability_ = std::make_unique<ui::KeyboardCapability>();
+    AshTestBase::SetUp();
+  }
+
+  void TearDown() override {
+    AshTestBase::TearDown();
+    keyboard_capability_.reset();
+  }
+
+ protected:
+  std::unique_ptr<ui::KeyboardCapability> keyboard_capability_;
+};
+
+TEST_F(KeyboardCapabilityTest, TestIsSixPackKey) {
+  for (const ui::KeyboardCode key_code : kSixPackKeyList) {
+    EXPECT_TRUE(keyboard_capability_->IsSixPackKey(key_code));
+  }
+
+  // A key not in the kSixPackKeyList is not a six pack key.
+  EXPECT_FALSE(keyboard_capability_->IsSixPackKey(ui::KeyboardCode::VKEY_A));
+}
+
+TEST_F(KeyboardCapabilityTest, TestIsTopRowKey) {
+  for (const ui::KeyboardCode key_code : kLegacyLayoutTwoTopRowKeyList) {
+    EXPECT_TRUE(keyboard_capability_->IsTopRowKey(key_code));
+  }
+
+  // A key not in the kLegacyLayoutTwoTopRowKeyList is not a top row key.
+  EXPECT_FALSE(keyboard_capability_->IsTopRowKey(ui::KeyboardCode::VKEY_A));
+}
+
+}  // namespace ash
diff --git a/ash/metrics/login_unlock_throughput_recorder.cc b/ash/metrics/login_unlock_throughput_recorder.cc
index 9615a122..9bc43c8 100644
--- a/ash/metrics/login_unlock_throughput_recorder.cc
+++ b/ash/metrics/login_unlock_throughput_recorder.cc
@@ -17,6 +17,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/trace_event/trace_event.h"
 #include "chromeos/ash/components/login/login_state/login_state.h"
 #include "chromeos/ash/components/metrics/login_event_recorder.h"
 #include "components/app_constants/constants.h"
@@ -31,6 +32,13 @@
 namespace ash {
 namespace {
 
+// Tracing ID and trace events row name.
+// This must be a constexpr.
+constexpr char kLoginThroughput[] = "LoginThroughput";
+
+// Unit tests often miss initialization and thus we use different label.
+constexpr char kLoginThroughputUnordered[] = "LoginThroughput-unordered";
+
 // A class used to wait for animations.
 class AnimationObserver : public views::BoundsAnimatorObserver {
  public:
@@ -92,12 +100,18 @@
 
   std::string suffix = GetDeviceModeSuffix();
   base::UmaHistogramPercentage(smoothness_name + suffix, smoothness);
+  ash::Shell::Get()->login_unlock_throughput_recorder()->AddLoginTimeMarker(
+      smoothness_name + suffix);
   base::UmaHistogramPercentage(jank_name + suffix, jank);
+  ash::Shell::Get()->login_unlock_throughput_recorder()->AddLoginTimeMarker(
+      jank_name + suffix);
   // TODO(crbug.com/1143898): Deprecate this metrics once the login/unlock
   // performance issue is resolved.
   base::UmaHistogramCustomTimes(duration_name + suffix,
                                 base::Milliseconds(duration_ms),
                                 base::Milliseconds(100), base::Seconds(5), 50);
+  ash::Shell::Get()->login_unlock_throughput_recorder()->AddLoginTimeMarker(
+      duration_name + suffix);
 }
 
 void ReportLogin(base::TimeTicks start,
@@ -110,6 +124,8 @@
   LoginEventRecorder::Get()->AddLoginTimeMarker("LoginAnimationEnd",
                                                 /*send_to_uma=*/false,
                                                 /*write_to_file=*/false);
+  ash::Shell::Get()->login_unlock_throughput_recorder()->AddLoginTimeMarker(
+      "LoginAnimationEnd");
   LoginEventRecorder::Get()->RunScheduledWriteLoginTimes();
   RecordMetrics(start, data, "Ash.LoginAnimation.Smoothness.",
                 "Ash.LoginAnimation.Jank.", "Ash.LoginAnimation.Duration.");
@@ -158,6 +174,9 @@
   }
 }
 
+LoginUnlockThroughputRecorder::TimeMarker::TimeMarker(const std::string& name)
+    : name_(name) {}
+
 void LoginUnlockThroughputRecorder::LoggedInStateChanged() {
   auto* login_state = LoginState::Get();
   auto logged_in_user = login_state->GetLoggedInUserType();
@@ -168,8 +187,13 @@
   if (!login_state->IsUserLoggedIn())
     return;
 
+  // The first event will name the tracing row.
+  if (login_time_markers_.empty())
+    AddLoginTimeMarker(kLoginThroughput);
+
   if (logged_in_user != LoginState::LOGGED_IN_USER_OWNER &&
       logged_in_user != LoginState::LOGGED_IN_USER_REGULAR) {
+    // Kiosk users fall here.
     return;
   }
 
@@ -177,6 +201,7 @@
   ui_recorder_.OnUserLoggedIn();
   auto* primary_root = Shell::GetPrimaryRootWindow();
   primary_user_logged_in_ = base::TimeTicks::Now();
+
   auto* rec = new ui::TotalAnimationThroughputReporter(
       primary_root->GetHost()->compositor(),
       base::BindOnce(&LoginUnlockThroughputRecorder::OnLoginAnimationFinish,
@@ -215,9 +240,12 @@
   if (windows_to_restore_.empty() && !primary_user_logged_in_.is_null()) {
     const base::TimeDelta duration_ms =
         base::TimeTicks::Now() - primary_user_logged_in_;
-    UMA_HISTOGRAM_CUSTOM_TIMES(
-        "Ash.LoginSessionRestore.AllBrowserWindowsCreated", duration_ms,
-        base::Milliseconds(1), base::Seconds(100), 100);
+    constexpr char kAshLoginSessionRestoreAllBrowserWindowsCreated[] =
+        "Ash.LoginSessionRestore.AllBrowserWindowsCreated";
+    UMA_HISTOGRAM_CUSTOM_TIMES(kAshLoginSessionRestoreAllBrowserWindowsCreated,
+                               duration_ms, base::Milliseconds(1),
+                               base::Seconds(100), 100);
+    AddLoginTimeMarker(kAshLoginSessionRestoreAllBrowserWindowsCreated);
   }
   restore_windows_not_shown_.insert(restore_window_id);
 }
@@ -234,9 +262,12 @@
       !primary_user_logged_in_.is_null()) {
     const base::TimeDelta duration_ms =
         base::TimeTicks::Now() - primary_user_logged_in_;
+    constexpr char kAshLoginSessionRestoreAllBrowserWindowsShown[] =
+        "Ash.LoginSessionRestore.AllBrowserWindowsShown";
     UMA_HISTOGRAM_CUSTOM_TIMES("Ash.LoginSessionRestore.AllBrowserWindowsShown",
                                duration_ms, base::Milliseconds(1),
                                base::Seconds(100), 100);
+    AddLoginTimeMarker(kAshLoginSessionRestoreAllBrowserWindowsShown);
   }
 
   if (!compositor)
@@ -260,9 +291,12 @@
       !primary_user_logged_in_.is_null()) {
     const base::TimeDelta duration_ms =
         base::TimeTicks::Now() - primary_user_logged_in_;
+    constexpr char kAshLoginSessionRestoreAllBrowserWindowsPresented[] =
+        "Ash.LoginSessionRestore.AllBrowserWindowsPresented";
     UMA_HISTOGRAM_CUSTOM_TIMES(
-        "Ash.LoginSessionRestore.AllBrowserWindowsPresented", duration_ms,
+        kAshLoginSessionRestoreAllBrowserWindowsPresented, duration_ms,
         base::Milliseconds(1), base::Seconds(100), 100);
+    AddLoginTimeMarker(kAshLoginSessionRestoreAllBrowserWindowsPresented);
   }
   restore_windows_presented_.insert(restore_window_id);
 }
@@ -329,9 +363,14 @@
       [](base::TimeTicks primary_user_logged_in) {
         const base::TimeDelta duration_ms =
             base::TimeTicks::Now() - primary_user_logged_in;
+        constexpr char kAshLoginSessionRestoreShelfLoginAnimationEnd[] =
+            "Ash.LoginSessionRestore.ShelfLoginAnimationEnd";
         UMA_HISTOGRAM_CUSTOM_TIMES(
-            "Ash.LoginSessionRestore.ShelfLoginAnimationEnd", duration_ms,
+            kAshLoginSessionRestoreShelfLoginAnimationEnd, duration_ms,
             base::Milliseconds(1), base::Seconds(100), 100);
+        ash::Shell::Get()
+            ->login_unlock_throughput_recorder()
+            ->AddLoginTimeMarker(kAshLoginSessionRestoreShelfLoginAnimationEnd);
       },
       primary_user_logged_in_);
 
@@ -347,10 +386,78 @@
   shelf_icons_loaded_ = true;
   const base::TimeDelta duration_ms =
       base::TimeTicks::Now() - primary_user_logged_in_;
-  UMA_HISTOGRAM_CUSTOM_TIMES("Ash.LoginSessionRestore.AllShelfIconsLoaded",
+  constexpr char kAshLoginSessionRestoreAllShelfIconsLoaded[] =
+      "Ash.LoginSessionRestore.AllShelfIconsLoaded";
+  UMA_HISTOGRAM_CUSTOM_TIMES(kAshLoginSessionRestoreAllShelfIconsLoaded,
                              duration_ms, base::Milliseconds(1),
                              base::Seconds(100), 100);
+  AddLoginTimeMarker(kAshLoginSessionRestoreAllShelfIconsLoaded);
   ScheduleWaitForShelfAnimationEnd();
 }
 
+void LoginUnlockThroughputRecorder::AddLoginTimeMarker(
+    const std::string& marker_name) {
+  // Unit tests often miss the full initialization flow so we use a
+  // different label in this case.
+  if (login_time_markers_.empty() && marker_name != kLoginThroughput) {
+    login_time_markers_.emplace_back(kLoginThroughputUnordered);
+  }
+
+  login_time_markers_.emplace_back(marker_name);
+  bool reported = false;
+
+#define REPORT_LOGIN_THROUGHPUT_EVENT(metric)                        \
+  if (marker_name == metric) {                                       \
+    TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(                \
+        "startup", metric, TRACE_ID_LOCAL(kLoginThroughput), begin); \
+    TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(                  \
+        "startup", metric, TRACE_ID_LOCAL(kLoginThroughput), end);   \
+    reported = true;                                                 \
+  }                                                                  \
+  class __STUB__
+
+  if (login_time_markers_.size() > 1) {
+    const base::TimeTicks begin =
+        login_time_markers_[login_time_markers_.size() - 2].time();
+    const base::TimeTicks end =
+        login_time_markers_[login_time_markers_.size() - 1].time();
+
+    REPORT_LOGIN_THROUGHPUT_EVENT(
+        "Ash.LoginSessionRestore.AllBrowserWindowsCreated");
+    REPORT_LOGIN_THROUGHPUT_EVENT(
+        "Ash.LoginSessionRestore.AllBrowserWindowsShown");
+    REPORT_LOGIN_THROUGHPUT_EVENT(
+        "Ash.LoginSessionRestore.AllShelfIconsLoaded");
+    REPORT_LOGIN_THROUGHPUT_EVENT(
+        "Ash.LoginSessionRestore.AllBrowserWindowsPresented");
+    REPORT_LOGIN_THROUGHPUT_EVENT(
+        "Ash.LoginSessionRestore.ShelfLoginAnimationEnd");
+    REPORT_LOGIN_THROUGHPUT_EVENT("LoginAnimationEnd");
+    REPORT_LOGIN_THROUGHPUT_EVENT(
+        "Ash.LoginAnimation.Smoothness.ClamshellMode");
+    REPORT_LOGIN_THROUGHPUT_EVENT("Ash.LoginAnimation.Smoothness.TabletMode");
+    REPORT_LOGIN_THROUGHPUT_EVENT("Ash.LoginAnimation.Jank.ClamshellMode");
+    REPORT_LOGIN_THROUGHPUT_EVENT("Ash.LoginAnimation.Jank.TabletMode");
+    REPORT_LOGIN_THROUGHPUT_EVENT("Ash.LoginAnimation.Duration.ClamshellMode");
+    REPORT_LOGIN_THROUGHPUT_EVENT("Ash.LoginAnimation.Duration.TabletMode");
+    REPORT_LOGIN_THROUGHPUT_EVENT(
+        "Ash.UnlockAnimation.Smoothness.ClamshellMode");
+    REPORT_LOGIN_THROUGHPUT_EVENT("Ash.UnlockAnimation.Smoothness.TabletMode");
+    REPORT_LOGIN_THROUGHPUT_EVENT("Ash.UnlockAnimation.Jank.ClamshellMode");
+    REPORT_LOGIN_THROUGHPUT_EVENT("Ash.UnlockAnimation.Jank.TabletMode");
+    REPORT_LOGIN_THROUGHPUT_EVENT("Ash.UnlockAnimation.Duration.ClamshellMode");
+    REPORT_LOGIN_THROUGHPUT_EVENT("Ash.UnlockAnimation.Duration.TabletMode");
+  } else {
+    // The first event will be used as a row name in the tracing UI.
+    const base::TimeTicks begin = login_time_markers_.front().time();
+    const base::TimeTicks end = begin;
+
+    REPORT_LOGIN_THROUGHPUT_EVENT(kLoginThroughput);
+  }
+#undef REPORT_LOGIN_THROUGHPUT_EVENT
+  DCHECK(reported) << "Failed to report " << marker_name
+                   << ", login_time_markers_.size()="
+                   << login_time_markers_.size();
+}
+
 }  // namespace ash
diff --git a/ash/metrics/login_unlock_throughput_recorder.h b/ash/metrics/login_unlock_throughput_recorder.h
index 7ff44f8c..7925dba 100644
--- a/ash/metrics/login_unlock_throughput_recorder.h
+++ b/ash/metrics/login_unlock_throughput_recorder.h
@@ -77,7 +77,32 @@
     return login_animation_throughput_reporter_.get();
   }
 
+  // Add a time marker for login animations events. A timeline will be sent to
+  // tracing after login is done.
+  void AddLoginTimeMarker(const std::string& marker_name);
+
  private:
+  class TimeMarker {
+   public:
+    explicit TimeMarker(const std::string& name);
+    TimeMarker(const TimeMarker& other) = default;
+    ~TimeMarker() = default;
+
+    const std::string& name() const { return name_; }
+    base::TimeTicks time() const { return time_; }
+
+    // Comparator for sorting.
+    bool operator<(const TimeMarker& other) const {
+      return time_ < other.time_;
+    }
+
+   private:
+    friend class std::vector<TimeMarker>;
+
+    const std::string name_;
+    const base::TimeTicks time_ = base::TimeTicks::Now();
+  };
+
   void OnLoginAnimationFinish(
       base::TimeTicks start,
       const cc::FrameSequenceMetrics::CustomReportData& data);
@@ -121,6 +146,8 @@
 
   base::flat_set<ShelfID> expected_shelf_icons_;
 
+  std::vector<TimeMarker> login_time_markers_;
+
   base::WeakPtrFactory<LoginUnlockThroughputRecorder> weak_ptr_factory_{this};
 };
 
diff --git a/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl.cc b/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl.cc
index ce9db90..e85887a 100644
--- a/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl.cc
+++ b/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl.cc
@@ -289,11 +289,10 @@
 void FastPairGattServiceClientImpl::GattDiscoveryCompleteForService(
     device::BluetoothAdapter* adapter,
     device::BluetoothRemoteGattService* service) {
-  gatt_service_discovery_timer_.Stop();
-
   // Verify that the discovered service and device are the ones we care about.
   if (service->GetUUID() == kFastPairBluetoothUuid &&
       service->GetDevice()->GetAddress() == device_address_) {
+    gatt_service_discovery_timer_.Stop();
     QP_LOG(INFO) << __func__
                  << ": Completed discovery for Fast Pair GATT service";
     RecordGattInitializationStep(FastPairGattConnectionSteps::kConnectionReady);
diff --git a/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl_unittest.cc b/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl_unittest.cc
index a71babf..2e258058 100644
--- a/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl_unittest.cc
+++ b/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl_unittest.cc
@@ -326,18 +326,7 @@
                             weak_ptr_factory_.GetWeakPtr()));
   }
 
-  void NotifyGattDiscoveryCompleteForService() {
-    auto gatt_service =
-        std::make_unique<testing::NiceMock<device::MockBluetoothGattService>>(
-            CreateTestBluetoothDevice(adapter_.get(),
-                                      ash::quick_pair::kFastPairBluetoothUuid)
-                .get(),
-            kTestServiceId, ash::quick_pair::kFastPairBluetoothUuid,
-            /*is_primary=*/true);
-    gatt_service_ = std::move(gatt_service);
-    ON_CALL(*(gatt_service_.get()), GetDevice)
-        .WillByDefault(
-            testing::Return(adapter_->GetDevice(kTestBleDeviceAddress)));
+  void SetGattServiceCharacteristics() {
     if (!keybased_char_error_) {
       fake_key_based_characteristic_ =
           std::make_unique<FakeBluetoothGattCharacteristic>(
@@ -410,6 +399,19 @@
 
     gatt_service_->AddMockCharacteristic(
         std::move(fake_account_key_characteristic));
+  }
+
+  void NotifyGattDiscoveryCompleteForService(const device::BluetoothUUID uuid) {
+    auto gatt_service =
+        std::make_unique<testing::NiceMock<device::MockBluetoothGattService>>(
+            CreateTestBluetoothDevice(adapter_.get(), uuid).get(),
+            kTestServiceId, uuid,
+            /*is_primary=*/true);
+    gatt_service_ = std::move(gatt_service);
+    ON_CALL(*(gatt_service_.get()), GetDevice)
+        .WillByDefault(
+            testing::Return(adapter_->GetDevice(kTestBleDeviceAddress)));
+    SetGattServiceCharacteristics();
     adapter_->NotifyGattDiscoveryCompleteForService(gatt_service_.get());
   }
 
@@ -480,7 +482,7 @@
     write_account_key_timeout_ = timeout;
   }
 
-  void FastForwardTimeByConnetingTimeout() {
+  void FastForwardTimeByConnectingTimeout() {
     task_environment_.FastForwardBy(kConnectingTestTimeout);
   }
 
@@ -580,8 +582,9 @@
   histogram_tester().ExpectTotalCount(kNotifyPasskeyCharacteristicTime, 0);
   histogram_tester().ExpectTotalCount(kFastPairGattConnectionStep, 0);
   SuccessfulGattConnectionSetUp();
-  FastForwardTimeByConnetingTimeout();
-  NotifyGattDiscoveryCompleteForService();
+  FastForwardTimeByConnectingTimeout();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_EQ(GetInitializedCallbackResult(),
             PairFailure::kGattServiceDiscoveryTimeout);
   EXPECT_FALSE(ServiceIsSet());
@@ -635,7 +638,8 @@
   histogram_tester().ExpectTotalCount(kNotifyPasskeyCharacteristicTime, 0);
   histogram_tester().ExpectTotalCount(kFastPairGattConnectionStep, 0);
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_TRUE(gatt_service_client_->IsConnected());
   histogram_tester().ExpectTotalCount(kFastPairGattConnectionStep, 4);
   histogram_tester().ExpectTotalCount(kTotalGattConnectionTime, 1);
@@ -660,7 +664,8 @@
   histogram_tester().ExpectTotalCount(kFastPairGattConnectionStep, 0);
   SetKeybasedCharacteristicError(true);
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_EQ(GetInitializedCallbackResult(),
             PairFailure::kKeyBasedPairingCharacteristicDiscovery);
   EXPECT_FALSE(ServiceIsSet());
@@ -671,7 +676,8 @@
 TEST_F(FastPairGattServiceClientTest, FailedPasskeyCharacteristics) {
   SetPasskeyCharacteristicError(true);
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_EQ(GetInitializedCallbackResult(),
             PairFailure::kPasskeyCharacteristicDiscovery);
   EXPECT_FALSE(ServiceIsSet());
@@ -683,7 +689,8 @@
   SetKeybasedCharacteristicError(false);
   SetPasskeyCharacteristicError(false);
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   WriteRequestToPasskey();
   EXPECT_EQ(GetInitializedCallbackResult(), absl::nullopt);
   EXPECT_TRUE(ServiceIsSet());
@@ -695,7 +702,8 @@
   histogram_tester().ExpectTotalCount(kNotifyPasskeyCharacteristicTime, 0);
   SuccessfulGattConnectionSetUp();
   SetPasskeyNotifySessionError(true);
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   WriteRequestToPasskey();
   EXPECT_EQ(GetWriteCallbackResult(),
             PairFailure::kPasskeyCharacteristicNotifySession);
@@ -708,7 +716,8 @@
   histogram_tester().ExpectTotalCount(kFastPairGattConnectionStep, 0);
   SuccessfulGattConnectionSetUp();
   SetKeybasedNotifySessionError(true);
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   WriteRequestToKeyBased();
   EXPECT_EQ(GetWriteCallbackResult(),
             PairFailure::kKeyBasedPairingCharacteristicNotifySession);
@@ -721,7 +730,8 @@
   histogram_tester().ExpectTotalCount(kNotifyPasskeyCharacteristicTime, 0);
   SetPasskeyNotifySessionTimeout(true);
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   WriteRequestToPasskey();
   EXPECT_EQ(GetWriteCallbackResult(),
             PairFailure::kPasskeyCharacteristicNotifySessionTimeout);
@@ -734,7 +744,8 @@
   histogram_tester().ExpectTotalCount(kFastPairGattConnectionStep, 0);
   SetKeybasedNotifySessionTimeout(true);
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   WriteRequestToKeyBased();
   EXPECT_EQ(GetWriteCallbackResult(),
             PairFailure::kKeyBasedPairingCharacteristicNotifySessionTimeout);
@@ -747,7 +758,8 @@
   histogram_tester().ExpectTotalCount(kNotifyKeyBasedCharacteristicTime, 0);
   histogram_tester().ExpectTotalCount(kFastPairGattConnectionStep, 0);
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_EQ(GetInitializedCallbackResult(), absl::nullopt);
   EXPECT_TRUE(ServiceIsSet());
   WriteRequestToKeyBased();
@@ -762,7 +774,8 @@
   histogram_tester().ExpectTotalCount(kWriteKeyBasedCharacteristicGattError, 0);
   SetKeyBasedWriteError();
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_EQ(GetInitializedCallbackResult(), absl::nullopt);
   EXPECT_TRUE(ServiceIsSet());
   WriteRequestToKeyBased();
@@ -775,7 +788,8 @@
 TEST_F(FastPairGattServiceClientTest, WriteKeyBasedRequestTimeout) {
   SetWriteRequestTimeout();
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_EQ(GetInitializedCallbackResult(), absl::nullopt);
   EXPECT_TRUE(ServiceIsSet());
   WriteRequestToKeyBased();
@@ -788,7 +802,8 @@
   histogram_tester().ExpectTotalCount(kWritePasskeyCharacteristicGattError, 0);
   histogram_tester().ExpectTotalCount(kNotifyPasskeyCharacteristicTime, 0);
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_EQ(GetInitializedCallbackResult(), absl::nullopt);
   EXPECT_TRUE(ServiceIsSet());
   WriteRequestToPasskey();
@@ -802,7 +817,8 @@
   histogram_tester().ExpectTotalCount(kWritePasskeyCharacteristicGattError, 0);
   SetPasskeyWriteError();
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_EQ(GetInitializedCallbackResult(), absl::nullopt);
   EXPECT_TRUE(ServiceIsSet());
   WriteRequestToPasskey();
@@ -816,7 +832,8 @@
   histogram_tester().ExpectTotalCount(kWritePasskeyCharacteristicGattError, 0);
   SetWritePasskeyTimeout();
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_EQ(GetInitializedCallbackResult(), absl::nullopt);
   EXPECT_TRUE(ServiceIsSet());
   WriteRequestToPasskey();
@@ -830,7 +847,8 @@
                                       0);
   histogram_tester().ExpectTotalCount(kWriteAccountKeyTimeMetric, 0);
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_EQ(GetInitializedCallbackResult(), absl::nullopt);
   EXPECT_TRUE(ServiceIsSet());
   WriteRequestToKeyBased();
@@ -849,7 +867,8 @@
   histogram_tester().ExpectTotalCount(kWriteAccountKeyTimeMetric, 0);
   SetAccountKeyCharacteristicWriteError(true);
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_EQ(GetInitializedCallbackResult(), absl::nullopt);
   EXPECT_TRUE(ServiceIsSet());
   WriteRequestToKeyBased();
@@ -868,7 +887,8 @@
   histogram_tester().ExpectTotalCount(kWriteAccountKeyTimeMetric, 0);
   SetWriteAccountKeyTimeout(true);
   SuccessfulGattConnectionSetUp();
-  NotifyGattDiscoveryCompleteForService();
+  NotifyGattDiscoveryCompleteForService(
+      ash::quick_pair::kFastPairBluetoothUuid);
   EXPECT_EQ(GetInitializedCallbackResult(), absl::nullopt);
   EXPECT_TRUE(ServiceIsSet());
   WriteRequestToKeyBased();
@@ -879,5 +899,13 @@
   histogram_tester().ExpectTotalCount(kWriteAccountKeyTimeMetric, 0);
 }
 
+TEST_F(FastPairGattServiceClientTest, TimeoutOnNonFastPairServiceDiscovery) {
+  SuccessfulGattConnectionSetUp();
+  NotifyGattDiscoveryCompleteForService(kNonFastPairUuid);
+  FastForwardTimeByConnectingTimeout();
+  EXPECT_EQ(GetInitializedCallbackResult(),
+            PairFailure::kGattServiceDiscoveryTimeout);
+}
+
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/shell.cc b/ash/shell.cc
index f6d8964b..dfdabe87 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -231,6 +231,7 @@
 #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/base/user_activity/user_activity_detector.h"
+#include "ui/chromeos/events/keyboard_capability.h"
 #include "ui/chromeos/user_activity_power_manager_notifier.h"
 #include "ui/color/color_provider_manager.h"
 #include "ui/compositor/layer.h"
@@ -985,6 +986,8 @@
 
   usb_peripheral_notification_controller_.reset();
 
+  keyboard_capability_.reset();
+
   message_center_ash_impl_.reset();
 
   // Destroys the MessageCenter singleton, so must happen late.
@@ -1043,6 +1046,9 @@
 
   message_center_ash_impl_ = std::make_unique<MessageCenterAshImpl>();
 
+  // Initialized early since it is used by some other objects.
+  keyboard_capability_ = std::make_unique<ui::KeyboardCapability>();
+
   // These controllers call Shell::Get() in their constructors, so they cannot
   // be in the member initialization list.
   touch_devices_controller_ = std::make_unique<TouchDevicesController>();
diff --git a/ash/shell.h b/ash/shell.h
index 67301eb..a5d24d7b 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -60,6 +60,7 @@
 
 namespace ui {
 class ContextFactory;
+class KeyboardCapability;
 class UserActivityDetector;
 class UserActivityPowerManagerNotifier;
 }  // namespace ui
@@ -521,6 +522,9 @@
   KeyboardBrightnessControlDelegate* keyboard_brightness_control_delegate() {
     return keyboard_brightness_control_delegate_.get();
   }
+  ui::KeyboardCapability* keyboard_capability() {
+    return keyboard_capability_.get();
+  }
   KeyboardControllerImpl* keyboard_controller() {
     return keyboard_controller_.get();
   }
@@ -1017,6 +1021,7 @@
       bluetooth_device_status_ui_handler_;
   std::unique_ptr<KeyboardControllerImpl> keyboard_controller_;
   std::unique_ptr<DisplayAlignmentController> display_alignment_controller_;
+  std::unique_ptr<ui::KeyboardCapability> keyboard_capability_;
   std::unique_ptr<DisplayColorManager> display_color_manager_;
   std::unique_ptr<DisplayErrorObserver> display_error_observer_;
   std::unique_ptr<ProjectingObserver> projecting_observer_;
diff --git a/ash/system/cast/cast_feature_pod_controller.cc b/ash/system/cast/cast_feature_pod_controller.cc
index 0f002fa..65a9d25 100644
--- a/ash/system/cast/cast_feature_pod_controller.cc
+++ b/ash/system/cast/cast_feature_pod_controller.cc
@@ -30,7 +30,7 @@
     : tray_controller_(tray_controller) {}
 
 CastFeaturePodController::~CastFeaturePodController() {
-  if (CastConfigController::Get() && button_)
+  if (CastConfigController::Get() && (button_ || tile_))
     CastConfigController::Get()->RemoveObserver(this);
 }
 
@@ -60,10 +60,11 @@
   DCHECK(features::IsQsRevampEnabled());
   auto tile = std::make_unique<FeatureTile>(base::BindRepeating(
       &CastFeaturePodController::OnIconPressed, weak_factory_.GetWeakPtr()));
+  tile_ = tile.get();
   tile->SetVectorIcon(kUnifiedMenuCastIcon);
   tile->SetLabel(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST));
-  // TODO(b/252872586): Show sublabel with cast device availability.
-  tile->SetSubLabelVisibility(false);
+  tile->SetSubLabel(
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_DEVICES_AVAILABLE));
   const std::u16string tooltip =
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_TOOLTIP);
   tile->SetTooltipText(tooltip);
@@ -82,6 +83,12 @@
     TrackVisibilityUMA();
   tile->SetVisible(visible);
 
+  // Refresh cast devices to update the "Devices available" sublabel visibility.
+  if (cast_config) {
+    cast_config->AddObserver(this);
+    cast_config->RequestDeviceRefresh();
+  }
+  UpdateSublabelVisibility();
   return tile;
 }
 
@@ -116,8 +123,10 @@
 
 void CastFeaturePodController::OnDevicesUpdated(
     const std::vector<SinkAndRoute>& devices) {
-  DCHECK(!features::IsQsRevampEnabled());
-  Update();
+  if (features::IsQsRevampEnabled())
+    UpdateSublabelVisibility();
+  else
+    Update();
 }
 
 void CastFeaturePodController::Update() {
@@ -132,4 +141,14 @@
   button_->SetVisible(visible);
 }
 
+void CastFeaturePodController::UpdateSublabelVisibility() {
+  DCHECK(features::IsQsRevampEnabled());
+  DCHECK(tile_);
+  auto* cast_config = CastConfigController::Get();
+  bool devices_available =
+      cast_config && (cast_config->HasSinksAndRoutes() ||
+                      cast_config->AccessCodeCastingEnabled());
+  tile_->SetSubLabelVisibility(devices_available);
+}
+
 }  // namespace ash
diff --git a/ash/system/cast/cast_feature_pod_controller.h b/ash/system/cast/cast_feature_pod_controller.h
index 159fc60c..5ddf2cc4 100644
--- a/ash/system/cast/cast_feature_pod_controller.h
+++ b/ash/system/cast/cast_feature_pod_controller.h
@@ -39,11 +39,17 @@
   void OnDevicesUpdated(const std::vector<SinkAndRoute>& devices) override;
 
  private:
+  // Updates feature pod button visibility. Used pre-QsRevamp.
   void Update();
 
-  // Unowned.
+  // Updates tile sublabel visibility. Used post-QsRevamp.
+  void UpdateSublabelVisibility();
+
   UnifiedSystemTrayController* const tray_controller_;
+
+  // Owned by views hierarchy.
   FeaturePodButton* button_ = nullptr;
+  FeatureTile* tile_ = nullptr;
 
   base::WeakPtrFactory<CastFeaturePodController> weak_factory_{this};
 };
diff --git a/ash/system/cast/cast_feature_pod_controller_unittest.cc b/ash/system/cast/cast_feature_pod_controller_unittest.cc
index 7e26df9..967ee67 100644
--- a/ash/system/cast/cast_feature_pod_controller_unittest.cc
+++ b/ash/system/cast/cast_feature_pod_controller_unittest.cc
@@ -28,9 +28,11 @@
   bool HasMediaRouterForPrimaryProfile() const override {
     return has_media_router_;
   }
-  bool HasSinksAndRoutes() const override { return false; }
+  bool HasSinksAndRoutes() const override { return has_sinks_and_routes_; }
   bool HasActiveRoute() const override { return false; }
-  bool AccessCodeCastingEnabled() const override { return false; }
+  bool AccessCodeCastingEnabled() const override {
+    return access_code_casting_enabled_;
+  }
   void RequestDeviceRefresh() override {}
   const std::vector<SinkAndRoute>& GetSinksAndRoutes() override {
     return sinks_and_routes_;
@@ -39,6 +41,8 @@
   void StopCasting(const std::string& route_id) override {}
 
   bool has_media_router_ = true;
+  bool has_sinks_and_routes_ = false;
+  bool access_code_casting_enabled_ = false;
   std::vector<SinkAndRoute> sinks_and_routes_;
 };
 
@@ -79,5 +83,34 @@
   EXPECT_FALSE(tile->GetVisible());
 }
 
+TEST_F(CastFeaturePodControllerTest, SubLabelVisibleWhenSinksAvailable) {
+  // When cast devices are available, the sub-label is visible.
+  cast_config_.has_sinks_and_routes_ = true;
+  std::unique_ptr<FeatureTile> tile = controller_->CreateTile();
+  EXPECT_TRUE(tile->sub_label()->GetVisible());
+  EXPECT_EQ(tile->sub_label()->GetText(), u"Devices available");
+}
+
+TEST_F(CastFeaturePodControllerTest,
+       SubLabelVisibleWhenAccessCodeCastingEnabled) {
+  // When access code casting is available, the sub-label is visible.
+  cast_config_.access_code_casting_enabled_ = true;
+  std::unique_ptr<FeatureTile> tile = controller_->CreateTile();
+  EXPECT_TRUE(tile->sub_label()->GetVisible());
+  EXPECT_EQ(tile->sub_label()->GetText(), u"Devices available");
+}
+
+TEST_F(CastFeaturePodControllerTest, SubLabelVisibleOnDevicesUpdated) {
+  // By default the sub-label is hidden.
+  std::unique_ptr<FeatureTile> tile = controller_->CreateTile();
+  EXPECT_FALSE(tile->sub_label()->GetVisible());
+
+  // If cast devices become available while the tray is open, the sub-label
+  // becomes visible.
+  cast_config_.has_sinks_and_routes_ = true;
+  controller_->OnDevicesUpdated({});
+  EXPECT_TRUE(tile->sub_label()->GetVisible());
+}
+
 }  // namespace
 }  // namespace ash
diff --git a/ash/system/unified/feature_tile.h b/ash/system/unified/feature_tile.h
index d060004..236a4a1 100644
--- a/ash/system/unified/feature_tile.h
+++ b/ash/system/unified/feature_tile.h
@@ -94,6 +94,7 @@
   // Sets the tooltip text of `drill_container_` and `drill_in_button_`.
   void SetDrillInButtonTooltipText(const std::u16string& text);
 
+  views::Label* sub_label() { return sub_label_; }
   views::LabelButton* drill_in_button() { return drill_in_button_; }
 
  private:
diff --git a/ash/system/unified/feature_tiles_container_view.cc b/ash/system/unified/feature_tiles_container_view.cc
index 2312664..e6e5e1b 100644
--- a/ash/system/unified/feature_tiles_container_view.cc
+++ b/ash/system/unified/feature_tiles_container_view.cc
@@ -12,6 +12,7 @@
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/views/layout/flex_layout.h"
 #include "ui/views/layout/flex_layout_view.h"
+#include "ui/views/view_utils.h"
 
 namespace ash {
 
@@ -46,7 +47,9 @@
  public:
   METADATA_HEADER(FeatureTileRow);
 
-  FeatureTileRow() {
+  explicit FeatureTileRow(FeatureTilesContainerView* container)
+      : container_(container) {
+    DCHECK(container_);
     SetPreferredSize(kFeatureTileRowSize);
     SetInteriorMargin(kFeatureTileRowInteriorMargin);
     SetDefault(views::kMarginsKey, kFeatureTileMargins);
@@ -56,6 +59,15 @@
   FeatureTileRow(const FeatureTileRow&) = delete;
   FeatureTileRow& operator=(const FeatureTileRow&) = delete;
   ~FeatureTileRow() override = default;
+
+  // views::View:
+  void ChildVisibilityChanged(views::View* child) override {
+    views::FlexLayoutView::ChildVisibilityChanged(child);
+    container_->RelayoutTiles();
+  }
+
+ private:
+  FeatureTilesContainerView* const container_;
 };
 
 BEGIN_METADATA(FeatureTileRow, views::FlexLayoutView)
@@ -87,22 +99,50 @@
   // A FeatureTileRow can hold a combination of primary and compact tiles
   // depending on the added tile weights.
   int row_weight = 0;
+  bool create_row = true;
   for (auto& tile : tiles) {
-    if (row_weight == 0) {
+    if (create_row) {
       // TODO(crbug/1371668): Create new page container if number of rows
       // surpasses `displayable_rows_`.
       feature_tile_rows_.push_back(
-          AddChildView(std::make_unique<FeatureTileRow>()));
+          AddChildView(std::make_unique<FeatureTileRow>(this)));
+      create_row = false;
     }
-    row_weight += GetTileWeight(tile->tile_type());
+    // Invisible tiles don't take any weight.
+    if (tile->GetVisible())
+      row_weight += GetTileWeight(tile->tile_type());
     DCHECK_LE(row_weight, kMaxRowWeight);
     feature_tile_rows_.back()->AddChildView(std::move(tile));
 
-    if (row_weight == kMaxRowWeight)
+    if (row_weight == kMaxRowWeight) {
       row_weight = 0;
+      create_row = true;
+    }
   }
 }
 
+void FeatureTilesContainerView::RelayoutTiles() {
+  // Tile visibility changing may change the number of required rows. Rebuild
+  // the rows from scratch.
+  std::vector<std::unique_ptr<FeatureTile>> tiles;
+  for (FeatureTileRow* row : feature_tile_rows_) {
+    // Copy the list of children since we will be modifying it during iteration.
+    std::vector<views::View*> children = row->children();
+    for (views::View* child : children) {
+      DCHECK(views::IsViewClass<FeatureTile>(child));
+      FeatureTile* tile = static_cast<FeatureTile*>(child);
+      // Transfer ownership of each FeatureTile to `tiles`.
+      tiles.push_back(row->RemoveChildViewT(tile));
+    }
+    // Remove this row. It will be rebuilt by AddTiles().
+    RemoveChildViewT(row);
+  }
+  feature_tile_rows_.clear();
+
+  // Rebuild the rows of tiles.
+  AddTiles(std::move(tiles));
+}
+
 void FeatureTilesContainerView::SetRowsFromHeight(int max_height) {
   int displayable_rows = CalculateRowsFromHeight(max_height);
 
diff --git a/ash/system/unified/feature_tiles_container_view.h b/ash/system/unified/feature_tiles_container_view.h
index 94b8c957..475aa7f6 100644
--- a/ash/system/unified/feature_tiles_container_view.h
+++ b/ash/system/unified/feature_tiles_container_view.h
@@ -42,6 +42,10 @@
   // TODO(b/252871301): Apply each feature tile.
   void AddTiles(std::vector<std::unique_ptr<FeatureTile>> tiles);
 
+  // Lays out the existing tiles into rows. Used when the visibility of a tile
+  // changes, which might change the number of required rows.
+  void RelayoutTiles();
+
   // Sets the number of rows of feature tiles based on the max height the
   // container can have.
   void SetRowsFromHeight(int max_height);
diff --git a/ash/system/unified/feature_tiles_container_view_unittest.cc b/ash/system/unified/feature_tiles_container_view_unittest.cc
index 89f6eda..6931b51 100644
--- a/ash/system/unified/feature_tiles_container_view_unittest.cc
+++ b/ash/system/unified/feature_tiles_container_view_unittest.cc
@@ -155,4 +155,35 @@
   EXPECT_EQ(FeatureTileRowCount(), 3);
 }
 
+TEST_F(FeatureTilesContainerViewTest, ChangeTileVisibility) {
+  // Create 3 full-size tiles. Normally they would require 2 rows.
+  auto tile_controller = std::make_unique<MockFeaturePodController>(
+      FeatureTile::TileType::kPrimary);
+  std::unique_ptr<FeatureTile> tile1 = tile_controller->CreateTile();
+  std::unique_ptr<FeatureTile> tile2 = tile_controller->CreateTile();
+  std::unique_ptr<FeatureTile> tile3 = tile_controller->CreateTile();
+
+  // Make the first tile invisible.
+  FeatureTile* tile1_ptr = tile1.get();
+  tile1_ptr->SetVisible(false);
+
+  // Add the tiles to the container.
+  std::vector<std::unique_ptr<FeatureTile>> tiles;
+  tiles.push_back(std::move(tile1));
+  tiles.push_back(std::move(tile2));
+  tiles.push_back(std::move(tile3));
+  container()->AddTiles(std::move(tiles));
+
+  // Only one row is created because the first tile is not visible.
+  EXPECT_EQ(FeatureTileRowCount(), 1);
+
+  // Making the tile visible causes a second row to be created.
+  tile1_ptr->SetVisible(true);
+  EXPECT_EQ(FeatureTileRowCount(), 2);
+
+  // Making the tile invisible causes the second row to be removed.
+  tile1_ptr->SetVisible(false);
+  EXPECT_EQ(FeatureTileRowCount(), 1);
+}
+
 }  // namespace ash
diff --git a/ash/webui/shortcut_customization_ui/backend/BUILD.gn b/ash/webui/shortcut_customization_ui/backend/BUILD.gn
index e6227c6d..0712361 100644
--- a/ash/webui/shortcut_customization_ui/backend/BUILD.gn
+++ b/ash/webui/shortcut_customization_ui/backend/BUILD.gn
@@ -19,6 +19,7 @@
     "//ash/webui/shortcut_customization_ui/mojom",
     "//components/prefs:prefs",
     "//ui/base/ime/ash",
+    "//ui/chromeos/events",
     "//ui/events/devices",
   ]
 }
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
index 63a7dcb..7868183 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.cc
@@ -27,6 +27,7 @@
 #include "mojo/public/cpp/bindings/remote_set.h"
 #include "ui/base/ime/ash/input_method_manager.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/chromeos/events/keyboard_capability.h"
 #include "ui/events/devices/device_data_manager.h"
 #include "ui/events/devices/input_device.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
@@ -35,35 +36,6 @@
 
 namespace {
 
-// A map between Top row keys to Function keys.
-// TODO(longbowei): This mapping is temporary, create a helper function in
-// `ui/chromeos/events/keyboard_layout_util.h` to handle fetching the layout
-// keys.
-constexpr auto kLayout2TopRowKeyToFKeyMap =
-    base::MakeFixedFlatMap<ui::KeyboardCode, ui::KeyboardCode>({
-        {ui::KeyboardCode::VKEY_BROWSER_BACK, ui::KeyboardCode::VKEY_F1},
-        {ui::KeyboardCode::VKEY_BROWSER_FORWARD, ui::KeyboardCode::VKEY_F2},
-        {ui::KeyboardCode::VKEY_BROWSER_REFRESH, ui::KeyboardCode::VKEY_F3},
-        {ui::KeyboardCode::VKEY_ZOOM, ui::KeyboardCode::VKEY_F4},
-        {ui::KeyboardCode::VKEY_MEDIA_LAUNCH_APP1, ui::KeyboardCode::VKEY_F5},
-        {ui::KeyboardCode::VKEY_BRIGHTNESS_DOWN, ui::KeyboardCode::VKEY_F6},
-        {ui::KeyboardCode::VKEY_BRIGHTNESS_UP, ui::KeyboardCode::VKEY_F7},
-        {ui::KeyboardCode::VKEY_VOLUME_MUTE, ui::KeyboardCode::VKEY_F8},
-        {ui::KeyboardCode::VKEY_VOLUME_DOWN, ui::KeyboardCode::VKEY_F9},
-        {ui::KeyboardCode::VKEY_VOLUME_UP, ui::KeyboardCode::VKEY_F10},
-    });
-
-// A map between six-pack keys to system keys.
-constexpr auto kSixPackKeyToSystemKeyMap =
-    base::MakeFixedFlatMap<ui::KeyboardCode, ui::KeyboardCode>({
-        {ui::KeyboardCode::VKEY_DELETE, ui::KeyboardCode::VKEY_BACK},
-        {ui::KeyboardCode::VKEY_HOME, ui::KeyboardCode::VKEY_LEFT},
-        {ui::KeyboardCode::VKEY_UP, ui::KeyboardCode::VKEY_PRIOR},
-        {ui::KeyboardCode::VKEY_END, ui::KeyboardCode::VKEY_RIGHT},
-        {ui::KeyboardCode::VKEY_NEXT, ui::KeyboardCode::VKEY_DOWN},
-        {ui::KeyboardCode::VKEY_INSERT, ui::KeyboardCode::VKEY_BACK},
-    });
-
 // This map is for KeyboardCodes that don't return a key_display from
 // `KeycodeToKeyString`. The string values here were arbitrarily chosen
 // based on the VKEY enum name.
@@ -138,30 +110,8 @@
   return pref_service->GetBoolean(prefs::kSendFunctionKeys);
 }
 
-bool IsTopRowKey(const ui::KeyboardCode& accelerator_keycode) {
-  // A set that includes all top row keys from different keyboards.
-  // TODO(longbowei): Now only include top row keys from layout2, add more top
-  // row keys from other keyboards in the future.
-  static const base::NoDestructor<base::flat_set<ui::KeyboardCode>>
-      top_row_action_keys({
-          ui::VKEY_BROWSER_BACK,
-          ui::VKEY_BROWSER_REFRESH,
-          ui::VKEY_ZOOM,
-          ui::VKEY_MEDIA_LAUNCH_APP1,
-          ui::VKEY_BRIGHTNESS_DOWN,
-          ui::VKEY_BRIGHTNESS_UP,
-          ui::VKEY_MEDIA_PLAY_PAUSE,
-          ui::VKEY_VOLUME_MUTE,
-          ui::VKEY_VOLUME_DOWN,
-          ui::VKEY_VOLUME_UP,
-      });
-  return base::Contains(*top_row_action_keys, accelerator_keycode);
-}
-
-bool IsSixPackKey(const ui::KeyboardCode& accelerator_keycode) {
-  return base::Contains(kSixPackKeyToSystemKeyMap, accelerator_keycode);
-}
-
+// TODO(zhangwenyu): Remove this and use member function in ui::accelerator
+// class.
 bool IsModifierSet(const ui::Accelerator accelerator, int modifier) {
   return accelerator.modifiers() & modifier;
 }
@@ -346,7 +296,7 @@
 
 mojom::AcceleratorInfoPtr
 AcceleratorConfigurationProvider::CreateBaseAcceleratorInfo(
-    ui::Accelerator accelerator) const {
+    const ui::Accelerator& accelerator) const {
   // TODO(longbowei): Some accelerators should not be locked when customization
   // is allowed.
   return CreateDefaultAcceleratorInfo(accelerator, /*locked=*/true,
@@ -356,30 +306,30 @@
 
 mojom::AcceleratorInfoPtr
 AcceleratorConfigurationProvider::CreateRemappedTopRowAcceleratorInfo(
-    ui::Accelerator accelerator) const {
+    const ui::Accelerator& accelerator) const {
   // Avoid remapping if [Search] is part of original accelerator.
   if (IsModifierSet(accelerator, ui::EF_COMMAND_DOWN) ||
       !TopRowKeysAreFunctionKeys() ||
-      !kLayout2TopRowKeyToFKeyMap.contains(accelerator.key_code())) {
+      !ui::kLayout2TopRowKeyToFKeyMap.contains(accelerator.key_code())) {
     // No remapping is done.
     return nullptr;
   }
   // If top row keys are function keys, top row shortcut will become
   // [Fkey] + [search] + [modifiers]
   ui::Accelerator updated_accelerator(
-      kLayout2TopRowKeyToFKeyMap.at(accelerator.key_code()),
+      ui::kLayout2TopRowKeyToFKeyMap.at(accelerator.key_code()),
       accelerator.modifiers() | ui::EF_COMMAND_DOWN, accelerator.key_state());
   return CreateBaseAcceleratorInfo(updated_accelerator);
 }
 
 mojom::AcceleratorInfoPtr
 AcceleratorConfigurationProvider::CreateRemappedSixPackAcceleratorInfo(
-    ui::Accelerator accelerator) const {
+    const ui::Accelerator& accelerator) const {
   // For all six-pack-keys, avoid remapping if [Search] is part of
   // original accelerator.
   if (IsModifierSet(accelerator, ui::EF_COMMAND_DOWN) ||
       !::features::IsImprovedKeyboardShortcutsEnabled() ||
-      !kSixPackKeyToSystemKeyMap.contains(accelerator.key_code())) {
+      !ui::kSixPackKeyToSystemKeyMap.contains(accelerator.key_code())) {
     return nullptr;
   }
   // Edge cases:
@@ -399,7 +349,7 @@
           ? accelerator.modifiers() | ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN
           : accelerator.modifiers() | ui::EF_COMMAND_DOWN;
   ui::Accelerator updated_accelerator =
-      ui::Accelerator(kSixPackKeyToSystemKeyMap.at(accelerator.key_code()),
+      ui::Accelerator(ui::kSixPackKeyToSystemKeyMap.at(accelerator.key_code()),
                       updated_modifiers, accelerator.key_state());
 
   return CreateBaseAcceleratorInfo(updated_accelerator);
@@ -407,10 +357,11 @@
 
 std::vector<mojom::AcceleratorInfoPtr>
 AcceleratorConfigurationProvider::CreateAcceleratorInfoVariants(
-    ui::Accelerator accelerator) const {
+    const ui::Accelerator& accelerator) const {
   std::vector<mojom::AcceleratorInfoPtr> alias_infos;
 
-  if (IsTopRowKey(accelerator.key_code())) {
+  if (Shell::Get()->keyboard_capability()->IsTopRowKey(
+          accelerator.key_code())) {
     // For |top_row_key|, replace the base accelerator info with top-row
     // remapped accelerator info if remapping is done. Otherwise, only show base
     // accelerator info.
@@ -421,7 +372,8 @@
     }
   }
 
-  if (IsSixPackKey(accelerator.key_code())) {
+  if (Shell::Get()->keyboard_capability()->IsSixPackKey(
+          accelerator.key_code())) {
     // For |six_pack_key|, show both the base accelerator info and the six-pack
     // remapped accelerator info if remapping is done. Otherwise, only show base
     // accelerator info.
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h
index b724c2f..6f646088 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h
@@ -97,15 +97,15 @@
 
   // Create base accelerator info using accelerator.
   mojom::AcceleratorInfoPtr CreateBaseAcceleratorInfo(
-      ui::Accelerator accelerator) const;
+      const ui::Accelerator& accelerator) const;
 
   // Create alias accelerator info for top row key if applicable.
   mojom::AcceleratorInfoPtr CreateRemappedTopRowAcceleratorInfo(
-      ui::Accelerator accelerator) const;
+      const ui::Accelerator& accelerator) const;
 
   // Create alias accelerator info for six pack key if applicable.
   mojom::AcceleratorInfoPtr CreateRemappedSixPackAcceleratorInfo(
-      ui::Accelerator accelerator) const;
+      const ui::Accelerator& accelerator) const;
 
   // Create alias accelerator infos when the accelerator contains a top row key
   // or six pack key. For |top_row_key|, replace the base accelerator with
@@ -114,7 +114,7 @@
   // vector here since it may display two accelerator infos for six pack
   // remapping case.
   std::vector<mojom::AcceleratorInfoPtr> CreateAcceleratorInfoVariants(
-      ui::Accelerator accelerator) const;
+      const ui::Accelerator& accelerator) const;
 
   std::vector<mojom::AcceleratorLayoutInfoPtr> layout_infos_;
 
diff --git a/ash/wm/float/float_controller.cc b/ash/wm/float/float_controller.cc
index 3f1a6fc98da..3a1506dd 100644
--- a/ash/wm/float/float_controller.cc
+++ b/ash/wm/float/float_controller.cc
@@ -44,6 +44,8 @@
     "Ash.Float.FloatWindowCountsPerSession";
 constexpr char kFloatWindowDurationHistogramName[] =
     "Ash.Float.FloatWindowDuration";
+constexpr char kFloatWindowMoveToAnotherDeskCountsHistogramName[] =
+    "Ash.Float.FloatWindowMoveToAnotherDeskCounts";
 
 namespace {
 
@@ -245,6 +247,9 @@
   // Record how many windows are floated per session.
   base::UmaHistogramCounts100(kFloatWindowCountsPerSessionHistogramName,
                               floated_window_counter_);
+  // Record how many windows are moved to another desk per session.
+  base::UmaHistogramCounts100(kFloatWindowMoveToAnotherDeskCountsHistogramName,
+                              floated_window_move_to_another_desk_counter_);
 }
 
 // static
@@ -491,6 +496,8 @@
   auto* original_desk_floated_window = FindFloatedWindowOfDesk(original_desk);
   if (!original_desk_floated_window)
     return;
+  // Records floated window being moved to another desk.
+  ++floated_window_move_to_another_desk_counter_;
   auto* target_desk_floated_window = FindFloatedWindowOfDesk(target_desk);
 
   // Float window might have been hidden on purpose and won't show
@@ -531,6 +538,8 @@
   DCHECK(float_info);
   DCHECK_EQ(float_info->desk(), active_desk);
   float_info->set_desk(target_desk);
+  // Records floated window being moved to another desk.
+  ++floated_window_move_to_another_desk_counter_;
   if (root != target_root) {
     // If `floated_window_` is dragged to a desk on a different display, we
     // also need to move it to the target display.
diff --git a/ash/wm/float/float_controller.h b/ash/wm/float/float_controller.h
index b8532b0f..4f43573e 100644
--- a/ash/wm/float/float_controller.h
+++ b/ash/wm/float/float_controller.h
@@ -140,6 +140,8 @@
   friend class TabletModeWindowState;
   friend class WindowFloatTest;
   FRIEND_TEST_ALL_PREFIXES(WindowFloatMetricsTest, FloatWindowCountPerSession);
+  FRIEND_TEST_ALL_PREFIXES(WindowFloatMetricsTest,
+                           FloatWindowMovedToAnotherDeskCountPerSession);
 
   // Calls `FloatImpl()` and additionally updates the magnetism if needed.
   void FloatForTablet(aura::Window* window,
@@ -179,6 +181,9 @@
   // Float window counter within a session, used for
   // `kFloatWindowCountsPerSessionHistogramName`.
   int floated_window_counter_ = 0;
+  // Counts of how many floated window are moved to another desk within a
+  // session. `kFloatWindowMoveToAnotherDeskCountsHistogramName`
+  int floated_window_move_to_another_desk_counter_ = 0;
 
   base::ScopedObservation<TabletModeController, TabletModeObserver>
       tablet_mode_observation_{this};
diff --git a/ash/wm/float/float_controller_unittest.cc b/ash/wm/float/float_controller_unittest.cc
index a2aa888..05de78c 100644
--- a/ash/wm/float/float_controller_unittest.cc
+++ b/ash/wm/float/float_controller_unittest.cc
@@ -689,6 +689,45 @@
   EXPECT_EQ(Shell::Get()->float_controller()->floated_window_counter_, 3);
 }
 
+// Tests the float window moved to another desk counts.
+TEST_F(WindowFloatMetricsTest, FloatWindowMovedToAnotherDeskCountPerSession) {
+  // Float a window, then move to another desk, tests that it counts properly.
+  std::unique_ptr<aura::Window> window_1 = CreateFloatedWindow();
+  // Create a new desk.
+  NewDesk();
+  auto* desks_controller = DesksController::Get();
+  auto* desk_2 = desks_controller->desks()[1].get();
+  EnterOverview();
+  auto* overview_session =
+      Shell::Get()->overview_controller()->overview_session();
+  // The window should exist on the grid of the first display.
+  auto* overview_item =
+      overview_session->GetOverviewItemForWindow(window_1.get());
+  auto* grid =
+      overview_session->GetGridWithRootWindow(Shell::GetPrimaryRootWindow());
+  EXPECT_EQ(1u, grid->size());
+  // Get position of `desk_2`'s desk mini view.
+  const auto* desks_bar_view = grid->desks_bar_view();
+  gfx::Point desk_2_mini_view_center =
+      desks_bar_view->mini_views()[1]->GetBoundsInScreen().CenterPoint();
+
+  // On overview, drag and drop floated `window_1` to `desk_2`.
+  DragItemToPoint(overview_item, desk_2_mini_view_center, GetEventGenerator(),
+                  /*by_touch_gestures=*/false,
+                  /*drop=*/true);
+
+  // Verify `window_1` belongs to `desk_2`.
+  auto* float_controller = Shell::Get()->float_controller();
+  ASSERT_EQ(float_controller->FindDeskOfFloatedWindow(window_1.get()), desk_2);
+  // Check total counts, it should count 1.
+  EXPECT_EQ(float_controller->floated_window_move_to_another_desk_counter_, 1);
+  // Move to `desk_2` and remove `desk_2` by combine 2 desks.
+  // Check total counts, it should count 2.
+  ActivateDesk(desk_2);
+  RemoveDesk(desk_2, DeskCloseType::kCombineDesks);
+  EXPECT_EQ(float_controller->floated_window_move_to_another_desk_counter_, 2);
+}
+
 // Tests that the float window duration histogram is properly recorded.
 TEST_F(WindowFloatMetricsTest, FloatWindowDuration) {
   constexpr char kHistogramName[] = "Ash.Float.FloatWindowDuration";
diff --git a/base/i18n/break_iterator.cc b/base/i18n/break_iterator.cc
index 16527bde..b63c253 100644
--- a/base/i18n/break_iterator.cc
+++ b/base/i18n/break_iterator.cc
@@ -32,20 +32,15 @@
 template <UBreakIteratorType break_type>
 class DefaultLocaleBreakIteratorCache {
  public:
-  DefaultLocaleBreakIteratorCache()
-      : main_status_(U_ZERO_ERROR),
-        main_(nullptr),
-        main_could_be_leased_(true) {
-    main_ = ubrk_open(break_type, nullptr, nullptr, 0, &main_status_);
+  DefaultLocaleBreakIteratorCache() {
+    main_ = UBreakIteratorPtr(
+        ubrk_open(break_type, nullptr, nullptr, 0, &main_status_));
     if (U_FAILURE(main_status_)) {
       NOTREACHED() << "ubrk_open failed for type " << break_type
                    << " with error " << main_status_;
     }
   }
-
-  virtual ~DefaultLocaleBreakIteratorCache() { ubrk_close(main_); }
-
-  UBreakIterator* Lease(UErrorCode& status) {
+  UBreakIteratorPtr Lease(UErrorCode& status) {
     if (U_FAILURE(status)) {
       return nullptr;
     }
@@ -55,16 +50,15 @@
     }
     {
       AutoLock scoped_lock(lock_);
-      if (main_could_be_leased_) {
-        // Just lease the main_ out.
-        main_could_be_leased_ = false;
-        return main_;
+      if (main_) {
+        return std::move(main_);
       }
     }
+
     // The main_ is already leased out to some other places, return a new
     // object instead.
-    UBreakIterator* result =
-        ubrk_open(break_type, nullptr, nullptr, 0, &status);
+    UBreakIteratorPtr result(
+        ubrk_open(break_type, nullptr, nullptr, 0, &status));
     if (U_FAILURE(status)) {
       NOTREACHED() << "ubrk_open failed for type " << break_type
                    << " with error " << status;
@@ -72,22 +66,16 @@
     return result;
   }
 
-  void Return(UBreakIterator* item) {
-    // If the return item is the main_, just remember we can lease it out
-    // next time.
-    if (item == main_) {
-      AutoLock scoped_lock(lock_);
-      main_could_be_leased_ = true;
-    } else {
-      // Close the item if it is not main_.
-      ubrk_close(item);
+  void Return(UBreakIteratorPtr item) {
+    AutoLock scoped_lock(lock_);
+    if (!main_) {
+      main_ = std::move(item);
     }
   }
 
  private:
-  UErrorCode main_status_;
-  raw_ptr<UBreakIterator> main_;
-  bool main_could_be_leased_ GUARDED_BY(lock_);
+  UErrorCode main_status_ = U_ZERO_ERROR;
+  UBreakIteratorPtr main_ GUARDED_BY(lock_);
   Lock lock_;
 };
 
@@ -100,34 +88,14 @@
 static LazyInstance<DefaultLocaleBreakIteratorCache<UBRK_LINE>>::Leaky
     line_break_cache = LAZY_INSTANCE_INITIALIZER;
 
-// Helper function so that 'iter` argument need not outlive the temporary
-// from which it is created.
-void CloseICUIterator(UBreakIterator* iter,
-                      BreakIterator::BreakType break_type) {
-  switch (break_type) {
-    // Free the iter if it is RULE_BASED
-    case BreakIterator::RULE_BASED:
-      ubrk_close(iter);
-      break;
-      // Otherwise, return the iter to the cache it leased from.`
-    case BreakIterator::BREAK_CHARACTER:
-      char_break_cache.Pointer()->Return(iter);
-      break;
-    case BreakIterator::BREAK_WORD:
-      word_break_cache.Pointer()->Return(iter);
-      break;
-    case BreakIterator::BREAK_SENTENCE:
-      sentence_break_cache.Pointer()->Return(iter);
-      break;
-    case BreakIterator::BREAK_LINE:
-    case BreakIterator::BREAK_NEWLINE:
-      line_break_cache.Pointer()->Return(iter);
-      break;
+}  // namespace
+
+void UBreakIteratorDeleter::operator()(UBreakIterator* ptr) {
+  if (ptr) {
+    ubrk_close(ptr);
   }
 }
 
-}  // namespace
-
 BreakIterator::BreakIterator(StringPiece16 str, BreakType break_type)
     : string_(str), break_type_(break_type) {}
 
@@ -135,9 +103,22 @@
     : string_(str), rules_(rules), break_type_(RULE_BASED) {}
 
 BreakIterator::~BreakIterator() {
-  if (iter_) {
-    CloseICUIterator(static_cast<UBreakIterator*>(iter_.ExtractAsDangling()),
-                     break_type_);
+  switch (break_type_) {
+    case RULE_BASED:
+      return;
+    case BREAK_CHARACTER:
+      char_break_cache.Pointer()->Return(std::move(iter_));
+      return;
+    case BREAK_WORD:
+      word_break_cache.Pointer()->Return(std::move(iter_));
+      return;
+    case BREAK_SENTENCE:
+      sentence_break_cache.Pointer()->Return(std::move(iter_));
+      return;
+    case BREAK_LINE:
+    case BREAK_NEWLINE:
+      line_break_cache.Pointer()->Return(std::move(iter_));
+      return;
   }
 }
 
@@ -159,9 +140,9 @@
       iter_ = line_break_cache.Pointer()->Lease(status);
       break;
     case RULE_BASED:
-      iter_ =
+      iter_ = UBreakIteratorPtr(
           ubrk_openRules(rules_.c_str(), static_cast<int32_t>(rules_.length()),
-                         nullptr, 0, &parse_error, &status);
+                         nullptr, 0, &parse_error, &status));
       if (U_FAILURE(status)) {
         NOTREACHED() << "ubrk_openRules failed to parse rule string at line "
                      << parse_error.line << ", offset " << parse_error.offset;
@@ -174,7 +155,7 @@
   }
 
   if (string_.data() != nullptr) {
-    ubrk_setText(static_cast<UBreakIterator*>(iter_), string_.data(),
+    ubrk_setText(iter_.get(), string_.data(),
                  static_cast<int32_t>(string_.size()), &status);
     if (U_FAILURE(status)) {
       return false;
@@ -182,7 +163,7 @@
   }
 
   // Move the iterator to the beginning of the string.
-  ubrk_first(static_cast<UBreakIterator*>(iter_));
+  ubrk_first(iter_.get());
   return true;
 }
 
@@ -196,7 +177,7 @@
     case BREAK_LINE:
     case BREAK_SENTENCE:
     case RULE_BASED:
-      pos = ubrk_next(static_cast<UBreakIterator*>(iter_));
+      pos = ubrk_next(iter_.get());
       if (pos == UBRK_DONE) {
         pos_ = npos;
         return false;
@@ -205,11 +186,11 @@
       return true;
     case BREAK_NEWLINE:
       do {
-        pos = ubrk_next(static_cast<UBreakIterator*>(iter_));
+        pos = ubrk_next(iter_.get());
         if (pos == UBRK_DONE)
           break;
         pos_ = static_cast<size_t>(pos);
-        status = ubrk_getRuleStatus(static_cast<UBreakIterator*>(iter_));
+        status = ubrk_getRuleStatus(iter_.get());
       } while (status >= UBRK_LINE_SOFT && status < UBRK_LINE_SOFT_LIMIT);
       if (pos == UBRK_DONE && prev_ == pos_) {
         pos_ = npos;
@@ -221,7 +202,7 @@
 
 bool BreakIterator::SetText(const char16_t* text, const size_t length) {
   UErrorCode status = U_ZERO_ERROR;
-  ubrk_setText(static_cast<UBreakIterator*>(iter_), text, length, &status);
+  ubrk_setText(iter_.get(), text, length, &status);
   pos_ = 0;  // implicit when ubrk_setText is done
   prev_ = npos;
   if (U_FAILURE(status)) {
@@ -237,7 +218,7 @@
 }
 
 BreakIterator::WordBreakStatus BreakIterator::GetWordBreakStatus() const {
-  int32_t status = ubrk_getRuleStatus(static_cast<UBreakIterator*>(iter_));
+  int32_t status = ubrk_getRuleStatus(iter_.get());
   if (break_type_ != BREAK_WORD && break_type_ != RULE_BASED)
     return IS_LINE_OR_CHAR_BREAK;
   // In ICU 60, trying to advance past the end of the text does not change
@@ -251,9 +232,8 @@
   if (break_type_ != BREAK_WORD && break_type_ != RULE_BASED)
     return false;
 
-  UBreakIterator* iter = static_cast<UBreakIterator*>(iter_);
-  UBool boundary = ubrk_isBoundary(iter, static_cast<int32_t>(position));
-  int32_t status = ubrk_getRuleStatus(iter);
+  UBool boundary = ubrk_isBoundary(iter_.get(), static_cast<int32_t>(position));
+  int32_t status = ubrk_getRuleStatus(iter_.get());
   return (!!boundary && status != UBRK_WORD_NONE);
 }
 
@@ -261,10 +241,9 @@
   if (break_type_ != BREAK_WORD && break_type_ != RULE_BASED)
     return false;
 
-  UBreakIterator* iter = static_cast<UBreakIterator*>(iter_);
-  UBool boundary = ubrk_isBoundary(iter, static_cast<int32_t>(position));
-  ubrk_next(iter);
-  int32_t next_status = ubrk_getRuleStatus(iter);
+  UBool boundary = ubrk_isBoundary(iter_.get(), static_cast<int32_t>(position));
+  ubrk_next(iter_.get());
+  int32_t next_status = ubrk_getRuleStatus(iter_.get());
   return (!!boundary && next_status != UBRK_WORD_NONE);
 }
 
@@ -272,16 +251,14 @@
   if (break_type_ != BREAK_SENTENCE && break_type_ != RULE_BASED)
     return false;
 
-  UBreakIterator* iter = static_cast<UBreakIterator*>(iter_);
-  return !!ubrk_isBoundary(iter, static_cast<int32_t>(position));
+  return !!ubrk_isBoundary(iter_.get(), static_cast<int32_t>(position));
 }
 
 bool BreakIterator::IsGraphemeBoundary(size_t position) const {
   if (break_type_ != BREAK_CHARACTER)
     return false;
 
-  UBreakIterator* iter = static_cast<UBreakIterator*>(iter_);
-  return !!ubrk_isBoundary(iter, static_cast<int32_t>(position));
+  return !!ubrk_isBoundary(iter_.get(), static_cast<int32_t>(position));
 }
 
 std::u16string BreakIterator::GetString() const {
diff --git a/base/i18n/break_iterator.h b/base/i18n/break_iterator.h
index 17d3bdb..f452c6c7 100644
--- a/base/i18n/break_iterator.h
+++ b/base/i18n/break_iterator.h
@@ -7,6 +7,7 @@
 
 #include <stddef.h>
 
+#include <memory>
 #include <string>
 
 #include "base/i18n/base_i18n_export.h"
@@ -63,9 +64,19 @@
 //     }
 //   }
 
+// ICU iterator type. It is forward declared to avoid including transitively the
+// full ICU headers toward every dependent files.
+struct UBreakIterator;
+
 namespace base {
 namespace i18n {
 
+struct UBreakIteratorDeleter {
+  void operator()(UBreakIterator*);
+};
+using UBreakIteratorPtr =
+    std::unique_ptr<UBreakIterator, UBreakIteratorDeleter>;
+
 class BASE_I18N_EXPORT BreakIterator {
  public:
   enum BreakType {
@@ -175,11 +186,7 @@
   size_t pos() const { return pos_; }
 
  private:
-  // ICU iterator, avoiding ICU ubrk.h dependence.
-  // This is actually an ICU UBreakiterator* type, which turns out to be
-  // a typedef for a void* in the ICU headers. Using void* directly prevents
-  // callers from needing access to the ICU public headers directory.
-  raw_ptr<void> iter_ = nullptr;
+  UBreakIteratorPtr iter_;
 
   // The string we're iterating over. Can be changed with SetText(...)
   StringPiece16 string_;
diff --git a/base/task/thread_pool/delayed_task_manager.cc b/base/task/thread_pool/delayed_task_manager.cc
index 4be81c1..fdc391d 100644
--- a/base/task/thread_pool/delayed_task_manager.cc
+++ b/base/task/thread_pool/delayed_task_manager.cc
@@ -211,16 +211,8 @@
 
   const DelayedTask& ripest_delayed_task = delayed_task_queue_.top();
   subtle::DelayPolicy delay_policy = ripest_delayed_task.task.delay_policy;
-
-  TimeTicks delayed_run_time = ripest_delayed_task.task.delayed_run_time;
-  if (align_wake_ups_) {
-    TimeTicks aligned_run_time =
-        ripest_delayed_task.task.earliest_delayed_run_time().SnappedToNextTick(
-            TimeTicks(), GetTaskLeewayForCurrentThread());
-    delayed_run_time = std::min(
-        aligned_run_time, ripest_delayed_task.task.latest_delayed_run_time());
-  }
-  return std::make_pair(delayed_run_time, delay_policy);
+  return std::make_pair(ripest_delayed_task.task.delayed_run_time,
+                        delay_policy);
 }
 
 void DelayedTaskManager::ScheduleProcessRipeTasksOnServiceThread() {
diff --git a/build/OWNERS.setnoparent b/build/OWNERS.setnoparent
index 9a91119..260254e 100644
--- a/build/OWNERS.setnoparent
+++ b/build/OWNERS.setnoparent
@@ -2,7 +2,7 @@
 # docs/code_reviews.md#owners-file-details for more details.
 
 # Overall project governance.
-file://ENG_REVIEW_OWNERS
+file://ATL_OWNERS
 
 # Third-party dependency review, see //docs/adding_to_third_party.md
 file://third_party/OWNERS
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 3ff3bed7..086956f 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-11.20221214.1.1
+11.20221214.2.1
diff --git a/build/fuchsia/test/flash_device.py b/build/fuchsia/test/flash_device.py
index 8e1efe29..5e0b9165 100755
--- a/build/fuchsia/test/flash_device.py
+++ b/build/fuchsia/test/flash_device.py
@@ -28,6 +28,13 @@
         Tuple of strings, containing (product, version number).
     """
 
+    # TODO(b/242191374): Remove when devices in swarming are no longer booted
+    # into zedboot.
+    if running_unattended():
+        with ScopedFfxConfig('discovery.zedboot.enabled', 'true'):
+            run_ffx_command(('target', 'reboot'), target_id=target)
+        run_ffx_command(('target', 'wait'), target)
+
     info_cmd = run_ffx_command(('target', 'show', '--json'),
                                target_id=target,
                                capture_output=True,
diff --git a/build/fuchsia/test/flash_device_unittests.py b/build/fuchsia/test/flash_device_unittests.py
index b0eaf07b..fb7e126 100755
--- a/build/fuchsia/test/flash_device_unittests.py
+++ b/build/fuchsia/test/flash_device_unittests.py
@@ -119,7 +119,10 @@
         match."""
 
         with mock.patch('os.path.exists', return_value=True), \
-                mock.patch('flash_device._add_exec_to_flash_binaries'):
+                mock.patch('flash_device._add_exec_to_flash_binaries'), \
+                mock.patch('flash_device.running_unattended',
+                           return_value=True), \
+                mock.patch('flash_device.subprocess.run'):
             self._ffx_mock.return_value.stdout = \
                 '[{"title": "Build", "child": [{"value": "wrong.version"}, ' \
                 '{"value": "wrong_product"}]}]'
@@ -127,7 +130,7 @@
                                 'check',
                                 None,
                                 should_pave=False)
-            self.assertEqual(self._ffx_mock.call_count, 3)
+            self.assertEqual(self._ffx_mock.call_count, 4)
 
     def test_update_system_info_mismatch_adds_exec_to_flash_binaries(self
                                                                      ) -> None:
diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc
index f9f43e4..05eea334 100644
--- a/cc/paint/paint_op_buffer.cc
+++ b/cc/paint/paint_op_buffer.cc
@@ -93,8 +93,7 @@
       has_draw_text_ops_(false),
       has_save_layer_ops_(false),
       has_save_layer_alpha_ops_(false),
-      has_effects_preventing_lcd_text_for_save_layer_alpha_(false),
-      are_ops_destroyed_(false) {}
+      has_effects_preventing_lcd_text_for_save_layer_alpha_(false) {}
 
 PaintOpBuffer::PaintOpBuffer(PaintOpBuffer&& other) {
   *this = std::move(other);
@@ -121,7 +120,6 @@
   has_save_layer_alpha_ops_ = other.has_save_layer_alpha_ops_;
   has_effects_preventing_lcd_text_for_save_layer_alpha_ =
       other.has_effects_preventing_lcd_text_for_save_layer_alpha_;
-  are_ops_destroyed_ = other.are_ops_destroyed_;
 
   // Make sure the other pob can destruct safely or is ready for reuse.
   other.reserved_ = 0;
@@ -130,7 +128,7 @@
 }
 
 void PaintOpBuffer::DestroyOps() {
-  if (!are_ops_destroyed_ && data_) {
+  if (data_) {
     for (size_t offset = 0; offset < used_;) {
       auto* op = reinterpret_cast<PaintOp*>(data_.get() + offset);
       offset += op->skip;
@@ -159,7 +157,6 @@
   has_save_layer_ops_ = false;
   has_save_layer_alpha_ops_ = false;
   has_effects_preventing_lcd_text_for_save_layer_alpha_ = false;
-  are_ops_destroyed_ = false;
 }
 
 void PaintOpBuffer::Playback(SkCanvas* canvas) const {
diff --git a/cc/paint/paint_op_buffer.h b/cc/paint/paint_op_buffer.h
index a4f9de177..6edc141 100644
--- a/cc/paint/paint_op_buffer.h
+++ b/cc/paint/paint_op_buffer.h
@@ -224,9 +224,6 @@
   bool has_effects_preventing_lcd_text_for_save_layer_alpha() const {
     return has_effects_preventing_lcd_text_for_save_layer_alpha_;
   }
-  bool are_ops_destroyed() const { return are_ops_destroyed_; }
-  void MarkOpsDestroyed() { are_ops_destroyed_ = true; }
-
   bool NeedsAdditionalInvalidationForLCDText(
       const PaintOpBuffer& old_buffer) const;
 
@@ -366,7 +363,6 @@
   bool has_save_layer_ops_ : 1;
   bool has_save_layer_alpha_ops_ : 1;
   bool has_effects_preventing_lcd_text_for_save_layer_alpha_ : 1;
-  bool are_ops_destroyed_ : 1;
 };
 
 }  // namespace cc
diff --git a/cc/paint/paint_op_buffer_iterator.cc b/cc/paint/paint_op_buffer_iterator.cc
index 2f0ddb865..b3e46ac2 100644
--- a/cc/paint/paint_op_buffer_iterator.cc
+++ b/cc/paint/paint_op_buffer_iterator.cc
@@ -44,9 +44,7 @@
                                : absl::variant<Iterator, OffsetIterator>(
                                      absl::in_place_type<OffsetIterator>,
                                      buffer,
-                                     offsets)) {
-  DCHECK(!buffer->are_ops_destroyed());
-}
+                                     offsets)) {}
 
 PaintOpBuffer::CompositeIterator::CompositeIterator(
     const CompositeIterator& other) = default;
@@ -58,7 +56,6 @@
     const std::vector<size_t>* offsets)
     : iter_(buffer, offsets),
       folded_draw_color_(SkColors::kTransparent, SkBlendMode::kSrcOver) {
-  DCHECK(!buffer->are_ops_destroyed());
   FindNextOp();
 }
 
diff --git a/cc/paint/paint_op_buffer_iterator.h b/cc/paint/paint_op_buffer_iterator.h
index c5c8977..c9386be 100644
--- a/cc/paint/paint_op_buffer_iterator.h
+++ b/cc/paint/paint_op_buffer_iterator.h
@@ -46,9 +46,7 @@
 
  private:
   Iterator(const PaintOpBuffer* buffer, const char* ptr, size_t op_offset)
-      : buffer_(buffer), ptr_(ptr), op_offset_(op_offset) {
-    DCHECK(!buffer->are_ops_destroyed());
-  }
+      : buffer_(buffer), ptr_(ptr), op_offset_(op_offset) {}
 
   // `buffer_` and `ptr_` are not a raw_ptr<...> for performance reasons
   // (based on analysis of sampling profiler data and tab_search:top100:2020).
@@ -65,7 +63,6 @@
   OffsetIterator(const PaintOpBuffer* buffer,
                  const std::vector<size_t>* offsets)
       : buffer_(buffer), ptr_(buffer_->data_.get()), offsets_(offsets) {
-    DCHECK(!buffer->are_ops_destroyed());
     if (!offsets || offsets->empty()) {
       *this = end();
       return;
@@ -119,9 +116,7 @@
                  const char* ptr,
                  size_t op_offset,
                  const std::vector<size_t>* offsets)
-      : buffer_(buffer), ptr_(ptr), offsets_(offsets), op_offset_(op_offset) {
-    DCHECK(!buffer->are_ops_destroyed());
-  }
+      : buffer_(buffer), ptr_(ptr), offsets_(offsets), op_offset_(op_offset) {}
 
   // `buffer_`, `ptr_`, and `offsets_` are not a raw_ptr<...> for performance
   // reasons (based on analysis of sampling profiler data and
diff --git a/cc/paint/paint_op_buffer_serializer.cc b/cc/paint/paint_op_buffer_serializer.cc
index b3e90a6..b63a7f1 100644
--- a/cc/paint/paint_op_buffer_serializer.cc
+++ b/cc/paint/paint_op_buffer_serializer.cc
@@ -74,25 +74,6 @@
   RestoreToCount(canvas.get(), saveCount, params);
 }
 
-void PaintOpBufferSerializer::SerializeAndDestroy(
-    PaintOpBuffer* buffer,
-    const std::vector<size_t>* offsets,
-    const Preamble& preamble) {
-  std::unique_ptr<SkCanvas> canvas = MakeAnalysisCanvas(options_);
-
-  // These PlaybackParams use the initial (identity) canvas matrix, as they are
-  // only used for serializing the preamble and the initial save / final restore
-  // SerializeBuffer will create its own PlaybackParams based on the
-  // post-preamble canvas.
-  PlaybackParams params = MakeParams(canvas.get());
-
-  int saveCount = canvas->getSaveCount();
-  Save(canvas.get(), params);
-  SerializePreamble(canvas.get(), preamble, params);
-  SerializeBufferAndDestroy(canvas.get(), buffer, offsets);
-  RestoreToCount(canvas.get(), saveCount, params);
-}
-
 void PaintOpBufferSerializer::Serialize(const PaintOpBuffer* buffer) {
   std::unique_ptr<SkCanvas> canvas = MakeAnalysisCanvas(options_);
   SerializeBuffer(canvas.get(), buffer, nullptr);
@@ -289,31 +270,6 @@
   }
 }
 
-void PaintOpBufferSerializer::SerializeBufferAndDestroy(
-    SkCanvas* canvas,
-    PaintOpBuffer* buffer,
-    const std::vector<size_t>* offsets) {
-  DCHECK(buffer);
-  // This updates the original_ctm to reflect the canvas transformation at
-  // start of this call to SerializeBuffer.
-  PlaybackParams params = MakeParams(canvas);
-  bool destroy_op_only = false;
-
-  for (PaintOpBuffer::PlaybackFoldingIterator iter(buffer, offsets); iter;
-       ++iter) {
-    PaintOp& op = const_cast<PaintOp&>(*iter);
-    if (!destroy_op_only) {
-      // If serialization failed, destroy PaintOps in |buffer|.
-      destroy_op_only = !WillSerializeNextOp(op, canvas, params, iter.alpha());
-    }
-    op.DestroyThis();
-  }
-
-  // Each PaintOp in |buffer| is destroyed. Update the flag |ops_destroyed| to
-  // true, so it skip calling destruction in PaintOpsBuffer::Reset().
-  const_cast<PaintOpBuffer*>(buffer)->MarkOpsDestroyed();
-}
-
 bool PaintOpBufferSerializer::SerializeOpWithFlags(
     SkCanvas* canvas,
     const PaintOpWithFlags& flags_op,
diff --git a/cc/paint/paint_op_buffer_serializer.h b/cc/paint/paint_op_buffer_serializer.h
index e81b3646..2a14de4d 100644
--- a/cc/paint/paint_op_buffer_serializer.h
+++ b/cc/paint/paint_op_buffer_serializer.h
@@ -58,11 +58,6 @@
   void Serialize(const PaintOpBuffer* buffer,
                  const std::vector<size_t>* offsets,
                  const Preamble& preamble);
-  // Sereialize the buffer as |Serialize| with a preamble. This function also
-  // destroys the PaintOps in |buffer| after serialization.
-  void SerializeAndDestroy(PaintOpBuffer* buffer,
-                           const std::vector<size_t>* offsets,
-                           const Preamble& preamble);
   // Serialize the buffer without a preamble. This function serializes the whole
   // buffer without any extra ops added.  No clearing is done.  This should
   // generally be used for internal PaintOpBuffers that want to be sent as-is.
@@ -86,9 +81,6 @@
   void SerializeBuffer(SkCanvas* canvas,
                        const PaintOpBuffer* buffer,
                        const std::vector<size_t>* offsets);
-  void SerializeBufferAndDestroy(SkCanvas* canvas,
-                                 PaintOpBuffer* buffer,
-                                 const std::vector<size_t>* offsets);
   // Returns whether searilization of |op| succeeded and we need to serialize
   // the next PaintOp in the PaintOpBuffer.
   bool WillSerializeNextOp(const PaintOp& op,
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index c78e343..ecb2594 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -2163,29 +2163,6 @@
                   PaintOpEq(RestoreOp()))));
 }
 
-TEST(PaintOpSerializationTest, DoNotPreservePaintOps) {
-  PaintOpBuffer buffer;
-  PushDrawIRectOps(&buffer);
-
-  PaintOpBufferSerializer::Preamble preamble;
-  preamble.content_size = gfx::Size(1000, 1000);
-  preamble.playback_rect = gfx::Rect(preamble.content_size);
-  preamble.full_raster_rect = preamble.playback_rect;
-  preamble.requires_clear = true;
-
-  std::unique_ptr<char, base::AlignedFreeDeleter> memory(
-      static_cast<char*>(base::AlignedAlloc(PaintOpBuffer::kInitialBufferSize,
-                                            PaintOpBuffer::kPaintOpAlign)));
-  TestOptionsProvider options_provider;
-  SimpleBufferSerializer serializer(memory.get(),
-                                    PaintOpBuffer::kInitialBufferSize,
-                                    options_provider.serialize_options());
-  serializer.SerializeAndDestroy(&buffer, nullptr, preamble);
-  ASSERT_NE(serializer.written(), 0u);
-
-  EXPECT_TRUE(buffer.are_ops_destroyed());
-}
-
 TEST(PaintOpSerializationTest, Preamble) {
   PaintOpBufferSerializer::Preamble preamble;
   preamble.content_size = gfx::Size(30, 40);
@@ -2205,7 +2182,7 @@
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
                                     options_provider.serialize_options());
-  serializer.SerializeAndDestroy(&buffer, nullptr, preamble);
+  serializer.Serialize(&buffer, nullptr, preamble);
   ASSERT_NE(serializer.written(), 0u);
 
   sk_sp<PaintOpBuffer> deserialized_buffer =
@@ -2299,7 +2276,7 @@
     // Avoid clearing.
     preamble.content_size = gfx::Size(1000, 1000);
     preamble.requires_clear = false;
-    serializer.SerializeAndDestroy(&buffer, nullptr, preamble);
+    serializer.Serialize(&buffer, nullptr, preamble);
     ASSERT_NE(serializer.written(), 0u);
 
     sk_sp<PaintOpBuffer> deserialized_buffer =
@@ -2343,7 +2320,7 @@
   SimpleBufferSerializer serializer(memory.get(),
                                     PaintOpBuffer::kInitialBufferSize,
                                     options_provider.serialize_options());
-  serializer.SerializeAndDestroy(&buffer, nullptr, preamble);
+  serializer.Serialize(&buffer, nullptr, preamble);
   ASSERT_NE(serializer.written(), 0u);
 
   sk_sp<PaintOpBuffer> deserialized_buffer =
diff --git a/chrome/VERSION b/chrome/VERSION
index 819e793..e893afa 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=110
 MINOR=0
-BUILD=5478
+BUILD=5479
 PATCH=0
diff --git a/chrome/android/expectations/lint-baseline.xml b/chrome/android/expectations/lint-baseline.xml
index 764ec4b3..9c47a92 100644
--- a/chrome/android/expectations/lint-baseline.xml
+++ b/chrome/android/expectations/lint-baseline.xml
@@ -13,28 +13,6 @@
     </issue>
 
     <issue
-        id="GestureBackNavigation"
-        message="If intercepting back events, this should be handled through the registration of callbacks on the window level; Please see https://developer.android.com/about/versions/13/features/predictive-back-gesture"
-        errorLine1="            if (keyCode == KeyEvent.KEYCODE_BACK) {"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="../../chrome/android/java/src/org/chromium/chrome/browser/findinpage/FindToolbar.java"
-            line="138"
-            column="28"/>
-    </issue>
-
-    <issue
-        id="GestureBackNavigation"
-        message="If intercepting back events, this should be handled through the registration of callbacks on the window level; Please see https://developer.android.com/about/versions/13/features/predictive-back-gesture"
-        errorLine1="        } else if (keyCode == KeyEvent.KEYCODE_BACK) {"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="../../chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java"
-            line="1143"
-            column="31"/>
-    </issue>
-
-    <issue
         id="ResourceType"
         message="Expected resource of type styleable"
         errorLine1="                R.style.SelectPopupDialog, SELECT_DIALOG_ATTRS);"
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java
index ab9880a..49802dc 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java
@@ -65,7 +65,6 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RequiresRestart;
 import org.chromium.base.test.util.Restriction;
@@ -205,6 +204,7 @@
                 TabUiTestHelper.leaveTabSwitcher(sActivityTestRule.getActivity());
             }
         }
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mSnackbarManager.dismissAllSnackbars(); });
     }
 
     private @TabListCoordinator.TabListMode int getMode() {
@@ -254,7 +254,9 @@
             for (int i = model.getCount() - urls.size(); i < model.getCount(); i++) {
                 tabs.add(model.getTabAt(i));
             }
-            filter.mergeListOfTabsToGroup(tabs.subList(1, tabs.size()), tabs.get(0), false, true);
+            // Don't notify to avoid snackbar appearing.
+            filter.mergeListOfTabsToGroup(tabs.subList(1, tabs.size()), tabs.get(0),
+                    /*isSameGroup=*/false, /*notify=*/false);
         });
     }
 
@@ -585,7 +587,6 @@
     @Test
     @MediumTest
     @Feature({"RenderTest"})
-    @DisabledTest(message = "https://crbug.com/1396326")
     @EnableFeatures({ChromeFeatureList.TAB_SELECTION_EDITOR_V2})
     public void testToolbarMenuItem_GroupActionAndUndo() throws Exception {
         prepareBlankTab(2, false);
diff --git a/chrome/android/java/res/drawable-v21/web_notification_small_icon_background.xml b/chrome/android/java/res/drawable-v21/web_notification_small_icon_background.xml
index a74dbce..f591b47 100644
--- a/chrome/android/java/res/drawable-v21/web_notification_small_icon_background.xml
+++ b/chrome/android/java/res/drawable-v21/web_notification_small_icon_background.xml
@@ -7,6 +7,5 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="oval">
-    <!-- Material Grey 500 from the non-public Color notification_icon_bg_color -->
-    <solid android:color="#ff9e9e9e"/>
+    <solid android:color="@color/baseline_neutral_500"/>
 </shape>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
index 184223a8..351e3d2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/CompositorViewHolder.java
@@ -1587,6 +1587,7 @@
     private void updateViewStateListener(ContentView newContentView) {
         if (mContentView != null) {
             mContentView.removeOnHierarchyChangeListener(this);
+            mContentView.setDeferKeepScreenOnChanges(false);
         }
         if (newContentView != null) {
             newContentView.addOnHierarchyChangeListener(this);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/findinpage/FindToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/findinpage/FindToolbar.java
index b22ef17..b963cd5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/findinpage/FindToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/findinpage/FindToolbar.java
@@ -134,6 +134,7 @@
         }
 
         @Override
+        @SuppressLint("GestureBackNavigation")
         public boolean onKey(View v, int keyCode, KeyEvent event) {
             if (keyCode == KeyEvent.KEYCODE_BACK) {
                 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 2cd0962..637c8ab4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -969,7 +969,7 @@
                     @LayoutType int layoutType, boolean showToolbar, boolean delayAnimation) {
                 if (layoutType == LayoutType.TAB_SWITCHER
                         || layoutType == LayoutType.START_SURFACE) {
-                    mLocationBarModel.setIsShowingTabSwitcher(false);
+                    mLocationBarModel.updateForNonStaticLayout(false, false);
                     mToolbar.setTabSwitcherMode(false, showToolbar, delayAnimation);
                     updateButtonStatus();
                     if (mToolbar.setForceTextureCapture(true)) {
@@ -1079,7 +1079,8 @@
                     layoutType);
         }
         if (layoutType == LayoutType.TAB_SWITCHER || layoutType == LayoutType.START_SURFACE) {
-            mLocationBarModel.setIsShowingTabSwitcher(true);
+            mLocationBarModel.updateForNonStaticLayout(
+                    layoutType == LayoutType.TAB_SWITCHER, layoutType == LayoutType.START_SURFACE);
             mToolbar.setTabSwitcherMode(true, showToolbar, false);
             updateButtonStatus();
             if (mLocationBarModel.shouldShowLocationBarInOverviewMode()) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
index 1bfb4f3a..d6004c0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/system/StatusBarColorController.java
@@ -227,6 +227,14 @@
                 };
 
                 mLayoutStateProvider.addObserver(mLayoutStateObserver);
+                // It is possible that the Start surface is showing when the LayoutStateProvider
+                // becomes available. We need to check the current active layout and update the
+                // status bar color if that happens.
+                if (mLayoutStateProvider.getActiveLayoutType() == LayoutType.START_SURFACE
+                        && !mIsInOverviewMode) {
+                    mIsInOverviewMode = true;
+                    updateStatusBarColor();
+                }
             }));
         }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
index 07bf21f..ca0d2d3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
@@ -6,7 +6,6 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.os.Build;
 import android.support.test.InstrumentationRegistry;
 
 import androidx.test.filters.LargeTest;
@@ -253,9 +252,6 @@
      */
     @Test
     @LargeTest
-    @DisableIf.Build(supported_abis_includes = "x86", message = "https://crbug.com/1062055")
-    @DisableIf.
-    Build(sdk_is_less_than = Build.VERSION_CODES.M, message = "https://crbug.com/1062055")
     public void testBackgroundedPageNotRecorded() throws Exception {
         runAndWaitForPageLoadMetricsRecorded(() -> {
             Intent intent = new Intent(Intent.ACTION_VIEW);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseEmptyListsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseEmptyListsTest.java
index f27548d..e84d283 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseEmptyListsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseEmptyListsTest.java
@@ -46,7 +46,8 @@
     @MediumTest
     @Feature({"Payments"})
     public void testResolveWithEmptyLists() throws TimeoutException {
-        mRule.addPaymentAppFactory("basic-card", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+        mRule.addPaymentAppFactory(
+                "https://example.test", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
         mRule.triggerUIAndWait("buy", mRule.getReadyForInput());
 
         Assert.assertEquals("USD $1.00", mRule.getOrderSummaryTotal());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseEmptyTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseEmptyTest.java
index e171b93..ca2f210 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseEmptyTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseEmptyTest.java
@@ -46,9 +46,9 @@
     @Feature({"Payments"})
     public void testResolveWithEmptyDictionary() throws TimeoutException {
         mRule.addPaymentAppFactory(
-                "https://bobpay.test", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+                "https://example.test", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
 
-        mRule.triggerUIAndWait("buyWithUrlMethod", mRule.getReadyToPay());
+        mRule.clickNodeAndWait("buy", mRule.getReadyToPay());
 
         Assert.assertEquals("USD $3.00", mRule.getOrderSummaryTotal());
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseRejectTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseRejectTest.java
index 9ec06060..678f709e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseRejectTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseRejectTest.java
@@ -32,7 +32,8 @@
     @MediumTest
     @Feature({"Payments"})
     public void testReject() throws TimeoutException {
-        mRule.addPaymentAppFactory("basic-card", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+        mRule.addPaymentAppFactory(
+                "https://example.test", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
         mRule.clickNodeAndWait("buy", mRule.getRendererClosedMojoConnection());
         mRule.expectResultContains(new String[] {"AbortError"});
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseSingleOptionShippingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseSingleOptionShippingTest.java
index e90df28..361a373 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseSingleOptionShippingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseSingleOptionShippingTest.java
@@ -53,7 +53,8 @@
     @DisabledTest(message = "crbug.com/1182234")
     @Feature({"Payments"})
     public void testFastApp() throws TimeoutException {
-        mRule.addPaymentAppFactory("basic-card", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+        mRule.addPaymentAppFactory(
+                "https://example.test", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
         mRule.triggerUIAndWait("buy", mRule.getReadyToPay());
         Assert.assertEquals("USD $1.00", mRule.getOrderSummaryTotal());
         Assert.assertEquals("$0.00", mRule.getShippingOptionCostSummaryOnBottomSheet());
@@ -68,8 +69,8 @@
     @DisabledTest(message = "crbug.com/1182234")
     @Feature({"Payments"})
     public void testSlowApp() throws TimeoutException {
-        mRule.addPaymentAppFactory(
-                "basic-card", AppPresence.HAVE_APPS, FactorySpeed.SLOW_FACTORY, AppSpeed.SLOW_APP);
+        mRule.addPaymentAppFactory("https://example.test", AppPresence.HAVE_APPS,
+                FactorySpeed.SLOW_FACTORY, AppSpeed.SLOW_APP);
         mRule.triggerUIAndWait("buy", mRule.getReadyToPay());
         Assert.assertEquals("USD $1.00", mRule.getOrderSummaryTotal());
         Assert.assertEquals("$0.00", mRule.getShippingOptionCostSummaryOnBottomSheet());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseSingleOptionShippingWithUpdateTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseSingleOptionShippingWithUpdateTest.java
index 4e24787..3b87cc1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseSingleOptionShippingWithUpdateTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseSingleOptionShippingWithUpdateTest.java
@@ -53,7 +53,8 @@
     @DisabledTest(message = "crbug.com/1182234")
     @Feature({"Payments"})
     public void testFastApp() throws TimeoutException {
-        mRule.addPaymentAppFactory("basic-card", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+        mRule.addPaymentAppFactory(
+                "https://example.test", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
         mRule.triggerUIAndWait("buy", mRule.getReadyToPay());
         Assert.assertEquals("USD $1.00", mRule.getOrderSummaryTotal());
         Assert.assertEquals("$0.00", mRule.getShippingOptionCostSummaryOnBottomSheet());
@@ -68,8 +69,8 @@
     @DisabledTest(message = "crbug.com/1182234")
     @Feature({"Payments"})
     public void testSlowApp() throws TimeoutException {
-        mRule.addPaymentAppFactory(
-                "basic-card", AppPresence.HAVE_APPS, FactorySpeed.SLOW_FACTORY, AppSpeed.SLOW_APP);
+        mRule.addPaymentAppFactory("https://example.test", AppPresence.HAVE_APPS,
+                FactorySpeed.SLOW_FACTORY, AppSpeed.SLOW_APP);
         mRule.triggerUIAndWait("buy", mRule.getReadyToPay());
         Assert.assertEquals("USD $1.00", mRule.getOrderSummaryTotal());
         Assert.assertEquals("$0.00", mRule.getShippingOptionCostSummaryOnBottomSheet());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseUSOnlyShippingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseUSOnlyShippingTest.java
index 0ed419ec..250eca1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseUSOnlyShippingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseUSOnlyShippingTest.java
@@ -40,7 +40,8 @@
     @DisabledTest(message = "crbug.com/1182234")
     @Feature({"Payments"})
     public void testCannotShipWithFastApp() throws TimeoutException {
-        mRule.addPaymentAppFactory("basic-card", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+        mRule.addPaymentAppFactory(
+                "https://example.test", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
         runCannotShipTest();
     }
 
@@ -49,8 +50,8 @@
     @DisabledTest(message = "crbug.com/1182234")
     @Feature({"Payments"})
     public void testCannotShipWithSlowApp() throws TimeoutException {
-        mRule.addPaymentAppFactory(
-                "basic-card", AppPresence.HAVE_APPS, FactorySpeed.SLOW_FACTORY, AppSpeed.SLOW_APP);
+        mRule.addPaymentAppFactory("https://example.test", AppPresence.HAVE_APPS,
+                FactorySpeed.SLOW_FACTORY, AppSpeed.SLOW_APP);
         runCannotShipTest();
     }
 
@@ -76,7 +77,8 @@
     @DisabledTest(message = "crbug.com/1182234")
     @Feature({"Payments"})
     public void testCanShipWithFastApp() throws TimeoutException {
-        mRule.addPaymentAppFactory("basic-card", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+        mRule.addPaymentAppFactory(
+                "https://example.test", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
         runCanShipTest();
     }
 
@@ -85,8 +87,8 @@
     @DisabledTest(message = "crbug.com/1182234")
     @Feature({"Payments"})
     public void testCanShipWithSlowApp() throws TimeoutException {
-        mRule.addPaymentAppFactory(
-                "basic-card", AppPresence.HAVE_APPS, FactorySpeed.SLOW_FACTORY, AppSpeed.SLOW_APP);
+        mRule.addPaymentAppFactory("https://example.test", AppPresence.HAVE_APPS,
+                FactorySpeed.SLOW_FACTORY, AppSpeed.SLOW_APP);
         runCanShipTest();
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
index 51eee72..2a84689 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
@@ -56,6 +56,7 @@
 import org.chromium.chrome.browser.omnibox.LocationBarCoordinator;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
+import org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper;
 import org.chromium.chrome.browser.theme.ThemeColorProvider;
 import org.chromium.chrome.browser.toolbar.ButtonDataImpl;
 import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarButtonVariant;
@@ -399,7 +400,11 @@
             cta.findViewById(org.chromium.chrome.tab_ui.R.id.tab_switcher_button).performClick();
         });
 
-        CriteriaHelper.pollUiThread(() -> mToolbar.getVisibility() != View.VISIBLE);
+        // When the Start surface refactoring is enabled, the ToolbarPhone is shown on the grid tab
+        // switcher rather than the Start surface toolbar.
+        if (!TabUiTestHelper.getIsStartSurfaceRefactorEnabledFromUIThread(cta)) {
+            CriteriaHelper.pollUiThread(() -> mToolbar.getVisibility() != View.VISIBLE);
+        }
         LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.TAB_SWITCHER);
         CriteriaHelper.pollUiThread(() -> {
             RecyclerView tabList = cta.findViewById(R.id.tab_list_view);
@@ -436,7 +441,11 @@
             Assert.assertTrue(mToolbar.getVisibility() == View.VISIBLE);
         }
 
-        CriteriaHelper.pollUiThread(() -> mToolbar.getVisibility() != View.VISIBLE);
+        // When the Start surface refactoring is enabled, the ToolbarPhone is shown on the grid tab
+        // switcher rather than the Start surface toolbar.
+        if (!TabUiTestHelper.getIsStartSurfaceRefactorEnabledFromUIThread(cta)) {
+            CriteriaHelper.pollUiThread(() -> mToolbar.getVisibility() != View.VISIBLE);
+        }
         LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.TAB_SWITCHER);
         CriteriaHelper.pollUiThread(() -> {
             RecyclerView tabList = cta.findViewById(R.id.tab_list_view);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelUnitTest.java
index 337eeff..5b17428 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelUnitTest.java
@@ -297,7 +297,26 @@
         verify(mLocationBarDataObserver, never()).onPrimaryColorChanged();
         verify(mLocationBarDataObserver, never()).onSecurityStateChanged();
 
-        regularLocationBarModel.setIsShowingTabSwitcher(true);
+        regularLocationBarModel.updateForNonStaticLayout(true, false);
+        verify(mLocationBarDataObserver).onTitleChanged();
+        verify(mLocationBarDataObserver).onUrlChanged();
+        verify(mLocationBarDataObserver).onPrimaryColorChanged();
+        verify(mLocationBarDataObserver).onSecurityStateChanged();
+    }
+
+    @Test
+    @MediumTest
+    public void testObserversNotified_setIsShowingStartSurface() {
+        LocationBarModel regularLocationBarModel =
+                new TestRegularLocationBarModel(null, mSearchEngineLogoUtils);
+        regularLocationBarModel.addObserver(mLocationBarDataObserver);
+
+        verify(mLocationBarDataObserver, never()).onTitleChanged();
+        verify(mLocationBarDataObserver, never()).onUrlChanged();
+        verify(mLocationBarDataObserver, never()).onPrimaryColorChanged();
+        verify(mLocationBarDataObserver, never()).onSecurityStateChanged();
+
+        regularLocationBarModel.updateForNonStaticLayout(false, true);
         verify(mLocationBarDataObserver).onTitleChanged();
         verify(mLocationBarDataObserver).onUrlChanged();
         verify(mLocationBarDataObserver).onPrimaryColorChanged();
@@ -429,7 +448,7 @@
                 .getUrlOfVisibleNavigationEntry(Mockito.anyLong(), Mockito.any());
         Assert.assertNotEquals(regularLocationBarModel.getCurrentGurl(), UrlConstants.ntpGurl());
 
-        regularLocationBarModel.setIsShowingTabSwitcher(true);
+        regularLocationBarModel.updateForNonStaticLayout(true, false);
         verify(mLocationBarDataObserver).onUrlChanged();
         regularLocationBarModel.setStartSurfaceState(StartSurfaceState.SHOWN_HOMEPAGE);
 
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index bcf3b7dd..5eccb867 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -8117,9 +8117,6 @@
         <message name="IDS_PASSWORD_MANAGER_UPDATE_BUTTON" desc="Label for the 'update' button in the Update Password infobar. This infobar asks if the user wishes to update the saved password for a site to a new password the user has just entered; the button applies the suggested update." formatter_data="android_java">
           Update
         </message>
-        <message name="IDS_PASSWORD_MANAGER_UPDATE_WITH_FOLLOWUP_BUTTON" desc="Label for the 'update' button in the Update Password prompt that will be followed by username confirmation dialog. This prompt asks if the user wishes to update the saved password for a site to a new password the user has just entered; the button applies the suggested update.">
-          Update...
-        </message>
         <message name="IDS_PASSWORD_MANAGER_CONTINUE_BUTTON" desc="Label for the 'continue' button in the Update Password prompt that will be followed by username confirmation dialog. This prompt asks if the user wishes to update the saved password for a site to a new password the user has just entered; the button applies the suggested update.">
           Continue
         </message>
diff --git a/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_UPDATE_WITH_FOLLOWUP_BUTTON.png.sha1 b/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_UPDATE_WITH_FOLLOWUP_BUTTON.png.sha1
deleted file mode 100644
index 16e98ecb..0000000
--- a/chrome/app/generated_resources_grd/IDS_PASSWORD_MANAGER_UPDATE_WITH_FOLLOWUP_BUTTON.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-7b2877a9058136fb0158c449dec09044a1c16896
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index a4bc515a..a22dcfc 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1630,6 +1630,50 @@
     </message>
   </if>
 
+  <!-- Preloading Page-->
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_TITLE" desc="Title for a section in Settings that controls pages preloading and informs the user about the data shared by this feature.">
+    Preload pages
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_SUMMARY" desc="Summary for a section in Settings that controls pages preloading and informs the user about the data shared by this feature.">
+    Choose whether to preload pages
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_NO_PRELOADING_TITLE" desc="Name of the no preloading option for the Preload Pages settings page. This option disables preloading pages that Chrome believes the user is likely to navigate to.">
+    No preloading
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_NO_PRELOADING_SUMMARY" desc="Short explanation of what the no preloading mode does in the Preload Pages setting.">
+    Pages load only after you open them.
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_TITLE" desc="Name of the standard preloading option for the Preload Pages settings page. This option enables preloading pages that Chrome believes the user is likely to navigate to.">
+    Standard preloading
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_SUMMARY" desc="Short explanation of what the standard preloading mode does in the Preload Pages setting.">
+    Some of the pages you visit are preloaded.
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_WHEN_ON_BULLET_ONE" desc="First bullet point in the standard preloading and when on column. Informs the user about what the standard preloading setting does.">
+    Browsing and searching is faster.
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_WHEN_ON_BULLET_TWO" desc="Second bullet point in the standard preloading and when on column. Informs the user about what the standard preloading setting does.">
+    Chrome preloads pages you're likely to visit, so that they load more quickly when you visit them.
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_TITLE" desc="Name of the extended preloading option for the Preload Pages settings page. This option enables more extensive preloading of pages that Chrome believes the user is likely to navigate to.">
+    Extended preloading
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_SUMMARY" desc="Subtitle for Preload Pages extended preloading mode. Informs the user about which preloading setting is being described on this page.">
+    More pages are preloaded. Pages may be preloaded through Google servers when requested by other sites.
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_WHEN_ON_BULLET_ONE" desc="First bullet point in the extended preloading and when on column. Informs the user about what the extended preloading setting does.">
+    Browsing and searching is faster than standard preloading.
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_WHEN_ON_BULLET_TWO" desc="Second bullet point in the extended preloading and when on column. Informs the user about what the extended preloading setting does.">
+    Chrome preloads even more pages that you're likely to visit, so that they load more quickly when you visit them.
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_THINGS_TO_CONSIDER_BULLET_TWO" desc="Second bullet point in the extended preloading and things to consider column. Informs the user about what the extended preloading setting does.">
+    When a site asks to privately preload links on their page, Chrome uses Google servers. This hides your identity from the preloaded site, but Google learns what sites get preloaded.
+  </message>
+  <message name="IDS_SETTINGS_PRELOAD_PAGES_THINGS_TO_CONSIDER_BULLET_ONE" desc="First bullet point in the things to consider column. Informs the user about what the preloading setting does.">
+    If you allow cookies, Chrome may use them when preloading.
+  </message>
+
   <!-- Privacy Page -->
   <message name="IDS_SETTINGS_PRIVACY" desc="Name of the settings page which allows users to modify privacy and security settings.">
     Privacy and security
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_SUMMARY.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_SUMMARY.png.sha1
new file mode 100644
index 0000000..f271bb3
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_SUMMARY.png.sha1
@@ -0,0 +1 @@
+b28cf99ada82ef518f153f61cc6b19521d66ac73
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_THINGS_TO_CONSIDER_BULLET_TWO.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_THINGS_TO_CONSIDER_BULLET_TWO.png.sha1
new file mode 100644
index 0000000..44d2b85
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_THINGS_TO_CONSIDER_BULLET_TWO.png.sha1
@@ -0,0 +1 @@
+4c8591114ebc638761f8655583235ae31b1bf96b
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_TITLE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_TITLE.png.sha1
new file mode 100644
index 0000000..f29b84fb
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_TITLE.png.sha1
@@ -0,0 +1 @@
+7fa0ae1eb00496e7831ae340fa203f87ac506f7c
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_WHEN_ON_BULLET_ONE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_WHEN_ON_BULLET_ONE.png.sha1
new file mode 100644
index 0000000..b4b33f7e
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_WHEN_ON_BULLET_ONE.png.sha1
@@ -0,0 +1 @@
+419305b1b0d597690105f5a7631dff67eae63204
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_WHEN_ON_BULLET_TWO.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_WHEN_ON_BULLET_TWO.png.sha1
new file mode 100644
index 0000000..85a4d08
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_EXTENDED_PRELOADING_WHEN_ON_BULLET_TWO.png.sha1
@@ -0,0 +1 @@
+69cb0327b91aaca49afdf5a227cea23289103be1
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_NO_PRELOADING_SUMMARY.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_NO_PRELOADING_SUMMARY.png.sha1
new file mode 100644
index 0000000..1b58ef6e0
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_NO_PRELOADING_SUMMARY.png.sha1
@@ -0,0 +1 @@
+6c9be8d814a22a338de2daecd6302de60ced4cbe
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_NO_PRELOADING_TITLE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_NO_PRELOADING_TITLE.png.sha1
new file mode 100644
index 0000000..a59af1b
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_NO_PRELOADING_TITLE.png.sha1
@@ -0,0 +1 @@
+5b59ee071464e9cd816a878c362993779ef14636
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_SUMMARY.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_SUMMARY.png.sha1
new file mode 100644
index 0000000..72ed35c
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_SUMMARY.png.sha1
@@ -0,0 +1 @@
+fd2d0eb86ab1e99b0d856b2c8e144594cfe1fdc5
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_TITLE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_TITLE.png.sha1
new file mode 100644
index 0000000..2b41a6b
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_TITLE.png.sha1
@@ -0,0 +1 @@
+f66d9df76e532dfb5be8f4224fb0a39ae6da621a
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_WHEN_ON_BULLET_ONE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_WHEN_ON_BULLET_ONE.png.sha1
new file mode 100644
index 0000000..7cccdab
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_WHEN_ON_BULLET_ONE.png.sha1
@@ -0,0 +1 @@
+3dbd409914242f18392fb50a5bf6f2878c34f8c9
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_WHEN_ON_BULLET_TWO.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_WHEN_ON_BULLET_TWO.png.sha1
new file mode 100644
index 0000000..3ee35cb
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_STANDARD_PRELOADING_WHEN_ON_BULLET_TWO.png.sha1
@@ -0,0 +1 @@
+3f64a15797c82e4faf2860330e3dd48f50779907
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_SUMMARY.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_SUMMARY.png.sha1
new file mode 100644
index 0000000..b9adf29
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_SUMMARY.png.sha1
@@ -0,0 +1 @@
+cde1cdd3eef3d1b501bf9c422c91272b2a552672
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_THINGS_TO_CONSIDER_BULLET_ONE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_THINGS_TO_CONSIDER_BULLET_ONE.png.sha1
new file mode 100644
index 0000000..cf4f51a
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_THINGS_TO_CONSIDER_BULLET_ONE.png.sha1
@@ -0,0 +1 @@
+415ef510ada665ad565bc40170ab8ac3763935e9
\ No newline at end of file
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_TITLE.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_TITLE.png.sha1
new file mode 100644
index 0000000..bd6f16c
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PRELOAD_PAGES_TITLE.png.sha1
@@ -0,0 +1 @@
+3132dfbab674dd41d5c96741364a8d92f629cb21
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index fe1d53a..a4b2e2e 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -7442,6 +7442,12 @@
      kOsDesktop | kOsAndroid,
      FEATURE_VALUE_TYPE(page_info::kPageInfoAboutThisSiteMoreInfo)},
 
+    {"page-info-about-this-site-non-en",
+     flag_descriptions::kPageInfoAboutThisSiteNonEnName,
+     flag_descriptions::kPageInfoAboutThisSiteNonEnDescription,
+     kOsDesktop | kOsAndroid,
+     FEATURE_VALUE_TYPE(page_info::kPageInfoAboutThisSiteNonEn)},
+
     {"page-info-about-this-page-description-placeholder",
      flag_descriptions::kPageInfoboutThisPageDescriptionPlaceholderName,
      flag_descriptions::kPageInfoboutThisPageDescriptionPlaceholderDescription,
@@ -7759,10 +7765,6 @@
      flag_descriptions::kMessagesForAndroidOfferNotificationDescription,
      kOsAndroid,
      FEATURE_VALUE_TYPE(messages::kMessagesForAndroidOfferNotification)},
-    {"messages-for-android-passwords",
-     flag_descriptions::kMessagesForAndroidPasswordsName,
-     flag_descriptions::kMessagesForAndroidPasswordsDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(messages::kMessagesForAndroidPasswords)},
     {"messages-for-android-permission-update",
      flag_descriptions::kMessagesForAndroidPermissionUpdateName,
      flag_descriptions::kMessagesForAndroidPermissionUpdateDescription,
@@ -7789,11 +7791,6 @@
      flag_descriptions::kMessagesForAndroidStackingAnimationDescription,
      kOsAndroid,
      FEATURE_VALUE_TYPE(messages::kMessagesForAndroidStackingAnimation)},
-    {"messages-for-android-update-password",
-     flag_descriptions::kMessagesForAndroidUpdatePasswordName,
-     flag_descriptions::kMessagesForAndroidUpdatePasswordDescription,
-     kOsAndroid,
-     FEATURE_VALUE_TYPE(messages::kMessagesForAndroidUpdatePassword)},
 #endif
 
 #if BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/ash/system_web_apps/system_web_app_manager.cc b/chrome/browser/ash/system_web_apps/system_web_app_manager.cc
index 89c62b2..e74a539 100644
--- a/chrome/browser/ash/system_web_apps/system_web_app_manager.cc
+++ b/chrome/browser/ash/system_web_apps/system_web_app_manager.cc
@@ -368,9 +368,19 @@
   // or `SetSystemAppsForTesting()` has been called.
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType) &&
       skip_app_installation_in_test_) {
-    install_options_list.clear();
-  }
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &SystemWebAppManager::OnAppsSynchronized,
+            weak_ptr_factory_.GetWeakPtr(), should_force_install_apps,
+            install_start_time,
+            /*install_results=*/
+            std::map<GURL,
+                     web_app::ExternallyManagedAppManager::InstallResult>(),
+            /*uninstall_results=*/std::map<GURL, bool>()));
 
+    return;
+  }
   provider_->externally_managed_app_manager().SynchronizeInstalledApps(
       std::move(install_options_list),
       web_app::ExternalInstallSource::kSystemInstalled,
diff --git a/chrome/browser/ash/web_applications/diagnostics_system_web_app_info.cc b/chrome/browser/ash/web_applications/diagnostics_system_web_app_info.cc
index 4c7296d..45d36cb 100644
--- a/chrome/browser/ash/web_applications/diagnostics_system_web_app_info.cc
+++ b/chrome/browser/ash/web_applications/diagnostics_system_web_app_info.cc
@@ -10,6 +10,7 @@
 #include "ash/webui/grit/ash_diagnostics_app_resources.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ash/web_applications/system_web_app_install_utils.h"
+#include "chrome/browser/ui/webui/ash/diagnostics_dialog.h"
 #include "chrome/browser/web_applications/user_display_mode.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
@@ -62,3 +63,16 @@
 bool DiagnosticsSystemAppDelegate::ShouldCaptureNavigations() const {
   return true;
 }
+
+Browser* DiagnosticsSystemAppDelegate::LaunchAndNavigateSystemWebApp(
+    Profile* profile,
+    web_app::WebAppProvider* provider,
+    const GURL& url,
+    const apps::AppLaunchParams& params) const {
+  // Opening Diagnostics as an SWA does not automatically close any Diagnostics
+  // Dialog instances so attempt to close it here.
+  ash::DiagnosticsDialog::MaybeCloseExistingDialog();
+
+  return SystemWebAppDelegate::LaunchAndNavigateSystemWebApp(profile, provider,
+                                                             url, params);
+}
diff --git a/chrome/browser/ash/web_applications/diagnostics_system_web_app_info.h b/chrome/browser/ash/web_applications/diagnostics_system_web_app_info.h
index a40a06c9..c642b65 100644
--- a/chrome/browser/ash/web_applications/diagnostics_system_web_app_info.h
+++ b/chrome/browser/ash/web_applications/diagnostics_system_web_app_info.h
@@ -22,6 +22,11 @@
   bool ShouldShowInLauncher() const override;
   gfx::Size GetMinimumWindowSize() const override;
   bool ShouldCaptureNavigations() const override;
+  Browser* LaunchAndNavigateSystemWebApp(
+      Profile* profile,
+      web_app::WebAppProvider* provider,
+      const GURL& url,
+      const apps::AppLaunchParams& params) const override;
 };
 
 // Returns a WebAppInstallInfo used to install the app.
diff --git a/chrome/browser/browsing_data/browsing_data_model_browsertest.cc b/chrome/browser/browsing_data/browsing_data_model_browsertest.cc
index 14b3d0f..a7a37bf2 100644
--- a/chrome/browser/browsing_data/browsing_data_model_browsertest.cc
+++ b/chrome/browser/browsing_data/browsing_data_model_browsertest.cc
@@ -335,3 +335,37 @@
   browsing_data_model = BuildBrowsingDataModel();
   ValidateBrowsingDataEntries(browsing_data_model.get(), {});
 }
+
+IN_PROC_BROWSER_TEST_F(BrowsingDataModelBrowserTest,
+                       InterestGroupsAccessReportedCorrectly) {
+  // Navigate to test page.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url()));
+  auto* content_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
+          web_contents()->GetPrimaryMainFrame());
+
+  // Validate that the allowed browsing data model is empty.
+  auto* allowed_browsing_data_model =
+      content_settings->allowed_browsing_data_model();
+  ValidateBrowsingDataEntries(allowed_browsing_data_model, {});
+
+  // Join an interest group.
+  JoinInterestGroup(web_contents(), https_test_server());
+  while (std::distance(allowed_browsing_data_model->begin(),
+                       allowed_browsing_data_model->end()) != 1) {
+    base::RunLoop run_loop;
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+        FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+    run_loop.Run();
+  }
+
+  // Validate that an interest group is reported to the browsing data model.
+  url::Origin testOrigin = https_test_server()->GetOrigin(kTestHost);
+  content::InterestGroupManager::InterestGroupDataKey data_key{testOrigin,
+                                                               testOrigin};
+  ValidateBrowsingDataEntries(allowed_browsing_data_model,
+                              {{kTestHost,
+                                data_key,
+                                {BrowsingDataModel::StorageType::kInterestGroup,
+                                 /*storage_size=*/0, /*cookie_count=*/0}}});
+}
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 6accc1b..03210d3 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3252,6 +3252,11 @@
         render_frame_host, api_origin, !allowed);
   }
 
+  content_settings::PageSpecificContentSettings::BrowsingDataAccessed(
+      render_frame_host,
+      content::InterestGroupManager::InterestGroupDataKey{api_origin,
+                                                          top_frame_origin},
+      BrowsingDataModel::StorageType::kInterestGroup, !allowed);
   return allowed;
 }
 
@@ -5124,15 +5129,15 @@
   auto* pref_service = profile->GetPrefs();
   DCHECK(pref_service);
 
-  DictionaryPrefUpdate pref_update(
+  ScopedDictPrefUpdate pref_update(
       pref_service, prefs::kDevToolsBackgroundServicesExpirationDict);
-  base::Value* exp_dict = pref_update.Get();
+  base::Value::Dict& exp_dict = pref_update.Get();
 
   // Convert |expiration_time| to minutes since that is the most granular
   // option that returns an int. base::Value does not accept int64.
   int expiration_time_minutes =
       expiration_time.ToDeltaSinceWindowsEpoch().InMinutes();
-  exp_dict->SetIntKey(base::NumberToString(service), expiration_time_minutes);
+  exp_dict.Set(base::NumberToString(service), expiration_time_minutes);
 }
 
 base::flat_map<int, base::Time>
diff --git a/chrome/browser/chrome_for_testing/BUILD.gn b/chrome/browser/chrome_for_testing/BUILD.gn
index 4a16523..d5bb3d7 100644
--- a/chrome/browser/chrome_for_testing/BUILD.gn
+++ b/chrome/browser/chrome_for_testing/BUILD.gn
@@ -9,8 +9,16 @@
   header = "buildflags.h"
 
   if (is_chrome_for_testing_branded) {
+    # TODO(crbug.com/1336611): To be renamed to CHROME_FOR_TESTING.
     flags = [ "GOOGLE_CHROME_FOR_TESTING_BRANDING=1" ]
   } else {
     flags = [ "GOOGLE_CHROME_FOR_TESTING_BRANDING=0" ]
   }
+
+  if (use_internal_chrome_for_testing_icons) {
+    # TODO(crbug.com/1336611): To be renamed to GOOGLE_CHROME_FOR_TESTING_BRANDING.
+    flags += [ "USE_INTERNAL_CHROME_FOR_TESTING_ICONS=1" ]
+  } else {
+    flags += [ "USE_INTERNAL_CHROME_FOR_TESTING_ICONS=0" ]
+  }
 }
diff --git a/chrome/browser/dips/dips_service_factory.cc b/chrome/browser/dips/dips_service_factory.cc
index c9cd92e..7b1fd07 100644
--- a/chrome/browser/dips/dips_service_factory.cc
+++ b/chrome/browser/dips/dips_service_factory.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/dips/dips_service_factory.h"
 
 #include "base/memory/singleton.h"
+#include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/dips/dips_service.h"
 
 // static
@@ -21,7 +22,9 @@
 DIPSServiceFactory::DIPSServiceFactory()
     : ProfileKeyedServiceFactory(
           "DIPSService",
-          ProfileSelections::BuildForRegularAndIncognito()) {}
+          ProfileSelections::BuildForRegularAndIncognito()) {
+  DependsOn(CookieSettingsFactory::GetInstance());
+}
 
 DIPSServiceFactory::~DIPSServiceFactory() = default;
 
diff --git a/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win.cc b/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win.cc
index 1d093c34..f6085f4 100644
--- a/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win.cc
+++ b/chrome/browser/enterprise/platform_auth/cloud_ap_provider_win.cc
@@ -20,6 +20,7 @@
 #include "base/callback.h"
 #include "base/callback_list.h"
 #include "base/check.h"
+#include "base/feature_list.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
@@ -40,6 +41,7 @@
 #include "base/win/post_async_results.h"
 #include "base/win/scoped_hstring.h"
 #include "chrome/browser/enterprise/platform_auth/cloud_ap_utils_win.h"
+#include "chrome/browser/enterprise/platform_auth/platform_auth_features.h"
 #include "net/cookies/cookie_util.h"
 #include "net/http/http_request_headers.h"
 #include "url/gurl.h"
@@ -200,14 +202,31 @@
                                      &cookie_info_count, &cookie_info);
     if (SUCCEEDED(hresult)) {
       DCHECK(!cookie_info_count || cookie_info);
-      // Add the new auth cookies to the existing set of cookies.
-      // TODO(crbug.com/1246839): Check if cookies whose name begins with
-      // `x-ms-` should be treated as headers.
       net::cookie_util::ParsedRequestCookies parsed_cookies;
-      for (DWORD i = 0; i < cookie_info_count; ++i) {
-        const ProofOfPossessionCookieInfo& cookie = cookie_info[i];
-        parsed_cookies.emplace_back(base::WideToASCII(cookie.name),
-                                    base::WideToASCII(cookie.data));
+      if (base::FeatureList::IsEnabled(
+              enterprise_auth::kCloudApAuthAttachAsHeader)) {
+        // If the auth cookie name begins with 'x-ms-', attach the cookie as a
+        // new header. Otherwise, append it to the existing list of cookies.
+        static constexpr base::StringPiece kHeaderPrefix("x-ms-");
+        for (DWORD i = 0; i < cookie_info_count; ++i) {
+          const ProofOfPossessionCookieInfo& cookie = cookie_info[i];
+          auto ascii_name = base::WideToASCII(cookie.name);
+          if (base::StartsWith(ascii_name, kHeaderPrefix,
+                               base::CompareCase::INSENSITIVE_ASCII)) {
+            auth_headers.SetHeader(std::move(ascii_name),
+                                   base::WideToASCII(cookie.data));
+          } else {
+            parsed_cookies.emplace_back(std::move(ascii_name),
+                                        base::WideToASCII(cookie.data));
+          }
+        }
+      } else {
+        // Append all auth cookies to the existing set of cookies.
+        for (DWORD i = 0; i < cookie_info_count; ++i) {
+          const ProofOfPossessionCookieInfo& cookie = cookie_info[i];
+          parsed_cookies.emplace_back(base::WideToASCII(cookie.name),
+                                      base::WideToASCII(cookie.data));
+        }
       }
       if (parsed_cookies.size() > 0) {
         auth_headers.SetHeader(
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_features.cc b/chrome/browser/enterprise/platform_auth/platform_auth_features.cc
index d5446af2..2cb141c 100644
--- a/chrome/browser/enterprise/platform_auth/platform_auth_features.cc
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_features.cc
@@ -10,6 +10,10 @@
 
 #if BUILDFLAG(IS_WIN)
 BASE_FEATURE(kCloudApAuth, "CloudApAuth", base::FEATURE_DISABLED_BY_DEFAULT);
+
+BASE_FEATURE(kCloudApAuthAttachAsHeader,
+             "CloudApAuthAttachAsHeader",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_WIN)
 
 }  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_features.h b/chrome/browser/enterprise/platform_auth/platform_auth_features.h
index ed44296..03aeee0 100644
--- a/chrome/browser/enterprise/platform_auth/platform_auth_features.h
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_features.h
@@ -14,6 +14,10 @@
 // Controls whether ambient authentication using the CloudAP framework is
 // enabled.
 BASE_DECLARE_FEATURE(kCloudApAuth);
+
+// Determines whether authentication data beginning with 'x-ms-' should be added
+// to requests as a header instead of a cookie.
+BASE_DECLARE_FEATURE(kCloudApAuthAttachAsHeader);
 #endif  // BUILDFLAG(IS_WIN)
 
 }  // namespace enterprise_auth
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
index 0c1f4cb..330fd15a 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
@@ -600,6 +600,66 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// AutofillPrivateSaveIbanFunction
+
+ExtensionFunction::ResponseAction AutofillPrivateSaveIbanFunction::Run() {
+  std::unique_ptr<api::autofill_private::SaveIban::Params> parameters =
+      api::autofill_private::SaveIban::Params::Create(args());
+  EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+  autofill::PersonalDataManager* personal_data =
+      autofill::PersonalDataManagerFactory::GetForProfile(
+          Profile::FromBrowserContext(browser_context()));
+  if (!personal_data || !personal_data->IsDataLoaded())
+    return RespondNow(Error(kErrorDataUnavailable));
+
+  api::autofill_private::IbanEntry* iban_entry = &parameters->iban;
+  DCHECK(iban_entry->value);
+
+  // The IBAN guid is specified if the user tries to update an existing IBAN via
+  // the Chrome payment settings page. Otherwise, leaving it blank creates a new
+  // IBAN.
+  std::string guid = iban_entry->guid ? *iban_entry->guid : "";
+  const autofill::IBAN* existing_iban = nullptr;
+  if (!guid.empty()) {
+    existing_iban = personal_data->GetIBANByGUID(guid);
+    if (!existing_iban)
+      return RespondNow(Error(kErrorDataUnavailable));
+  }
+  autofill::IBAN iban =
+      existing_iban ? *existing_iban : autofill::IBAN(base::GenerateGUID());
+
+  iban.SetRawInfo(autofill::IBAN_VALUE, base::UTF8ToUTF16(*iban_entry->value));
+
+  if (iban_entry->nickname)
+    iban.set_nickname(base::UTF8ToUTF16(*iban_entry->nickname));
+
+  if (guid.empty()) {
+    personal_data->AddIBAN(iban);
+  } else if (existing_iban->Compare(iban) != 0) {
+    personal_data->UpdateIBAN(iban);
+  }
+
+  return RespondNow(NoArguments());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AutofillPrivateGetIbanListFunction
+
+ExtensionFunction::ResponseAction AutofillPrivateGetIbanListFunction::Run() {
+  autofill::PersonalDataManager* personal_data =
+      autofill::PersonalDataManagerFactory::GetForProfile(
+          Profile::FromBrowserContext(browser_context()));
+
+  DCHECK(personal_data && personal_data->IsDataLoaded());
+
+  autofill_util::IbanEntryList iban_list =
+      autofill_util::GenerateIbanList(*personal_data);
+  return RespondNow(ArgumentList(
+      api::autofill_private::GetIbanList::Results::Create(iban_list)));
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // AutofillPrivateGetUpiIdListFunction
 
 ExtensionFunction::ResponseAction AutofillPrivateGetUpiIdListFunction::Run() {
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.h b/chrome/browser/extensions/api/autofill_private/autofill_private_api.h
index 4baad0d..e99c32b 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.h
+++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.h
@@ -218,6 +218,40 @@
   ResponseAction Run() override;
 };
 
+class AutofillPrivateSaveIbanFunction : public ExtensionFunction {
+ public:
+  AutofillPrivateSaveIbanFunction() = default;
+  AutofillPrivateSaveIbanFunction(const AutofillPrivateSaveIbanFunction&) =
+      delete;
+  AutofillPrivateSaveIbanFunction& operator=(
+      const AutofillPrivateSaveIbanFunction&) = delete;
+  DECLARE_EXTENSION_FUNCTION("autofillPrivate.saveIban",
+                             AUTOFILLPRIVATE_SAVEIBAN)
+
+ protected:
+  ~AutofillPrivateSaveIbanFunction() override = default;
+
+  // ExtensionFunction overrides.
+  ResponseAction Run() override;
+};
+
+class AutofillPrivateGetIbanListFunction : public ExtensionFunction {
+ public:
+  AutofillPrivateGetIbanListFunction() = default;
+  AutofillPrivateGetIbanListFunction(
+      const AutofillPrivateGetIbanListFunction&) = delete;
+  AutofillPrivateGetIbanListFunction& operator=(
+      const AutofillPrivateGetIbanListFunction&) = delete;
+  DECLARE_EXTENSION_FUNCTION("autofillPrivate.getIbanList",
+                             AUTOFILLPRIVATE_GETIBANLIST)
+
+ protected:
+  ~AutofillPrivateGetIbanListFunction() override = default;
+
+  // ExtensionFunction overrides.
+  ResponseAction Run() override;
+};
+
 class AutofillPrivateGetUpiIdListFunction : public ExtensionFunction {
  public:
   AutofillPrivateGetUpiIdListFunction() = default;
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_apitest.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_apitest.cc
index a1a3411..8290eada 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_private_apitest.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_private_apitest.cc
@@ -83,4 +83,8 @@
   EXPECT_TRUE(RunAutofillSubtest("addAndUpdateCreditCard")) << message_;
 }
 
+IN_PROC_BROWSER_TEST_P(AutofillPrivateApiTest, AddAndUpdateIban) {
+  EXPECT_TRUE(RunAutofillSubtest("addAndUpdateIban")) << message_;
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_event_router.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_event_router.cc
index 8d2e770..b293d90 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_private_event_router.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_private_event_router.cc
@@ -57,8 +57,11 @@
   autofill_util::CreditCardEntryList creditCardList =
       extensions::autofill_util::GenerateCreditCardList(*personal_data_);
 
+  autofill_util::IbanEntryList ibanList =
+      extensions::autofill_util::GenerateIbanList(*personal_data_);
+
   auto args(api::autofill_private::OnPersonalDataChanged::Create(
-      addressList, creditCardList));
+      addressList, creditCardList, ibanList));
 
   std::unique_ptr<Event> extension_event(
       new Event(events::AUTOFILL_PRIVATE_ON_PERSONAL_DATA_CHANGED,
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_util.cc b/chrome/browser/extensions/api/autofill_private/autofill_util.cc
index 32103a4..dae0d41 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_util.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_util.cc
@@ -20,6 +20,7 @@
 #include "components/autofill/core/browser/autofill_type.h"
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/data_model/iban.h"
 #include "components/autofill/core/browser/field_types.h"
 #include "components/autofill/core/browser/geo/autofill_country.h"
 #include "components/autofill/core/browser/ui/country_combobox_model.h"
@@ -210,6 +211,27 @@
   return card;
 }
 
+autofill_private::IbanEntry IbanToIbanEntry(
+    const autofill::IBAN& iban,
+    const autofill::PersonalDataManager& personal_data) {
+  autofill_private::IbanEntry iban_entry;
+
+  // Populated IBAN fields need to be converted to an `IbanEntry` to be rendered
+  // in the settings page.
+  iban_entry.guid = iban.guid();
+  if (!iban.nickname().empty())
+    iban_entry.nickname = base::UTF16ToUTF8(iban.nickname());
+
+  iban_entry.value = base::UTF16ToUTF8(iban.value());
+
+  // Create IBAN metadata and add it to `iban_entry`.
+  iban_entry.metadata.emplace();
+  iban_entry.metadata->summary_label =
+      base::UTF16ToUTF8(iban.GetIdentifierStringForAutofillDisplay());
+
+  return iban_entry;
+}
+
 }  // namespace
 
 namespace extensions {
@@ -261,6 +283,15 @@
   return list;
 }
 
+IbanEntryList GenerateIbanList(
+    const autofill::PersonalDataManager& personal_data) {
+  IbanEntryList list;
+  for (const autofill::IBAN* iban : personal_data.GetLocalIBANs())
+    list.push_back(IbanToIbanEntry(*iban, personal_data));
+
+  return list;
+}
+
 }  // namespace autofill_util
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_util.h b/chrome/browser/extensions/api/autofill_private/autofill_util.h
index 5d7bbaf..4fad671 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_util.h
+++ b/chrome/browser/extensions/api/autofill_private/autofill_util.h
@@ -18,6 +18,7 @@
 using AddressEntryList = std::vector<api::autofill_private::AddressEntry>;
 using CountryEntryList = std::vector<api::autofill_private::CountryEntry>;
 using CreditCardEntryList = std::vector<api::autofill_private::CreditCardEntry>;
+using IbanEntryList = std::vector<api::autofill_private::IbanEntry>;
 
 // Uses |personal_data| to generate a list of up-to-date AddressEntry objects.
 AddressEntryList GenerateAddressList(
@@ -32,6 +33,11 @@
 CreditCardEntryList GenerateCreditCardList(
     const autofill::PersonalDataManager& personal_data);
 
+// Uses |personal_data| to generate a list of up-to-date IbanEntry
+// objects.
+IbanEntryList GenerateIbanList(
+    const autofill::PersonalDataManager& personal_data);
+
 }  // namespace autofill_util
 
 }  // namespace extensions
diff --git a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl_unittest.cc b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl_unittest.cc
index b5777acad..d22b5e8 100644
--- a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl_unittest.cc
+++ b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl_unittest.cc
@@ -114,6 +114,11 @@
       const std::string& segmentation_key,
       scoped_refptr<segmentation_platform::InputContext> input_context,
       SegmentSelectionCallback callback) override {}
+  void GetClassificationResult(
+      const std::string& segmentation_key,
+      const PredictionOptions& prediction_options,
+      scoped_refptr<segmentation_platform::InputContext> input_context,
+      segmentation_platform::ClassificationResultCallback callback) override {}
   void EnableMetrics(bool signal_collection_allowed) override {}
   segmentation_platform::ServiceProxy* GetServiceProxy() override {
     return nullptr;
diff --git a/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.cc b/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.cc
index 3741a14..be51222 100644
--- a/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.cc
+++ b/chrome/browser/feedback/system_logs/log_sources/chrome_internal_log_source.cc
@@ -204,11 +204,13 @@
     DCHECK(stats);
 
     // Get the HWID.
-    std::string hwid;
-    if (!stats->GetMachineStatistic(chromeos::system::kHardwareClassKey, &hwid))
+    absl::optional<base::StringPiece> hwid =
+        stats->GetMachineStatistic(chromeos::system::kHardwareClassKey);
+    if (!hwid) {
       VLOG(1) << "Couldn't get machine statistic 'hardware_class'.";
-    else
-      response->emplace(kHWIDKey, hwid);
+    } else {
+      response->emplace(kHWIDKey, std::string(hwid.value()));
+    }
 
     // Get the firmware version.
     response->emplace(kChromeOsFirmwareVersion,
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index a15bdb2e..b2e5bcd 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -4322,6 +4322,11 @@
     "expiry_milestone": 112
   },
   {
+      "name": "iph-price-notifications-while-browsing",
+      "owners": [ "danieltwhite", "ajuma" ],
+      "expiry_milestone": 114
+  },
+  {
     "name": "isolate-origins",
     "owners": [ "site-isolation-dev", "alexmos", "creis", "lukasza" ],
     // This is useful for isolating additional origins beyond the normal site
@@ -4641,11 +4646,6 @@
     "expiry_milestone": 111
   },
   {
-    "name": "messages-for-android-passwords",
-    "owners": [ "pavely", "lazzzis" ],
-    "expiry_milestone": 105
-  },
-  {
     "name": "messages-for-android-permission-update",
     "owners": [ "pavely", "lazzzis" ],
     "expiry_milestone": 105
@@ -4676,11 +4676,6 @@
     "expiry_milestone": 118
   },
   {
-    "name": "messages-for-android-update-password",
-    "owners": [ "pavely", "lazzzis" ],
-    "expiry_milestone": 105
-  },
-  {
     "name": "messages-preinstall",
     "owners": [ "jonmann", "jshikaram" ],
     "expiry_milestone": 106
@@ -5426,6 +5421,11 @@
     "expiry_milestone": 112
   },
   {
+    "name": "page-info-about-this-site-non-en",
+    "owners": [ "dullweber" ],
+    "expiry_milestone": 112
+  },
+  {
     "name": "page-info-cookies-subpage",
     "owners": [ "zsalata@google.com", "olesiamarukhno@google.com", "dullweber" ],
     "expiry_milestone": 112
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index ebaf861..8872845 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2226,10 +2226,11 @@
 const char kPageEntitiesPageContentAnnotationsDescription[] =
     "Enables annotating the page entities model for each page load on-device.";
 
-const char kPageInfoAboutThisSiteName[] =
-    "'About this site' section in page info";
-const char kPageInfoAboutThisSiteDescription[] =
-    "Enable the 'About this site' section in the page info.";
+const char kPageInfoAboutThisSiteNonEnName[] =
+    "'About this site' section in page info in all languages";
+const char kPageInfoAboutThisSiteNonEnDescription[] =
+    "Enable the 'About this site' section in the page info UI in languages "
+    "other than English.";
 
 const char kPageInfoboutThisPageDescriptionPlaceholderName[] =
     "AboutThisPage description placeholder";
@@ -3714,10 +3715,6 @@
 extern const char kMessagesForAndroidOfferNotificationDescription[] =
     "When enabled, offer notification will use the new Messages UI";
 
-const char kMessagesForAndroidPasswordsName[] = "Passwords Messages UI";
-const char kMessagesForAndroidPasswordsDescription[] =
-    "When enabled, password prompt will use the new Messages UI.";
-
 const char kMessagesForAndroidPermissionUpdateName[] =
     "Permission Update Messages UI";
 const char kMessagesForAndroidPermissionUpdateDescription[] =
@@ -3744,11 +3741,6 @@
 const char kMessagesForAndroidStackingAnimationDescription[] =
     "When enabled, Messages UI will use the new stacking animation.";
 
-const char kMessagesForAndroidUpdatePasswordName[] =
-    "Update password Messages UI";
-const char kMessagesForAndroidUpdatePasswordDescription[] =
-    "When enabled, update password prompt will use the new Messages UI.";
-
 const char kNetworkServiceInProcessName[] =
     "Run the network service on the browser process";
 const char kNetworkServiceInProcessDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index bcc9cd4..752a9f4c 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1248,8 +1248,8 @@
 extern const char kPageEntitiesPageContentAnnotationsName[];
 extern const char kPageEntitiesPageContentAnnotationsDescription[];
 
-extern const char kPageInfoAboutThisSiteName[];
-extern const char kPageInfoAboutThisSiteDescription[];
+extern const char kPageInfoAboutThisSiteNonEnName[];
+extern const char kPageInfoAboutThisSiteNonEnDescription[];
 
 extern const char kPageInfoboutThisPageDescriptionPlaceholderName[];
 extern const char kPageInfoboutThisPageDescriptionPlaceholderDescription[];
@@ -2131,9 +2131,6 @@
 extern const char kMessagesForAndroidOfferNotificationName[];
 extern const char kMessagesForAndroidOfferNotificationDescription[];
 
-extern const char kMessagesForAndroidPasswordsName[];
-extern const char kMessagesForAndroidPasswordsDescription[];
-
 extern const char kMessagesForAndroidPermissionUpdateName[];
 extern const char kMessagesForAndroidPermissionUpdateDescription[];
 
@@ -2152,9 +2149,6 @@
 extern const char kMessagesForAndroidStackingAnimationName[];
 extern const char kMessagesForAndroidStackingAnimationDescription[];
 
-extern const char kMessagesForAndroidUpdatePasswordName[];
-extern const char kMessagesForAndroidUpdatePasswordDescription[];
-
 extern const char kNetworkServiceInProcessName[];
 extern const char kNetworkServiceInProcessDescription[];
 
diff --git a/chrome/browser/login_detection/login_detection_prefs.cc b/chrome/browser/login_detection/login_detection_prefs.cc
index 7f176835f..d309537 100644
--- a/chrome/browser/login_detection/login_detection_prefs.cc
+++ b/chrome/browser/login_detection/login_detection_prefs.cc
@@ -39,35 +39,35 @@
 }
 
 void SaveSiteToOAuthSignedInList(PrefService* pref_service, const GURL& url) {
-  DictionaryPrefUpdate update(pref_service, kOAuthSignedInSitesPref);
-  base::Value* dict = update.Get();
-  dict->SetKey(GetSiteNameForURL(url), base::TimeToValue(base::Time::Now()));
+  ScopedDictPrefUpdate update(pref_service, kOAuthSignedInSitesPref);
+  base::Value::Dict& dict = update.Get();
+  dict.Set(GetSiteNameForURL(url), base::TimeToValue(base::Time::Now()));
 
   // Try making space by removing sites having invalid sign-in time. This should
   // not happen unless the pref is corrupt somehow.
-  if (dict->DictSize() > GetOauthLoggedInSitesMaxSize()) {
+  if (dict.size() > GetOauthLoggedInSitesMaxSize()) {
     std::vector<std::string> invalid_sites;
-    for (auto site_entry : dict->DictItems()) {
+    for (auto site_entry : dict) {
       if (!base::ValueToTime(site_entry.second))
         invalid_sites.push_back(site_entry.first);
     }
     for (const auto& invalid_site : invalid_sites)
-      dict->RemoveKey(invalid_site);
+      dict.Remove(invalid_site);
   }
 
   // Limit the dict to its allowed max size, by removing the site entries which
   // are signed-in the earliest.
-  while (dict->DictSize() > GetOauthLoggedInSitesMaxSize()) {
+  while (dict.size() > GetOauthLoggedInSitesMaxSize()) {
     // Holds the pair of site name, its last login time for the site that was
     // least recently signed-in to be removed.
     absl::optional<std::pair<std::string, base::Time>> site_entry_to_remove;
-    for (auto site_entry : dict->DictItems()) {
+    for (auto site_entry : dict) {
       base::Time signin_time = *base::ValueToTime(site_entry.second);
       if (!site_entry_to_remove || signin_time < site_entry_to_remove->second) {
         site_entry_to_remove = std::make_pair(site_entry.first, signin_time);
       }
     }
-    dict->RemoveKey(site_entry_to_remove->first);
+    dict.Remove(site_entry_to_remove->first);
   }
 }
 
diff --git a/chrome/browser/metrics/perf/perf_events_collector.cc b/chrome/browser/metrics/perf/perf_events_collector.cc
index b7c0c970..d17a54c4 100644
--- a/chrome/browser/metrics/perf/perf_events_collector.cc
+++ b/chrome/browser/metrics/perf/perf_events_collector.cc
@@ -195,7 +195,8 @@
 
 // TLB miss cycles using raw PMU event codes.
 const char kPerfITLBMissCyclesCmdTremont[] = "-- record -a -e r1085 -c 30001";
-const char kPerfDTLBMissCyclesCmdTremont[] = "-- record -a -e r1008 -c 350003";
+const char kPerfDTLBMissCyclesCmdTremont[] =
+    "-- record -a -e r1008 -g -c 350003";
 
 const char kPerfLLCMissesCmd[] = "-- record -a -e r412e -g -c 30007";
 // Precise events (request zero skid) for last level cache misses.
diff --git a/chrome/browser/nearby_sharing/nearby_share_settings.cc b/chrome/browser/nearby_sharing/nearby_share_settings.cc
index de7e1ad..5530bb20 100644
--- a/chrome/browser/nearby_sharing/nearby_share_settings.cc
+++ b/chrome/browser/nearby_sharing/nearby_share_settings.cc
@@ -213,11 +213,12 @@
 
 void NearbyShareSettings::SetAllowedContacts(
     const std::vector<std::string>& allowed_contacts) {
-  base::ListValue list;
+  base::Value::List list;
   for (const auto& id : allowed_contacts) {
     list.Append(id);
   }
-  pref_service_->Set(prefs::kNearbySharingAllowedContactsPrefName, list);
+  pref_service_->SetList(prefs::kNearbySharingAllowedContactsPrefName,
+                         std::move(list));
 }
 
 void NearbyShareSettings::Bind(
diff --git a/chrome/browser/page_load_metrics/integration_tests/interaction_to_next_paint_browsertest.cc b/chrome/browser/page_load_metrics/integration_tests/interaction_to_next_paint_browsertest.cc
index 72ff9c1..e334026 100644
--- a/chrome/browser/page_load_metrics/integration_tests/interaction_to_next_paint_browsertest.cc
+++ b/chrome/browser/page_load_metrics/integration_tests/interaction_to_next_paint_browsertest.cc
@@ -41,6 +41,9 @@
   // program. We are leveraging the Performance Observer to ensure we
   // received the input in renderer.
   void InjectWaitJavaScript();
+
+  // Perform hit test and frame waiter to ensure the frame is ready.
+  void WaitForFrameReady();
 };
 
 bool InteractionToNextPaintTest::ExtractUKMPageLoadMetric(
@@ -99,7 +102,7 @@
 
   // Extract the UKM INP values from ukm_recorder.
   int64_t INP_numOfInteraction_value;
-  int64_t INP_100th_value;
+  int64_t INP_worst_value;
   int64_t INP_98th_value;
 
   bool extract_num_of_interaction = ExtractUKMPageLoadMetric(
@@ -110,7 +113,7 @@
       ukm_recorder(),
       ukm::builders::PageLoad::
           kInteractiveTiming_WorstUserInteractionLatency_MaxEventDurationName,
-      &INP_100th_value);
+      &INP_worst_value);
   bool extract_98th_interaction = ExtractUKMPageLoadMetric(
       ukm_recorder(),
       ukm::builders::PageLoad::
@@ -126,7 +129,7 @@
   // Since the INP value takes 98th percentile all interactions,
   // the 98th percentile and 100th percentile should be the same when
   // we have less than 50 interactions.
-  EXPECT_EQ(INP_98th_value, INP_100th_value);
+  EXPECT_EQ(INP_98th_value, INP_worst_value);
 
   // The duration value in trace data is rounded to 8md
   // which means the value before rounding should be in the
@@ -141,7 +144,7 @@
 
 void InteractionToNextPaintTest::InjectWaitJavaScript() {
   EXPECT_TRUE(ExecJs(web_contents(), R"(
-   waitForClick = async () => {
+   waitForFirstInput = async () => {
       const observePromise = new Promise(resolve => {
         new PerformanceObserver(e => {
           e.getEntries().forEach(entry => {
@@ -154,7 +157,7 @@
 
     let eventCounts = {mouseup: 0, pointerup: 0, click: 0};
     let eventPromise;
-    waitForEvents = () => {
+    registerEventListeners = () => {
       eventPromise = new Promise(resolve => {
         for (let evt in eventCounts) {
           window.addEventListener(evt, function(e) {
@@ -169,10 +172,20 @@
       });
       return true;
     };
-    runwaitForEvents = async () => {
+    awaitEventListeners = async () => {
       return await eventPromise;
     };
   )"));
+  ASSERT_TRUE(EvalJs(web_contents(), "registerEventListeners()").ExtractBool());
+}
+
+void InteractionToNextPaintTest::WaitForFrameReady() {
+  // We should wait for the main frame's hit-test data to be ready before
+  // sending the click event below to avoid flakiness.
+  content::WaitForHitTestData(web_contents()->GetPrimaryMainFrame());
+  // Ensure the compositor thread is aware of the mouse events.
+  content::MainThreadFrameObserver frame_observer(GetRenderWidgetHost());
+  frame_observer.Wait();
 }
 
 IN_PROC_BROWSER_TEST_F(InteractionToNextPaintTest, INP_ClickOnPage) {
@@ -190,22 +203,15 @@
   // Create Performance Observers to observe first input and events in the
   // program.
   InjectWaitJavaScript();
-  ASSERT_TRUE(EvalJs(web_contents(), "waitForEvents()").ExtractBool());
-
-  // We should wait for the main frame's hit-test data to be ready before
-  // sending the click event below to avoid flakiness.
-  content::WaitForHitTestData(web_contents()->GetPrimaryMainFrame());
-  // Ensure the compositor thread is aware of the mouse events.
-  content::MainThreadFrameObserver frame_observer(GetRenderWidgetHost());
-  frame_observer.Wait();
+  WaitForFrameReady();
 
   // Simulate a click on div which has no renderer actions as our interaction.
   content::SimulateMouseClick(web_contents(), 0,
                               blink::WebMouseEvent::Button::kLeft);
 
-  // Start the waitForClick Performance Observer.
-  ASSERT_TRUE(EvalJs(web_contents(), "waitForClick()").ExtractBool());
-  ASSERT_TRUE(EvalJs(web_contents(), "runwaitForEvents()").ExtractBool());
+  // Start the waitForFirstInput Performance Observer.
+  ASSERT_TRUE(EvalJs(web_contents(), "waitForFirstInput()").ExtractBool());
+  ASSERT_TRUE(EvalJs(web_contents(), "awaitEventListeners()").ExtractBool());
   waiter->Wait();
 
   // Navigate to blank page to ensure the data gets flushed from renderer to
@@ -231,21 +237,57 @@
   // Create Performance Observers to observe first input and events in the
   // program.
   InjectWaitJavaScript();
-
-  ASSERT_TRUE(EvalJs(web_contents(), "waitForEvents()").ExtractBool());
-  // We should wait for the main frame's hit-test data to be ready before
-  // sending the click event below to avoid flakiness.
-  content::WaitForHitTestData(web_contents()->GetPrimaryMainFrame());
-  // Ensure the compositor thread is aware of the mouse events.
-  content::MainThreadFrameObserver frame_observer(GetRenderWidgetHost());
-  frame_observer.Wait();
+  WaitForFrameReady();
 
   // Simulate a click on button which has default browser-driven presentation.
   content::SimulateMouseClickOrTapElementWithId(web_contents(), "button");
 
-  // Start the waitForClick Performance Observer.
-  ASSERT_TRUE(EvalJs(web_contents(), "waitForClick()").ExtractBool());
-  ASSERT_TRUE(EvalJs(web_contents(), "runwaitForEvents()").ExtractBool());
+  // Start the waitForFirstInput Performance Observer.
+  ASSERT_TRUE(EvalJs(web_contents(), "waitForFirstInput()").ExtractBool());
+  ASSERT_TRUE(EvalJs(web_contents(), "awaitEventListeners()").ExtractBool());
+  waiter->Wait();
+
+  // Navigate to blank page to ensure the data gets flushed from renderer to
+  // browser.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank")));
+
+  auto analyzer = StopTracingAndAnalyze();
+  ASSERT_TRUE(VerifyUKMAndTraceData(*analyzer));
+}
+
+IN_PROC_BROWSER_TEST_F(InteractionToNextPaintTest, INP_ClickWithPresentation) {
+  // Add waiter to wait for the interaction is arrived in browser.
+  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
+      web_contents());
+  waiter->AddNumInteractionsExpectation(1);
+
+  // Start tracing to record tracing data.
+  StartTracing({"devtools.timeline"});
+  LoadHTML(R"HTML(
+    <div id="div">content</div>
+  )HTML");
+
+  // Create Performance Observers to observe first input and events in the
+  // program.
+  InjectWaitJavaScript();
+
+  // Inject javascript to add event listener to change color
+  // after click. By doing this, we test click with
+  // presentation change for INP.
+  EXPECT_TRUE(ExecJs(web_contents(), R"(
+    const element = document.getElementById('div');
+    element.addEventListener("pointerdown", () => {
+      element.style="color:red";
+    });
+  )"));
+  WaitForFrameReady();
+
+  // Simulate a click on button which has default browser-driven presentation.
+  content::SimulateMouseClickOrTapElementWithId(web_contents(), "div");
+
+  // Start the waitForFirstInput Performance Observer.
+  ASSERT_TRUE(EvalJs(web_contents(), "waitForFirstInput()").ExtractBool());
+  ASSERT_TRUE(EvalJs(web_contents(), "awaitEventListeners()").ExtractBool());
   waiter->Wait();
 
   // Navigate to blank page to ensure the data gets flushed from renderer to
diff --git a/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc b/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc
index b87c353..b295292e 100644
--- a/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc
+++ b/chrome/browser/page_load_metrics/integration_tests/largest_contentful_paint_browsertest.cc
@@ -854,16 +854,16 @@
                   blink::LargestContentfulPaintType::kSVG |
                   blink::LargestContentfulPaintType::kDataURI;
   // percent-encoding of the svg url obtained by encodeURIComponent("<svg
-  // xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect
-  // stroke-width='2' stroke='black' x='1' y='1' width='14' height='14'
-  // fill='lime'/></svg>"
+  // xmlns='http://www.w3.org/2000/svg' width='16' height='16'
+  // viewBox='0 0 16 16'><rect stroke-width='2' stroke='black' x='1' y='1'
+  // width='14' height='14'fill='lime'/></svg>"
   std::string imgSrc =
       "data:image/"
       "svg+xml, "
-      "%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%"
-      "200%2016%2016'%3E%3Crect%20stroke-width%3D'2'%20stroke%3D'black'%20x%3D'"
-      "1'%20y%3D'1'%20width%3D'14'%20height%3D'14'%20fill%3D'lime'%2F%3E%3C%"
-      "2Fsvg%3E";
+      "%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20width%3D%2716%27%"
+      "20height%3D%2716%27%20viewBox%3D%270%200%2016%2016%27%3E%3Crect%20strok"
+      "e-width%3D%272%27%20stroke%3D%27black%27%20x%3D%271%27%20y%3D%271%27%20"
+      "width%3D%2714%27%20height%3D%2714%27%20fill%3D%27lime%27/%3E%3C/svg%3E";
 
   TestImage(imgSrc, flag_set);
 }
diff --git a/chrome/browser/password_manager/android/save_update_password_message_delegate.cc b/chrome/browser/password_manager/android/save_update_password_message_delegate.cc
index 6e843c9..674f470a 100644
--- a/chrome/browser/password_manager/android/save_update_password_message_delegate.cc
+++ b/chrome/browser/password_manager/android/save_update_password_message_delegate.cc
@@ -21,7 +21,6 @@
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/google_chrome_strings.h"
 #include "components/messages/android/message_dispatcher_bridge.h"
-#include "components/messages/android/messages_feature.h"
 #include "components/password_manager/core/browser/password_form.h"
 #include "components/password_manager/core/browser/password_form_metrics_recorder.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
@@ -36,6 +35,10 @@
 using password_manager::features::kPasswordEditDialogWithDetails;
 
 namespace {
+
+// Duration of message before timeout; 20 seconds.
+const int kMessageDismissDurationMs = 20000;
+
 // Log the outcome of the save/update password workflow.
 // It differentiates whether the the flow was accepted/cancelled immediately
 // or after calling the password edit dialog.
@@ -182,13 +185,7 @@
       base::BindOnce(&SaveUpdatePasswordMessageDelegate::HandleMessageDismissed,
                      base::Unretained(this)));
 
-  // The message duration experiment is controlled by a parameter, associated
-  // with MessagesForAndroidPasswords feature and thus should be adjusted for
-  // both save and update password prompt.
-  int message_dismiss_duration_ms =
-      messages::GetSavePasswordMessageDismissDurationMs();
-  if (message_dismiss_duration_ms != 0)
-    message_->SetDuration(message_dismiss_duration_ms);
+  message_->SetDuration(kMessageDismissDurationMs);
 
   const password_manager::PasswordForm& pending_credentials =
       passwords_state_.form_manager()->GetPendingCredentials();
@@ -339,8 +336,6 @@
     return IDS_PASSWORD_MANAGER_SAVE_BUTTON;
   if (!use_followup_button_text)
     return IDS_PASSWORD_MANAGER_UPDATE_BUTTON;
-  if (messages::UseFollowupButtonTextForUpdatePasswordButton())
-    return IDS_PASSWORD_MANAGER_UPDATE_WITH_FOLLOWUP_BUTTON;
   return IDS_PASSWORD_MANAGER_CONTINUE_BUTTON;
 }
 
diff --git a/chrome/browser/payments/payment_handler_enable_delegations_browsertest.cc b/chrome/browser/payments/payment_handler_enable_delegations_browsertest.cc
index 0526e68..676b5e1 100644
--- a/chrome/browser/payments/payment_handler_enable_delegations_browsertest.cc
+++ b/chrome/browser/payments/payment_handler_enable_delegations_browsertest.cc
@@ -22,13 +22,14 @@
 
   void SetUpOnMainThread() override {
     PaymentRequestPlatformBrowserTestBase::SetUpOnMainThread();
-    NavigateTo("/payment_handler.html");
+    NavigateTo("a.com", "/payment_handler.html");
   }
 };
 
 IN_PROC_BROWSER_TEST_F(PaymentHandlerEnableDelegationsTest, EnableDelegations) {
+  std::string method_name;
+  InstallPaymentApp("a.com", "/payment_handler_sw.js", &method_name);
   std::string expected = "success";
-  EXPECT_EQ(expected, content::EvalJs(GetActiveWebContents(), "install()"));
   EXPECT_EQ(
       expected,
       content::EvalJs(
@@ -39,10 +40,10 @@
 
 IN_PROC_BROWSER_TEST_F(PaymentHandlerEnableDelegationsTest,
                        InvalidDelegations) {
-  std::string expected = "success";
-  EXPECT_EQ(expected, content::EvalJs(GetActiveWebContents(), "install()"));
+  std::string method_name;
+  InstallPaymentApp("a.com", "/payment_handler_sw.js", &method_name);
 
-  expected =
+  std::string expected =
       "TypeError: Failed to execute 'enableDelegations' on 'PaymentManager': "
       "The provided value 'invalid_delegation' is not a valid enum value of "
       "type PaymentDelegation.";
diff --git a/chrome/browser/payments/payment_handler_enforce_full_delegation_browsertest.cc b/chrome/browser/payments/payment_handler_enforce_full_delegation_browsertest.cc
index cb9b55d..333669a 100644
--- a/chrome/browser/payments/payment_handler_enforce_full_delegation_browsertest.cc
+++ b/chrome/browser/payments/payment_handler_enforce_full_delegation_browsertest.cc
@@ -42,21 +42,17 @@
                        ShowPaymentSheetWhenOnlySomeAppsAreSkipped) {
   std::string expected = "success";
 
-  std::string method_name1 =
-      https_server()->GetURL("a.com", "/method_manifest.json").spec();
+  std::string method_name1;
   NavigateTo("a.com", "/enforce_full_delegation.test/index.html");
-  EXPECT_EQ(expected,
-            content::EvalJs(GetActiveWebContents(),
-                            content::JsReplace("install($1)", method_name1)));
+  InstallPaymentApp("a.com", "/enforce_full_delegation.test/app.js",
+                    &method_name1);
   EXPECT_EQ(expected, content::EvalJs(GetActiveWebContents(),
                                       "enableDelegations(['payerName'])"));
 
-  std::string method_name2 =
-      https_server()->GetURL("b.com", "/method_manifest.json").spec();
+  std::string method_name2;
   NavigateTo("b.com", "/enforce_full_delegation.test/index.html");
-  EXPECT_EQ(expected,
-            content::EvalJs(GetActiveWebContents(),
-                            content::JsReplace("install($1)", method_name2)));
+  InstallPaymentApp("b.com", "/enforce_full_delegation.test/app.js",
+                    &method_name2);
   EXPECT_EQ(expected,
             content::EvalJs(GetActiveWebContents(), "enableDelegations([])"));
 
@@ -87,12 +83,17 @@
 
 IN_PROC_BROWSER_TEST_P(PaymentHandlerEnforceFullDelegationTest,
                        WhenEnabled_ShowPaymentSheet_WhenDisabled_Reject) {
-  NavigateTo("/enforce_full_delegation.test/index.html");
+  NavigateTo("a.com", "/enforce_full_delegation.test/index.html");
+
+  std::string method_name;
+  InstallPaymentApp("a.com", "/enforce_full_delegation.test/app.js",
+                    &method_name);
 
   std::string expected = "success";
-  EXPECT_EQ(expected, content::EvalJs(GetActiveWebContents(), "install()"));
-  EXPECT_EQ(expected, content::EvalJs(GetActiveWebContents(),
-                                      "addDefaultSupportedMethod()"));
+  EXPECT_EQ(expected,
+            content::EvalJs(
+                GetActiveWebContents(),
+                content::JsReplace("addSupportedMethods([$1])", method_name)));
   EXPECT_EQ(expected,
             content::EvalJs(GetActiveWebContents(), "enableDelegations([])"));
   EXPECT_EQ(expected,
@@ -112,7 +113,7 @@
   if (GetParam() == ENABLED) {
     EXPECT_EQ(0u, test_controller()->app_descriptions().size());
     ExpectBodyContains(
-        "Skipping \"MaxPay\" for not providing all of the requested "
+        "Skipping \"Test App Name\" for not providing all of the requested "
         "PaymentOptions.");
   } else {
     EXPECT_EQ(1u, test_controller()->app_descriptions().size());
diff --git a/chrome/browser/payments/payment_handler_exploit_browsertest.cc b/chrome/browser/payments/payment_handler_exploit_browsertest.cc
index 3becc1e..4d8396c8 100644
--- a/chrome/browser/payments/payment_handler_exploit_browsertest.cc
+++ b/chrome/browser/payments/payment_handler_exploit_browsertest.cc
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/test/payments/payment_request_platform_browsertest_base.h"
 #include "content/public/browser/render_process_host.h"
@@ -12,10 +11,6 @@
 #include "url/gurl.h"
 #include "url/origin.h"
 
-#if BUILDFLAG(IS_ANDROID)
-#include "components/payments/content/android/payment_feature_list.h"
-#endif
-
 namespace payments {
 namespace {
 
@@ -29,18 +24,21 @@
   }
 
   GURL GetTestServerUrl(const std::string& relative_path) {
-    return https_server()->GetURL(relative_path);
+    return https_server()->GetURL("a.com", relative_path);
   }
 
   void SmokeTest() {
+    std::string method_name;
+    InstallPaymentApp("a.com", "/payment_handler_sw.js", &method_name);
     std::string expected = "success";
-    EXPECT_EQ(expected, content::EvalJs(GetActiveWebContents(), "install()"));
-    EXPECT_EQ(expected, content::EvalJs(GetActiveWebContents(), "launch()"));
+    EXPECT_EQ(expected,
+              content::EvalJs(GetActiveWebContents(),
+                              content::JsReplace("launch($1)", method_name)));
   }
 
   void TestThatRendererCannotOpenCrossOriginWindow() {
-    std::string expected = "success";
-    EXPECT_EQ(expected, content::EvalJs(GetActiveWebContents(), "install()"));
+    std::string method_name;
+    InstallPaymentApp("a.com", "/payment_handler_sw.js", &method_name);
 
     GURL service_worker_scope = GetTestServerUrl("/");
     int service_worker_process_id = -1;
@@ -56,7 +54,8 @@
     EXPECT_FALSE(url::IsSameOriginWith(cross_origin_url, service_worker_scope));
     set_payment_handler_window_url(cross_origin_url);
 
-    content::ExecuteScriptAsync(GetActiveWebContents(), "launch()");
+    content::ExecuteScriptAsync(GetActiveWebContents(),
+                                content::JsReplace("launch($1)", method_name));
 
     EXPECT_EQ(
         "Received bad user message: "
@@ -65,13 +64,11 @@
         crash_observer.Wait());
   }
 
-  base::test::ScopedFeatureList features_;
-
  private:
   // PaymentRequestPlatformBrowserTestBase:
   void SetUpOnMainThread() override {
     PaymentRequestPlatformBrowserTestBase::SetUpOnMainThread();
-    NavigateTo("/payment_handler.html");
+    NavigateTo("a.com", "/payment_handler.html");
   }
 
   // content::ServiceWorkerHostInterceptor:
diff --git a/chrome/browser/payments/payment_handler_uninstall_browsertest.cc b/chrome/browser/payments/payment_handler_uninstall_browsertest.cc
index ea1f1f5..dc730b75 100644
--- a/chrome/browser/payments/payment_handler_uninstall_browsertest.cc
+++ b/chrome/browser/payments/payment_handler_uninstall_browsertest.cc
@@ -17,24 +17,35 @@
 
   void SetUpOnMainThread() override {
     PaymentRequestPlatformBrowserTestBase::SetUpOnMainThread();
-    NavigateTo("/payment_handler.html");
+    NavigateTo("a.com", "/payment_handler.html");
   }
 };
 
 IN_PROC_BROWSER_TEST_F(PaymentHandlerUninstallTest, URLBasedPaymentMethod) {
-  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(), "install()"));
+  std::string method_name;
+  InstallPaymentApp("a.com", "/payment_handler_sw.js", &method_name);
 
   // Launch the payment request and confirm checkout completion.
   ResetEventWaiterForSingleEvent(TestEvent::kPaymentCompleted);
-  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(), "launch()"));
+  EXPECT_EQ("success",
+            content::EvalJs(GetActiveWebContents(),
+                            content::JsReplace("launch($1)", method_name)));
   WaitForObservedEvent();
 
   // Uninstall the payment app and verify that a new request.show() gets
   // rejected after the app uninstallation.
-  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(), "uninstall()"));
+  EXPECT_EQ("success",
+            content::EvalJs(
+                GetActiveWebContents(),
+                content::JsReplace("uninstall($1)",
+                                   https_server()->GetURL(
+                                       "a.com", "/payment_handler_sw.js"))));
   ResetEventWaiterForSingleEvent(TestEvent::kNotSupportedError);
-  EXPECT_EQ("success", content::EvalJs(GetActiveWebContents(),
-                                       "launchWithoutWaitForResponse()"));
+  EXPECT_EQ(
+      "success",
+      content::EvalJs(
+          GetActiveWebContents(),
+          content::JsReplace("launchWithoutWaitForResponse($1)", method_name)));
   WaitForObservedEvent();
 }
 
diff --git a/chrome/browser/payments/secure_payment_confirmation_opt_out_browsertest.cc b/chrome/browser/payments/secure_payment_confirmation_opt_out_browsertest.cc
index dd4d582..5f50fee5 100644
--- a/chrome/browser/payments/secure_payment_confirmation_opt_out_browsertest.cc
+++ b/chrome/browser/payments/secure_payment_confirmation_opt_out_browsertest.cc
@@ -18,59 +18,7 @@
 
 using Event2 = JourneyLogger::Event2;
 
-// The SPC opt-out experience should only be available if the Blink runtime flag
-// is set.
 IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutTest,
-                       RequiresRuntimeFlag) {
-  test_controller()->SetHasAuthenticator(true);
-  NavigateTo("a.com", "/secure_payment_confirmation.html");
-  std::vector<uint8_t> credential_id = {'c', 'r', 'e', 'd'};
-  std::vector<uint8_t> user_id = {'u', 's', 'e', 'r'};
-  webdata_services::WebDataServiceWrapperFactory::
-      GetPaymentManifestWebDataServiceForBrowserContext(
-          GetActiveWebContents()->GetBrowserContext(),
-          ServiceAccessType::EXPLICIT_ACCESS)
-          ->AddSecurePaymentConfirmationCredential(
-              std::make_unique<SecurePaymentConfirmationCredential>(
-                  std::move(credential_id), "a.com", std::move(user_id)),
-              /*consumer=*/this);
-
-  // Initiate SPC, with opt-out enabled.
-  ResetEventWaiterForSingleEvent(TestEvent::kUIDisplayed);
-  const bool show_opt_out = true;
-  ExecuteScriptAsync(
-      GetActiveWebContents(),
-      content::JsReplace(
-          "getSecurePaymentConfirmationStatus(undefined, undefined, $1)",
-          show_opt_out));
-  WaitForObservedEvent();
-
-  // Because the runtime flag isn't set, showOptOut should still have been set
-  // to false, and so there is no opt-out link to click.
-  EXPECT_FALSE(test_controller()->ClickOptOut());
-
-  // Close the dialog to trigger JourneyLogger metrics, and verify that opt out
-  // was not recorded as offered or taken.
-  test_controller()->CloseDialog();
-  EXPECT_EQ(
-      GetWebAuthnErrorMessage(),
-      content::EvalJs(GetActiveWebContents(), "getOutstandingStatusPromise()"));
-  ExpectEvent2Histogram({Event2::kInitiated, Event2::kShown,
-                         Event2::kUserAborted, Event2::kHadInitialFormOfPayment,
-                         Event2::kRequestMethodSecurePaymentConfirmation});
-}
-
-class SecurePaymentConfirmationOptOutEnabledTest
-    : public SecurePaymentConfirmationOptOutTest {
- public:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    SecurePaymentConfirmationTest::SetUpCommandLine(command_line);
-    command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
-                                    "SecurePaymentConfirmationOptOut");
-  }
-};
-
-IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutEnabledTest,
                        ShowOptOutOfferedAndTaken) {
   test_controller()->SetHasAuthenticator(true);
   NavigateTo("a.com", "/secure_payment_confirmation.html");
@@ -109,7 +57,7 @@
                          Event2::kRequestMethodSecurePaymentConfirmation});
 }
 
-IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutEnabledTest,
+IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutTest,
                        ShowOptOutOfferedButNotTaken) {
   test_controller()->SetHasAuthenticator(true);
   NavigateTo("a.com", "/secure_payment_confirmation.html");
@@ -146,7 +94,7 @@
                          Event2::kRequestMethodSecurePaymentConfirmation});
 }
 
-IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutEnabledTest,
+IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutTest,
                        ShowOptOutNotOffered) {
   test_controller()->SetHasAuthenticator(true);
   NavigateTo("a.com", "/secure_payment_confirmation.html");
@@ -185,7 +133,7 @@
                          Event2::kRequestMethodSecurePaymentConfirmation});
 }
 
-IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutEnabledTest,
+IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutTest,
                        ShowOptOutDefaultsToNotOffered) {
   test_controller()->SetHasAuthenticator(true);
   NavigateTo("a.com", "/secure_payment_confirmation.html");
@@ -220,7 +168,7 @@
                          Event2::kRequestMethodSecurePaymentConfirmation});
 }
 
-IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutEnabledTest,
+IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutTest,
                        ShowOptOutNoMatchingCredsOfferedAndTaken) {
   // Don't install a credential, so that the 'No Matching Credentials' UI will
   // be shown.
@@ -250,7 +198,7 @@
                          Event2::kRequestMethodSecurePaymentConfirmation});
 }
 
-IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutEnabledTest,
+IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutTest,
                        ShowOptOutNoMatchingCredsOfferedButNotTaken) {
   // Don't install a credential, so that the 'No Matching Credentials' UI will
   // be shown.
@@ -279,7 +227,7 @@
                          Event2::kRequestMethodSecurePaymentConfirmation});
 }
 
-IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutEnabledTest,
+IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutTest,
                        ShowOptOutNoMatchingCredsNotOffered) {
   // Don't install a credential, so that the 'No Matching Credentials' UI will
   // be shown.
@@ -310,7 +258,7 @@
                          Event2::kRequestMethodSecurePaymentConfirmation});
 }
 
-IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutEnabledTest,
+IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutTest,
                        ShowOptOutNoMatchingCredsDefaultsToNotOffered) {
   // Don't install a credential, so that the 'No Matching Credentials' UI will
   // be shown.
@@ -337,5 +285,57 @@
                          Event2::kRequestMethodSecurePaymentConfirmation});
 }
 
+class SecurePaymentConfirmationOptOutDisabledTest
+    : public SecurePaymentConfirmationOptOutTest {
+ public:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    SecurePaymentConfirmationTest::SetUpCommandLine(command_line);
+    command_line->AppendSwitchASCII(switches::kDisableBlinkFeatures,
+                                    "SecurePaymentConfirmationOptOut");
+  }
+};
+
+// The SPC opt-out experience should not be available if the Blink runtime flag
+// is disabled.
+IN_PROC_BROWSER_TEST_F(SecurePaymentConfirmationOptOutDisabledTest,
+                       RequiresRuntimeFlag) {
+  test_controller()->SetHasAuthenticator(true);
+  NavigateTo("a.com", "/secure_payment_confirmation.html");
+  std::vector<uint8_t> credential_id = {'c', 'r', 'e', 'd'};
+  std::vector<uint8_t> user_id = {'u', 's', 'e', 'r'};
+  webdata_services::WebDataServiceWrapperFactory::
+      GetPaymentManifestWebDataServiceForBrowserContext(
+          GetActiveWebContents()->GetBrowserContext(),
+          ServiceAccessType::EXPLICIT_ACCESS)
+          ->AddSecurePaymentConfirmationCredential(
+              std::make_unique<SecurePaymentConfirmationCredential>(
+                  std::move(credential_id), "a.com", std::move(user_id)),
+              /*consumer=*/this);
+
+  // Initiate SPC, with opt-out enabled.
+  ResetEventWaiterForSingleEvent(TestEvent::kUIDisplayed);
+  const bool show_opt_out = true;
+  ExecuteScriptAsync(
+      GetActiveWebContents(),
+      content::JsReplace(
+          "getSecurePaymentConfirmationStatus(undefined, undefined, $1)",
+          show_opt_out));
+  WaitForObservedEvent();
+
+  // Because the runtime flag isn't set, showOptOut should still have been set
+  // to false, and so there is no opt-out link to click.
+  EXPECT_FALSE(test_controller()->ClickOptOut());
+
+  // Close the dialog to trigger JourneyLogger metrics, and verify that opt out
+  // was not recorded as offered or taken.
+  test_controller()->CloseDialog();
+  EXPECT_EQ(
+      GetWebAuthnErrorMessage(),
+      content::EvalJs(GetActiveWebContents(), "getOutstandingStatusPromise()"));
+  ExpectEvent2Histogram({Event2::kInitiated, Event2::kShown,
+                         Event2::kUserAborted, Event2::kHadInitialFormOfPayment,
+                         Event2::kRequestMethodSecurePaymentConfirmation});
+}
+
 }  // namespace
 }  // namespace payments
diff --git a/chrome/browser/policy/extension_policy_browsertest.cc b/chrome/browser/policy/extension_policy_browsertest.cc
index 3f1f3cac..50de34bd 100644
--- a/chrome/browser/policy/extension_policy_browsertest.cc
+++ b/chrome/browser/policy/extension_policy_browsertest.cc
@@ -41,6 +41,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
 #include "chrome/browser/web_applications/test/web_app_test_observers.h"
+#include "chrome/browser/web_applications/test/web_app_test_utils.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_install_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
@@ -2343,21 +2344,42 @@
   EXPECT_EQ(fallback_app_name_, registrar.GetAppShortName(*app_id));
 }
 
+using ExternalPrefMigrationTestCases =
+    web_app::test::ExternalPrefMigrationTestCases;
+
 class WebAppInstallForceListPolicyPlaceholderWithAppFallbackNameTest
     : public WebAppInstallForceListPolicyTest,
-      public testing::WithParamInterface<bool> {
+      public testing::WithParamInterface<ExternalPrefMigrationTestCases> {
  public:
   WebAppInstallForceListPolicyPlaceholderWithAppFallbackNameTest() {
     test_page_ = "/close-socket";
     fallback_app_name_ = "fallback app name";
-    bool enable_migration = GetParam();
-    if (enable_migration) {
-      scoped_feature_list_.InitWithFeatures(
-          {features::kUseWebAppDBInsteadOfExternalPrefs}, {});
-    } else {
-      scoped_feature_list_.InitWithFeatures(
-          {}, {features::kUseWebAppDBInsteadOfExternalPrefs});
+    std::vector<base::test::FeatureRef> enabled_features;
+    std::vector<base::test::FeatureRef> disabled_features;
+
+    switch (GetParam()) {
+      case ExternalPrefMigrationTestCases::kDisableMigrationReadPref:
+        disabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        disabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+      case ExternalPrefMigrationTestCases::kDisableMigrationReadDB:
+        disabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        enabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+      case ExternalPrefMigrationTestCases::kEnableMigrationReadPref:
+        enabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        disabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+      case ExternalPrefMigrationTestCases::kEnableMigrationReadDB:
+        enabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        enabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
     }
+    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
   ~WebAppInstallForceListPolicyPlaceholderWithAppFallbackNameTest() override =
@@ -2395,7 +2417,11 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     WebAppInstallForceListPolicyPlaceholderWithAppFallbackNameTest,
-    ::testing::Bool());
+    ::testing::Values(ExternalPrefMigrationTestCases::kDisableMigrationReadPref,
+                      ExternalPrefMigrationTestCases::kDisableMigrationReadDB,
+                      ExternalPrefMigrationTestCases::kEnableMigrationReadPref,
+                      ExternalPrefMigrationTestCases::kEnableMigrationReadDB),
+    web_app::test::GetExternalPrefMigrationTestName);
 
 // Fixture for tests that have two profiles with a different policy for each.
 class ExtensionPolicyTest2Contexts : public PolicyTest {
diff --git a/chrome/browser/policy/networking/network_configuration_updater.cc b/chrome/browser/policy/networking/network_configuration_updater.cc
index 4f8b25c..8bf66f7 100644
--- a/chrome/browser/policy/networking/network_configuration_updater.cc
+++ b/chrome/browser/policy/networking/network_configuration_updater.cc
@@ -275,8 +275,7 @@
 void NetworkConfigurationUpdater::ImportCertificates(
     base::Value::List certificates_onc) {
   std::unique_ptr<OncParsedCertificates> incoming_certs =
-      std::make_unique<OncParsedCertificates>(
-          base::Value(std::move(certificates_onc)));
+      std::make_unique<OncParsedCertificates>(certificates_onc);
 
   bool server_or_authority_certs_changed =
       certs_->server_or_authority_certificates() !=
diff --git a/chrome/browser/policy/networking/network_configuration_updater_ash_unittest.cc b/chrome/browser/policy/networking/network_configuration_updater_ash_unittest.cc
index 3d9ea59..4f1655b 100644
--- a/chrome/browser/policy/networking/network_configuration_updater_ash_unittest.cc
+++ b/chrome/browser/policy/networking/network_configuration_updater_ash_unittest.cc
@@ -278,7 +278,7 @@
   ASSERT_TRUE(certs->is_list());
   ASSERT_TRUE(certs->GetList().size() > client_certificate_index);
 
-  base::ListValue selected_certs;
+  base::Value::List selected_certs;
   selected_certs.Append(certs->GetList()[client_certificate_index].Clone());
 
   chromeos::onc::OncParsedCertificates parsed_selected_certs(selected_certs);
@@ -343,8 +343,8 @@
         fake_toplevel_onc.FindKey(onc::toplevel_config::kCertificates);
     ASSERT_TRUE(certs->is_list());
 
-    fake_certificates_ =
-        std::make_unique<chromeos::onc::OncParsedCertificates>(*certs);
+    fake_certificates_ = std::make_unique<chromeos::onc::OncParsedCertificates>(
+        certs->GetList());
 
     certificate_importer_ = new FakeCertificateImporter;
     client_certificate_importer_owned_.reset(certificate_importer_);
diff --git a/chrome/browser/policy/networking/user_network_configuration_updater_ash.cc b/chrome/browser/policy/networking/user_network_configuration_updater_ash.cc
index bb1091d..0a730ba 100644
--- a/chrome/browser/policy/networking/user_network_configuration_updater_ash.cc
+++ b/chrome/browser/policy/networking/user_network_configuration_updater_ash.cc
@@ -90,7 +90,7 @@
       /*network_configs=*/nullptr, /*global_network_config=*/nullptr,
       &certificates_value);
   chromeos::onc::OncParsedCertificates onc_parsed_certificates(
-      base::Value(std::move(certificates_value)));
+      certificates_value);
   for (const auto& server_or_authority_cert :
        onc_parsed_certificates.server_or_authority_certificates()) {
     if (server_or_authority_cert.type() ==
diff --git a/chrome/browser/privacy_guide/privacy_guide.h b/chrome/browser/privacy_guide/privacy_guide.h
index b6965eb..159e22c 100644
--- a/chrome/browser/privacy_guide/privacy_guide.h
+++ b/chrome/browser/privacy_guide/privacy_guide.h
@@ -36,6 +36,29 @@
   kMaxValue = kSafeBrowsingStandardToStandard,
 };
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+//
+// Must be kept in sync with SettingsPrivacyGuideInteractions in
+// histograms/enums.xml and SettingsPrivacyGuideInteractions in
+// resources/settings/metrics_browser_proxy.ts.
+//
+// A Java counterpart will be generated for this enum.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.privacy_guide
+enum class PrivacyGuideInteractions {
+  kWelcomeNextButton = 0,
+  kMSBBNextButton = 1,
+  kHistorySyncNextButton = 2,
+  kSafeBrowsingNextButton = 3,
+  kCookiesNextButton = 4,
+  kCompletionNextButton = 5,
+  kSettingsLinkRowEntry = 6,
+  kPromoEntry = 7,
+  kSWAACompletionLink = 8,
+  kPrivacySandboxCompletionLink = 9,
+  kMaxValue = kPrivacySandboxCompletionLink,
+};
+
 }  // namespace privacy_guide_metrics
 
 #endif  // CHROME_BROWSER_PRIVACY_GUIDE_PRIVACY_GUIDE_H_
diff --git a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_v4.xml b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_v4.xml
index 996e3c6..402797b 100644
--- a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_v4.xml
+++ b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_v4.xml
@@ -130,7 +130,7 @@
 
         <LinearLayout
             android:id="@+id/action_buttons"
-            android:orientation="vertical"
+            android:orientation="horizontal"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:weightSum="2"
@@ -139,25 +139,26 @@
             android:layout_marginHorizontal="24dp"
             android:visibility="invisible" >
 
+            <org.chromium.ui.widget.ButtonCompat
+                android:id="@+id/settings_button"
+                android:focusable="true"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginEnd="@dimen/privacy_sandbox_consent_button_margin_between"
+                android:text="@string/privacy_sandbox_m1_notice_eea_settings_button"
+                style="@style/TextButton" />
 
             <org.chromium.ui.widget.ButtonCompat
                 android:id="@+id/ack_button"
                 android:focusable="true"
-                android:layout_width="match_parent"
+                android:layout_width="0dp"
                 android:layout_weight="1"
-                android:layout_height="0dp"
-                android:layout_marginBottom="@dimen/privacy_sandbox_notice_button_margin_between"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/privacy_sandbox_consent_button_margin_between"
                 android:text="@string/privacy_sandbox_m1_notice_eea_ack_button"
                 style="@style/FilledButton.Flat" />
-            <org.chromium.ui.widget.ButtonCompat
-                android:id="@+id/settings_button"
-                android:focusable="true"
-                android:layout_width="match_parent"
-                android:layout_weight="1"
-                android:layout_height="0dp"
-                android:layout_gravity="center_horizontal"
-                android:text="@string/privacy_sandbox_m1_notice_eea_settings_button"
-                style="@style/TextButton" />
 
         </LinearLayout>
     </org.chromium.components.browser_ui.widget.BoundedLinearLayout>
diff --git a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_row_v4.xml b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_row_v4.xml
index 3a7fc24..2cfc0d7 100644
--- a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_row_v4.xml
+++ b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_row_v4.xml
@@ -144,7 +144,7 @@
 
         <LinearLayout
             android:id="@+id/action_buttons"
-            android:orientation="vertical"
+            android:orientation="horizontal"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:weightSum="2"
@@ -153,23 +153,24 @@
             android:visibility="invisible">
 
             <org.chromium.ui.widget.ButtonCompat
-                android:id="@+id/ack_button"
-                android:focusable="true"
-                android:layout_width="match_parent"
-                android:layout_weight="1"
-                android:layout_height="0dp"
-                android:layout_marginBottom="@dimen/privacy_sandbox_notice_button_margin_between"
-                android:text="@string/privacy_sandbox_m1_notice_row_ack_button"
-                style="@style/FilledButton.Flat" />
-            <org.chromium.ui.widget.ButtonCompat
                 android:id="@+id/settings_button"
                 android:focusable="true"
-                android:layout_width="match_parent"
+                android:layout_width="0dp"
                 android:layout_weight="1"
-                android:layout_height="0dp"
+                android:layout_height="wrap_content"
                 android:layout_gravity="center_horizontal"
+                android:layout_marginEnd="@dimen/privacy_sandbox_consent_button_margin_between"
                 android:text="@string/privacy_sandbox_m1_notice_row_settings_button"
                 style="@style/TextButton" />
+            <org.chromium.ui.widget.ButtonCompat
+                android:id="@+id/ack_button"
+                android:focusable="true"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/privacy_sandbox_consent_button_margin_between"
+                android:text="@string/privacy_sandbox_m1_notice_row_ack_button"
+                style="@style/FilledButton.Flat" />
 
         </LinearLayout>
     </org.chromium.components.browser_ui.widget.BoundedLinearLayout>
diff --git a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/v4/FledgeFragmentV4.java b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/v4/FledgeFragmentV4.java
index a1fbfe7..ee603a9 100644
--- a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/v4/FledgeFragmentV4.java
+++ b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/v4/FledgeFragmentV4.java
@@ -9,10 +9,13 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
 
 import org.chromium.chrome.browser.preferences.Pref;
+import org.chromium.chrome.browser.privacy_sandbox.FledgePreference;
+import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxBridge;
 import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxSettingsBaseFragment;
 import org.chromium.chrome.browser.privacy_sandbox.R;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -21,14 +24,20 @@
 import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
 import org.chromium.components.browser_ui.settings.SettingsUtils;
 import org.chromium.components.browser_ui.settings.TextMessagePreference;
+import org.chromium.components.favicon.LargeIconBridge;
 import org.chromium.components.prefs.PrefService;
 import org.chromium.components.user_prefs.UserPrefs;
 
+import java.util.List;
+
 /**
  * Fragment for the Privacy Sandbox -> Fledge preferences.
  */
 public class FledgeFragmentV4 extends PrivacySandboxSettingsBaseFragment
-        implements Preference.OnPreferenceChangeListener {
+        implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener {
+    @VisibleForTesting
+    static final int MAX_DISPLAYED_SITES = 15;
+
     private static final String FLEDGE_TOGGLE_PREFERENCE = "fledge_toggle";
     private static final String CURRENT_SITES_PREFERENCE = "current_fledge_sites";
     private static final String EMPTY_FLEDGE_PREFERENCE = "fledge_empty";
@@ -40,6 +49,7 @@
     private TextMessagePreference mEmptyFledgePreference;
     private TextMessagePreference mDisabledFledgePreference;
     private ChromeBasePreference mAllSitesPreference;
+    private LargeIconBridge mLargeIconBridge;
 
     static boolean isFledgePrefEnabled() {
         PrefService prefService = UserPrefs.get(Profile.getLastUsedRegularProfile());
@@ -84,10 +94,20 @@
     @Override
     public void onResume() {
         super.onResume();
+        PrivacySandboxBridge.getFledgeJoiningEtldPlusOneForDisplay(this::populateCurrentSites);
         updatePreferenceVisibility();
     }
 
     @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        if (mLargeIconBridge != null) {
+            mLargeIconBridge.destroy();
+        }
+        mLargeIconBridge = null;
+    }
+
+    @Override
     public boolean onPreferenceChange(@NonNull Preference preference, Object value) {
         if (preference.getKey().equals(FLEDGE_TOGGLE_PREFERENCE)) {
             setFledgePrefEnabled((boolean) value);
@@ -98,6 +118,39 @@
         return false;
     }
 
+    @Override
+    public boolean onPreferenceClick(@NonNull Preference preference) {
+        if (preference instanceof FledgePreference) {
+            PrivacySandboxBridge.setFledgeJoiningAllowed(
+                    ((FledgePreference) preference).getSite(), false);
+            mCurrentSitesCategory.removePreference(preference);
+            updatePreferenceVisibility();
+            return true;
+        }
+
+        return false;
+    }
+
+    private void populateCurrentSites(List<String> currentSites) {
+        if (mLargeIconBridge == null) {
+            mLargeIconBridge = new LargeIconBridge(Profile.getLastUsedRegularProfile());
+        }
+
+        mCurrentSitesCategory.removeAll();
+        int nrDisplayedSites = Math.min(currentSites.size(), MAX_DISPLAYED_SITES);
+        for (int i = 0; i < nrDisplayedSites; i++) {
+            String site = currentSites.get(i);
+            FledgePreference preference =
+                    new FledgePreference(getContext(), site, mLargeIconBridge);
+            preference.setImage(R.drawable.btn_close,
+                    getResources().getString(
+                            R.string.privacy_sandbox_remove_site_button_description, site));
+            preference.setDividerAllowedAbove(false);
+            preference.setOnPreferenceClickListener(this);
+            mCurrentSitesCategory.addPreference(preference);
+        }
+    }
+
     private void updatePreferenceVisibility() {
         boolean fledgeEnabled = isFledgePrefEnabled();
         boolean sitesEmpty = mCurrentSitesCategory.getPreferenceCount() == 0;
diff --git a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/FakePrivacySandboxBridge.java b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/FakePrivacySandboxBridge.java
index 8f8412b..5505604 100644
--- a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/FakePrivacySandboxBridge.java
+++ b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/FakePrivacySandboxBridge.java
@@ -8,7 +8,7 @@
 
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.Set;
 
 /**
@@ -19,10 +19,10 @@
     private boolean mIsPrivacySandboxRestricted /* = false*/;
 
     private final HashMap<String, Topic> mTopics = new HashMap<>();
-    private final Set<Topic> mCurrentTopTopics = new HashSet<>();
-    private final Set<Topic> mBlockedTopics = new HashSet<>();
-    private final Set<String> mCurrentFledgeSites = new HashSet<>();
-    private final Set<String> mBlockedFledgeSites = new HashSet<>();
+    private final Set<Topic> mCurrentTopTopics = new LinkedHashSet<>();
+    private final Set<Topic> mBlockedTopics = new LinkedHashSet<>();
+    private final Set<String> mCurrentFledgeSites = new LinkedHashSet<>();
+    private final Set<String> mBlockedFledgeSites = new LinkedHashSet<>();
     private @PromptType int mPromptType = PromptType.NONE;
     private Integer mLastPromptAction;
 
diff --git a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/v4/FledgeFragmentV4Test.java b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/v4/FledgeFragmentV4Test.java
index b9d08e6..585a8649 100644
--- a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/v4/FledgeFragmentV4Test.java
+++ b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/v4/FledgeFragmentV4Test.java
@@ -15,20 +15,25 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withParent;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import static org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxTestUtils.clickImageButtonNextToText;
 import static org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxTestUtils.getRootViewSanitized;
 import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;
 
 import android.view.View;
 
+import androidx.test.espresso.contrib.RecyclerViewActions;
 import androidx.test.filters.SmallTest;
 
 import org.hamcrest.Matcher;
 import org.junit.After;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,9 +42,13 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Features;
+import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.preferences.Pref;
+import org.chromium.chrome.browser.privacy_sandbox.FakePrivacySandboxBridge;
+import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxBridge;
+import org.chromium.chrome.browser.privacy_sandbox.PrivacySandboxBridgeJni;
 import org.chromium.chrome.browser.privacy_sandbox.R;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
@@ -62,6 +71,9 @@
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 @Features.EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4)
 public final class FledgeFragmentV4Test {
+    private static final String SITE_NAME_1 = "first.com";
+    private static final String SITE_NAME_2 = "second.com";
+
     @Rule
     public ChromeBrowserTestRule mChromeBrowserTestRule = new ChromeBrowserTestRule();
 
@@ -75,6 +87,17 @@
     public SettingsActivityTestRule<FledgeFragmentV4> mSettingsActivityTestRule =
             new SettingsActivityTestRule<>(FledgeFragmentV4.class);
 
+    @Rule
+    public JniMocker mocker = new JniMocker();
+
+    private FakePrivacySandboxBridge mFakePrivacySandboxBridge;
+
+    @Before
+    public void setUp() {
+        mFakePrivacySandboxBridge = new FakePrivacySandboxBridge();
+        mocker.mock(PrivacySandboxBridgeJni.TEST_HOOKS, mFakePrivacySandboxBridge);
+    }
+
     @After
     public void tearDown() {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
@@ -108,6 +131,15 @@
                 () -> FledgeFragmentV4.isFledgePrefEnabled());
     }
 
+    private void scrollToSetting(Matcher<View> matcher) {
+        onView(withId(R.id.recycler_view))
+                .perform(RecyclerViewActions.scrollTo(hasDescendant(matcher)));
+    }
+
+    private String generateSiteFromNr(int nr) {
+        return "site-" + nr + ".com";
+    }
+
     @Test
     @SmallTest
     @Feature({"RenderTest"})
@@ -128,6 +160,16 @@
 
     @Test
     @SmallTest
+    @Feature({"RenderTest"})
+    public void testRenderFledgePopulated() throws IOException {
+        setFledgePrefEnabled(true);
+        mFakePrivacySandboxBridge.setCurrentFledgeSites(SITE_NAME_1, SITE_NAME_2);
+        startFledgeSettings();
+        mRenderTestRule.render(getFledgeRootView(), "fledge_page_populated");
+    }
+
+    @Test
+    @SmallTest
     public void testToggleUncheckedWhenFledgeOff() {
         setFledgePrefEnabled(false);
         startFledgeSettings();
@@ -144,7 +186,7 @@
 
     @Test
     @SmallTest
-    public void testTurnFledgeOn() {
+    public void testTurnFledgeOnWhenSitesListEmpty() {
         setFledgePrefEnabled(false);
         startFledgeSettings();
         onView(getFledgeToggleMatcher()).perform(click());
@@ -158,6 +200,29 @@
 
     @Test
     @SmallTest
+    public void testTurnFledgeOnWhenSitesListPopulated() {
+        setFledgePrefEnabled(false);
+        mFakePrivacySandboxBridge.setCurrentFledgeSites(SITE_NAME_1, SITE_NAME_2);
+        startFledgeSettings();
+
+        // Check that the sites list is not displayed when Fledge is disabled.
+        onView(withText(SITE_NAME_1)).check(doesNotExist());
+        onView(withText(SITE_NAME_2)).check(doesNotExist());
+
+        // Click on the toggle.
+        onView(getFledgeToggleMatcher()).perform(click());
+
+        // Check that the all sites pref is displayed
+        onViewWaiting(withText(R.string.settings_fledge_page_see_all_sites_label))
+                .check(matches(isDisplayed()));
+
+        // Check that the sites list is displayed when Fledge is enabled.
+        onView(withText(SITE_NAME_1)).check(matches(isDisplayed()));
+        onView(withText(SITE_NAME_2)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    @SmallTest
     public void testTurnFledgeOff() {
         setFledgePrefEnabled(true);
         startFledgeSettings();
@@ -169,6 +234,60 @@
         onView(withText(R.string.settings_fledge_page_current_sites_description_empty))
                 .check(doesNotExist());
     }
-    // TODO(http://b/261823248): Add Managed state tests when the Privacy Sandbox policy it
+
+    @Test
+    @SmallTest
+    public void testPopulateSitesList() {
+        setFledgePrefEnabled(true);
+        mFakePrivacySandboxBridge.setCurrentFledgeSites(SITE_NAME_1, SITE_NAME_2);
+        startFledgeSettings();
+
+        onView(withText(SITE_NAME_1)).check(matches(isDisplayed()));
+        onView(withText(SITE_NAME_2)).check(matches(isDisplayed()));
+    }
+
+    @Test
+    @SmallTest
+    public void testMaxDisplayedSites() {
+        setFledgePrefEnabled(true);
+        for (int i = 0; i < FledgeFragmentV4.MAX_DISPLAYED_SITES + 1; i++) {
+            mFakePrivacySandboxBridge.setFledgeJoiningAllowed(generateSiteFromNr(i), true);
+        }
+        startFledgeSettings();
+
+        // Scroll to pref below last displayed site.
+        scrollToSetting(withText(R.string.settings_fledge_page_see_all_sites_label));
+
+        // Verify that only MAX_DISPLAY_SITES are shown.
+        onView(withText(generateSiteFromNr(FledgeFragmentV4.MAX_DISPLAYED_SITES - 1)))
+                .check(matches(isDisplayed()));
+        onView(withText(generateSiteFromNr(FledgeFragmentV4.MAX_DISPLAYED_SITES)))
+                .check(doesNotExist());
+    }
+
+    @Test
+    @SmallTest
+    public void testRemoveSitesFromList() {
+        setFledgePrefEnabled(true);
+        mFakePrivacySandboxBridge.setCurrentFledgeSites(SITE_NAME_1, SITE_NAME_2);
+        startFledgeSettings();
+
+        // Remove the first site from the list and check that it is blocked.
+        clickImageButtonNextToText(SITE_NAME_1);
+        onView(withText(SITE_NAME_1)).check(doesNotExist());
+        assertThat(PrivacySandboxBridge.getBlockedFledgeJoiningTopFramesForDisplay(),
+                hasItem(SITE_NAME_1));
+
+        // Remove the second site from the list and check that it is blocked.
+        clickImageButtonNextToText(SITE_NAME_2);
+        onView(withText(SITE_NAME_2)).check(doesNotExist());
+        assertThat(PrivacySandboxBridge.getBlockedFledgeJoiningTopFramesForDisplay(),
+                hasItem(SITE_NAME_2));
+
+        // Check that the empty state UI is displayed when the sites list is empty.
+        onView(withText(R.string.settings_fledge_page_current_sites_description_empty))
+                .check(matches(isDisplayed()));
+    }
+    // TODO(http://b/261823248): Add Managed state tests when the Privacy Sandbox policy is
     // implemented.
 }
diff --git a/chrome/browser/reading_list/android/reading_list_manager_impl.cc b/chrome/browser/reading_list/android/reading_list_manager_impl.cc
index 1e7e7ad..93bb27a9 100644
--- a/chrome/browser/reading_list/android/reading_list_manager_impl.cc
+++ b/chrome/browser/reading_list/android/reading_list_manager_impl.cc
@@ -69,8 +69,9 @@
     const ReadingListModel* model) {
   // Constructs the bookmark tree.
   root_->DeleteAll();
-  for (const auto& url : model->Keys())
+  for (const auto& url : model->GetKeys()) {
     AddOrUpdateBookmark(model->GetEntryByURL(url));
+  }
 
   loaded_ = true;
 
@@ -146,8 +147,9 @@
     return nullptr;
 
   // Add or swap the reading list entry.
-  const auto& new_entry = reading_list_model_->AddEntry(
-      url, title, reading_list::ADDED_VIA_CURRENT_APP);
+  const auto& new_entry = reading_list_model_->AddOrReplaceEntry(
+      url, title, reading_list::ADDED_VIA_CURRENT_APP,
+      /*estimated_read_time=*/base::TimeDelta());
   const auto* node = FindBookmarkByURL(new_entry.URL());
   return node;
 }
@@ -231,7 +233,7 @@
     LOG(ERROR) << "Failed to convert the following title to string16:" << title;
     return;
   }
-  reading_list_model_->SetEntryTitle(url, str_title);
+  reading_list_model_->SetEntryTitleIfExists(url, str_title);
 }
 
 void ReadingListManagerImpl::SetReadStatus(const GURL& url, bool read) {
@@ -240,7 +242,7 @@
   if (!entry)
     return;
 
-  reading_list_model_->SetReadStatus(url, read);
+  reading_list_model_->SetReadStatusIfExists(url, read);
   auto* node = FindBookmarkByURL(url);
   if (node) {
     node->SetMetaInfo(kReadStatusKey,
diff --git a/chrome/browser/reading_list/android/reading_list_manager_impl_unittest.cc b/chrome/browser/reading_list/android/reading_list_manager_impl_unittest.cc
index d824ff8..32f6654 100644
--- a/chrome/browser/reading_list/android/reading_list_manager_impl_unittest.cc
+++ b/chrome/browser/reading_list/android/reading_list_manager_impl_unittest.cc
@@ -20,7 +20,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 using BookmarkNode = bookmarks::BookmarkNode;
-using ReadingListEntries = ReadingListModelImpl::ReadingListEntries;
 
 namespace {
 
@@ -315,7 +314,9 @@
 TEST_F(ReadingListManagerImplTest, ReadingListDidAddEntry) {
   GURL url(kURL);
   EXPECT_CALL(*observer(), ReadingListChanged()).RetiresOnSaturation();
-  reading_list_model()->AddEntry(url, kTitle, reading_list::ADDED_VIA_SYNC);
+  reading_list_model()->AddOrReplaceEntry(
+      url, kTitle, reading_list::ADDED_VIA_SYNC,
+      /*estimated_read_time=*/base::TimeDelta());
 
   const auto* node = manager()->Get(url);
   EXPECT_TRUE(node);
diff --git a/chrome/browser/reading_list/android/reading_list_notification_service_unittest.cc b/chrome/browser/reading_list/android/reading_list_notification_service_unittest.cc
index c9c2a36..28d0deed 100644
--- a/chrome/browser/reading_list/android/reading_list_notification_service_unittest.cc
+++ b/chrome/browser/reading_list/android/reading_list_notification_service_unittest.cc
@@ -100,8 +100,10 @@
   }
 
   void AddReadingList() {
-    reading_list_model()->AddEntry(GURL("https://a.example.com"), "title",
-                                   reading_list::ADDED_VIA_CURRENT_APP);
+    reading_list_model()->AddOrReplaceEntry(
+        GURL("https://a.example.com"), "title",
+        reading_list::ADDED_VIA_CURRENT_APP,
+        /*estimated_read_time=*/base::TimeDelta());
   }
 
  private:
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
index 33381c4..0432ff9 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
@@ -124,11 +124,11 @@
     BrailleCommandHandler.init();
     ClipboardHandler.init();
     CommandHandler.init();
-    DesktopAutomationHandler.init();
     DownloadHandler.init();
     EventStreamLogger.init();
     FindHandler.init();
     FocusAutomationHandler.init();
+    GestureCommandHandler.init();
     JaPhoneticData.init(JaPhoneticMap.MAP);
     LiveRegions.init();
     LocaleOutputHelper.init();
@@ -138,6 +138,11 @@
     PanelBackground.init();
     RangeAutomationHandler.init();
 
+    // Allow all async initializers to run simultaneously, but wait for them to
+    // complete before continuing.
+    await Promise.all([
+      DesktopAutomationHandler.init(),
+    ]);
     ChromeVoxState.resolveReadyPromise_();
   }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/gesture_command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/gesture_command_handler.js
index bb0b9c2..75f61f9a 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/gesture_command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/gesture_command_handler.js
@@ -154,5 +154,3 @@
 
 /** @private {GestureCommandHandler} */
 GestureCommandHandler.instance_;
-
-ChromeVoxState.ready().then(() => GestureCommandHandler.init());
diff --git a/chrome/browser/resources/chromeos/accessibility/common/rect_util_test.js b/chrome/browser/resources/chromeos/accessibility/common/rect_util_test.js
index 7a73076..6728106 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/rect_util_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/rect_util_test.js
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-GEN_INCLUDE(['../select_to_speak/select_to_speak_e2e_test_base.js']);
+GEN_INCLUDE(['testing/common_e2e_test_base.js']);
 
 /** Test fixture for rect_util.js. */
-RectUtilTest = class extends SelectToSpeakE2ETest {
+AccessibilityExtensionRectUtilTest = class extends CommonE2ETestBase {
   /** @override */
   async setUpDeferred() {
     await super.setUpDeferred();
@@ -13,7 +13,7 @@
   }
 };
 
-AX_TEST_F('RectUtilTest', 'Adjacent', function() {
+AX_TEST_F('AccessibilityExtensionRectUtilTest', 'Adjacent', function() {
   const baseRect = {left: 10, top: 10, width: 10, height: 10};
   const adjacentRects = [
     {left: 0, top: 0, width: 10, height: 10},
@@ -76,7 +76,7 @@
   }
 });
 
-AX_TEST_F('RectUtilTest', 'Close', function() {
+AX_TEST_F('AccessibilityExtensionRectUtilTest', 'Close', function() {
   const centerRect = {left: 10, top: 10, width: 10, height: 10};
   assertTrue(RectUtil.close(centerRect, centerRect, 0));
 
@@ -133,7 +133,7 @@
   assertFalse(RectUtil.close(farRect, centerRect, 20));
 });
 
-AX_TEST_F('RectUtilTest', 'Equals', function() {
+AX_TEST_F('AccessibilityExtensionRectUtilTest', 'Equals', function() {
   const rect1 = {left: 0, top: 0, width: 10, height: 10};
   const rect2 = {left: 0, top: 0, width: 10, height: 10};
   const rect3 = {left: 1, top: 0, width: 10, height: 10};
@@ -158,7 +158,7 @@
   assertFalse(RectUtil.equal(rect6, rect1), 'equal should be symmetric');
 });
 
-AX_TEST_F('RectUtilTest', 'Center', function() {
+AX_TEST_F('AccessibilityExtensionRectUtilTest', 'Center', function() {
   const rect1 = {left: 0, top: 0, width: 10, height: 10};
   const rect2 = {left: 10, top: 20, width: 10, height: 40};
 
@@ -171,7 +171,7 @@
   assertEquals(40, center2.y, 'Center2 y should be 40');
 });
 
-AX_TEST_F('RectUtilTest', 'Union', function() {
+AX_TEST_F('AccessibilityExtensionRectUtilTest', 'Union', function() {
   const rect1 = {left: 0, top: 0, width: 10, height: 10};
   const rect2 = {left: 4, top: 4, width: 2, height: 2};
   const rect3 = {left: 10, top: 20, width: 10, height: 40};
@@ -203,7 +203,7 @@
       'Union of rect1 and rect5 does not match expected value');
 });
 
-AX_TEST_F('RectUtilTest', 'UnionAll', function() {
+AX_TEST_F('AccessibilityExtensionRectUtilTest', 'UnionAll', function() {
   const rect1 = {left: 0, top: 0, width: 10, height: 10};
   const rect2 = {left: 0, top: 10, width: 10, height: 10};
   const rect3 = {left: 10, top: 0, width: 10, height: 10};
@@ -223,53 +223,55 @@
       'Union of rects 1-5 does not match expected value');
 });
 
-AX_TEST_F('RectUtilTest', 'ExpandToFitWithPadding', function() {
-  const padding = 5;
-  let inner = {left: 100, top: 100, width: 100, height: 100};
-  let outer = {left: 120, top: 120, width: 20, height: 20};
-  let expected = {left: 95, top: 95, width: 110, height: 110};
-  assertTrue(
-      RectUtil.equal(
-          expected, RectUtil.expandToFitWithPadding(padding, outer, inner)),
-      'When outer is contained in inner, expandToFitWithPadding does not ' +
-          'match expected value');
+AX_TEST_F(
+    'AccessibilityExtensionRectUtilTest', 'ExpandToFitWithPadding', function() {
+      const padding = 5;
+      let inner = {left: 100, top: 100, width: 100, height: 100};
+      let outer = {left: 120, top: 120, width: 20, height: 20};
+      let expected = {left: 95, top: 95, width: 110, height: 110};
+      assertTrue(
+          RectUtil.equal(
+              expected, RectUtil.expandToFitWithPadding(padding, outer, inner)),
+          'When outer is contained in inner, expandToFitWithPadding does not ' +
+              'match expected value');
 
-  inner = {left: 100, top: 100, width: 100, height: 100};
-  outer = {left: 50, top: 50, width: 200, height: 200};
-  assertTrue(
-      RectUtil.equal(
-          outer, RectUtil.expandToFitWithPadding(padding, outer, inner)),
-      'When outer contains inner, expandToFitWithPadding should equal outer');
+      inner = {left: 100, top: 100, width: 100, height: 100};
+      outer = {left: 50, top: 50, width: 200, height: 200};
+      assertTrue(
+          RectUtil.equal(
+              outer, RectUtil.expandToFitWithPadding(padding, outer, inner)),
+          'When outer contains inner, expandToFitWithPadding should equal ' +
+              'outer');
 
-  inner = {left: 100, top: 100, width: 100, height: 100};
-  outer = {left: 10, top: 10, width: 10, height: 10};
-  expected = {left: 10, top: 10, width: 195, height: 195};
-  assertTrue(
-      RectUtil.equal(
-          expected, RectUtil.expandToFitWithPadding(padding, outer, inner)),
-      'When there is no overlap, expandToFitWithPadding does not match ' +
-          'expected value');
+      inner = {left: 100, top: 100, width: 100, height: 100};
+      outer = {left: 10, top: 10, width: 10, height: 10};
+      expected = {left: 10, top: 10, width: 195, height: 195};
+      assertTrue(
+          RectUtil.equal(
+              expected, RectUtil.expandToFitWithPadding(padding, outer, inner)),
+          'When there is no overlap, expandToFitWithPadding does not match ' +
+              'expected value');
 
-  inner = {left: 100, top: 100, width: 100, height: 100};
-  outer = {left: 120, top: 50, width: 200, height: 200};
-  expected = {left: 95, top: 50, width: 225, height: 200};
-  assertTrue(
-      RectUtil.equal(
-          expected, RectUtil.expandToFitWithPadding(padding, outer, inner)),
-      'When there is some overlap, expandToFitWithPadding does not match ' +
-          'expected value');
+      inner = {left: 100, top: 100, width: 100, height: 100};
+      outer = {left: 120, top: 50, width: 200, height: 200};
+      expected = {left: 95, top: 50, width: 225, height: 200};
+      assertTrue(
+          RectUtil.equal(
+              expected, RectUtil.expandToFitWithPadding(padding, outer, inner)),
+          'When there is some overlap, expandToFitWithPadding does not match ' +
+              'expected value');
 
-  inner = {left: 100, top: 100, width: 100, height: 100};
-  outer = {left: 97, top: 95, width: 108, height: 110};
-  expected = {left: 95, top: 95, width: 110, height: 110};
-  assertTrue(
-      RectUtil.equal(
-          expected, RectUtil.expandToFitWithPadding(padding, outer, inner)),
-      'When outer contains inner but without sufficient padding, ' +
-          'expandToFitWithPadding does not match expected value');
-});
+      inner = {left: 100, top: 100, width: 100, height: 100};
+      outer = {left: 97, top: 95, width: 108, height: 110};
+      expected = {left: 95, top: 95, width: 110, height: 110};
+      assertTrue(
+          RectUtil.equal(
+              expected, RectUtil.expandToFitWithPadding(padding, outer, inner)),
+          'When outer contains inner but without sufficient padding, ' +
+              'expandToFitWithPadding does not match expected value');
+    });
 
-AX_TEST_F('RectUtilTest', 'Contains', function() {
+AX_TEST_F('AccessibilityExtensionRectUtilTest', 'Contains', function() {
   const outer = {left: 10, top: 10, width: 10, height: 10};
   assertTrue(RectUtil.contains(outer, outer), 'Rect should contain itself');
 
@@ -326,7 +328,7 @@
 });
 
 
-AX_TEST_F('RectUtilTest', 'Difference', function() {
+AX_TEST_F('AccessibilityExtensionRectUtilTest', 'Difference', function() {
   const outer = {left: 10, top: 10, width: 10, height: 10};
   assertTrue(
       RectUtil.equal(RectUtil.ZERO_RECT, RectUtil.difference(outer, outer)),
@@ -368,7 +370,7 @@
       'Difference to the right should be the largest');
 });
 
-AX_TEST_F('RectUtilTest', 'Intersection', function() {
+AX_TEST_F('AccessibilityExtensionRectUtilTest', 'Intersection', function() {
   const rect1 = {left: 10, top: 10, width: 10, height: 10};
   assertTrue(
       RectUtil.equal(rect1, RectUtil.intersection(rect1, rect1)),
@@ -426,7 +428,7 @@
       'Intersection should be symmetric');
 });
 
-AX_TEST_F('RectUtilTest', 'Overlaps', function() {
+AX_TEST_F('AccessibilityExtensionRectUtilTest', 'Overlaps', function() {
   var rect1 = {left: 0, top: 0, width: 100, height: 100};
   var rect2 = {left: 80, top: 0, width: 100, height: 20};
   var rect3 = {left: 0, top: 80, width: 20, height: 100};
@@ -439,7 +441,7 @@
   assertFalse(RectUtil.overlaps(rect2, rect3));
 });
 
-AX_TEST_F('RectUtilTest', 'RectFromPoints', function() {
+AX_TEST_F('AccessibilityExtensionRectUtilTest', 'RectFromPoints', function() {
   var rect = {left: 10, top: 20, width: 50, height: 60};
 
   assertNotEquals(
diff --git a/chrome/browser/resources/settings/chromeos/device_page/audio.html b/chrome/browser/resources/settings/chromeos/device_page/audio.html
index f6819bc..bede8fc 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/audio.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/audio.html
@@ -143,7 +143,8 @@
           <div>
             <!--TODO(b/260277007): Replace icon once mic-on and mic-off icons
                 available. -->
-            <cr-icon-button id="audioInputGainMuteButton" iron-icon="cr:mic">
+            <cr-icon-button id="audioInputGainMuteButton" iron-icon="cr:mic"
+                on-click="onInputMuteClicked">
             </cr-icon-button>
             <cr-slider id ="audioInputGainVolumeSlider" min="0" max="100"
                 value="50">
diff --git a/chrome/browser/resources/settings/chromeos/device_page/audio.ts b/chrome/browser/resources/settings/chromeos/device_page/audio.ts
index 3fc04aa1..e8f0767 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/audio.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/audio.ts
@@ -62,6 +62,7 @@
       AudioSystemPropertiesObserverReceiver;
   private crosAudioConfig_: CrosAudioConfigInterface;
   private isOutputMuted_: boolean;
+  private isInputMuted_: boolean;
 
   constructor() {
     super();
@@ -87,12 +88,18 @@
     // kMutedByPolicy.
     this.isOutputMuted_ =
         this.audioSystemProperties_.outputMuteState !== MuteState.kNotMuted;
+    this.isInputMuted_ =
+        this.audioSystemProperties_.inputMuteState !== MuteState.kNotMuted;
   }
 
   getIsOutputMutedForTest(): boolean {
     return this.isOutputMuted_;
   }
 
+  getIsInputMutedForTest(): boolean {
+    return this.isInputMuted_;
+  }
+
   private observeAudioSystemProperties_(): void {
     // Use fake observer implementation to access additional properties not
     // available on mojo interface.
@@ -111,6 +118,15 @@
         MuteState.kMutedByPolicy;
   }
 
+  protected onInputMuteClicked(): void {
+    // TODO(b/260277007): Remove condition when setInputMuted added to mojo
+    // definition.
+    if (!this.crosAudioConfig_.setInputMuted) {
+      return;
+    }
+    this.crosAudioConfig_.setInputMuted(!this.isInputMuted_);
+  }
+
   /** Handles updating active input device. */
   protected onInputDeviceChanged(): void {
     // TODO(b/260277007): Remove condition when setActiveDevice added to mojo
diff --git a/chrome/browser/resources/settings/metrics_browser_proxy.ts b/chrome/browser/resources/settings/metrics_browser_proxy.ts
index a953e35..1527e13e 100644
--- a/chrome/browser/resources/settings/metrics_browser_proxy.ts
+++ b/chrome/browser/resources/settings/metrics_browser_proxy.ts
@@ -113,7 +113,8 @@
  * These values are persisted to logs. Entries should not be renumbered and
  * numeric values should never be reused.
  *
- * Must be kept in sync with SettingsPrivacyGuideInteractions in emus.xml.
+ * Must be kept in sync with SettingsPrivacyGuideInteractions in emus.xml and
+ * PrivacyGuideInteractions in privacy_guide/privacy_guide.h.
  */
 export enum PrivacyGuideInteractions {
   WELCOME_NEXT_BUTTON = 0,
diff --git a/chrome/browser/resources/settings/privacy_page/cookies_page.html b/chrome/browser/resources/settings/privacy_page/cookies_page.html
index 4e72b71..64ab611 100644
--- a/chrome/browser/resources/settings/privacy_page/cookies_page.html
+++ b/chrome/browser/resources/settings/privacy_page/cookies_page.html
@@ -51,105 +51,184 @@
       <img id="banner" alt=""
           src="chrome://settings/images/cookies_banner.svg">
     </picture>
-    <div id="generalControls">
-      <h2>$i18n{cookiePageGeneralControls}</h2>
-      <settings-radio-group id="primarySettingGroup" no-set-pref
-          pref="{{prefs.generated.cookie_primary_setting}}"
-          selectable-elements="cr-radio-button, settings-collapse-radio-button"
-          on-change="onCookiePrimarySettingChanged_">
-        <settings-collapse-radio-button id="allowAll"
-            pref="[[prefs.generated.cookie_primary_setting]]"
-            name="[[cookiePrimarySettingEnum_.ALLOW_ALL]]"
-            label="$i18n{cookiePageAllowAll}"
-            expand-aria-label="$i18n{cookiePageAllowAllExpandA11yLabel}">
-          <div slot="collapse">
-            <div class="bullet-line">
-              <iron-icon icon="settings:cookie"></iron-icon>
-              <div class="secondary">$i18n{cookiePageAllowAllBulOne}</div>
-            </div>
-            <div class="bullet-line">
-              <iron-icon icon="settings:cookie"></iron-icon>
-              <div class="secondary">$i18n{cookiePageAllowAllBulTwo}</div>
-            </div>
-          </div>
-        </settings-collapse-radio-button>
-        <settings-collapse-radio-button id="blockThirdPartyIncognito"
-            pref="[[prefs.generated.cookie_primary_setting]]"
-            name="[[cookiePrimarySettingEnum_.BLOCK_THIRD_PARTY_INCOGNITO]]"
-            label="$i18n{cookiePageBlockThirdIncognito}"
-            expand-aria-label=
-                "$i18n{cookiePageBlockThirdIncognitoExpandA11yLabel}">
-          <div slot="collapse">
-            <div class="bullet-line">
-              <iron-icon icon="settings:cookie"></iron-icon>
-              <div class="secondary">
-                    $i18n{cookiePageBlockThirdIncognitoBulOne}
+    <template is="dom-if" if="[[isPrivacySandboxSettings4_]]">
+      <!-- TODO(crbug.com/1378703): Update strings -->
+      <div id="generalControls">
+        <!-- TODO(crbug.com/1378703): Add new description labels -->
+        <h2>$i18n{cookiePageGeneralControls}</h2>
+        <settings-radio-group id="primarySettingGroup" no-set-pref
+            pref="{{prefs.profile.cookie_controls_mode}}"
+            selectable-elements="
+                cr-radio-button, settings-collapse-radio-button"
+            on-change="onCookieControlsModeChanged_">
+          <settings-collapse-radio-button id="allowThirdParty"
+              pref="[[prefs.profile.cookie_controls_mode]]"
+              name="[[cookieControlsModeEnum_.OFF]]"
+              label="$i18n{cookiePageAllowAll}"
+              expand-aria-label="$i18n{cookiePageAllowAllExpandA11yLabel}">
+            <div slot="collapse">
+              <div class="bullet-line">
+                <iron-icon icon="settings:cookie"></iron-icon>
+                <div class="secondary">$i18n{cookiePageAllowAllBulOne}</div>
+              </div>
+              <div class="bullet-line">
+                <iron-icon icon="settings:cookie"></iron-icon>
+                <div class="secondary">$i18n{cookiePageAllowAllBulTwo}</div>
               </div>
             </div>
-            <div class="bullet-line">
-              <iron-icon icon="settings:block"></iron-icon>
-              <div class="secondary">
-                    $i18n{cookiePageBlockThirdIncognitoBulTwo}
+          </settings-collapse-radio-button>
+          <settings-collapse-radio-button id="blockThirdPartyIncognito"
+              pref="[[prefs.profile.cookie_controls_mode]]"
+              name="[[cookieControlsModeEnum_.INCOGNITO_ONLY]]"
+              label="$i18n{cookiePageBlockThirdIncognito}"
+              expand-aria-label=
+                  "$i18n{cookiePageBlockThirdIncognitoExpandA11yLabel}">
+            <div slot="collapse">
+              <div class="bullet-line">
+                <iron-icon icon="settings:cookie"></iron-icon>
+                <div class="secondary">
+                      $i18n{cookiePageBlockThirdIncognitoBulOne}
+                </div>
+              </div>
+              <div class="bullet-line">
+                <iron-icon icon="settings:block"></iron-icon>
+                <div class="secondary">
+                      $i18n{cookiePageBlockThirdIncognitoBulTwo}
+                </div>
               </div>
             </div>
-          </div>
-        </settings-collapse-radio-button>
-        <settings-collapse-radio-button id="blockThirdParty"
-            pref="[[prefs.generated.cookie_primary_setting]]"
-            name="[[cookiePrimarySettingEnum_.BLOCK_THIRD_PARTY]]"
-            label="$i18n{cookiePageBlockThird}"
-            expand-aria-label="$i18n{cookiePageBlockThirdExpandA11yLabel}">
-          <div slot="collapse">
-            <div class="bullet-line">
-              <iron-icon icon="settings:cookie"></iron-icon>
-              <div class="secondary">$i18n{cookiePageBlockThirdBulOne}</div>
+          </settings-collapse-radio-button>
+          <settings-collapse-radio-button id="blockThirdParty"
+              pref="[[prefs.profile.cookie_controls_mode]]"
+              name="[[cookieControlsModeEnum_.BLOCK_THIRD_PARTY]]"
+              label="$i18n{cookiePageBlockThird}"
+              expand-aria-label="$i18n{cookiePageBlockThirdExpandA11yLabel}">
+            <div slot="collapse">
+              <div class="bullet-line">
+                <iron-icon icon="settings:cookie"></iron-icon>
+                <div class="secondary">$i18n{cookiePageBlockThirdBulOne}</div>
+              </div>
+              <div class="bullet-line">
+                <iron-icon icon="settings:block"></iron-icon>
+                <div class="secondary">$i18n{cookiePageBlockThirdBulTwo}</div>
+              </div>
+              <template is="dom-if" if="[[enableFirstPartySetsUI_]]">
+                <settings-toggle-button
+                    id="firstPartySetsToggle"
+                    pref="{{prefs.privacy_sandbox.first_party_sets_enabled}}"
+                    label="$i18n{cookiePageFpsLabel}"
+                    sub-label="$i18n{cookiePageFpsSubLabel}"
+                    disabled="[[firstPartySetsToggleDisabled_(
+                        prefs.profile.cookie_controls_mode.value)]]">
+                </settings-toggle-button>
+              </template>
             </div>
-            <div class="bullet-line">
-              <iron-icon icon="settings:block"></iron-icon>
-              <div class="secondary">$i18n{cookiePageBlockThirdBulTwo}</div>
+          </settings-collapse-radio-button>
+        </settings-radio-group>
+      </div>
+    </template>
+    <template is="dom-if" if="[[!isPrivacySandboxSettings4_]]">
+      <div id="generalControls">
+        <h2>$i18n{cookiePageGeneralControls}</h2>
+        <settings-radio-group id="primarySettingGroup" no-set-pref
+            pref="{{prefs.generated.cookie_primary_setting}}"
+            selectable-elements="
+                cr-radio-button, settings-collapse-radio-button"
+            on-change="onCookiePrimarySettingChanged_">
+          <settings-collapse-radio-button id="allowAll"
+              pref="[[prefs.generated.cookie_primary_setting]]"
+              name="[[cookiePrimarySettingEnum_.ALLOW_ALL]]"
+              label="$i18n{cookiePageAllowAll}"
+              expand-aria-label="$i18n{cookiePageAllowAllExpandA11yLabel}">
+            <div slot="collapse">
+              <div class="bullet-line">
+                <iron-icon icon="settings:cookie"></iron-icon>
+                <div class="secondary">$i18n{cookiePageAllowAllBulOne}</div>
+              </div>
+              <div class="bullet-line">
+                <iron-icon icon="settings:cookie"></iron-icon>
+                <div class="secondary">$i18n{cookiePageAllowAllBulTwo}</div>
+              </div>
             </div>
-            <template is="dom-if" if="[[enableFirstPartySetsUI_]]">
-              <settings-toggle-button
-                  id="firstPartySetsToggle"
-                  pref="{{prefs.privacy_sandbox.first_party_sets_enabled}}"
-                  label="$i18n{cookiePageFpsLabel}"
-                  sub-label="$i18n{cookiePageFpsSubLabel}"
-                  disabled="[[firstPartySetsToggleDisabled_(
-                      prefs.generated.cookie_primary_setting.value)]]">
-              </settings-toggle-button>
-            </template>
-          </div>
-        </settings-collapse-radio-button>
-        <settings-collapse-radio-button id="blockAll"
-            pref="[[blockAllPref_]]"
-            name="[[cookiePrimarySettingEnum_.BLOCK_ALL]]"
-            label="$i18n{cookiePageBlockAll}"
-            expand-aria-label="$i18n{cookiePageBlockAllExpandA11yLabel}">
-          <div slot="collapse">
-            <div class="bullet-line">
-              <iron-icon icon="settings:block"></iron-icon>
-              <div class="secondary">$i18n{cookiePageBlockAllBulOne}</div>
+          </settings-collapse-radio-button>
+          <settings-collapse-radio-button id="blockThirdPartyIncognito"
+              pref="[[prefs.generated.cookie_primary_setting]]"
+              name="[[cookiePrimarySettingEnum_.BLOCK_THIRD_PARTY_INCOGNITO]]"
+              label="$i18n{cookiePageBlockThirdIncognito}"
+              expand-aria-label=
+                  "$i18n{cookiePageBlockThirdIncognitoExpandA11yLabel}">
+            <div slot="collapse">
+              <div class="bullet-line">
+                <iron-icon icon="settings:cookie"></iron-icon>
+                <div class="secondary">
+                      $i18n{cookiePageBlockThirdIncognitoBulOne}
+                </div>
+              </div>
+              <div class="bullet-line">
+                <iron-icon icon="settings:block"></iron-icon>
+                <div class="secondary">
+                      $i18n{cookiePageBlockThirdIncognitoBulTwo}
+                </div>
+              </div>
             </div>
-            <div class="bullet-line">
-              <iron-icon icon="settings:block"></iron-icon>
-              <div class="secondary">$i18n{cookiePageBlockAllBulTwo}</div>
+          </settings-collapse-radio-button>
+          <settings-collapse-radio-button id="blockThirdParty"
+              pref="[[prefs.generated.cookie_primary_setting]]"
+              name="[[cookiePrimarySettingEnum_.BLOCK_THIRD_PARTY]]"
+              label="$i18n{cookiePageBlockThird}"
+              expand-aria-label="$i18n{cookiePageBlockThirdExpandA11yLabel}">
+            <div slot="collapse">
+              <div class="bullet-line">
+                <iron-icon icon="settings:cookie"></iron-icon>
+                <div class="secondary">$i18n{cookiePageBlockThirdBulOne}</div>
+              </div>
+              <div class="bullet-line">
+                <iron-icon icon="settings:block"></iron-icon>
+                <div class="secondary">$i18n{cookiePageBlockThirdBulTwo}</div>
+              </div>
+              <template is="dom-if" if="[[enableFirstPartySetsUI_]]">
+                <settings-toggle-button
+                    id="firstPartySetsToggle"
+                    pref="{{prefs.privacy_sandbox.first_party_sets_enabled}}"
+                    label="$i18n{cookiePageFpsLabel}"
+                    sub-label="$i18n{cookiePageFpsSubLabel}"
+                    disabled="[[firstPartySetsToggleDisabled_(
+                        prefs.generated.cookie_primary_setting.value)]]">
+                </settings-toggle-button>
+              </template>
             </div>
-            <div class="bullet-line one-line">
-              <iron-icon icon="settings:block"></iron-icon>
-              <div class="secondary">$i18n{cookiePageBlockAllBulThree}</div>
+          </settings-collapse-radio-button>
+          <settings-collapse-radio-button id="blockAll"
+              pref="[[blockAllPref_]]"
+              name="[[cookiePrimarySettingEnum_.BLOCK_ALL]]"
+              label="$i18n{cookiePageBlockAll}"
+              expand-aria-label="$i18n{cookiePageBlockAllExpandA11yLabel}">
+            <div slot="collapse">
+              <div class="bullet-line">
+                <iron-icon icon="settings:block"></iron-icon>
+                <div class="secondary">$i18n{cookiePageBlockAllBulOne}</div>
+              </div>
+              <div class="bullet-line">
+                <iron-icon icon="settings:block"></iron-icon>
+                <div class="secondary">$i18n{cookiePageBlockAllBulTwo}</div>
+              </div>
+              <div class="bullet-line one-line">
+                <iron-icon icon="settings:block"></iron-icon>
+                <div class="secondary">$i18n{cookiePageBlockAllBulThree}</div>
+              </div>
             </div>
-          </div>
-        </settings-collapse-radio-button>
-      </settings-radio-group>
-    </div>
-    <settings-toggle-button id="clearOnExit" class="hr"
-        pref="{{prefs.generated.cookie_session_only}}"
-        label="$i18n{cookiePageClearOnExit}"
-<if expr="not chromeos_ash">
-        sub-label="[[getClearOnExitSubLabel_()]]"
-</if>
-        on-settings-boolean-control-change="onClearOnExitChange_">
-    </settings-toggle-button>
+          </settings-collapse-radio-button>
+        </settings-radio-group>
+      </div>
+      <settings-toggle-button id="clearOnExit" class="hr"
+          pref="{{prefs.generated.cookie_session_only}}"
+          label="$i18n{cookiePageClearOnExit}"
+  <if expr="not chromeos_ash">
+          sub-label="[[getClearOnExitSubLabel_()]]"
+  </if>
+          on-settings-boolean-control-change="onClearOnExitChange_">
+      </settings-toggle-button>
+    </template>
     <settings-do-not-track-toggle id="doNotTrack"
         prefs="{{prefs}}">
     </settings-do-not-track-toggle>
@@ -169,6 +248,8 @@
     <div id="exceptionHeader">
       <h2>$i18n{siteSettingsCustomizedBehaviors}</h2>
     </div>
+    <!-- TODO(crbug.com/1378703): Update site-list element to display
+      correct exceptions -->
     <site-list id="allowExceptionsList"
         category="[[cookiesContentSettingType_]]"
         category-subtype="[[contentSetting_.ALLOW]]"
diff --git a/chrome/browser/resources/settings/privacy_page/cookies_page.ts b/chrome/browser/resources/settings/privacy_page/cookies_page.ts
index b3b2de7..2f3f84f1 100644
--- a/chrome/browser/resources/settings/privacy_page/cookies_page.ts
+++ b/chrome/browser/resources/settings/privacy_page/cookies_page.ts
@@ -21,10 +21,10 @@
 import '../controls/settings_radio_group.js';
 
 import {CrToastElement} from 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
-import {assert} from 'chrome://resources/js/assert_ts.js';
-import {focusWithoutInk} from 'chrome://resources/js/focus_without_ink.js';
 import {I18nMixin, I18nMixinInterface} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {WebUiListenerMixin, WebUiListenerMixinInterface} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
+import {assert} from 'chrome://resources/js/assert_ts.js';
+import {focusWithoutInk} from 'chrome://resources/js/focus_without_ink.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {SettingsRadioGroupElement} from '../controls/settings_radio_group.js';
@@ -34,10 +34,9 @@
 import {PrefsMixin, PrefsMixinInterface} from '../prefs/prefs_mixin.js';
 import {routes} from '../route.js';
 import {Route, RouteObserverMixin, RouteObserverMixinInterface, Router} from '../router.js';
-import {ContentSetting, ContentSettingsTypes} from '../site_settings/constants.js';
+import {ContentSetting, ContentSettingsTypes, CookieControlsMode} from '../site_settings/constants.js';
 import {CookiePrimarySetting} from '../site_settings/site_settings_prefs_browser_proxy.js';
 
-import {SettingsCollapseRadioButtonElement} from './collapse_radio_button.js';
 import {getTemplate} from './cookies_page.html.js';
 
 /**
@@ -54,11 +53,6 @@
 
 export interface SettingsCookiesPageElement {
   $: {
-    allowAll: SettingsCollapseRadioButtonElement,
-    blockAll: SettingsCollapseRadioButtonElement,
-    blockThirdPartyIncognito: SettingsCollapseRadioButtonElement,
-    blockThirdParty: SettingsCollapseRadioButtonElement,
-    primarySettingGroup: SettingsRadioGroupElement,
     toast: CrToastElement,
   };
 }
@@ -107,6 +101,12 @@
         value: CookiePrimarySetting,
       },
 
+      /** Cookie control modes for use in bindings. */
+      cookieControlsModeEnum_: {
+        type: Object,
+        value: CookieControlsMode,
+      },
+
       /**
        * Used for HTML bindings. This is defined as a property rather than
        * within the ready callback, because the value needs to be available
@@ -149,12 +149,18 @@
         type: Boolean,
         value: () => loadTimeData.getBoolean('firstPartySetsUIEnabled'),
       },
+
+      isPrivacySandboxSettings4_: {
+        type: Boolean,
+        value: () => loadTimeData.getBoolean('isPrivacySandboxSettings4'),
+      },
     };
   }
 
   static get observers() {
     return [`onGeneratedPrefsUpdated_(prefs.generated.cookie_session_only,
-        prefs.generated.cookie_primary_setting)`];
+        prefs.generated.cookie_primary_setting,
+        prefs.generated.cookie_default_content_setting)`];
   }
 
   searchTerm: string;
@@ -162,6 +168,7 @@
   private exceptionListsReadOnly_: boolean;
   private blockAllPref_: chrome.settingsPrivate.PrefObject;
   focusConfig: FocusConfig;
+  private isPrivacySandboxSettings4_: boolean;
 
   private metricsBrowserProxy_: MetricsBrowserProxy =
       MetricsBrowserProxyImpl.getInstance();
@@ -202,6 +209,20 @@
   }
 
   private onGeneratedPrefsUpdated_() {
+    if (this.isPrivacySandboxSettings4_) {
+      // If the default cookie content setting is managed, the exception lists
+      // should be disabled. `profile.cookie_controls_mode` doesn't control the
+      // ability to create exceptions but the content setting does.
+      const defaultContentSettingPref =
+          this.getPref('generated.cookie_default_content_setting');
+      this.exceptionListsReadOnly_ = defaultContentSettingPref.enforcement ===
+          chrome.settingsPrivate.Enforcement.ENFORCED;
+      return;
+    }
+
+    // TODO(crbug.com/1378703): Clean up after the feature is launched and these
+    // generated preferences are deprecated. New page won't have 'session only'
+    // controls.
     const sessionOnlyPref = this.getPref('generated.cookie_session_only');
 
     // If the clear on exit toggle is managed this implies a content setting
@@ -224,11 +245,49 @@
         }));
   }
 
+  private onCookieControlsModeChanged_() {
+    // TODO(crbug.com/1378703): Use this.$.primarySettingGroup after the feature
+    // is launched and element isn't in dom-if anymore.
+    const primarySettingGroup: SettingsRadioGroupElement =
+        this.shadowRoot!.querySelector('#primarySettingGroup')!;
+
+    const selection = Number(primarySettingGroup.selected);
+    // TODO(crbug.com/1378703): Record metrics based on the selection.
+
+    // If this change resulted in the user now blocking 3P cookies where they
+    // previously were not, and any of privacy sandbox APIs are enabled,
+    // the privacy sandbox toast should be shown.
+    const currentCookieControlsMode =
+        this.getPref('profile.cookie_controls_mode').value;
+    const areAnyPrivacySandboxApisEnabled =
+        this.getPref('privacy_sandbox.m1.topics_enabled').value ||
+        this.getPref('privacy_sandbox.m1.fledge_enabled').value ||
+        this.getPref('privacy_sandbox.m1.fledge_enabled').value;
+    const areThirdPartyCookiesAllowed =
+        currentCookieControlsMode === CookieControlsMode.OFF ||
+        currentCookieControlsMode === CookieControlsMode.INCOGNITO_ONLY;
+
+    if (areAnyPrivacySandboxApisEnabled && areThirdPartyCookiesAllowed &&
+        selection === CookieControlsMode.BLOCK_THIRD_PARTY) {
+      if (!loadTimeData.getBoolean('isPrivacySandboxRestricted')) {
+        this.$.toast.show();
+      }
+      this.metricsBrowserProxy_.recordAction(
+          'Settings.PrivacySandbox.Block3PCookies');
+    } else {
+      this.$.toast.hide();
+    }
+
+    primarySettingGroup.sendPrefChange();
+  }
+
   /**
    * Record interaction metrics for the primary cookie radio setting.
    */
   private onCookiePrimarySettingChanged_() {
-    const selection = Number(this.$.primarySettingGroup.selected);
+    const primarySettingGroup: SettingsRadioGroupElement =
+        this.shadowRoot!.querySelector('#primarySettingGroup')!;
+    const selection = Number(primarySettingGroup.selected);
     if (selection === CookiePrimarySetting.ALLOW_ALL) {
       this.metricsBrowserProxy_.recordSettingsPageHistogram(
           PrivacyElementInteractions.COOKIES_ALL);
@@ -268,7 +327,7 @@
       this.$.toast.hide();
     }
 
-    this.$.primarySettingGroup.sendPrefChange();
+    primarySettingGroup.sendPrefChange();
   }
 
   private onClearOnExitChange_() {
@@ -290,12 +349,18 @@
     this.metricsBrowserProxy_.recordAction(
         'Settings.PrivacySandbox.OpenedFromCookiesPageToast');
     this.$.toast.hide();
+    // TODO(crbug.com/1378703): Open new privacy sandbox settings page.
     // TODO(crbug/1159942): Replace this with an ordinary OpenWindowProxy call.
     this.shadowRoot!.querySelector<HTMLAnchorElement>(
                         '#privacySandboxLink')!.click();
   }
 
   private firstPartySetsToggleDisabled_() {
+    if (this.isPrivacySandboxSettings4_) {
+      return this.getPref('profile.cookie_controls_mode').value !==
+          CookieControlsMode.BLOCK_THIRD_PARTY;
+    }
+
     return this.getPref('generated.cookie_primary_setting').value !==
         CookiePrimarySetting.BLOCK_THIRD_PARTY;
   }
diff --git a/chrome/browser/resources/signin/BUILD.gn b/chrome/browser/resources/signin/BUILD.gn
index c0e23253..75db7a9 100644
--- a/chrome/browser/resources/signin/BUILD.gn
+++ b/chrome/browser/resources/signin/BUILD.gn
@@ -24,6 +24,7 @@
     "sync_confirmation/images/sync_confirmation_refreshed_illustration.svg",
     "sync_confirmation/images/sync_confirmation_signin_intercept_illustration.svg",
     "sync_confirmation/images/sync_confirmation_signin_intercept_illustration_dark.svg",
+    "sync_confirmation/images/tangible_sync_illustration.svg",
   ]
   if (!is_chromeos_ash) {
     static_files += [
diff --git a/chrome/browser/resources/signin/sync_confirmation/images/tangible_sync_illustration.svg b/chrome/browser/resources/signin/sync_confirmation/images/tangible_sync_illustration.svg
new file mode 100644
index 0000000..1ea0c2f
--- /dev/null
+++ b/chrome/browser/resources/signin/sync_confirmation/images/tangible_sync_illustration.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 328 68" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m41.306 63.052 53.619-14.437" stroke="#E8EAED" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M80.459 13.388 36.304 25.276a1.304 1.304 0 0 0-.918 1.597l7.475 28.031a1.297 1.297 0 0 0 1.59.922l44.154-11.888a1.304 1.304 0 0 0 .918-1.597L82.048 14.31a1.297 1.297 0 0 0-1.59-.921Z" fill="#fff" stroke="#E8EAED" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M235.629 19.627a5.782 5.782 0 0 0 .319-6.393 5.748 5.748 0 0 0-1.5-1.667l-9.202-6.873a5.725 5.725 0 0 0-4.262-1.09 5.714 5.714 0 0 0-3.776 2.264 5.784 5.784 0 0 0-.312 6.407c.389.65.902 1.216 1.51 1.666l9.202 6.873a5.72 5.72 0 0 0 4.255 1.076 5.743 5.743 0 0 0 3.766-2.263Z" fill="#FBBC04"/><path d="m119.528 1.912-4.537 10.904a1.378 1.378 0 0 1-1.092.836 1.37 1.37 0 0 1-1.267-.532l-7.129-9.4a1.383 1.383 0 0 1 .917-2.205L118.085.011a1.367 1.367 0 0 1 1.268.532 1.388 1.388 0 0 1 .175 1.37Z" fill="#34A853"/><path d="M7.457 50.18.702 58.895c-.387.5-.298 1.223.2 1.613l8.674 6.786c.498.39 1.216.3 1.604-.2l6.754-8.716c.388-.5.299-1.222-.2-1.612l-8.673-6.787a1.14 1.14 0 0 0-1.604.2Z" fill="#E8EAED"/><path d="m270.32 9.944-17.684 46.269a2.219 2.219 0 0 0 1.268 2.862l18.256 7.045a2.201 2.201 0 0 0 2.848-1.274l17.684-46.269a2.219 2.219 0 0 0-1.268-2.862L273.168 8.67a2.2 2.2 0 0 0-2.848 1.274Z" fill="#fff" stroke="#E8EAED" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M273.05 13.501c.684 0 1.239-.557 1.239-1.245 0-.688-.555-1.246-1.239-1.246-.685 0-1.239.558-1.239 1.246s.554 1.245 1.239 1.245Z" fill="#E8EAED"/><path d="m267.403 35.037-3.381 8.849c-.272.711.081 1.51.789 1.783l8.806 3.398a1.371 1.371 0 0 0 1.774-.794l3.382-8.848a1.382 1.382 0 0 0-.79-1.783l-8.805-3.398a1.372 1.372 0 0 0-1.775.793Z" fill="#4285F4"/><path d="M321.079 58.573c3.822 0 6.921-3.113 6.921-6.954 0-3.84-3.099-6.955-6.921-6.955s-6.921 3.114-6.921 6.955c0 3.84 3.099 6.954 6.921 6.954Z" fill="#E8EAED"/><path d="m65.84 28.687 2.46 9.146a1.389 1.389 0 0 0 1.688.984l9.134-2.385a1.365 1.365 0 0 0 .972-1.679l-2.459-9.146a1.389 1.389 0 0 0-1.688-.984l-9.135 2.385a1.364 1.364 0 0 0-.971 1.679Z" fill="#4285F4"/></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/signin/sync_confirmation/sync_confirmation_app.html b/chrome/browser/resources/signin/sync_confirmation/sync_confirmation_app.html
index e332907..9e9851e 100644
--- a/chrome/browser/resources/signin/sync_confirmation/sync_confirmation_app.html
+++ b/chrome/browser/resources/signin/sync_confirmation/sync_confirmation_app.html
@@ -2,6 +2,11 @@
   :host {
     --banner-size: auto;
     --footer-margin: 24px;
+    --tangible-sync-illustration-url: url(./images/tangible_sync_illustration.svg);
+    --sync-benefits-list-color: var(--google-grey-50);
+    --sync-benefits-list-text-color: black;
+    --sync-benefit-icon-color: var(--google-blue-600);
+    --sync-info-title-color: var(--google-grey-800);
     color: var(--cr-primary-text-color);
     display: block;
   }
@@ -216,6 +221,11 @@
     font-weight: normal;
   }
 
+  #tangible-sync-button-container cr-button {
+    font-size: 0.75rem;
+    min-width: 111px;
+  }
+
   #headerContainer.signin-intercept-design {
     background: center 36px no-repeat
         url(./images/sync_confirmation_signin_intercept_illustration.svg);
@@ -255,19 +265,25 @@
     margin: auto;
   }
 
+  #tangible-sync-button-container.action-container,
   .signin-intercept-design .action-container {
     --action-container-padding: 24px;
     column-gap: 12px;
   }
 
   #contentContainer.tangible-sync {
-    align-items: center;
+    --action-container-padding: 24px;
+    --sync-info-desc-bottom-margin: 48px;
+    --container-bottom-padding: calc(var(--sync-info-desc-bottom-margin) +
+        var(--cr-button-height) + var(--action-container-padding));
+    align-items: stretch;
     display: flex;
     flex-direction: column;
     margin: auto;
-    /* Saves space for button row. */
-    padding: 32px 0 96px;
-    width: 500px;
+    max-width: 500px;
+    /* Bottom padding is needed to save space for the button row in
+    the modal dialog view. */
+    padding: 32px 0 var(--container-bottom-padding);
   }
 
   .tangible-sync #avatarContainer {
@@ -285,29 +301,31 @@
   }
 
   .tangible-sync #syncConfirmationHeading {
-    font-size: 2.92em;
+    font-family: 'Google Sans', arial, sans-serif;
+    font-size: 1.25rem;
     font-weight: 500;
-    line-height: 44px;
-    margin: 24px 0 16px;
+    line-height: 24px;
+    margin: 16px 0 12px;
   }
 
   #syncInfoTitle {
-    font-size: 1.6em;
+    color: var(--sync-info-title-color);
+    font-size: 0.94rem;
     line-height: 24px;
   }
 
   #syncBenefitsList {
-    background: var(--google-grey-50);
+    background: var(--sync-benefits-list-color);
     border-radius: 24px;
+    color: var(--sync-benefits-list-text-color);
     display: flex;
     flex-direction: column;
-    font-size: 1.05em;
+    font-size: 0.81rem;
     font-weight: 400;
     line-height: 20px;
-    margin: 24px 0 16px;
-    padding: 24px;
+    margin: 16px 38px;
+    padding: 20px 24px;
     row-gap: 16px;
-    width: 452px;
   }
 
   .sync-benefit {
@@ -319,14 +337,25 @@
   }
 
   .sync-benefit-icon {
-    color: var(--google-blue-600);
+    color: var(--sync-benefit-icon-color);
     height: 24px;
     width: 24px;
   }
 
   #syncInfoDesc {
-    font-size: 0.95em;
+    font-size: 0.75rem;
     line-height: 20px;
+    margin: 0 38px;
+  }
+
+  #tangible-sync-illustration {
+    content: var(--tangible-sync-illustration-url);
+    height: 68px;
+    left: 50%;
+    position: absolute;
+    top: 40px;
+    transform: translateX(-50%);
+    width: 328px;
   }
 
   @media (prefers-color-scheme: dark) {
@@ -483,6 +512,7 @@
 </template>
 
 <template is="dom-if" if="[[isTangibleSync_]]">
+  <img id="tangible-sync-illustration">
   <div id="contentContainer" class="tangible-sync">
     <div id="avatarContainer">
       <img id="avatar" alt="" src="[[accountImageSrc_]]">
@@ -493,7 +523,7 @@
     <h1 id="syncConfirmationHeading" consent-description>
       $i18n{syncConfirmationTitle}
     </h1>
-    <div id="syncInfoTitle" class="secondary" consent-description>
+    <div id="syncInfoTitle" consent-description>
       $i18n{syncConfirmationSyncInfoTitle}
     </div>
     <div id="syncBenefitsList">
@@ -514,7 +544,7 @@
 
   <div class="action-row">
     <paper-spinner-lite active="[[anyButtonClicked_]]"></paper-spinner-lite>
-    <div id="buttonsContainer" class="action-container">
+    <div id="tangible-sync-button-container" class="action-container">
       <cr-button id="confirmButton" class="action-button" on-click="onConfirm_"
           disabled="[[anyButtonClicked_]]" consent-confirmation
           autofocus="[[isModalDialog_]]">
diff --git a/chrome/browser/segmentation_platform/segmentation_platform_profile_observer_unittest.cc b/chrome/browser/segmentation_platform/segmentation_platform_profile_observer_unittest.cc
index 1c97e3a..0cd70de 100644
--- a/chrome/browser/segmentation_platform/segmentation_platform_profile_observer_unittest.cc
+++ b/chrome/browser/segmentation_platform/segmentation_platform_profile_observer_unittest.cc
@@ -39,6 +39,12 @@
               (const std::string&,
                scoped_refptr<InputContext>,
                SegmentSelectionCallback));
+  MOCK_METHOD(void,
+              GetClassificationResult,
+              (const std::string&,
+               const PredictionOptions&,
+               scoped_refptr<InputContext>,
+               ClassificationResultCallback));
   MOCK_METHOD(void, EnableMetrics, (bool));
   MOCK_METHOD(void, GetServiceStatus, ());
   MOCK_METHOD(bool, IsPlatformInitialized, ());
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.cc b/chrome/browser/signin/dice_web_signin_interceptor.cc
index bfffe26..79b94f0 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor.cc
@@ -832,11 +832,11 @@
 
 void DiceWebSigninInterceptor::RecordProfileCreationDeclined(
     const std::string& email) {
-  DictionaryPrefUpdate update(profile_->GetPrefs(),
+  ScopedDictPrefUpdate update(profile_->GetPrefs(),
                               kProfileCreationInterceptionDeclinedPref);
   std::string key = GetPersistentEmailHash(email);
-  absl::optional<int> declined_count = update->FindIntKey(key);
-  update->SetIntKey(key, declined_count.value_or(0) + 1);
+  absl::optional<int> declined_count = update->FindInt(key);
+  update->Set(key, declined_count.value_or(0) + 1);
 }
 
 bool DiceWebSigninInterceptor::HasUserDeclinedProfileCreation(
diff --git a/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn b/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
index c635bf68..e9b5d32 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
+++ b/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
@@ -57,9 +57,6 @@
     "//ui/android:ui_java_resources",
   ]
   sources = [
-    "java/res/drawable-v24/fast_checkout_item_background_bottom.xml",
-    "java/res/drawable-v24/fast_checkout_item_background_top.xml",
-    "java/res/drawable-v24/fast_checkout_list_view_background_middle.xml",
     "java/res/drawable/fast_checkout_item_background_bottom.xml",
     "java/res/drawable/fast_checkout_item_background_top.xml",
     "java/res/drawable/fast_checkout_list_view_background_middle.xml",
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable-v24/fast_checkout_item_background_bottom.xml b/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable-v24/fast_checkout_item_background_bottom.xml
deleted file mode 100644
index c327650..0000000
--- a/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable-v24/fast_checkout_item_background_bottom.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2022 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:shape="rectangle"
-    app:surfaceElevation="@dimen/default_elevation_1">
-    <!-- TODO(crbug.com/1354596): Merge background resource for TTF Payments/Passwords and FC. -->
-    <corners android:topLeftRadius="@dimen/fast_checkout_inner_corner_radius"
-    android:topRightRadius="@dimen/fast_checkout_inner_corner_radius"
-    android:bottomLeftRadius="@dimen/fast_checkout_outer_corner_radius"
-    android:bottomRightRadius="@dimen/fast_checkout_outer_corner_radius"/>
-</org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable-v24/fast_checkout_item_background_top.xml b/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable-v24/fast_checkout_item_background_top.xml
deleted file mode 100644
index 5dc489f..0000000
--- a/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable-v24/fast_checkout_item_background_top.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2022 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:shape="rectangle"
-    app:surfaceElevation="@dimen/default_elevation_1">
-    <!-- TODO(crbug.com/1354596): Merge background resource for TTF Payments/Passwords and FC. -->
-    <corners android:topLeftRadius="@dimen/fast_checkout_outer_corner_radius"
-    android:topRightRadius="@dimen/fast_checkout_outer_corner_radius"
-    android:bottomLeftRadius="@dimen/fast_checkout_inner_corner_radius"
-    android:bottomRightRadius="@dimen/fast_checkout_inner_corner_radius"/>
-</org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable-v24/fast_checkout_list_view_background_middle.xml b/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable-v24/fast_checkout_list_view_background_middle.xml
deleted file mode 100644
index de16b1b..0000000
--- a/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable-v24/fast_checkout_list_view_background_middle.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2022 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:shape="rectangle"
-    app:surfaceElevation="@dimen/default_elevation_1">
-    <corners android:radius="@dimen/fast_checkout_inner_corner_radius"/>
-</org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable/fast_checkout_item_background_bottom.xml b/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable/fast_checkout_item_background_bottom.xml
index 3b2077c5..c327650 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable/fast_checkout_item_background_bottom.xml
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable/fast_checkout_item_background_bottom.xml
@@ -4,14 +4,14 @@
 Use of this source code is governed by a BSD-style license that can be
 found in the LICENSE file.
 -->
-
-<shape
+<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-  <!-- TODO(crbug.com/1354596): Merge background resource for TTF Payments/Passwords and FC. -->
-  <solid android:color="@color/default_bg_color_elev_1_baseline" />
-  <corners android:topLeftRadius="@dimen/fast_checkout_inner_corner_radius"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:shape="rectangle"
+    app:surfaceElevation="@dimen/default_elevation_1">
+    <!-- TODO(crbug.com/1354596): Merge background resource for TTF Payments/Passwords and FC. -->
+    <corners android:topLeftRadius="@dimen/fast_checkout_inner_corner_radius"
     android:topRightRadius="@dimen/fast_checkout_inner_corner_radius"
     android:bottomLeftRadius="@dimen/fast_checkout_outer_corner_radius"
     android:bottomRightRadius="@dimen/fast_checkout_outer_corner_radius"/>
-</shape>
+</org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable/fast_checkout_item_background_top.xml b/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable/fast_checkout_item_background_top.xml
index 23687fa..5dc489f 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable/fast_checkout_item_background_top.xml
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable/fast_checkout_item_background_top.xml
@@ -4,14 +4,14 @@
 Use of this source code is governed by a BSD-style license that can be
 found in the LICENSE file.
 -->
-
-<shape
+<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-  <!-- TODO(crbug.com/1354596): Merge background resource for TTF Payments/Passwords and FC. -->
-  <solid android:color="@color/default_bg_color_elev_1_baseline" />
-  <corners android:topLeftRadius="@dimen/fast_checkout_outer_corner_radius"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:shape="rectangle"
+    app:surfaceElevation="@dimen/default_elevation_1">
+    <!-- TODO(crbug.com/1354596): Merge background resource for TTF Payments/Passwords and FC. -->
+    <corners android:topLeftRadius="@dimen/fast_checkout_outer_corner_radius"
     android:topRightRadius="@dimen/fast_checkout_outer_corner_radius"
     android:bottomLeftRadius="@dimen/fast_checkout_inner_corner_radius"
     android:bottomRightRadius="@dimen/fast_checkout_inner_corner_radius"/>
-</shape>
+</org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable/fast_checkout_list_view_background_middle.xml b/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable/fast_checkout_list_view_background_middle.xml
index 3cdd2555..de16b1b 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable/fast_checkout_list_view_background_middle.xml
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/res/drawable/fast_checkout_list_view_background_middle.xml
@@ -4,9 +4,10 @@
 Use of this source code is governed by a BSD-style license that can be
 found in the LICENSE file.
 -->
-<shape
+<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-  <solid android:color="@color/default_bg_color_elev_1_baseline" />
-  <corners android:radius="@dimen/fast_checkout_inner_corner_radius"/>
-</shape>
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:shape="rectangle"
+    app:surfaceElevation="@dimen/default_elevation_1">
+    <corners android:radius="@dimen/fast_checkout_inner_corner_radius"/>
+</org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
index b082241..1cb55911 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
@@ -7,6 +7,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -1136,6 +1137,7 @@
         mOmniboxPrerender.clear(profile);
     }
 
+    @SuppressLint("GestureBackNavigation")
     private boolean handleKeyEvent(View view, int keyCode, KeyEvent event) {
         boolean isRtl = view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
         if (mAutocompleteCoordinator.handleKeyEvent(keyCode, event)) {
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
index b0e58f7..ee012d2 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
@@ -168,6 +168,7 @@
     private boolean mIsUsingBrandColor;
     private boolean mShouldShowOmniboxInOverviewMode;
     private boolean mIsShowingTabSwitcher;
+    private boolean mIsShowingStartSurface;
     @StartSurfaceState
     private int mStartSurfaceState;
 
@@ -525,10 +526,12 @@
     public boolean isInOverviewAndShowingOmnibox() {
         if (!mShouldShowOmniboxInOverviewMode) return false;
 
-        return mLayoutStateProvider != null && mIsShowingTabSwitcher
-                && (mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE
-                        || mStartSurfaceState == StartSurfaceState.SHOWING_HOMEPAGE
-                        || mStartSurfaceState == StartSurfaceState.SHOWING_START);
+        return mLayoutStateProvider != null
+                && (mIsShowingStartSurface
+                        || mIsShowingTabSwitcher
+                                && (mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE
+                                        || mStartSurfaceState == StartSurfaceState.SHOWING_HOMEPAGE
+                                        || mStartSurfaceState == StartSurfaceState.SHOWING_START));
     }
 
     /**
@@ -808,11 +811,16 @@
     }
 
     /**
-     * Set whether tab switcher is showing or not and notify changes.
-     * @param isShowingTabSwitcher Whether tab switcher is showing or not.
+     * Set whether the start surface is showing or not and notify changes.
+     * TODO(1347089): Remove {@link isShowingTabSwitcher} when the Start surface refactoring is
+     * enabled by default.
+     * @param isShowingTabSwitcher Whether tab switcher layout is showing or not.
+     * @param isShowingStartSurface Whether Start surface layout is showing or not.
      */
-    public void setIsShowingTabSwitcher(boolean isShowingTabSwitcher) {
+    public void updateForNonStaticLayout(
+            boolean isShowingTabSwitcher, boolean isShowingStartSurface) {
         mIsShowingTabSwitcher = isShowingTabSwitcher;
+        mIsShowingStartSurface = isShowingStartSurface;
         notifyTitleChanged();
         notifyUrlChanged();
         notifyPrimaryColorChanged();
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index 621c04c..c46f2e7 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -1944,6 +1944,12 @@
         animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
         animators.add(animator);
 
+        animator = ObjectAnimator.ofFloat(mHomeButton, TRANSLATION_X,
+                MathUtils.flipSignIf(-mHomeButton.getWidth() * density, isRtl));
+        animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
+        animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
+        animators.add(animator);
+
         if (mToggleTabStackButton != null) {
             animator = ObjectAnimator.ofFloat(
                     mToggleTabStackButton, TRANSLATION_X, toolbarButtonTranslationX);
@@ -1977,6 +1983,11 @@
         animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
         animators.add(animator);
 
+        animator = ObjectAnimator.ofFloat(mHomeButton, TRANSLATION_X, 0);
+        animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
+        animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
+        animators.add(animator);
+
         if (mToggleTabStackButton != null) {
             animator = ObjectAnimator.ofFloat(mToggleTabStackButton, TRANSLATION_X, 0);
             animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
diff --git a/chrome/browser/ui/ash/media_client_impl.cc b/chrome/browser/ui/ash/media_client_impl.cc
index 1c52765..d8c3e793 100644
--- a/chrome/browser/ui/ash/media_client_impl.cc
+++ b/chrome/browser/ui/ash/media_client_impl.cc
@@ -674,18 +674,15 @@
   camera_switch_notification_shown_timestamp_ = base::TimeTicks::Now();
 
   const std::u16string device_name_u16 = base::UTF8ToUTF16(device_name);
-  std::u16string message;
+  const std::u16string message = l10n_util::GetStringFUTF16(
+      IDS_CAMERA_PRIVACY_SWITCH_ON_NOTIFICATION_MESSAGE, device_name_u16);
+  // TODO(b/262380194) Show a message which includes the app name once we can
+  // reliably determine which app is being used by what camera that has an
+  // active privacy switch.
   if (const std::string app_name = GetNameOfAppAccessingCameraInternal();
       !app_name.empty()) {
-    message = l10n_util::GetStringFUTF16(
-        IDS_CAMERA_PRIVACY_SWITCH_ON_NOTIFICATION_MESSAGE_WITH_APP_NAME,
-        base::UTF8ToUTF16(app_name), device_name_u16);
-
     last_device_for_app_.insert_or_assign(
         app_name, std::make_pair(device_id, device_name));
-  } else {
-    message = l10n_util::GetStringFUTF16(
-        IDS_CAMERA_PRIVACY_SWITCH_ON_NOTIFICATION_MESSAGE, device_name_u16);
   }
 
   const std::string notification_id =
diff --git a/chrome/browser/ui/ash/media_client_impl_unittest.cc b/chrome/browser/ui/ash/media_client_impl_unittest.cc
index d35ea1b..d845808 100644
--- a/chrome/browser/ui/ash/media_client_impl_unittest.cc
+++ b/chrome/browser/ui/ash/media_client_impl_unittest.cc
@@ -459,6 +459,8 @@
       MakeCapabilityAccess(app1_id, false);
   const apps::CapabilityAccessUpdate capability_access_update =
       MakeCapabilityAccessUpdate(capability_access.get());
+  const char* generic_notification_message_prefix =
+      "An app is trying to access";
 
   user_manager_.AddUser(account_id_);
   ASSERT_TRUE(user_manager::UserManager::Get()->GetActiveUser());
@@ -485,85 +487,31 @@
   ShowCameraOffNotification("device_id", "device_name");
   EXPECT_EQ(notification_display_service->NumberOfActiveNotifications(), 1u);
   EXPECT_TRUE(notification_display_service->HasNotificationMessageContaining(
-      app1_name));
+      generic_notification_message_prefix));
   EXPECT_EQ(notification_display_service->show_called_times(), 1u);
 
   // Start a second app that's also using the camera.
   LaunchApp(app2_id, app2_name, true);
-  // Since there is no eager update required it's fine to show the notification
-  // with the previous app name.
   EXPECT_TRUE(notification_display_service->HasNotificationMessageContaining(
-      app1_name));
+      generic_notification_message_prefix));
   EXPECT_EQ(notification_display_service->show_called_times(), 1u);
 
   // Launching an App with `use_camera=false` is like minimizing/closing the
   // app for the purpose of this test.
   LaunchApp(app1_id, app1_name, false);
-  // As the state change of the first application hasn't propagated to the
-  // observer yet the outdated notification should still be around.
-  EXPECT_TRUE(notification_display_service->HasNotificationMessageContaining(
-      app1_name));
 
   OnCapabilityAccessUpdate(capability_access_update);
 
-  // After the observer reacted to the change the notification with the updated
-  // app name should be visible.
+  // After the observer reacted to the change the notification should not pop up
+  // again but update the message body if necessary (which it isn't currently).
   EXPECT_EQ(notification_display_service->show_called_times(), 2u);
-  EXPECT_FALSE(notification_display_service->HasNotificationMessageContaining(
-      app1_name));
   EXPECT_TRUE(notification_display_service->HasNotificationMessageContaining(
-      app2_name));
+      generic_notification_message_prefix));
   ASSERT_EQ(notification_display_service->NumberOfActiveNotifications(), 1u);
   EXPECT_EQ(notification_display_service->GetActiveNotifications()
                 .front()
                 ->priority(),
             message_center::NotificationPriority::LOW_PRIORITY);
-
-  // Again no eager updating of the notification when launching, stopping and
-  // notifying the observer while the previously active app is still using the
-  // camera.
-  const char* app3_id = "app3";
-  const char* app3_name = "Ignored";
-  const apps::CapabilityAccessPtr ignored_capability_access =
-      MakeCapabilityAccess(app3_id, false);
-  const apps::CapabilityAccessUpdate ignored_capability_access_update =
-      MakeCapabilityAccessUpdate(ignored_capability_access.get());
-
-  LaunchApp(app3_id, app3_name, true);
-  LaunchApp(app3_id, app3_name, false);
-  OnCapabilityAccessUpdate(ignored_capability_access_update);
-  EXPECT_EQ(notification_display_service->show_called_times(), 2u);
-  EXPECT_EQ(notification_display_service->NumberOfActiveNotifications(), 1u);
-  EXPECT_FALSE(notification_display_service->HasNotificationMessageContaining(
-      app3_name));
-  EXPECT_TRUE(notification_display_service->HasNotificationMessageContaining(
-      app2_name));
-
-  // App that's missing from the app registry cache.
-  const char* app4_id = "app4";
-  const apps::CapabilityAccessPtr missing_capability_access =
-      MakeCapabilityAccess(app4_id, false);
-  const apps::CapabilityAccessUpdate missing_capability_access_update =
-      MakeCapabilityAccessUpdate(missing_capability_access.get());
-
-  OnCapabilityAccessUpdate(missing_capability_access_update);
-  EXPECT_EQ(notification_display_service->show_called_times(), 2u);
-  EXPECT_EQ(notification_display_service->NumberOfActiveNotifications(), 1u);
-  EXPECT_TRUE(notification_display_service->HasNotificationMessageContaining(
-      app2_name));
-
-  // In case the capability access is dropped for the last client before the
-  // active client callback from the remote finishes we also want to hide the
-  // notification and prevent blinking.
-  const apps::CapabilityAccessPtr app2_capability_access =
-      MakeCapabilityAccess(app2_id, false);
-  const apps::CapabilityAccessUpdate app2_capability_access_update =
-      MakeCapabilityAccessUpdate(app2_capability_access.get());
-
-  LaunchAppUpdateActiveClientCount(app2_id, app2_name, false, 0);
-  OnCapabilityAccessUpdate(app2_capability_access_update);
-  EXPECT_EQ(notification_display_service->show_called_times(), 2u);
-  EXPECT_EQ(notification_display_service->NumberOfActiveNotifications(), 0u);
 }
 
 TEST_F(MediaClientAppUsingCameraInBrowserEnvironmentTest,
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
index 74b470e..a35409c 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
@@ -537,6 +537,11 @@
     provider->Start();
 
     system_web_app_manager->ScheduleStart();
+
+    base::RunLoop run_loop;
+    provider->on_external_managers_synchronized().Post(FROM_HERE,
+                                                       run_loop.QuitClosure());
+    run_loop.Run();
   }
 
   // Note that this resets previously installed SWAs.
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index f6c547b2..a839ea0 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -1987,6 +1987,10 @@
       return blink::mojom::DisplayMode::kWindowControlsOverlay;
     }
 
+    if (app_controller_ && app_controller_->AppUsesTabbed()) {
+      return blink::mojom::DisplayMode::kTabbed;
+    }
+
     if (app_controller_ && app_controller_->AppUsesBorderlessMode() &&
         window_->IsBorderlessModeEnabled()) {
       return blink::mojom::DisplayMode::kBorderless;
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 52940d4..6d900345 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -1190,8 +1190,9 @@
                                      &title)) {
     return false;
   }
-  model->AddEntry(url, base::UTF16ToUTF8(title),
-                  reading_list::EntrySource::ADDED_VIA_CURRENT_APP);
+  model->AddOrReplaceEntry(url, base::UTF16ToUTF8(title),
+                           reading_list::EntrySource::ADDED_VIA_CURRENT_APP,
+                           /*estimated_read_time=*/base::TimeDelta());
   browser->window()->MaybeShowFeaturePromo(
       feature_engagement::kIPHReadingListDiscoveryFeature);
   base::UmaHistogramEnumeration(
@@ -1211,7 +1212,7 @@
   const ReadingListEntry* entry = model->GetEntryByURL(url);
   // Mark current tab as read.
   if (entry && !entry->IsRead())
-    model->SetReadStatus(url, true);
+    model->SetReadStatusIfExists(url, true);
   return entry != nullptr;
 }
 
diff --git a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
index d6ddb6c..a0d488113 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
@@ -3207,7 +3207,7 @@
       InstallWebAppWithProtocolHandlers({protocol_handler}, {file_handler});
 
   // Skip the file handler dialog by simulating prior user approval of the API.
-  provider()->sync_bridge().SetAppFileHandlerApprovalState(
+  provider()->sync_bridge_unsafe().SetAppFileHandlerApprovalState(
       app_id, web_app::ApiApprovalState::kAllowed);
 
   // Pass a file:// url on the command line.
diff --git a/chrome/browser/ui/views/dropdown_bar_host.cc b/chrome/browser/ui/views/dropdown_bar_host.cc
index cf671ff..8d1c065f 100644
--- a/chrome/browser/ui/views/dropdown_bar_host.cc
+++ b/chrome/browser/ui/views/dropdown_bar_host.cc
@@ -53,7 +53,7 @@
   views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL);
   params.delegate = this;
   params.name = "DropdownBarHost";
-  params.parent = browser_view_->GetWidget()->GetNativeView();
+  params.parent = browser_view_->GetWidgetForAnchoring()->GetNativeView();
   params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
 #if BUILDFLAG(IS_MAC)
   params.activatable = views::Widget::InitParams::Activatable::kYes;
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index a11cca5..457e24d 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -1270,6 +1270,15 @@
   return 1.f;
 }
 
+views::Widget* BrowserView::GetWidgetForAnchoring() {
+#if BUILDFLAG(IS_MAC)
+  if (UsesImmersiveFullscreenMode()) {
+    return IsFullscreen() ? overlay_widget_.get() : GetWidget();
+  }
+#endif
+  return GetWidget();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // BrowserView, BrowserWindow implementation:
 
@@ -3972,10 +3981,9 @@
   // TODO(https://crbug.com/1036519): Remove BrowserViewLayout dependence on
   // Widget and move to the constructor.
   SetLayoutManager(std::make_unique<BrowserViewLayout>(
-      std::make_unique<BrowserViewLayoutDelegateImpl>(this),
-      GetWidget()->GetNativeView(), this, top_container_,
-      tab_strip_region_view_, tabstrip_, toolbar_, infobar_container_,
-      contents_container_, side_search_side_panel_,
+      std::make_unique<BrowserViewLayoutDelegateImpl>(this), this,
+      top_container_, tab_strip_region_view_, tabstrip_, toolbar_,
+      infobar_container_, contents_container_, side_search_side_panel_,
       left_aligned_side_panel_separator_, unified_side_panel_,
       right_aligned_side_panel_separator_, lens_side_panel_,
       immersive_mode_controller_.get(), contents_separator_));
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index 79911e54..97f6f59 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -357,6 +357,11 @@
   // Returns the current shown ratio of the top browser controls.
   float GetTopControlsSlideBehaviorShownRatio() const;
 
+  // Returns the widget for anchoring bubbles and dialogs.
+  // This returns BrowserFrame except on fullscreen macOS where the toolbar is
+  // hosted in an OverlayWidget.
+  views::Widget* GetWidgetForAnchoring();
+
   // See ImmersiveModeController for description.
   ImmersiveModeController* immersive_mode_controller() const {
     return immersive_mode_controller_.get();
diff --git a/chrome/browser/ui/views/frame/browser_view_layout.cc b/chrome/browser/ui/views/frame/browser_view_layout.cc
index 472db7e..e1b2314 100644
--- a/chrome/browser/ui/views/frame/browser_view_layout.cc
+++ b/chrome/browser/ui/views/frame/browser_view_layout.cc
@@ -124,7 +124,8 @@
 
  private:
   gfx::NativeView GetHostView() const override {
-    return browser_view_layout_->host_view_;
+    return browser_view_layout_->browser_view_->GetWidgetForAnchoring()
+        ->GetNativeView();
   }
 
   // Add/remove observer.
@@ -145,7 +146,6 @@
 
 BrowserViewLayout::BrowserViewLayout(
     std::unique_ptr<BrowserViewLayoutDelegate> delegate,
-    gfx::NativeView host_view,
     BrowserView* browser_view,
     views::View* top_container,
     TabStripRegionView* tab_strip_region_view,
@@ -161,7 +161,6 @@
     ImmersiveModeController* immersive_mode_controller,
     views::View* contents_separator)
     : delegate_(std::move(delegate)),
-      host_view_(host_view),
       browser_view_(browser_view),
       top_container_(top_container),
       tab_strip_region_view_(tab_strip_region_view),
diff --git a/chrome/browser/ui/views/frame/browser_view_layout.h b/chrome/browser/ui/views/frame/browser_view_layout.h
index 980cb72..54ac0917 100644
--- a/chrome/browser/ui/views/frame/browser_view_layout.h
+++ b/chrome/browser/ui/views/frame/browser_view_layout.h
@@ -50,7 +50,6 @@
 
   // |browser_view| may be null in tests.
   BrowserViewLayout(std::unique_ptr<BrowserViewLayoutDelegate> delegate,
-                    gfx::NativeView host_view,
                     BrowserView* browser_view,
                     views::View* top_container,
                     TabStripRegionView* tab_strip_region_view,
@@ -165,9 +164,6 @@
   // The delegate interface. May be a mock in tests.
   const std::unique_ptr<BrowserViewLayoutDelegate> delegate_;
 
-  // The view against which the web dialog is positioned and parented.
-  gfx::NativeView const host_view_;
-
   // The owning browser view.
   const raw_ptr<BrowserView, DanglingUntriaged> browser_view_;
 
diff --git a/chrome/browser/ui/views/frame/browser_view_layout_unittest.cc b/chrome/browser/ui/views/frame/browser_view_layout_unittest.cc
index 0390c50..441a2e1 100644
--- a/chrome/browser/ui/views/frame/browser_view_layout_unittest.cc
+++ b/chrome/browser/ui/views/frame/browser_view_layout_unittest.cc
@@ -213,7 +213,6 @@
     delegate_ = delegate.get();
     auto layout = std::make_unique<BrowserViewLayout>(
         std::move(delegate),
-        /*host_view=*/nullptr,
         /*browser_view=*/nullptr, top_container_, tab_strip_region_view,
         tab_strip_, toolbar_, infobar_container_, contents_container_,
         /*left_aligned_side_panel=*/nullptr,
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index d7ed6c2a..f46f5359 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -138,6 +138,7 @@
 #include "ui/views/controls/highlight_path_generator.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/style/typography.h"
+#include "ui/views/view.h"
 #include "ui/views/view_utils.h"
 #include "ui/views/widget/widget.h"
 
@@ -1466,9 +1467,13 @@
 ui::ImageModel LocationBarView::GetLocationIcon(
     LocationIconView::Delegate::IconFetchedCallback on_icon_fetched) const {
   return omnibox_view_
-             ? omnibox_view_->GetIcon(GetLayoutConstant(LOCATION_BAR_ICON_SIZE),
-                                      location_icon_view_->GetForegroundColor(),
-                                      std::move(on_icon_fetched))
+             ? omnibox_view_->GetIcon(
+                   GetLayoutConstant(LOCATION_BAR_ICON_SIZE),
+                   location_icon_view_->GetForegroundColor(),
+                   View::GetColorProvider()->GetColor(kColorOmniboxResultsIcon),
+                   View::GetColorProvider()->GetColor(
+                       kColorOmniboxResultsStarterPackIcon),
+                   std::move(on_icon_fetched))
              : ui::ImageModel();
 }
 
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index 2664450..e27ec7e 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -200,12 +200,11 @@
   remove_suggestion_button_->SetTooltipText(
       l10n_util::GetStringUTF16(IDS_OMNIBOX_REMOVE_SUGGESTION));
   auto* const focus_ring = views::FocusRing::Get(remove_suggestion_button_);
-  focus_ring->SetHasFocusPredicate(
-      [&](View* view) {
-        return view->GetVisible() && GetMatchSelected() &&
-               (popup_contents_view_->GetSelection().state ==
-                OmniboxPopupSelection::FOCUSED_BUTTON_REMOVE_SUGGESTION);
-      });
+  focus_ring->SetHasFocusPredicate([&](View* view) {
+    return view->GetVisible() && GetMatchSelected() &&
+           (popup_contents_view_->GetSelection().state ==
+            OmniboxPopupSelection::FOCUSED_BUTTON_REMOVE_SUGGESTION);
+  });
   focus_ring->SetColorId(kColorOmniboxResultsFocusIndicator);
 
   button_row_ = AddChildView(std::make_unique<OmniboxSuggestionButtonRowView>(
@@ -508,7 +507,6 @@
     node_data->AddState(ax::mojom::State::kHovered);
 }
 
-
 void OmniboxResultView::OnThemeChanged() {
   views::View::OnThemeChanged();
   ApplyThemeAndRefreshIcons(true);
@@ -541,11 +539,9 @@
   // in suggestion texts.
   ui::ColorId vector_icon_color_id;
   if (match_.type == AutocompleteMatchType::HISTORY_CLUSTER) {
-    // TODO(crbug.com/1327076): If we launch history cluster icons with blue or
-    //  another non-default color, use an appropriately named constant, e.g.
-    //  `kColorOmniboxResultSpecialIcon[Selected]`.
-    vector_icon_color_id = GetMatchSelected() ? kColorOmniboxResultsUrlSelected
-                                              : kColorOmniboxResultsUrl;
+    // TODO(manukh): Fix this when fixing icon inconsistencies between the
+    //   dropdown and omnibox.
+    vector_icon_color_id = kColorOmniboxResultsStarterPackIcon;
   } else if (match_.type == AutocompleteMatchType::STARTER_PACK) {
     vector_icon_color_id = kColorOmniboxResultsStarterPackIcon;
   } else {
diff --git a/chrome/browser/ui/views/payments/payment_request_show_promise_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_show_promise_browsertest.cc
index 8282776..6f2df6a 100644
--- a/chrome/browser/ui/views/payments/payment_request_show_promise_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_request_show_promise_browsertest.cc
@@ -25,17 +25,13 @@
       const PaymentRequestShowPromiseTest&) = delete;
 
  protected:
-  PaymentRequestShowPromiseTest() {}
-  ~PaymentRequestShowPromiseTest() override {}
+  PaymentRequestShowPromiseTest() = default;
+  ~PaymentRequestShowPromiseTest() override = default;
 
-  // Installs the payment handler for window.location.href payment method that
+  // Installs the payment handler for window.location.origin payment method that
   // responds to "paymentrequest" events by echoing back the "total" object.
   void InstallEchoPaymentHandler() {
-    std::string contents;
-    ASSERT_TRUE(content::ExecuteScriptAndExtractString(
-        GetActiveWebContents(), "install();", &contents));
-    ASSERT_EQ(contents, "instruments.set(): Payment handler installed.")
-        << contents;
+    InstallPaymentApp("a.com", "/show_promise/app.js", &payment_method_);
   }
 
   // Shows the browser payment sheet.
@@ -45,11 +41,9 @@
                                  DialogEvent::SPEC_DONE_UPDATING,
                                  DialogEvent::PROCESSING_SPINNER_HIDDEN,
                                  DialogEvent::DIALOG_OPENED});
-    // buyWithCurrentUrl() uses the URL of the webpage as the payment method,
-    // which is necessary because service workers cannot use "basic-card"
-    // payment method (the default payment method of the test page).
-    ASSERT_TRUE(content::ExecuteScript(GetActiveWebContents(),
-                                       "buyWithCurrentUrlMethod();"));
+    ASSERT_TRUE(
+        content::ExecuteScript(GetActiveWebContents(),
+                               content::JsReplace("buy($1)", payment_method_)));
     WaitForObservedEvent();
     EXPECT_TRUE(web_modal::WebContentsModalDialogManager::FromWebContents(
                     GetActiveWebContents())
@@ -122,6 +116,8 @@
         {DialogEvent::PROCESSING_SPINNER_SHOWN, DialogEvent::DIALOG_CLOSED});
     ClickOnDialogViewAndWait(DialogViewID::PAY_BUTTON, dialog_view());
   }
+
+  std::string payment_method_;
 };
 
 IN_PROC_BROWSER_TEST_F(PaymentRequestShowPromiseTest, SingleOptionShipping) {
@@ -230,7 +226,9 @@
   base::HistogramTester histogram_tester;
   NavigateTo("/show_promise/digital_goods.html");
   InstallEchoPaymentHandler();
-  ASSERT_TRUE(content::ExecuteScript(GetActiveWebContents(), "create();"));
+  ASSERT_TRUE(content::ExecuteScript(
+      GetActiveWebContents(),
+      content::JsReplace("create($1)", payment_method_)));
   ResetEventWaiterForSequence(
       {DialogEvent::PROCESSING_SPINNER_SHOWN,
        DialogEvent::PROCESSING_SPINNER_HIDDEN, DialogEvent::SPEC_DONE_UPDATING,
@@ -247,7 +245,7 @@
   InstallEchoPaymentHandler();
   EXPECT_EQ("AbortError: rejected",
             content::EvalJs(GetActiveWebContents(),
-                            "buy(/*useUrlPaymentMethod=*/true);"));
+                            content::JsReplace("buy($1)", payment_method_)));
 }
 
 IN_PROC_BROWSER_TEST_F(PaymentRequestShowPromiseTest, Timeout) {
@@ -256,7 +254,8 @@
   EXPECT_EQ(
       "AbortError: Timed out waiting for a PaymentRequest.show(promise) to "
       "resolve.",
-      content::EvalJs(GetActiveWebContents(), "buy();"));
+      content::EvalJs(GetActiveWebContents(),
+                      content::JsReplace("buy($1)", payment_method_)));
 }
 
 IN_PROC_BROWSER_TEST_F(PaymentRequestShowPromiseTest,
@@ -272,7 +271,8 @@
   EXPECT_EQ(
       "TypeError: Failed to construct 'PaymentDetailsUpdate': Total amount "
       "value should be non-negative",
-      content::EvalJs(GetActiveWebContents(), "buyWithCurrentUrlMethod();"));
+      content::EvalJs(GetActiveWebContents(),
+                      content::JsReplace("buy($1)", payment_method_)));
 }
 
 IN_PROC_BROWSER_TEST_F(PaymentRequestShowPromiseTest,
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_view.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble_view.cc
index 2ec1fb6..d1132d6 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_view.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_view.cc
@@ -330,8 +330,6 @@
 
   // Set |parent_window| because some valid anchors can become hidden.
   DCHECK(browser_->window());
-  set_parent_window(
-      platform_util::GetViewForWindow(browser_->window()->GetNativeWindow()));
   UpdateAnchorPosition();
 
   views::Widget* widget = views::BubbleDialogDelegateView::CreateBubble(this);
@@ -374,12 +372,18 @@
 }
 
 void PermissionPromptBubbleView::UpdateAnchorPosition() {
-  DCHECK_EQ(browser_->window()->GetNativeWindow(), parent_window());
-
   bubble_anchor_util::AnchorConfiguration configuration =
       bubble_anchor_util::GetPermissionPromptBubbleAnchorConfiguration(
           browser_);
   SetAnchorView(configuration.anchor_view);
+  // In fullscreen, `anchor_view` may be nullptr because the toolbar is hidden,
+  // therefore anchor to the browser window instead.
+  if (configuration.anchor_view) {
+    set_parent_window(configuration.anchor_view->GetWidget()->GetNativeView());
+  } else {
+    set_parent_window(
+        platform_util::GetViewForWindow(browser_->window()->GetNativeWindow()));
+  }
   SetHighlightedButton(configuration.highlighted_button);
   if (!configuration.anchor_view)
     SetAnchorRect(bubble_anchor_util::GetPageInfoAnchorRect(browser_));
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_constants.h b/chrome/browser/ui/views/side_panel/read_anything/read_anything_constants.h
index 3f25f9d..21365712 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_constants.h
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_constants.h
@@ -13,10 +13,10 @@
 // Visual constants for Read Anything feature.
 const int kInternalInsets = 8;
 const int kSeparatorTopBottomPadding = 4;
+const int kMinimumComboboxWidth = 110;
 
-const int kButtonPadding = 8;
-const int kSmallIconSize = 18;
-const int kLargeIconSize = 20;
+const int kButtonPadding = 4;
+const int kIconSize = 16;
 const int kColorsIconSize = 24;
 
 const char kReadAnythingDefaultFontName[] = "Standard font";
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_font_combobox.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_font_combobox.cc
index 926babd..676117f3 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_font_combobox.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_font_combobox.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_font_combobox.h"
 
+#include "chrome/browser/ui/views/side_panel/read_anything/read_anything_constants.h"
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_model.h"
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -63,4 +64,8 @@
     delegate_->OnFontChoiceChanged(GetSelectedIndex().value());
 }
 
+gfx::Size ReadAnythingFontCombobox::GetMinimumSize() const {
+  return gfx::Size(kMinimumComboboxWidth, CalculatePreferredSize().height());
+}
+
 ReadAnythingFontCombobox::~ReadAnythingFontCombobox() = default;
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_font_combobox.h b/chrome/browser/ui/views/side_panel/read_anything/read_anything_font_combobox.h
index 5887b1e..aa35c508 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_font_combobox.h
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_font_combobox.h
@@ -24,6 +24,9 @@
   ReadAnythingFontCombobox& operator=(const ReadAnythingFontCombobox&) = delete;
   ~ReadAnythingFontCombobox() override;
 
+  // views::Combobox:
+  gfx::Size GetMinimumSize() const override;
+
  private:
   class MenuModel;
 
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_menu_button.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_menu_button.cc
index d724d9cd..74d5888 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_menu_button.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_menu_button.cc
@@ -22,7 +22,7 @@
                                      base::Unretained(this))) {
   ConfigureInkDropForToolbar(this);
   views::InstallPillHighlightPathGenerator(this);
-  SetIcon(icon, kLargeIconSize, gfx::kPlaceholderColor);
+  SetIcon(icon, kIconSize, gfx::kPlaceholderColor);
   SetAccessibleName(tooltip);
   SetTooltipText(tooltip);
   SetMenuModel(menu_model);
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.cc
index a7189fd..7ddb943 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.cc
@@ -24,7 +24,9 @@
 #include "ui/views/controls/highlight_path_generator.h"
 #include "ui/views/controls/separator.h"
 #include "ui/views/layout/box_layout.h"
-#include "ui/views/layout/layout_types.h"
+#include "ui/views/layout/flex_layout.h"
+
+// #include "ui/views/layout/layout_types.h"
 
 ReadAnythingToolbarView::ReadAnythingToolbarView(
     ReadAnythingCoordinator* coordinator,
@@ -33,33 +35,34 @@
     : delegate_(toolbar_delegate), coordinator_(std::move(coordinator)) {
   coordinator_->AddObserver(this);
 
-  // Create and set a BoxLayout LayoutManager for this view.
-  auto layout = std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kHorizontal);
-  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
-  layout->set_cross_axis_alignment(
-      views::BoxLayout::CrossAxisAlignment::kStretch);
-  layout->set_inside_border_insets(gfx::Insets(kInternalInsets));
-
-  SetLayoutManager(std::move(layout));
+  // Set a FlexLayout LayoutManager for this view.
+  SetLayoutManager(std::make_unique<views::FlexLayout>())
+      ->SetOrientation(views::LayoutOrientation::kHorizontal)
+      .SetMainAxisAlignment(views::LayoutAlignment::kStart)
+      .SetInteriorMargin(gfx::Insets(kInternalInsets));
 
   // Create a font selection combobox for the toolbar. The font combobox uses
   // a custom MenuModel, so we have a separate View for it for convenience.
   auto combobox =
       std::make_unique<ReadAnythingFontCombobox>(font_combobox_delegate);
 
+  // Font combobox should shrink as panel gets smaller.
+  combobox->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum));
+
   // Create the decrease/increase text size buttons.
   auto decrease_size_button = std::make_unique<ReadAnythingButtonView>(
       base::BindRepeating(&ReadAnythingToolbarView::DecreaseFontSizeCallback,
                           weak_pointer_factory_.GetWeakPtr()),
-      kTextDecreaseIcon, kSmallIconSize, gfx::kPlaceholderColor,
+      kTextDecreaseIcon, kIconSize, gfx::kPlaceholderColor,
       l10n_util::GetStringUTF16(
           IDS_READ_ANYTHING_DECREASE_FONT_SIZE_BUTTON_LABEL));
 
   auto increase_size_button = std::make_unique<ReadAnythingButtonView>(
       base::BindRepeating(&ReadAnythingToolbarView::IncreaseFontSizeCallback,
                           weak_pointer_factory_.GetWeakPtr()),
-      kTextIncreaseIcon, kLargeIconSize, gfx::kPlaceholderColor,
+      kTextIncreaseIcon, kIconSize, gfx::kPlaceholderColor,
       l10n_util::GetStringUTF16(
           IDS_READ_ANYTHING_INCREASE_FONT_SIZE_BUTTON_LABEL));
 
@@ -170,17 +173,17 @@
   letter_spacing_button_->SetBackground(
       views::CreateSolidBackground(background_skcolor));
 
-  decrease_text_size_button_->UpdateIcon(kTextDecreaseIcon, kSmallIconSize,
+  decrease_text_size_button_->UpdateIcon(kTextDecreaseIcon, kIconSize,
                                          foreground_skcolor);
 
-  increase_text_size_button_->UpdateIcon(kTextIncreaseIcon, kLargeIconSize,
+  increase_text_size_button_->UpdateIcon(kTextIncreaseIcon, kIconSize,
                                          foreground_skcolor);
 
-  colors_button_->SetIcon(kPaletteIcon, kLargeIconSize, foreground_skcolor);
+  colors_button_->SetIcon(kPaletteIcon, kIconSize, foreground_skcolor);
 
-  line_spacing_button_->SetIcon(kLineSpacingIcon, kLargeIconSize,
+  line_spacing_button_->SetIcon(kLineSpacingIcon, kIconSize,
                                 foreground_skcolor);
-  letter_spacing_button_->SetIcon(kLetterSpacingIcon, kLargeIconSize,
+  letter_spacing_button_->SetIcon(kLetterSpacingIcon, kIconSize,
                                   foreground_skcolor);
 
   for (views::Separator* separator : separators_) {
diff --git a/chrome/browser/ui/views/toolbar/side_panel_toolbar_button_unittest.cc b/chrome/browser/ui/views/toolbar/side_panel_toolbar_button_unittest.cc
index 211537b69..7c35082a 100644
--- a/chrome/browser/ui/views/toolbar/side_panel_toolbar_button_unittest.cc
+++ b/chrome/browser/ui/views/toolbar/side_panel_toolbar_button_unittest.cc
@@ -57,8 +57,9 @@
                     "this test shouldn't run";
   }
   // Verify the dot indicator is seen when there is an unseen entry.
-  model()->AddEntry(GURL("http://foo/1"), "Tab 1",
-                    reading_list::EntrySource::ADDED_VIA_CURRENT_APP);
+  model()->AddOrReplaceEntry(GURL("http://foo/1"), "Tab 1",
+                             reading_list::EntrySource::ADDED_VIA_CURRENT_APP,
+                             /*estimated_read_time=*/base::TimeDelta());
   SidePanelToolbarButton* const side_panel_button = GetSidePanelToolbarButton();
   ASSERT_TRUE(side_panel_button->GetDotIndicatorVisibilityForTesting());
 
@@ -71,8 +72,9 @@
 
   // Verify the dot indicator is hidden when entries are added while the panel
   // is open.
-  model()->AddEntry(GURL("http://foo/2"), "Tab 2",
-                    reading_list::EntrySource::ADDED_VIA_CURRENT_APP);
+  model()->AddOrReplaceEntry(GURL("http://foo/2"), "Tab 2",
+                             reading_list::EntrySource::ADDED_VIA_CURRENT_APP,
+                             /*estimated_read_time=*/base::TimeDelta());
   ASSERT_FALSE(side_panel_button->GetDotIndicatorVisibilityForTesting());
 }
 
diff --git a/chrome/browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc b/chrome/browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc
index 4dc7eab..3f523c3 100644
--- a/chrome/browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc
@@ -641,4 +641,23 @@
       GURL("https://www.example.com"));
 }
 
+IN_PROC_BROWSER_TEST_F(WebAppTabStripBrowserTest, TabbedModeMediaCSS) {
+  GURL start_url = embedded_test_server()->GetURL(
+      "/banners/"
+      "manifest_test_page.html?manifest=manifest_tabbed_display_override.json");
+  AppId app_id = InstallWebAppFromPage(browser(), start_url);
+
+  Browser* app_browser = LaunchWebAppBrowser(browser()->profile(), app_id);
+  content::WebContents* web_contents =
+      app_browser->tab_strip_model()->GetActiveWebContents();
+
+  std::string match_media_standalone =
+      "window.matchMedia('(display-mode: standalone)').matches;";
+  std::string match_media_tabbed =
+      "window.matchMedia('(display-mode: tabbed)').matches;";
+  EXPECT_TRUE(registrar().IsTabbedWindowModeEnabled(app_id));
+  ASSERT_FALSE(EvalJs(web_contents, match_media_standalone).ExtractBool());
+  ASSERT_TRUE(EvalJs(web_contents, match_media_tabbed).ExtractBool());
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/ui/web_applications/app_browser_controller.cc b/chrome/browser/ui/web_applications/app_browser_controller.cc
index 8fcbee8d..393180bf 100644
--- a/chrome/browser/ui/web_applications/app_browser_controller.cc
+++ b/chrome/browser/ui/web_applications/app_browser_controller.cc
@@ -270,6 +270,10 @@
   return false;
 }
 
+bool AppBrowserController::AppUsesTabbed() const {
+  return false;
+}
+
 bool AppBrowserController::IsIsolatedWebApp() const {
   return false;
 }
diff --git a/chrome/browser/ui/web_applications/app_browser_controller.h b/chrome/browser/ui/web_applications/app_browser_controller.h
index 9482a33..61d41c4 100644
--- a/chrome/browser/ui/web_applications/app_browser_controller.h
+++ b/chrome/browser/ui/web_applications/app_browser_controller.h
@@ -180,6 +180,9 @@
   // Returns true when an app's effective display mode is borderless.
   virtual bool AppUsesBorderlessMode() const;
 
+  // Returns true when an app's effective display mode is tabbed.
+  virtual bool AppUsesTabbed() const;
+
   virtual bool IsIsolatedWebApp() const;
 
   // Returns true when the app's effective display mode is
diff --git a/chrome/browser/ui/web_applications/web_app_browser_controller.cc b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
index e66f185..6beca771 100644
--- a/chrome/browser/ui/web_applications/web_app_browser_controller.cc
+++ b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
@@ -178,6 +178,10 @@
   return display == DisplayMode::kBorderless;
 }
 
+bool WebAppBrowserController::AppUsesTabbed() const {
+  return registrar().IsTabbedWindowModeEnabled(app_id());
+}
+
 bool WebAppBrowserController::IsIsolatedWebApp() const {
   return registrar().IsIsolated(app_id());
 }
diff --git a/chrome/browser/ui/web_applications/web_app_browser_controller.h b/chrome/browser/ui/web_applications/web_app_browser_controller.h
index bb00439..6c883b5 100644
--- a/chrome/browser/ui/web_applications/web_app_browser_controller.h
+++ b/chrome/browser/ui/web_applications/web_app_browser_controller.h
@@ -92,6 +92,7 @@
   bool IsHostedApp() const override;
   std::unique_ptr<TabMenuModelFactory> GetTabMenuModelFactory() const override;
   bool AppUsesWindowControlsOverlay() const override;
+  bool AppUsesTabbed() const override;
   bool IsWindowControlsOverlayEnabled() const override;
   void ToggleWindowControlsOverlayEnabled(
       base::OnceClosure on_complete) override;
diff --git a/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc b/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc
index d6d5ba9..e52dad0 100644
--- a/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc
+++ b/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc
@@ -31,6 +31,11 @@
 #include "ui/views/widget/any_widget_observer.h"
 #include "ui/views/widget/widget.h"
 
+#if BUILDFLAG(IS_WIN)
+#include "base/base_paths_win.h"
+#include "base/test/scoped_path_override.h"
+#endif  // BUILDFLAG(OS_WIN)
+
 using web_app::AppId;
 using GetAppsCallback =
     base::OnceCallback<void(std::vector<app_home::mojom::AppInfoPtr>)>;
@@ -439,6 +444,14 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AppHomePageHandlerTest, SetRunOnOsLoginMode) {
+#if BUILDFLAG(IS_WIN)
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  // This prevents the test from leaving shortcuts in the Windows startup
+  // directory that cause Chrome to get launched when Windows starts on a bot.
+  // See https://crbug.com/1239809
+  base::ScopedPathOverride override_user_startup{base::DIR_USER_STARTUP};
+#endif  // BUILDFLAG(IS_WIN)
+
   std::unique_ptr<TestAppHomePageHandler> page_handler =
       GetAppHomePageHandler();
   EXPECT_CALL(page_, AddApp(MatchAppName(kTestAppName)))
diff --git a/chrome/browser/ui/webui/ash/diagnostics_dialog.cc b/chrome/browser/ui/webui/ash/diagnostics_dialog.cc
index d9946de4..e7c99d6 100644
--- a/chrome/browser/ui/webui/ash/diagnostics_dialog.cc
+++ b/chrome/browser/ui/webui/ash/diagnostics_dialog.cc
@@ -11,6 +11,10 @@
 #include "ash/webui/diagnostics_ui/diagnostics_ui.h"
 #include "ash/webui/diagnostics_ui/url_constants.h"
 #include "base/strings/strcat.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
+#include "chrome/browser/ui/browser_window.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 
@@ -38,6 +42,17 @@
 // static
 void DiagnosticsDialog::ShowDialog(DiagnosticsDialog::DiagnosticsPage page,
                                    gfx::NativeWindow parent) {
+  // Close any instance of Diagnostics opened as an SWA.
+  auto* profile = ProfileManager::GetActiveUserProfile();
+  auto* browser =
+      ash::FindSystemWebAppBrowser(profile, ash::SystemWebAppType::DIAGNOSTICS);
+  if (browser) {
+    browser->window()->Close();
+  }
+
+  // Close any existing Diagnostics dialog before reopening.
+  MaybeCloseExistingDialog();
+
   DiagnosticsDialog* dialog = new DiagnosticsDialog(page);
 
   // Ensure log controller configuration matches current session.
@@ -49,16 +64,20 @@
   dialog->ShowSystemDialog(parent);
 }
 
+void DiagnosticsDialog::MaybeCloseExistingDialog() {
+  SystemWebDialogDelegate* existing_dialog =
+      SystemWebDialogDelegate::FindInstance(kDiagnosticsDialogId);
+  if (existing_dialog) {
+    existing_dialog->Close();
+  }
+}
+
 DiagnosticsDialog::DiagnosticsDialog(DiagnosticsDialog::DiagnosticsPage page)
     : SystemWebDialogDelegate(GURL(GetUrlForPage(page)),
                               /*title=*/std::u16string()) {}
 
 DiagnosticsDialog::~DiagnosticsDialog() = default;
 
-const std::string& DiagnosticsDialog::Id() {
-  return id_;
-}
-
 void DiagnosticsDialog::GetDialogSize(gfx::Size* size) const {
   const display::Display display =
       display::Screen::GetScreen()->GetPrimaryDisplay();
diff --git a/chrome/browser/ui/webui/ash/diagnostics_dialog.h b/chrome/browser/ui/webui/ash/diagnostics_dialog.h
index d8b7fafb..1b6d702 100644
--- a/chrome/browser/ui/webui/ash/diagnostics_dialog.h
+++ b/chrome/browser/ui/webui/ash/diagnostics_dialog.h
@@ -10,6 +10,15 @@
 
 namespace ash {
 
+namespace {
+
+// ID used to lookup existing DiagnosticsDialog instance from
+// SystemWebDialogDelegate list and ensure only one instance of
+// DiagnosticsDialog exists at a time.
+constexpr char kDiagnosticsDialogId[] = "diagnostics-dialog";
+
+}  // namespace
+
 class DiagnosticsDialog : public SystemWebDialogDelegate {
  public:
   // Denotes different sub-pages of the diagnostics app.
@@ -28,6 +37,9 @@
   static void ShowDialog(DiagnosticsPage page = DiagnosticsPage::kDefault,
                          gfx::NativeWindow parent = gfx::kNullNativeWindow);
 
+  // Closes an existing Diagnostics dialog if it exists.
+  static void MaybeCloseExistingDialog();
+
  protected:
   explicit DiagnosticsDialog(DiagnosticsPage page);
   ~DiagnosticsDialog() override;
@@ -36,14 +48,10 @@
   DiagnosticsDialog& operator=(const DiagnosticsDialog&) = delete;
 
   // SystemWebDialogDelegate
-  const std::string& Id() override;
   bool ShouldCloseDialogOnEscape() const override;
 
   // ui::WebDialogDelegate
   void GetDialogSize(gfx::Size* size) const override;
-
- private:
-  const std::string id_ = "diagnostics-dialog";
 };
 }  // namespace ash
 
diff --git a/chrome/browser/ui/webui/ash/onc_import_message_handler.cc b/chrome/browser/ui/webui/ash/onc_import_message_handler.cc
index 557f4650..1ea26ed 100644
--- a/chrome/browser/ui/webui/ash/onc_import_message_handler.cc
+++ b/chrome/browser/ui/webui/ash/onc_import_message_handler.cc
@@ -124,8 +124,8 @@
 
   auto cert_importer = std::make_unique<onc::CertificateImporterImpl>(
       content::GetIOThreadTaskRunner({}), nssdb);
-  auto certs = std::make_unique<chromeos::onc::OncParsedCertificates>(
-      base::Value(std::move(certificates)));
+  auto certs =
+      std::make_unique<chromeos::onc::OncParsedCertificates>(certificates);
   if (certs->has_error()) {
     has_error = true;
     result += "Some certificates could not be parsed.\n";
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.cc b/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.cc
index 3e718e0..c9e62e5a 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.cc
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.cc
@@ -10,6 +10,8 @@
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shell.h"
 #include "ash/wm/window_dimmer.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/string_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/webui/ash/parent_access/parent_access_ui.mojom.h"
@@ -27,17 +29,66 @@
 constexpr int kDialogWidthDp = 600;
 constexpr float kDimmerOpacity = 0.7f;
 
+constexpr char kParentAccessWidgetShowDialogErrorHistogramBase[] =
+    "ChromeOS.FamilyLinkUser.ParentAccessWidgetShowDialogError";
+// TODO(b/262555804) use shared constants for flow type variant suffixes.
+constexpr char kParentAccessWidgetShowDialogErrorSuffixAll[] = "All";
+constexpr char kParentAccessWidgetShowDialogErrorSuffixWebApprovals[] =
+    "WebApprovals";
+
+void RecordParentAccessWidgetShowDialogError(
+    ParentAccessDialogProvider::ShowErrorType error_type,
+    absl::optional<parent_access_ui::mojom::ParentAccessParams::FlowType>
+        flow_type) {
+  base::UmaHistogramEnumeration(
+      ParentAccessDialogProvider::
+          GetParentAccessWidgetShowDialogErrorHistogramForFlowType(flow_type),
+      error_type);
+
+  // Always record metric for "all" flow type.
+  base::UmaHistogramEnumeration(
+      ParentAccessDialogProvider::
+          GetParentAccessWidgetShowDialogErrorHistogramForFlowType(
+              absl::nullopt),
+      error_type);
+}
 }  // namespace
 
+// static
+const std::string ParentAccessDialogProvider::
+    GetParentAccessWidgetShowDialogErrorHistogramForFlowType(
+        absl::optional<parent_access_ui::mojom::ParentAccessParams::FlowType>
+            flow_type) {
+  const std::string separator = ".";
+  if (!flow_type.has_value()) {
+    return base::JoinString({kParentAccessWidgetShowDialogErrorHistogramBase,
+                             kParentAccessWidgetShowDialogErrorSuffixAll},
+                            separator);
+  }
+  switch (flow_type.value()) {
+    case parent_access_ui::mojom::ParentAccessParams::FlowType::kWebsiteAccess:
+      return base::JoinString(
+          {kParentAccessWidgetShowDialogErrorHistogramBase,
+           kParentAccessWidgetShowDialogErrorSuffixWebApprovals},
+          separator);
+  }
+}
+
 ParentAccessDialogProvider::ShowError ParentAccessDialogProvider::Show(
     parent_access_ui::mojom::ParentAccessParamsPtr params,
     ParentAccessDialog::Callback callback) {
   Profile* profile = ProfileManager::GetPrimaryUserProfile();
   if (!profile->IsChild()) {
+    RecordParentAccessWidgetShowDialogError(
+        ParentAccessDialogProvider::ShowErrorType::kNotAChildUser,
+        params->flow_type);
     return ParentAccessDialogProvider::ShowError::kNotAChildUser;
   }
 
   if (ParentAccessDialog::GetInstance()) {
+    RecordParentAccessWidgetShowDialogError(
+        ParentAccessDialogProvider::ShowErrorType::kAlreadyVisible,
+        params->flow_type);
     return ParentAccessDialogProvider::ShowError::kDialogAlreadyVisible;
   }
 
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.h b/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.h
index cbeaec3..05d867c 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.h
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog.h
@@ -13,6 +13,7 @@
 #include "chrome/browser/ui/webui/ash/parent_access/parent_access_ui.mojom.h"
 #include "chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_delegate.h"
 #include "chrome/browser/ui/webui/ash/system_web_dialog_delegate.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
 
@@ -116,6 +117,23 @@
   // error.  virtual so it can be overridden for tests to fake dialog behavior.
   virtual ShowError Show(parent_access_ui::mojom::ParentAccessParamsPtr params,
                          ParentAccessDialog::Callback callback);
+
+  // Used for metrics. Those values are logged to UMA. Entries should not be
+  // renumbered and numeric values should never be reused. Please keep in sync
+  // with "FamilyLinkUserParentAccessWidgetShowDialogError" in
+  // src/tools/metrics/histograms/enums.xml.
+  enum class ShowErrorType {
+    kUnknown = 0,
+    kAlreadyVisible = 1,
+    kNotAChildUser = 2,
+    kMaxValue = kNotAChildUser
+  };
+
+  // Returns the name of parent access widget error histogram for a flow type.
+  static const std::string
+  GetParentAccessWidgetShowDialogErrorHistogramForFlowType(
+      absl::optional<parent_access_ui::mojom::ParentAccessParams::FlowType>
+          flow_type);
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog_browsertest.cc b/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog_browsertest.cc
index 0aae330..c690a74 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog_browsertest.cc
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_dialog_browsertest.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/webui/ash/parent_access/parent_access_browsertest_base.h"
 #include "chrome/browser/ui/webui/ash/parent_access/parent_access_ui.mojom.h"
@@ -253,6 +254,7 @@
 
 IN_PROC_BROWSER_TEST_F(ParentAccessDialogBrowserTest,
                        ErrorOnDialogAlreadyVisible) {
+  base::HistogramTester histogram_tester;
   // Show the dialog.
   ParentAccessDialogProvider provider;
   ParentAccessDialogProvider::ShowError error = provider.Show(
@@ -282,6 +284,19 @@
   EXPECT_EQ(error,
             ParentAccessDialogProvider::ShowError::kDialogAlreadyVisible);
   EXPECT_NE(ParentAccessDialog::GetInstance(), nullptr);
+
+  // Verify that metrics were recorded.
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessDialogProvider::
+          GetParentAccessWidgetShowDialogErrorHistogramForFlowType(
+              absl::nullopt),
+      ParentAccessDialogProvider::ShowErrorType::kAlreadyVisible, 1);
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessDialogProvider::
+          GetParentAccessWidgetShowDialogErrorHistogramForFlowType(
+              parent_access_ui::mojom::ParentAccessParams::FlowType::
+                  kWebsiteAccess),
+      ParentAccessDialogProvider::ShowErrorType::kAlreadyVisible, 1);
 }
 
 using ParentAccessDialogRegularUserBrowserTest =
@@ -290,6 +305,7 @@
 // Verify that the dialog is not shown for non child users.
 IN_PROC_BROWSER_TEST_F(ParentAccessDialogRegularUserBrowserTest,
                        ErrorForNonChildUser) {
+  base::HistogramTester histogram_tester;
   // Show the dialog.
   ParentAccessDialogProvider provider;
   ParentAccessDialogProvider::ShowError error = provider.Show(
@@ -302,6 +318,19 @@
   // Verify it is not showing.
   EXPECT_EQ(error, ParentAccessDialogProvider::ShowError::kNotAChildUser);
   EXPECT_EQ(ParentAccessDialog::GetInstance(), nullptr);
+
+  // Verify that metrics were recorded.
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessDialogProvider::
+          GetParentAccessWidgetShowDialogErrorHistogramForFlowType(
+              absl::nullopt),
+      ParentAccessDialogProvider::ShowErrorType::kNotAChildUser, 1);
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessDialogProvider::
+          GetParentAccessWidgetShowDialogErrorHistogramForFlowType(
+              parent_access_ui::mojom::ParentAccessParams::FlowType::
+                  kWebsiteAccess),
+      ParentAccessDialogProvider::ShowErrorType::kNotAChildUser, 1);
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_state_tracker.cc b/chrome/browser/ui/webui/ash/parent_access/parent_access_state_tracker.cc
index 23571b8..e944358 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_state_tracker.cc
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_state_tracker.cc
@@ -12,8 +12,8 @@
 constexpr char kParentAccessFlowResultHistogramBase[] =
     "ChromeOS.FamilyLinkUser.ParentAccess.FlowResult";
 
+// TODO(b/262555804) use shared constants for flow type variant suffixes.
 constexpr char kParentAccessFlowResultSuffixAll[] = "All";
-
 constexpr char kParentAccessFlowResultSuffixWebApprovals[] = "WebApprovals";
 }  // namespace
 
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl.cc b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl.cc
index 943f138..9082e466 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl.cc
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl.cc
@@ -9,6 +9,7 @@
 
 #include "base/base64.h"
 #include "base/command_line.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
 #include "base/system/sys_info.h"
 #include "base/time/time.h"
@@ -47,8 +48,51 @@
   }
 }
 
+constexpr char kParentAccessWidgetErrorHistogramBase[] =
+    "ChromeOS.FamilyLinkUser.ParentAccessWidgetError";
+// TODO(b/262555804) use shared constants for flow type variant suffixes.
+constexpr char kParentAccessWidgetErrorSuffixAll[] = "All";
+constexpr char kParentAccessWidgetErrorSuffixWebApprovals[] = "WebApprovals";
 }  // namespace
 
+// static
+std::string
+ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+    absl::optional<parent_access_ui::mojom::ParentAccessParams::FlowType>
+        flow_type) {
+  const std::string separator = ".";
+  if (!flow_type.has_value()) {
+    return base::JoinString({kParentAccessWidgetErrorHistogramBase,
+                             kParentAccessWidgetErrorSuffixAll},
+                            separator);
+  }
+  switch (flow_type.value()) {
+    case parent_access_ui::mojom::ParentAccessParams::FlowType::kWebsiteAccess:
+      return base::JoinString({kParentAccessWidgetErrorHistogramBase,
+                               kParentAccessWidgetErrorSuffixWebApprovals},
+                              separator);
+  }
+}
+
+void ParentAccessUIHandlerImpl::RecordParentAccessWidgetError(
+    ParentAccessUIHandlerImpl::ParentAccessWidgetError error) {
+  if (delegate_) {
+    // TODO(b/260144025): Reduce the number of times params are cloned.
+    parent_access_ui::mojom::ParentAccessParamsPtr params =
+        delegate_->CloneParentAccessParams();
+    base::UmaHistogramEnumeration(
+        ParentAccessUIHandlerImpl::
+            GetParentAccessWidgetErrorHistogramForFlowType(params->flow_type),
+        error);
+  }
+
+  // Always record metric for "all" flow type.
+  base::UmaHistogramEnumeration(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          absl::nullopt),
+      error);
+}
+
 ParentAccessUIHandlerImpl::ParentAccessUIHandlerImpl(
     mojo::PendingReceiver<parent_access_ui::mojom::ParentAccessUIHandler>
         receiver,
@@ -99,7 +143,8 @@
   if (error.state() != GoogleServiceAuthError::NONE) {
     DLOG(ERROR) << "ParentAccessUIHandlerImpl: OAuth2 token request failed. "
                 << error.state() << ": " << error.ToString();
-
+    RecordParentAccessWidgetError(
+        ParentAccessUIHandlerImpl::ParentAccessWidgetError::kOAuthError);
     std::move(callback).Run(
         parent_access_ui::mojom::GetOAuthTokenStatus::kError,
         "" /* No token */);
@@ -115,6 +160,9 @@
   if (!delegate_) {
     LOG(ERROR) << "Delegate not available in ParentAccessUIHandler - WebUI was "
                   "probably created without a dialog";
+    RecordParentAccessWidgetError(
+        ParentAccessUIHandlerImpl::ParentAccessWidgetError::
+            kDelegateNotAvailable);
     std::move(callback).Run(parent_access_ui::mojom::ParentAccessParams::New());
     return;
   }
@@ -129,6 +177,9 @@
   if (!delegate_) {
     LOG(ERROR) << "Delegate not available in ParentAccessUIHandler - WebUI was "
                   "probably created without a dialog";
+    RecordParentAccessWidgetError(
+        ParentAccessUIHandlerImpl::ParentAccessWidgetError::
+            kDelegateNotAvailable);
     std::move(callback).Run();
     return;
   }
@@ -172,6 +223,9 @@
   if (!delegate_) {
     LOG(ERROR) << "Delegate not available in ParentAccessUIHandler - WebUI was "
                   "probably created without a dialog";
+    RecordParentAccessWidgetError(
+        ParentAccessUIHandlerImpl::ParentAccessWidgetError::
+            kDelegateNotAvailable);
     std::move(callback).Run("");
     return;
   }
@@ -222,6 +276,9 @@
                           &decoded_parent_access_callback)) {
     LOG(ERROR) << "ParentAccessHandler::ParentAccessResult: Error decoding "
                   "parent_access_result from base64";
+    RecordParentAccessWidgetError(
+        ParentAccessUIHandlerImpl::ParentAccessWidgetError::kDecodingError);
+
     message->type =
         parent_access_ui::mojom::ParentAccessServerMessageType::kError;
     std::move(callback).Run(std::move(message));
@@ -233,6 +290,8 @@
   if (!parent_access_callback.ParseFromString(decoded_parent_access_callback)) {
     LOG(ERROR) << "ParentAccessHandler::ParentAccessResult: Error parsing "
                   "decoded_parent_access_result to proto";
+    RecordParentAccessWidgetError(
+        ParentAccessUIHandlerImpl::ParentAccessWidgetError::kParsingError);
 
     message->type =
         parent_access_ui::mojom::ParentAccessServerMessageType::kError;
@@ -266,6 +325,8 @@
           << "ParentAccessHandler::OnParentAccessCallback: Unknown type of "
              "callback received and ignored: "
           << parent_access_callback.callback_case();
+      RecordParentAccessWidgetError(
+          ParentAccessUIHandlerImpl::ParentAccessWidgetError::kUnknownCallback);
       message->type =
           parent_access_ui::mojom::ParentAccessServerMessageType::kIgnore;
       std::move(callback).Run(std::move(message));
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl.h b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl.h
index f8aab6e..4205b97 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl.h
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl.h
@@ -14,6 +14,7 @@
 #include "chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_delegate.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace content {
 class WebUI;
@@ -64,11 +65,32 @@
   const kids::platform::parentaccess::client::proto::ParentAccessToken*
   GetParentAccessTokenForTest();
 
+  // Returns the name of parent access widget error histogram for a flow type.
+  static std::string GetParentAccessWidgetErrorHistogramForFlowType(
+      absl::optional<parent_access_ui::mojom::ParentAccessParams::FlowType>
+          flow_type);
+
+  // Used for metrics. These values are logged to UMA. Entries should not be
+  // renumbered and numeric values should never be reused. Please keep in sync
+  // with "FamilyLinkUserParentAccessWidgetError" in
+  // src/tools/metrics/histograms/enums.xml.
+  enum class ParentAccessWidgetError {
+    kOAuthError = 0,
+    kDelegateNotAvailable = 1,
+    kDecodingError = 2,
+    kParsingError = 3,
+    kUnknownCallback = 4,
+    kMaxValue = kUnknownCallback
+  };
+
  private:
   void OnAccessTokenFetchComplete(GetOAuthTokenCallback callback,
                                   GoogleServiceAuthError error,
                                   signin::AccessTokenInfo access_token_info);
 
+  void RecordParentAccessWidgetError(
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError error);
+
   // Used to fetch OAuth2 access tokens.
   signin::IdentityManager* identity_manager_ = nullptr;
   std::unique_ptr<signin::AccessTokenFetcher> oauth2_access_token_fetcher_;
diff --git a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl_unittest.cc b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl_unittest.cc
index 569ebbd..4c67ad2 100644
--- a/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl_unittest.cc
+++ b/chrome/browser/ui/webui/ash/parent_access/parent_access_ui_handler_impl_unittest.cc
@@ -125,6 +125,7 @@
 
 // Verifies that access token fetch errors are recorded.
 TEST_F(ParentAccessUIHandlerImplTest, GetOAuthTokenError) {
+  base::HistogramTester histogram_tester;
   base::RunLoop run_loop;
   parent_access_ui_handler_->GetOAuthToken(base::BindLambdaForTesting(
       [&](parent_access_ui::mojom::GetOAuthTokenStatus status,
@@ -139,6 +140,17 @@
       GoogleServiceAuthError::FromServiceError("FAKE SERVICE ERROR"));
 
   run_loop.Run();
+
+  // Expect metric to be recorded.
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          absl::nullopt),
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError::kOAuthError, 1);
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          parent_access_ui::mojom::ParentAccessParams::FlowType::
+              kWebsiteAccess),
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError::kOAuthError, 1);
 }
 
 // Verifies that only one access token fetch is possible at a time.
@@ -361,6 +373,7 @@
 
 // Verifies that the ConsentDeclined status is ignored.
 TEST_F(ParentAccessUIHandlerImplTest, ConsentDeclinedParsed) {
+  base::HistogramTester histogram_tester;
   // Construct the ParentAccessCallback
   kids::platform::parentaccess::client::proto::ParentAccessCallback
       parent_access_callback;
@@ -384,10 +397,22 @@
             run_loop.Quit();
           }));
   run_loop.Run();
+
+  // Expect metric to be recorded.
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          absl::nullopt),
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError::kUnknownCallback, 1);
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          parent_access_ui::mojom::ParentAccessParams::FlowType::
+              kWebsiteAccess),
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError::kUnknownCallback, 1);
 }
 
 // Verifies that the OnPageSizeChanged status is ignored.
 TEST_F(ParentAccessUIHandlerImplTest, OnPageSizeChangedIgnored) {
+  base::HistogramTester histogram_tester;
   // Construct the ParentAccessCallback
   kids::platform::parentaccess::client::proto::ParentAccessCallback
       parent_access_callback;
@@ -411,10 +436,23 @@
             run_loop.Quit();
           }));
   run_loop.Run();
+
+  // Expect metric to be recorded.
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          absl::nullopt),
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError::kUnknownCallback, 1);
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          parent_access_ui::mojom::ParentAccessParams::FlowType::
+              kWebsiteAccess),
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError::kUnknownCallback, 1);
 }
 
 // Verifies that the OnCommunicationEstablished status is ignored.
 TEST_F(ParentAccessUIHandlerImplTest, OnCommunicationEstablishedIgnored) {
+  base::HistogramTester histogram_tester;
+
   // Construct the ParentAccessCallback
   kids::platform::parentaccess::client::proto::ParentAccessCallback
       parent_access_callback;
@@ -438,6 +476,90 @@
             run_loop.Quit();
           }));
   run_loop.Run();
+
+  // Expect metric to be recorded.
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          absl::nullopt),
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError::kUnknownCallback, 1);
 }
 
+// Verifies metric is recorded for no delegate error.
+TEST_F(ParentAccessUIHandlerImplTest, NoDelegateErrorMetricRecorded) {
+  base::HistogramTester histogram_tester;
+
+  // Construct a ParentAccessUIHandler without a delegate.
+  mojo::Remote<parent_access_ui::mojom::ParentAccessUIHandler> remote;
+  auto parent_access_ui_handler_no_delegate =
+      std::make_unique<ParentAccessUIHandlerImpl>(
+          remote.BindNewPipeAndPassReceiver(),
+          identity_test_env_->identity_manager(), nullptr);
+
+  // Send a result status.
+  base::RunLoop run_loop;
+  parent_access_ui_handler_no_delegate->OnParentAccessDone(
+      parent_access_ui::mojom::ParentAccessResult::kApproved,
+      base::BindLambdaForTesting([&]() -> void { run_loop.Quit(); }));
+
+  run_loop.Run();
+
+  // Expect metric to be recorded.
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          absl::nullopt),
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError::kDelegateNotAvailable,
+      1);
+}
+
+// Verifies metric is recorded when received callback cannot be decoded.
+TEST_F(ParentAccessUIHandlerImplTest, DecodingErrorMetricRecorded) {
+  base::HistogramTester histogram_tester;
+  base::RunLoop run_loop;
+
+  // Receive non-decodable callback.
+  parent_access_ui_handler_->OnParentAccessCallbackReceived(
+      "not_a_callback",
+      base::BindLambdaForTesting(
+          [&](parent_access_ui::mojom::ParentAccessServerMessagePtr message)
+              -> void { run_loop.Quit(); }));
+  run_loop.Run();
+
+  // Expect metric to be recorded.
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          absl::nullopt),
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError::kDecodingError, 1);
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          parent_access_ui::mojom::ParentAccessParams::FlowType::
+              kWebsiteAccess),
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError::kDecodingError, 1);
+}
+
+// Verifies metric is recorded when received callback cannot be parsed to proto.
+TEST_F(ParentAccessUIHandlerImplTest, ParsingErrorMetricRecorded) {
+  base::HistogramTester histogram_tester;
+
+  // Receive non-parseable callback.
+  base::RunLoop run_loop;
+  std::string encoded_not_a_callback;
+  base::Base64Encode("not_a_callback", &encoded_not_a_callback);
+  parent_access_ui_handler_->OnParentAccessCallbackReceived(
+      encoded_not_a_callback,
+      base::BindLambdaForTesting(
+          [&](parent_access_ui::mojom::ParentAccessServerMessagePtr message)
+              -> void { run_loop.Quit(); }));
+  run_loop.Run();
+
+  // Expect metric to be recorded.
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          absl::nullopt),
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError::kParsingError, 1);
+  histogram_tester.ExpectUniqueSample(
+      ParentAccessUIHandlerImpl::GetParentAccessWidgetErrorHistogramForFlowType(
+          parent_access_ui::mojom::ParentAccessParams::FlowType::
+              kWebsiteAccess),
+      ParentAccessUIHandlerImpl::ParentAccessWidgetError::kParsingError, 1);
+}
 }  // namespace ash
diff --git a/chrome/browser/ui/webui/metrics_internals/metrics_internals_handler.cc b/chrome/browser/ui/webui/metrics_internals/metrics_internals_handler.cc
index f155434d..3381cc2 100644
--- a/chrome/browser/ui/webui/metrics_internals/metrics_internals_handler.cc
+++ b/chrome/browser/ui/webui/metrics_internals/metrics_internals_handler.cc
@@ -12,18 +12,24 @@
 #include "components/metrics/metrics_service.h"
 #include "components/metrics/metrics_service_observer.h"
 
-MetricsInternalsHandler::MetricsInternalsHandler()
-    : uma_log_observer_(
-          metrics::MetricsServiceObserver::MetricsServiceType::UMA) {
-  g_browser_process->metrics_service()->AddLogsObserver(&uma_log_observer_);
+MetricsInternalsHandler::MetricsInternalsHandler() {
+  if (!ShouldUseMetricsServiceObserver()) {
+    uma_log_observer_ = std::make_unique<metrics::MetricsServiceObserver>(
+        metrics::MetricsServiceObserver::MetricsServiceType::UMA);
+    g_browser_process->metrics_service()->AddLogsObserver(
+        uma_log_observer_.get());
+  }
 }
 
 MetricsInternalsHandler::~MetricsInternalsHandler() {
-  g_browser_process->metrics_service()->RemoveLogsObserver(&uma_log_observer_);
+  if (uma_log_observer_) {
+    g_browser_process->metrics_service()->RemoveLogsObserver(
+        uma_log_observer_.get());
+  }
 }
 
 void MetricsInternalsHandler::OnJavascriptAllowed() {
-  uma_log_notified_subscription_ = uma_log_observer_.AddNotifiedCallback(
+  uma_log_notified_subscription_ = GetUmaObserver()->AddNotifiedCallback(
       base::BindRepeating(&MetricsInternalsHandler::OnUmaLogCreatedOrEvent,
                           weak_ptr_factory_.GetWeakPtr()));
 }
@@ -47,6 +53,21 @@
       "fetchUmaLogsData",
       base::BindRepeating(&MetricsInternalsHandler::HandleFetchUmaLogsData,
                           base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "isUsingMetricsServiceObserver",
+      base::BindRepeating(
+          &MetricsInternalsHandler::HandleIsUsingMetricsServiceObserver,
+          base::Unretained(this)));
+}
+
+bool MetricsInternalsHandler::ShouldUseMetricsServiceObserver() {
+  return g_browser_process->metrics_service()->logs_event_observer() != nullptr;
+}
+
+metrics::MetricsServiceObserver* MetricsInternalsHandler::GetUmaObserver() {
+  return ShouldUseMetricsServiceObserver()
+             ? g_browser_process->metrics_service()->logs_event_observer()
+             : uma_log_observer_.get();
 }
 
 void MetricsInternalsHandler::HandleFetchVariationsSummary(
@@ -79,11 +100,19 @@
 
   std::string logs_json;
   bool result =
-      uma_log_observer_.ExportLogsAsJson(include_log_proto_data, &logs_json);
+      GetUmaObserver()->ExportLogsAsJson(include_log_proto_data, &logs_json);
   DCHECK(result);
   ResolveJavascriptCallback(callback_id, base::Value(std::move(logs_json)));
 }
 
+void MetricsInternalsHandler::HandleIsUsingMetricsServiceObserver(
+    const base::Value::List& args) {
+  AllowJavascript();
+  const base::Value& callback_id = args[0];
+  ResolveJavascriptCallback(callback_id,
+                            base::Value(ShouldUseMetricsServiceObserver()));
+}
+
 void MetricsInternalsHandler::OnUmaLogCreatedOrEvent() {
   FireWebUIListener("uma-log-created-or-event");
 }
diff --git a/chrome/browser/ui/webui/metrics_internals/metrics_internals_handler.h b/chrome/browser/ui/webui/metrics_internals/metrics_internals_handler.h
index b3155137..3d7f03f2 100644
--- a/chrome/browser/ui/webui/metrics_internals/metrics_internals_handler.h
+++ b/chrome/browser/ui/webui/metrics_internals/metrics_internals_handler.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_METRICS_INTERNALS_METRICS_INTERNALS_HANDLER_H_
 #define CHROME_BROWSER_UI_WEBUI_METRICS_INTERNALS_METRICS_INTERNALS_HANDLER_H_
 
+#include <memory>
+
 #include "base/callback_list.h"
 #include "base/memory/weak_ptr.h"
 #include "components/metrics/metrics_service_observer.h"
@@ -26,13 +28,27 @@
   void RegisterMessages() override;
 
  private:
+  // Returns true if the metrics service has its own logs event observer, which
+  // indicates that we should use that observer instead of our own. This happens
+  // if this is a debug build, or if the |kExportUmaLogsToFile| command line
+  // flag is present.
+  bool ShouldUseMetricsServiceObserver();
+
+  // Returns the UMA observer to use for tracking UMA logs. I.e., if the metrics
+  // service has its own observer, return that one. Otherwise, return the one
+  // owned by the WebUI page (|uma_log_observer_|).
+  metrics::MetricsServiceObserver* GetUmaObserver();
+
   void HandleFetchVariationsSummary(const base::Value::List& args);
   void HandleFetchUmaSummary(const base::Value::List& args);
   void HandleFetchUmaLogsData(const base::Value::List& args);
+  void HandleIsUsingMetricsServiceObserver(const base::Value::List& args);
   void OnUmaLogCreatedOrEvent();
 
-  // This UMA log observer keeps track of logs since its creation.
-  metrics::MetricsServiceObserver uma_log_observer_;
+  // This UMA log observer keeps track of logs since its creation. It is unused
+  // if the UMA metrics service has its own observer that has observed all
+  // events since browser startup.
+  std::unique_ptr<metrics::MetricsServiceObserver> uma_log_observer_;
 
   // The callback subscription to |uma_log_observer_| that notifies the WebUI
   // of changes. When this subscription is destroyed, it is automatically
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index 03fcd172..0fd7d31a 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -205,7 +205,8 @@
   AddSettingsPageUIHandler(
       std::make_unique<ClearBrowsingDataHandler>(web_ui, profile));
   AddSettingsPageUIHandler(std::make_unique<SafetyCheckHandler>());
-  AddSettingsPageUIHandler(std::make_unique<SiteSettingsPermissionsHandler>());
+  AddSettingsPageUIHandler(
+      std::make_unique<SiteSettingsPermissionsHandler>(profile));
   AddSettingsPageUIHandler(std::make_unique<DownloadsHandler>(profile));
   AddSettingsPageUIHandler(std::make_unique<ExtensionControlHandler>());
   AddSettingsPageUIHandler(std::make_unique<FontHandler>(profile));
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc
index 106e45eb..0f56d33 100644
--- a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.cc
@@ -4,7 +4,21 @@
 
 #include "chrome/browser/ui/webui/settings/site_settings_permissions_handler.h"
 
-SiteSettingsPermissionsHandler::SiteSettingsPermissionsHandler() = default;
+#include "base/json/values_util.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/ui/webui/settings/site_settings_helper.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/content_settings_pattern.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "components/content_settings/core/common/features.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+constexpr char kRevokedPermissionsKey[] = "revoked";
+
+SiteSettingsPermissionsHandler::SiteSettingsPermissionsHandler(Profile* profile)
+    : profile_(profile) {}
 SiteSettingsPermissionsHandler::~SiteSettingsPermissionsHandler() = default;
 
 void SiteSettingsPermissionsHandler::HandleGetRevokedUnusedSitePermissionsList(
@@ -14,12 +28,50 @@
   CHECK_EQ(1U, args.size());
   const base::Value& callback_id = args[0];
 
-  // TODO(crbug.com/1345920): Replace with content from Unused Site Permissions
-  // service.
-  base::Value::List result;
+  base::Value::List result = PopulateUnusedSitePermissionsData();
+
   ResolveJavascriptCallback(callback_id, base::Value(std::move(result)));
 }
 
+base::Value::List
+SiteSettingsPermissionsHandler::PopulateUnusedSitePermissionsData() {
+  base::Value::List result;
+
+  if (!base::FeatureList::IsEnabled(
+          content_settings::features::kSafetyCheckUnusedSitePermissions)) {
+    return result;
+  }
+
+  HostContentSettingsMap* hcsm =
+      HostContentSettingsMapFactory::GetForProfile(profile_);
+
+  ContentSettingsForOneType settings;
+  hcsm->GetSettingsForOneType(
+      ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS, &settings);
+
+  for (const auto& revoked_permissions : settings) {
+    base::Value::Dict revoked_permission_value;
+
+    GURL url = GURL(revoked_permissions.primary_pattern.ToString());
+    // Converting URL to a origin is normally an anti-pattern but here it is
+    // ok since the URL belongs to a single origin. Therefore, it has a
+    // fully defined URL+scheme+port which makes converting URL to origin
+    // successful.
+    url::Origin origin = url::Origin::Create(url);
+    revoked_permission_value.Set(site_settings::kOrigin, origin.Serialize());
+
+    const base::Value& stored_value = revoked_permissions.setting_value;
+    DCHECK(stored_value.is_dict());
+
+    revoked_permission_value.Set(
+        kRevokedPermissionsKey,
+        stored_value.GetDict().FindList(kRevokedPermissionsKey)->Clone());
+
+    result.Append(std::move(revoked_permission_value));
+  }
+  return result;
+}
+
 void SiteSettingsPermissionsHandler::RegisterMessages() {
   // Usage of base::Unretained(this) is safe, because web_ui() owns `this` and
   // won't release ownership until destruction.
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h
index 3d55269..2457cd8 100644
--- a/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h
+++ b/chrome/browser/ui/webui/settings/site_settings_permissions_handler.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_SITE_SETTINGS_PERMISSIONS_HANDLER_H_
 #define CHROME_BROWSER_UI_WEBUI_SETTINGS_SITE_SETTINGS_PERMISSIONS_HANDLER_H_
 
+#include "base/memory/raw_ptr.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
 
 /**
@@ -14,11 +16,15 @@
 
 class SiteSettingsPermissionsHandler : public settings::SettingsPageUIHandler {
  public:
-  SiteSettingsPermissionsHandler();
+  explicit SiteSettingsPermissionsHandler(Profile* profile);
 
   ~SiteSettingsPermissionsHandler() override;
 
  private:
+  friend class SiteSettingsPermissionsHandlerTest;
+  FRIEND_TEST_ALL_PREFIXES(SiteSettingsPermissionsHandlerTest,
+                           PopulateUnusedSitePermissionsData);
+
   // SettingsPageUIHandler implementation.
   void OnJavascriptAllowed() override;
   void OnJavascriptDisallowed() override;
@@ -26,9 +32,15 @@
   // WebUIMessageHandler implementation.
   void RegisterMessages() override;
 
-  // Returns the list of origins that haven't been visited recently with
-  // associated permissions.
+  // Returns the list of revoked permissions to be used in
+  // "Unused site permissions" module.
   void HandleGetRevokedUnusedSitePermissionsList(const base::Value::List& args);
+
+  // Returns the list of revoked permissions that belongs to origins which
+  // haven't been visited recently.
+  base::Value::List PopulateUnusedSitePermissionsData();
+
+  const raw_ptr<Profile> profile_;
 };
 
 #endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_SITE_SETTINGS_PERMISSIONS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc
new file mode 100644
index 0000000..5d9966e
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc
@@ -0,0 +1,110 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ctime>
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/test/simple_test_clock.h"
+#include "base/time/clock.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/ui/webui/settings/site_settings_helper.h"
+#include "chrome/browser/ui/webui/settings/site_settings_permissions_handler.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "components/content_settings/core/common/features.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_web_ui.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+constexpr char kRevokedKey[] = "revoked";
+
+class SiteSettingsPermissionsHandlerTest : public testing::Test {
+ public:
+  SiteSettingsPermissionsHandlerTest() = default;
+
+  void SetUp() override {
+    // Fully initialize |profile_| in the constructor since some children
+    // classes need it right away for SetUp().
+    TestingProfile::Builder profile_builder;
+    profile_builder.AddTestingFactory(
+        HistoryServiceFactory::GetInstance(),
+        HistoryServiceFactory::GetDefaultFactory());
+    profile_ = profile_builder.Build();
+
+    // Set clock for HostContentSettingsMap.
+    base::Time time;
+    ASSERT_TRUE(base::Time::FromString("2022-09-07 13:00", &time));
+    clock_.SetNow(time);
+    hcsm_ = HostContentSettingsMapFactory::GetForProfile(profile());
+    hcsm_->SetClockForTesting(&clock_);
+
+    handler_ = std::make_unique<SiteSettingsPermissionsHandler>(profile());
+    handler()->set_web_ui(web_ui());
+    handler()->AllowJavascript();
+  }
+
+  void TearDown() override {
+    if (profile_) {
+      auto* partition = profile_->GetDefaultStoragePartition();
+      if (partition) {
+        partition->WaitForDeletionTasksForTesting();
+      }
+    }
+  }
+
+  TestingProfile* profile() { return profile_.get(); }
+  content::TestWebUI* web_ui() { return &web_ui_; }
+  SiteSettingsPermissionsHandler* handler() { return handler_.get(); }
+  HostContentSettingsMap* hcsm() { return hcsm_.get(); }
+  base::SimpleTestClock* clock() { return &clock_; }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  std::unique_ptr<SiteSettingsPermissionsHandler> handler_;
+  std::unique_ptr<TestingProfile> profile_;
+  content::TestWebUI web_ui_;
+  scoped_refptr<HostContentSettingsMap> hcsm_;
+  base::SimpleTestClock clock_;
+};
+
+TEST_F(SiteSettingsPermissionsHandlerTest, PopulateUnusedSitePermissionsData) {
+  base::test::ScopedFeatureList scoped_feature;
+  scoped_feature.InitAndEnableFeature(
+      content_settings::features::kSafetyCheckUnusedSitePermissions);
+
+  const std::string url1 = "https://example1.com";
+  const std::string url2 = "https://example2.com";
+
+  base::Value::Dict dict = base::Value::Dict();
+  base::Value::List permission_type_list = base::Value::List();
+  permission_type_list.Append(
+      static_cast<int32_t>(ContentSettingsType::GEOLOCATION));
+  dict.Set(kRevokedKey, base::Value::List(std::move(permission_type_list)));
+
+  // Add url1 to rovoked permissions list.
+  hcsm()->SetWebsiteSettingDefaultScope(
+      GURL(url1), GURL(url1),
+      ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS,
+      base::Value(std::move(dict)));
+
+  // Add GEOLOCATION setting for url2 but do not add to revoked list.
+  const content_settings::ContentSettingConstraints constraint{
+      .track_last_visit_for_autoexpiration = true};
+  hcsm()->SetContentSettingDefaultScope(
+      GURL(url2), GURL(url2), ContentSettingsType::GEOLOCATION,
+      ContentSetting::CONTENT_SETTING_ALLOW, constraint);
+
+  // Only url1 should be in the revoked permissions list, as permissions of
+  // url2 is not revoked.
+  const auto& revoked_permissions =
+      handler()->PopulateUnusedSitePermissionsData();
+  EXPECT_EQ(revoked_permissions.size(), 1UL);
+  EXPECT_EQ(url1,
+            *revoked_permissions[0].FindStringKey(site_settings::kOrigin));
+}
diff --git a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler.cc b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler.cc
index a3d67ce..9f52ffd1 100644
--- a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler.cc
@@ -112,10 +112,10 @@
       }
 
       case kMarkAsRead:
-        reading_list_model_->SetReadStatus(url_, true);
+        reading_list_model_->SetReadStatusIfExists(url_, true);
         break;
       case kMarkAsUnread:
-        reading_list_model_->SetReadStatus(url_, false);
+        reading_list_model_->SetReadStatusIfExists(url_, false);
         break;
       case kDelete:
         reading_list_model_->RemoveEntryByURL(url_);
@@ -197,7 +197,7 @@
 }
 
 void ReadingListPageHandler::UpdateReadStatus(const GURL& url, bool read) {
-  reading_list_model_->SetReadStatus(url, read);
+  reading_list_model_->SetReadStatusIfExists(url, read);
   base::RecordAction(
       base::UserMetricsAction(read ? "DesktopReadingList.MarkAsRead"
                                    : "DesktopReadingList.MarkAsUnread"));
@@ -332,7 +332,7 @@
 ReadingListPageHandler::CreateReadLaterEntriesByStatusData() {
   auto entries = reading_list::mojom::ReadLaterEntriesByStatus::New();
 
-  for (const auto& url : reading_list_model_->Keys()) {
+  for (const auto& url : reading_list_model_->GetKeys()) {
     const ReadingListEntry* entry = reading_list_model_->GetEntryByURL(url);
     DCHECK(entry);
     if (entry->IsRead()) {
diff --git a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler_unittest.cc b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler_unittest.cc
index dac25f4..0232ef4 100644
--- a/chrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/side_panel/reading_list/reading_list_page_handler_unittest.cc
@@ -94,10 +94,12 @@
     AddTabWithTitle(browser(), GURL(kTabUrl3), kTabName3);
     AddTabWithTitle(browser(), GURL(kTabUrl4), kTabName4);
 
-    model()->AddEntry(GURL(kTabUrl1), kTabName1,
-                      reading_list::EntrySource::ADDED_VIA_CURRENT_APP);
-    model()->AddEntry(GURL(kTabUrl3), kTabName3,
-                      reading_list::EntrySource::ADDED_VIA_CURRENT_APP);
+    model()->AddOrReplaceEntry(GURL(kTabUrl1), kTabName1,
+                               reading_list::EntrySource::ADDED_VIA_CURRENT_APP,
+                               /*estimated_read_time=*/base::TimeDelta());
+    model()->AddOrReplaceEntry(GURL(kTabUrl3), kTabName3,
+                               reading_list::EntrySource::ADDED_VIA_CURRENT_APP,
+                               /*estimated_read_time=*/base::TimeDelta());
   }
 
   void TearDown() override {
@@ -350,8 +352,9 @@
   // tab not being on the reading list.
   EXPECT_EQ(handler()->GetCurrentPageActionButtonStateForTesting(),
             reading_list::mojom::CurrentPageActionButtonState::kAdd);
-  model()->AddEntry(GURL(kTabUrl3), kTabName3,
-                    reading_list::EntrySource::ADDED_VIA_CURRENT_APP);
+  model()->AddOrReplaceEntry(GURL(kTabUrl3), kTabName3,
+                             reading_list::EntrySource::ADDED_VIA_CURRENT_APP,
+                             /*estimated_read_time=*/base::TimeDelta());
 
   // Expect CurrentPageActionButtonState to be mark as read, due to the current
   // tab being unread on the reading list.
diff --git a/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc b/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc
index 0693def..547ac48 100644
--- a/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc
+++ b/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc
@@ -216,6 +216,9 @@
         isSigninInterceptFre
             ? IDS_SYNC_CONFIRMATION_TANGIBLE_SYNC_INFO_TITLE_SIGNIN_INTERCEPT
             : IDS_SYNC_CONFIRMATION_TANGIBLE_SYNC_INFO_TITLE;
+    illustration_path = "images/tangible_sync_illustration.svg";
+    illustration_id =
+        IDR_SIGNIN_SYNC_CONFIRMATION_IMAGES_TANGIBLE_SYNC_ILLUSTRATION_SVG;
   }
 
   // Registering and resolving the strings with placeholders
diff --git a/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc b/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc
index e3104c3..fe92068 100644
--- a/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc
+++ b/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc
@@ -314,7 +314,7 @@
 
 struct TestParam {
   TestLacrosParam lacros_params;
-  bool is_external_pref_migration_enabled = false;
+  test::ExternalPrefMigrationTestCases pref_migration_test_param;
 };
 
 class WebAppPolicyManagerTest : public ChromeRenderViewHostTestHarness,
@@ -421,11 +421,6 @@
     std::vector<base::test::FeatureRef> disabled_features;
     enabled_features.push_back(
         features::kDesktopPWAsEnforceWebAppSettingsPolicy);
-    // Add external pref migration enable flags.
-    if (GetParam().is_external_pref_migration_enabled)
-      enabled_features.push_back(features::kUseWebAppDBInsteadOfExternalPrefs);
-    else
-      disabled_features.push_back(features::kUseWebAppDBInsteadOfExternalPrefs);
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     if (GetParam().lacros_params == TestLacrosParam::kLacrosEnabled) {
       enabled_features.push_back(features::kWebAppsCrosapi);
@@ -434,6 +429,29 @@
       disabled_features.push_back(ash::features::kLacrosPrimary);
     }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+    switch (GetParam().pref_migration_test_param) {
+      case test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref:
+        disabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        disabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+      case test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB:
+        disabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        enabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+      case test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref:
+        enabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        disabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+      case test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB:
+        enabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        enabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+    }
     scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
@@ -519,8 +537,6 @@
   if (ShouldSkipPWASpecificTest())
     return;
 
-  base::RunLoop().RunUntilIdle();
-
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
   EXPECT_TRUE(install_requests.empty());
@@ -902,6 +918,7 @@
 TEST_P(WebAppPolicyManagerTest, UninstallAppInstalledInPreviousSession) {
   if (ShouldSkipPWASpecificTest())
     return;
+
   // Simulate two policy apps and a regular app that were installed in the
   // previous session.
   SimulatePreviouslyInstalledApp(GURL(kWindowedUrl),
@@ -935,7 +952,6 @@
 TEST_P(WebAppPolicyManagerTest, UninstallAppInstalledInCurrentSession) {
   if (ShouldSkipPWASpecificTest())
     return;
-  base::RunLoop().RunUntilIdle();
 
   // Add two sites, one that opens in a window and one that opens in a tab.
   base::Value::List first_list;
@@ -1087,7 +1103,7 @@
 TEST_P(WebAppPolicyManagerTest, SayRefreshTwoTimesQuickly) {
   if (ShouldSkipPWASpecificTest())
     return;
-  base::RunLoop().RunUntilIdle();
+
   // Add an app.
   {
     base::Value::List list;
@@ -1102,7 +1118,17 @@
     profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                    std::move(list));
   }
-  base::RunLoop().RunUntilIdle();
+
+  // `OnAppsSynchronized` should be triggered twice.
+  base::RunLoop loop;
+  policy_manager().SetOnAppsSynchronizedCompletedCallbackForTesting(
+      loop.QuitClosure());
+  loop.Run();
+
+  base::RunLoop loop2;
+  policy_manager().SetOnAppsSynchronizedCompletedCallbackForTesting(
+      loop2.QuitClosure());
+  loop2.Run();
 
   // Both apps should have been installed.
   std::vector<ExternalInstallOptions> expected_options_list;
@@ -1167,8 +1193,6 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 TEST_P(WebAppPolicyManagerTest, DisableSystemWebApps) {
-  base::RunLoop().RunUntilIdle();
-
   auto disabled_apps = policy_manager().GetDisabledSystemWebApps();
   EXPECT_TRUE(disabled_apps.empty());
 
@@ -1330,14 +1354,53 @@
     WebAppPolicyManagerTest,
     testing::Values(
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-        TestParam({TestLacrosParam::kLacrosDisabled,
-                   /*is_external_pref_migration_enabled=*/false}),
-        TestParam({TestLacrosParam::kLacrosDisabled,
-                   /*is_external_pref_migration_enabled=*/true}),
+        TestParam(
+            {TestLacrosParam::kLacrosDisabled,
+             test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref}),
+        TestParam(
+            {TestLacrosParam::kLacrosDisabled,
+             test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB}),
+        TestParam(
+            {TestLacrosParam::kLacrosDisabled,
+             test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref}),
+        TestParam(
+            {TestLacrosParam::kLacrosDisabled,
+             test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB}),
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-        TestParam({TestLacrosParam::kLacrosEnabled,
-                   /*is_external_pref_migration_enabled=*/false}),
-        TestParam({TestLacrosParam::kLacrosEnabled,
-                   /*is_external_pref_migration_enabled=*/true})));
+        TestParam(
+            {TestLacrosParam::kLacrosEnabled,
+             test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref}),
+        TestParam(
+            {TestLacrosParam::kLacrosEnabled,
+             test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB}),
+        TestParam(
+            {TestLacrosParam::kLacrosEnabled,
+             test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref}),
+        TestParam(
+            {TestLacrosParam::kLacrosEnabled,
+             test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB})),
+    [](const ::testing::TestParamInfo<TestParam>& info) {
+      std::string test_name = "Test_";
+      if (info.param.lacros_params == TestLacrosParam::kLacrosEnabled)
+        test_name.append("LacrosEnabled_");
+      else
+        test_name.append("LacrosDisabled_");
+
+      switch (info.param.pref_migration_test_param) {
+        case test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref:
+          test_name.append("DisableMigration_ReadFromPrefs");
+          break;
+        case test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB:
+          test_name.append("DisableMigration_ReadFromDB");
+          break;
+        case test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref:
+          test_name.append("EnableMigration_ReadFromPrefs");
+          break;
+        case test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB:
+          test_name.append("EnableMigration_ReadFromDB");
+          break;
+      }
+      return test_name;
+    });
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/externally_managed_app_manager.cc b/chrome/browser/web_applications/externally_managed_app_manager.cc
index 5c2844c..f382c6b 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager.cc
+++ b/chrome/browser/web_applications/externally_managed_app_manager.cc
@@ -15,6 +15,8 @@
 #include "base/stl_util.h"
 #include "base/task/single_thread_task_runner.h"
 #include "build/chromeos_buildflags.h"
+#include "chrome/browser/web_applications/locks/full_system_lock.h"
+#include "chrome/browser/web_applications/web_app_command_scheduler.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "components/webapps/browser/install_result_code.h"
@@ -88,7 +90,6 @@
     std::vector<ExternalInstallOptions> desired_apps_install_options,
     ExternalInstallSource install_source,
     SynchronizeCallback callback) {
-  DCHECK(registrar_);
   DCHECK(base::ranges::all_of(
       desired_apps_install_options,
       [&install_source](const ExternalInstallOptions& install_options) {
@@ -97,14 +98,29 @@
   // Only one concurrent SynchronizeInstalledApps() expected per
   // ExternalInstallSource.
   DCHECK(!base::Contains(synchronize_requests_, install_source));
+  command_scheduler_->ScheduleCallbackWithLock<FullSystemLock>(
+      "ExternallyManagedAppManager::SynchronizeInstalledApps",
+      std::make_unique<FullSystemLockDescription>(),
+      base::BindOnce(
+          &ExternallyManagedAppManager::SynchronizeInstalledAppsOnLockAcquired,
+          weak_ptr_factory_.GetWeakPtr(),
+          std::move(desired_apps_install_options), std::move(install_source),
+          std::move(callback)));
+}
 
+void ExternallyManagedAppManager::SynchronizeInstalledAppsOnLockAcquired(
+    std::vector<ExternalInstallOptions> desired_apps_install_options,
+    ExternalInstallSource install_source,
+    SynchronizeCallback callback,
+    FullSystemLock& lock) {
   std::vector<GURL> installed_urls;
   for (const auto& apps_it :
-       registrar_->GetExternallyInstalledApps(install_source)) {
+       lock.registrar().GetExternallyInstalledApps(install_source)) {
     // TODO(crbug.com/1339965): Remove this check once we cleanup
     // ExternallyInstalledWebAppPrefs on external app uninstall.
     bool has_same_external_source =
-        registrar_->GetAppById(apps_it.first)
+        lock.registrar()
+            .GetAppById(apps_it.first)
             ->GetSources()
             .test(ConvertExternalInstallSourceToSource(install_source));
     if (has_same_external_source) {
diff --git a/chrome/browser/web_applications/externally_managed_app_manager.h b/chrome/browser/web_applications/externally_managed_app_manager.h
index 1b7eb9d0..8e15a41 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager.h
+++ b/chrome/browser/web_applications/externally_managed_app_manager.h
@@ -24,6 +24,7 @@
 
 namespace web_app {
 
+class FullSystemLock;
 class WebAppRegistrar;
 class WebAppInstallFinalizer;
 class WebAppCommandScheduler;
@@ -190,6 +191,12 @@
     std::map<GURL, bool> uninstall_results;
   };
 
+  void SynchronizeInstalledAppsOnLockAcquired(
+      std::vector<ExternalInstallOptions> desired_apps_install_options,
+      ExternalInstallSource install_source,
+      SynchronizeCallback callback,
+      FullSystemLock& lock);
+
   void InstallForSynchronizeCallback(
       ExternalInstallSource source,
       const GURL& install_url,
diff --git a/chrome/browser/web_applications/externally_managed_app_manager_impl.cc b/chrome/browser/web_applications/externally_managed_app_manager_impl.cc
index 73a6064..2228de8 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager_impl.cc
+++ b/chrome/browser/web_applications/externally_managed_app_manager_impl.cc
@@ -12,9 +12,12 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/feature_list.h"
+#include "base/functional/bind.h"
 #include "base/task/single_thread_task_runner.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/externally_managed_app_registration_task.h"
+#include "chrome/browser/web_applications/locks/full_system_lock.h"
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_command_scheduler.h"
 #include "chrome/browser/web_applications/web_app_install_finalizer.h"
@@ -158,6 +161,19 @@
 void ExternallyManagedAppManagerImpl::MaybeStartNext() {
   if (current_install_ || is_in_shutdown_)
     return;
+  command_scheduler()->ScheduleCallbackWithLock<FullSystemLock>(
+      "ExternallyManagedAppManagerImpl::MaybeStartNext",
+      std::make_unique<FullSystemLockDescription>(),
+      base::BindOnce(
+          &ExternallyManagedAppManagerImpl::MaybeStartNextOnLockAcquired,
+          weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ExternallyManagedAppManagerImpl::MaybeStartNextOnLockAcquired(
+    FullSystemLock& lock) {
+  if (current_install_ || is_in_shutdown_) {
+    return;
+  }
 
   while (!pending_installs_.empty()) {
     std::unique_ptr<TaskAndCallback> front =
@@ -173,7 +189,7 @@
     }
 
     absl::optional<AppId> app_id =
-        registrar()->LookupExternalAppId(install_options.install_url);
+        lock.registrar().LookupExternalAppId(install_options.install_url);
 
     // If the URL is not in web_app registrar,
     // then no external source has installed it.
@@ -182,10 +198,10 @@
       return;
     }
 
-    if (registrar()->IsInstalled(app_id.value())) {
+    if (lock.registrar().IsInstalled(app_id.value())) {
       if (install_options.wait_for_windows_closed &&
-          ui_manager()->GetNumWindowsForApp(app_id.value()) != 0) {
-        ui_manager()->NotifyOnAllAppWindowsClosed(
+          lock.ui_manager().GetNumWindowsForApp(app_id.value()) != 0) {
+        lock.ui_manager().NotifyOnAllAppWindowsClosed(
             app_id.value(),
             base::BindOnce(&ExternallyManagedAppManagerImpl::Install,
                            weak_ptr_factory_.GetWeakPtr(), install_options,
@@ -196,9 +212,9 @@
       // If the app is already installed, only reinstall it if the app is a
       // placeholder app and the client asked for it to be reinstalled.
       if (install_options.reinstall_placeholder &&
-          registrar()->IsPlaceholderApp(app_id.value(),
-                                        ConvertExternalInstallSourceToSource(
-                                            install_options.install_source))) {
+          lock.registrar().IsPlaceholderApp(
+              app_id.value(), ConvertExternalInstallSourceToSource(
+                                  install_options.install_source))) {
         StartInstallationTask(std::move(front));
         return;
       }
@@ -211,7 +227,7 @@
         return;
       } else {
         // Add install source before returning the result.
-        ScopedRegistryUpdate update(sync_bridge());
+        ScopedRegistryUpdate update(&lock.sync_bridge());
         WebApp* app_to_update = update->UpdateApp(app_id.value());
         app_to_update->AddSource(ConvertExternalInstallSourceToSource(
             install_options.install_source));
diff --git a/chrome/browser/web_applications/externally_managed_app_manager_impl.h b/chrome/browser/web_applications/externally_managed_app_manager_impl.h
index 9fe18dd..794125722 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager_impl.h
+++ b/chrome/browser/web_applications/externally_managed_app_manager_impl.h
@@ -27,6 +27,7 @@
 
 namespace web_app {
 
+class FullSystemLock;
 class ExternallyManagedAppRegistrationTaskBase;
 
 // Installs, uninstalls, and updates any External Web Apps. This class should
@@ -74,6 +75,7 @@
   void PostMaybeStartNext();
 
   void MaybeStartNext();
+  void MaybeStartNextOnLockAcquired(FullSystemLock& lock);
 
   void StartInstallationTask(std::unique_ptr<TaskAndCallback> task);
 
diff --git a/chrome/browser/web_applications/externally_managed_app_manager_impl_unittest.cc b/chrome/browser/web_applications/externally_managed_app_manager_impl_unittest.cc
index 596ca26..1ca55fcf 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager_impl_unittest.cc
+++ b/chrome/browser/web_applications/externally_managed_app_manager_impl_unittest.cc
@@ -466,7 +466,7 @@
   }
 
   void TearDown() override {
-    command_scheduler().Shutdown();
+    provider().Shutdown();
     WebAppTest::TearDown();
   }
 
@@ -510,7 +510,6 @@
               barrier_closure.Run();
             }));
     run_loop.Run();
-
     return results;
   }
 
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.cc b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
index 83ccc9a..4893400 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
@@ -118,7 +118,11 @@
   system_web_apps_delegate_map_ = system_web_apps_delegate_map;
 }
 
-void WebAppPolicyManager::Start(base::OnceClosure on_done) {
+void WebAppPolicyManager::Start(base::OnceClosure initialization_complete) {
+  DCHECK(initialization_complete_.is_null());
+
+  initialization_complete_ = std::move(initialization_complete);
+
   // When Lacros is enabled, don't run PWA-specific logic in Ash.
   // TODO(crbug.com/1251491): Consider factoring out logic that should only run
   // in Ash into a separate class. This way, when running in Ash, we won't need
@@ -131,8 +135,7 @@
       ->PostTask(FROM_HERE,
                  base::BindOnce(
                      &WebAppPolicyManager::InitChangeRegistrarAndRefreshPolicy,
-                     weak_ptr_factory_.GetWeakPtr(), enable_pwa_support)
-                     .Then(std::move(on_done)));
+                     weak_ptr_factory_.GetWeakPtr(), enable_pwa_support));
 }
 
 void WebAppPolicyManager::ReinstallPlaceholderAppIfNecessary(const GURL& url) {
@@ -204,6 +207,10 @@
             weak_ptr_factory_.GetWeakPtr()));
     RefreshPolicyInstalledIsolatedWebApps();
 #endif
+  } else {
+    if (initialization_complete_) {
+      std::move(initialization_complete_).Run();
+    }
   }
   ObserveDisabledSystemFeaturesPolicy();
 }
@@ -538,7 +545,7 @@
 
 void WebAppPolicyManager::SetOnAppsSynchronizedCompletedCallbackForTesting(
     base::OnceClosure callback) {
-  on_apps_synchronized_ = std::move(callback);
+  on_apps_synchronized_for_testing_ = std::move(callback);
 }
 
 void WebAppPolicyManager::SetRefreshPolicySettingsCompletedCallbackForTesting(
@@ -631,8 +638,13 @@
                                   url_and_result.second.code);
   }
 
-  if (on_apps_synchronized_)
-    std::move(on_apps_synchronized_).Run();
+  if (on_apps_synchronized_for_testing_) {
+    std::move(on_apps_synchronized_for_testing_).Run();
+  }
+
+  if (initialization_complete_) {
+    std::move(initialization_complete_).Run();
+  }
 }
 
 WebAppPolicyManager::WebAppSetting::WebAppSetting() {
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.h b/chrome/browser/web_applications/policy/web_app_policy_manager.h
index 6ea29f7..8bf8375 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.h
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.h
@@ -65,6 +65,8 @@
   void SetSystemWebAppDelegateMap(
       const ash::SystemWebAppDelegateMap* system_web_apps_delegate_map);
 
+  // `initialization_complete` waits for the first `SynchronizeInstalledApps` to
+  // finish if it's triggered on `Start`.
   void Start(base::OnceClosure initialization_complete);
 
   void ReinstallPlaceholderAppIfNecessary(const GURL& url);
@@ -190,7 +192,7 @@
 
   // Testing callbacks
   base::OnceClosure refresh_policy_settings_completed_;
-  base::OnceClosure on_apps_synchronized_;
+  base::OnceClosure on_apps_synchronized_for_testing_;
 
   bool is_refreshing_ = false;
   bool needs_refresh_ = false;
@@ -200,6 +202,9 @@
   std::unique_ptr<WebAppSetting> default_settings_;
 
   ExternallyInstalledWebAppPrefs externally_installed_app_prefs_;
+
+  base::OnceClosure initialization_complete_;
+
 #if BUILDFLAG(IS_CHROMEOS)
   std::unique_ptr<IsolatedWebAppPolicyManager> iwa_policy_manager_;
 #endif
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc b/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
index 80b3da22..ef22e92 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
@@ -80,17 +80,35 @@
 
 class WebAppPolicyManagerBrowserTest
     : public InProcessBrowserTest,
-      public testing::WithParamInterface<bool> {
+      public testing::WithParamInterface<test::ExternalPrefMigrationTestCases> {
  public:
   WebAppPolicyManagerBrowserTest() {
-    bool enable_migration = GetParam();
-    if (enable_migration) {
-      scoped_feature_list_.InitWithFeatures(
-          {features::kUseWebAppDBInsteadOfExternalPrefs}, {});
-    } else {
-      scoped_feature_list_.InitWithFeatures(
-          {}, {features::kUseWebAppDBInsteadOfExternalPrefs});
+    std::vector<base::test::FeatureRef> enabled_features;
+    std::vector<base::test::FeatureRef> disabled_features;
+
+    switch (GetParam()) {
+      case test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref:
+        disabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        disabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+      case test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB:
+        disabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        enabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+      case test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref:
+        enabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        disabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
+      case test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB:
+        enabled_features.push_back(features::kMigrateExternalPrefsToWebAppDB);
+        enabled_features.push_back(
+            features::kUseWebAppDBInsteadOfExternalPrefs);
+        break;
     }
+    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
   void SetUpOnMainThread() override {
@@ -280,8 +298,14 @@
 
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         WebAppPolicyManagerBrowserTest,
-                         ::testing::Bool());
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    WebAppPolicyManagerBrowserTest,
+    ::testing::Values(
+        test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref,
+        test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB,
+        test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref,
+        test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB),
+    test::GetExternalPrefMigrationTestName);
 
 }  // namespace web_app
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index b7da3bb8..2f3c39b5 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1670997253-c02d820175fafb1f65bd29789f8f98abb302a59a.profdata
+chrome-mac-main-1671040780-cb5fb27c8f38268f5e4a15483637a42b79405c90.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 7b284d4..d401ef2 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1670986696-fb2016fae7ed8692ed824dea922f4b0f50a876e0.profdata
+chrome-win32-main-1671008076-19a9c8909deb1a4f7619d99c4ef88613e515e068.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 8342ce2c..0d62531af 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1670997253-b16361a8b9ac29ffea5f88bdfbbb02022ef7e8ce.profdata
+chrome-win64-main-1671029899-ba84905e9939d7394e959eb36f6891b3bd5d0d8c.profdata
diff --git a/chrome/common/chrome_constants.cc b/chrome/common/chrome_constants.cc
index 12abb13..9d23ec57 100644
--- a/chrome/common/chrome_constants.cc
+++ b/chrome/common/chrome_constants.cc
@@ -13,10 +13,10 @@
 #define FPL FILE_PATH_LITERAL
 
 #if BUILDFLAG(IS_MAC)
-#if BUILDFLAG(GOOGLE_CHROME_FOR_TESTING_BRANDING)
-#define PRODUCT_STRING "Google Chrome for Testing"
-#elif BUILDFLAG(GOOGLE_CHROME_BRANDING)
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 #define PRODUCT_STRING "Google Chrome"
+#elif BUILDFLAG(USE_INTERNAL_CHROME_FOR_TESTING_ICONS)
+#define PRODUCT_STRING "Google Chrome for Testing"
 #elif BUILDFLAG(CHROMIUM_BRANDING)
 #define PRODUCT_STRING "Chromium"
 #else
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index eb2762e4..cd38cee 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -353,7 +353,7 @@
 // disruptive notifications.
 BASE_FEATURE(kDisruptiveNotificationPermissionRevocation,
              "DisruptiveNotificationPermissionRevocation",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enable DNS over HTTPS (DoH).
 BASE_FEATURE(kDnsOverHttps,
diff --git a/chrome/common/extensions/api/autofill_private.idl b/chrome/common/extensions/api/autofill_private.idl
index 24bbb9f..93ba6a2 100644
--- a/chrome/common/extensions/api/autofill_private.idl
+++ b/chrome/common/extensions/api/autofill_private.idl
@@ -185,6 +185,21 @@
     AutofillMetadata? metadata;
   };
 
+  // An IBAN entry which can be saved in the autofill section of the
+  // settings UI.
+  dictionary IbanEntry {
+    // Globally unique identifier for this entry.
+    DOMString? guid;
+
+    // IBAN value.
+    DOMString? value;
+
+    // IBAN's nickname.
+    DOMString? nickname;
+
+    AutofillMetadata? metadata;
+  };
+
   // Parameters to be passed to validatePhoneNumbers().
   dictionary ValidatePhoneParams {
     // The phone numbers to validate.
@@ -205,6 +220,7 @@
   callback ValidatePhoneNumbersCallback =
       void(DOMString[] validatedPhoneNumbers);
   callback GetCreditCardListCallback = void(CreditCardEntry[] entries);
+  callback GetIbanListCallback = void(IbanEntry[] entries);
   callback GetUpiIdListCallback = void(DOMString[] entries);
 
   interface Functions {
@@ -237,6 +253,11 @@
     // |card|: The card entry to save.
     static void saveCreditCard(CreditCardEntry card);
 
+    // Saves the given IBAN. If `iban` has an empty string as its ID, it will be
+    // assigned a new one and added as a new entry.
+    // |iban|: The IBAN entry to save.
+    static void saveIban(IbanEntry iban);
+
     // Removes the entry (address or credit card) with the given ID.
     // |guid|: ID of the entry to remove.
     static void removeEntry(DOMString guid);
@@ -255,6 +276,11 @@
     [supportsPromises] static void getCreditCardList(
         GetCreditCardListCallback callback);
 
+    // Gets the list of IBANs.
+    // `callback`: Callback which will be called with the list of IBANs.
+    [supportsPromises] static void getIbanList(
+        GetIbanListCallback callback);
+
     // Clears the data associated with a wallet card which was saved
     // locally so that the saved copy is masked (e.g., "Card ending
     // in 1234").
@@ -289,8 +315,11 @@
   interface Events {
     // Fired when the personal data has changed, meaning that an entry has
     // been added, removed, or changed.
-    // |entries| The updated list of entries.
+    // `addressEntries`: the updated list of addresses.
+    // `creditCardEntries`: the updated list of credit cards.
+    // `ibans`: the updated list of IBANs.
     static void onPersonalDataChanged(AddressEntry[] addressEntries,
-        CreditCardEntry[] creditCardEntries);
+        CreditCardEntry[] creditCardEntries,
+        IbanEntry[] ibans);
   };
 };
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 418acccb..6a1f4147 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -7025,6 +7025,7 @@
       "../browser/ui/webui/settings/settings_utils_unittest.cc",
       "../browser/ui/webui/settings/site_settings_handler_unittest.cc",
       "../browser/ui/webui/settings/site_settings_helper_unittest.cc",
+      "../browser/ui/webui/settings/site_settings_permissions_handler_unittest.cc",
       "../browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc",
       "../browser/ui/webui/side_panel/reading_list/reading_list_page_handler_unittest.cc",
       "../browser/ui/webui/side_panel/user_notes/user_notes_page_handler_unittest.cc",
diff --git a/chrome/test/data/extensions/api_test/autofill_private/test.js b/chrome/test/data/extensions/api_test/autofill_private/test.js
index 0118533a..5190d52 100644
--- a/chrome/test/data/extensions/api_test/autofill_private/test.js
+++ b/chrome/test/data/extensions/api_test/autofill_private/test.js
@@ -21,6 +21,7 @@
 var NUMBER = '4111 1111 1111 1111';
 var EXP_MONTH = '02';
 var EXP_YEAR = '2999';
+var IBAN_VALUE = 'AD14 0008 0001 0012 3456 7890';
 
 var failOnceCalled = function() {
   chrome.test.fail();
@@ -302,6 +303,104 @@
         }));
   },
 
+  function addNewIban() {
+    function filterIbanProperties(ibans) {
+      return ibans.map(iban => {
+        var filteredIban = {};
+        ['value', 'nickname'].forEach(property => {
+          filteredIban[property] = iban[property];
+        });
+        return filteredIban;
+      });
+    }
+
+    chrome.autofillPrivate.getIbanList(chrome.test.callbackPass(function(
+        ibanList) {
+      chrome.test.assertEq([], ibanList);
+
+      // Set up the callback that verifies that the IBAN was correctly added.
+      chrome.test.listenOnce(
+          chrome.autofillPrivate.onPersonalDataChanged,
+          chrome.test.callbackPass(function(addressList, cardList, ibanList) {
+            chrome.test.assertEq(
+                [{value: IBAN_VALUE, nickname: undefined}],
+                filterIbanProperties(ibanList));
+          }));
+
+      chrome.autofillPrivate.saveIban({
+        value: IBAN_VALUE,
+      });
+    }));
+  },
+
+  function noChangesToExistingIban() {
+    chrome.autofillPrivate.getIbanList(chrome.test.callbackPass(function(
+        ibanList) {
+      // The IBAN from the addNewIban function should still be there.
+      chrome.test.assertEq(1, ibanList.length);
+      var ibanGuid = ibanList[0].guid;
+
+      // Set up the listener that verifies that onPersonalDataChanged shouldn't
+      // be called.
+      chrome.autofillPrivate.onPersonalDataChanged.addListener(failOnceCalled);
+
+      // Save the IBAN with the same info, shouldn't invoke
+      // onPersonalDataChanged.
+      chrome.autofillPrivate.saveIban({
+        guid: ibanGuid,
+        value: IBAN_VALUE,
+      });
+
+      // Reset onPersonalDataChanged.
+      chrome.autofillPrivate.onPersonalDataChanged.removeListener(
+          failOnceCalled);
+    }));
+  },
+
+  function updateExistingIban() {
+    var UPDATED_IBAN_VALUE = 'AL35 2021 1109 0000 0000 0123 4567';
+    var UPDATED_NICKNAME = 'New nickname';
+
+    function filterIbanProperties(ibans) {
+      return ibans.map(iban => {
+        var filteredIban = {};
+        ['guid', 'value', 'nickname'].forEach(property => {
+          filteredIban[property] = iban[property];
+        });
+        return filteredIban;
+      });
+    }
+
+    chrome.autofillPrivate.getIbanList(chrome.test.callbackPass(function(
+        ibanList) {
+      // The IBAN from the addNewIban function should still be there.
+      chrome.test.assertEq(1, ibanList.length);
+      var ibanGuid = ibanList[0].guid;
+
+      // Set up the callback that verifies that the IBAN was correctly
+      // updated.
+      chrome.test.listenOnce(
+          chrome.autofillPrivate.onPersonalDataChanged,
+          chrome.test.callbackPass(function(addressList, cardList, ibanList) {
+            chrome.test.assertEq(
+                [{
+                  guid: ibanGuid,
+                  value: UPDATED_IBAN_VALUE,
+                  nickname: UPDATED_NICKNAME
+                }],
+                filterIbanProperties(ibanList));
+          }));
+
+      // Update the IBAN by saving an IBAN with the same guid and using some
+      // different information.
+      chrome.autofillPrivate.saveIban({
+        guid: ibanGuid,
+        value: UPDATED_IBAN_VALUE,
+        nickname: UPDATED_NICKNAME
+      });
+    }));
+  },
+
   function removeEntry() {
     var guid;
 
@@ -397,7 +496,9 @@
   'addAndUpdateCreditCard': [
     'addNewCreditCard', 'noChangesToExistingCreditCard',
     'updateExistingCreditCard'
-  ]
+  ],
+  'addAndUpdateIban':
+      ['addNewIban', 'noChangesToExistingIban', 'updateExistingIban']
 };
 
 chrome.test.getConfig(function(config) {
diff --git a/chrome/test/data/extensions/api_test/side_panel/extension/service_worker.js b/chrome/test/data/extensions/api_test/side_panel/extension/service_worker.js
index a9286d9..e907cbc 100644
--- a/chrome/test/data/extensions/api_test/side_panel/extension/service_worker.js
+++ b/chrome/test/data/extensions/api_test/side_panel/extension/service_worker.js
@@ -63,4 +63,14 @@
     chrome.test.assertEq(expected, result);
     chrome.test.succeed();
   },
+
+  // Don't set the optional path but set enabled to true.
+  async function setEnabledToTrueWithoutAPath() {
+    const newTabId = tabId + 200;
+    const expected = {tabId: newTabId, enabled: true};
+    await chrome.sidePanel.setOptions(expected);
+    const result = await chrome.sidePanel.getOptions({tabId: newTabId});
+    chrome.test.assertEq(expected, result);
+    chrome.test.succeed();
+  }
 ]);
diff --git a/chrome/test/data/webui/settings/chromeos/device_page_tests.js b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
index 8bfaaf9..30d57f2a 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
@@ -912,6 +912,18 @@
           audioPage.audioSystemProperties_.outputMuteState);
       assertFalse(audioPage.isOutputMuted_);
     });
+
+    test('simulate input mute button press test', async function() {
+      const inputMuteButton =
+          audioPage.shadowRoot.querySelector('#audioInputGainMuteButton');
+
+      assertFalse(audioPage.getIsInputMutedForTest());
+
+      inputMuteButton.click();
+      await flushTasks();
+
+      assertTrue(audioPage.getIsInputMutedForTest());
+    });
   });
 
   suite(assert(TestNames.Pointers), function() {
diff --git a/chrome/test/data/webui/settings/cookies_page_test.ts b/chrome/test/data/webui/settings/cookies_page_test.ts
index 1cbc09dc..b3c9803 100644
--- a/chrome/test/data/webui/settings/cookies_page_test.ts
+++ b/chrome/test/data/webui/settings/cookies_page_test.ts
@@ -5,7 +5,7 @@
 // clang-format off
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {ContentSetting, ContentSettingsTypes,CookiePrimarySetting, SettingsCookiesPageElement, SiteSettingsPrefsBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
+import {ContentSetting, SettingsCollapseRadioButtonElement, ContentSettingsTypes,CookiePrimarySetting, SettingsCookiesPageElement, SiteSettingsPrefsBrowserProxyImpl, CookieControlsMode} from 'chrome://settings/lazy_load.js';
 import {CrLinkRowElement, CrSettingsPrefs, MetricsBrowserProxyImpl, PrivacyElementInteractions, Router, routes, SettingsPrefsElement, SettingsToggleButtonElement} from 'chrome://settings/settings.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {isChildVisible} from 'chrome://webui-test/test_util.js';
@@ -23,11 +23,333 @@
   let page: SettingsCookiesPageElement;
   let settingsPrefs: SettingsPrefsElement;
 
+  function blockThirdParty(): SettingsCollapseRadioButtonElement {
+    return page.shadowRoot!.querySelector('#blockThirdParty')!;
+  }
+
+  function blockThirdPartyIncognito(): SettingsCollapseRadioButtonElement {
+    return page.shadowRoot!.querySelector('#blockThirdPartyIncognito')!;
+  }
+
+  function allowThirdParty(): SettingsCollapseRadioButtonElement {
+    return page.shadowRoot!.querySelector('#allowThirdParty')!;
+  }
+
+  suiteSetup(function() {
+    settingsPrefs = document.createElement('settings-prefs');
+    return CrSettingsPrefs.initialized;
+  });
+
+  setup(function() {
+    testMetricsBrowserProxy = new TestMetricsBrowserProxy();
+    MetricsBrowserProxyImpl.setInstance(testMetricsBrowserProxy);
+    siteSettingsBrowserProxy = new TestSiteSettingsPrefsBrowserProxy();
+    SiteSettingsPrefsBrowserProxyImpl.setInstance(siteSettingsBrowserProxy);
+
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    page = document.createElement('settings-cookies-page');
+    page.prefs = settingsPrefs.prefs!;
+
+    // Enable one of the PS APIs.
+    page.set('prefs.privacy_sandbox.m1.topics_enabled.value', true);
+    page.set(
+        'prefs.profile.cookie_controls_mode.value', CookieControlsMode.OFF);
+    document.body.appendChild(page);
+    flush();
+  });
+
+  teardown(function() {
+    page.remove();
+    Router.getInstance().resetRouteForTesting();
+  });
+
+  test('ElementVisibility', async function() {
+    // TODO(): Remove assertFalse checks after the feature is launched.
+    await flushTasks();
+    assertTrue(isChildVisible(page, '#exceptionHeader'));
+    assertTrue(isChildVisible(page, '#allowExceptionsList'));
+    assertTrue(isChildVisible(page, '#sessionOnlyExceptionsList'));
+    assertTrue(isChildVisible(page, '#blockExceptionsList'));
+
+    assertFalse(isChildVisible(page, '#clearOnExit'));
+
+    assertTrue(isChildVisible(page, '#doNotTrack'));
+    assertTrue(isChildVisible(page, '#networkPrediction'));
+
+    assertTrue(isChildVisible(page, '#allowThirdParty'));
+    assertTrue(isChildVisible(page, '#blockThirdParty'));
+    assertTrue(isChildVisible(page, '#blockThirdPartyIncognito'));
+    assertFalse(isChildVisible(page, '#allowAll'));
+    assertFalse(isChildVisible(page, '#blockAll'));
+  });
+
+  test('NetworkPredictionClickRecorded', async function() {
+    page.shadowRoot!.querySelector<HTMLElement>('#networkPrediction')!.click();
+    const result =
+        await testMetricsBrowserProxy.whenCalled('recordSettingsPageHistogram');
+    assertEquals(PrivacyElementInteractions.NETWORK_PREDICTION, result);
+  });
+
+  test('CookiesRadioClicksRecorded', function() {
+    // TODO(crbug.com/1378703): Add historgram tests.
+    blockThirdParty().click();
+    assertEquals(
+        page.getPref('profile.cookie_controls_mode.value'),
+        CookieControlsMode.BLOCK_THIRD_PARTY);
+
+    blockThirdPartyIncognito().click();
+    assertEquals(
+        page.getPref('profile.cookie_controls_mode.value'),
+        CookieControlsMode.INCOGNITO_ONLY);
+
+    allowThirdParty().click();
+    assertEquals(
+        page.getPref('profile.cookie_controls_mode.value'),
+        CookieControlsMode.OFF);
+  });
+
+  test('CookieSettingExceptions_Search', async function() {
+    // TODO(crbug.com/1378703): Update after changes to site lists.
+    const exceptionPrefs = createSiteSettingsPrefs([], [
+      createContentSettingTypeToValuePair(
+          ContentSettingsTypes.COOKIES,
+          [
+            createRawSiteException('http://foo-block.com', {
+              embeddingOrigin: '',
+              setting: ContentSetting.BLOCK,
+            }),
+            createRawSiteException('http://foo-allow.com', {
+              embeddingOrigin: '',
+            }),
+            createRawSiteException('http://foo-session.com', {
+              embeddingOrigin: '',
+              setting: ContentSetting.SESSION_ONLY,
+            }),
+          ]),
+    ]);
+    page.searchTerm = 'foo';
+    siteSettingsBrowserProxy.setPrefs(exceptionPrefs);
+    await siteSettingsBrowserProxy.whenCalled('getExceptionList');
+    flush();
+
+    const exceptionLists = page.shadowRoot!.querySelectorAll('site-list');
+    assertEquals(exceptionLists.length, 3);
+
+    for (const list of exceptionLists) {
+      assertTrue(isChildVisible(list, 'site-list-entry'));
+    }
+
+    page.searchTerm = 'unrelated.com';
+    flush();
+
+    for (const list of exceptionLists) {
+      assertFalse(isChildVisible(list, 'site-list-entry'));
+    }
+  });
+
+  test('ExceptionLists_ReadOnly', function() {
+    // TODO(crbug.com/1378703): Update after changes to site lists.
+    // Check all exception lists are read only when the preference
+    // reports as managed.
+    page.set('prefs.generated.cookie_default_content_setting', {
+      value: ContentSetting.ALLOW,
+      enforcement: chrome.settingsPrivate.Enforcement.ENFORCED,
+    });
+    let exceptionLists = page.shadowRoot!.querySelectorAll('site-list');
+    assertEquals(exceptionLists.length, 3);
+    for (const list of exceptionLists) {
+      assertTrue(!!list.readOnlyList);
+    }
+
+    // Return preference to unmanaged state and check all exception lists
+    // are no longer read only.
+    page.set('prefs.generated.cookie_default_content_setting', {
+      value: ContentSetting.ALLOW,
+    });
+    exceptionLists = page.shadowRoot!.querySelectorAll('site-list');
+    assertEquals(exceptionLists.length, 3);
+    for (const list of exceptionLists) {
+      assertFalse(!!list.readOnlyList);
+    }
+  });
+
+  test('privacySandboxToast', async function() {
+    assertFalse(page.$.toast.open);
+
+    // Disabling third-party cookies should display the privacy sandbox toast.
+    blockThirdParty().click();
+    await flushTasks();
+    assertEquals(
+        page.getPref('profile.cookie_controls_mode.value'),
+        CookieControlsMode.BLOCK_THIRD_PARTY);
+    // TODO(crbug.com/1378703): Check historgrams.
+    assertTrue(page.$.toast.open);
+
+    // Clicking the toast link should be recorded in UMA and should dismiss
+    // the toast.
+    page.$.toast.querySelector('cr-button')!.click();
+    // TODO(crbug.com/1378703): Check historgrams.
+    assertFalse(page.$.toast.open);
+
+    // Renabling 3P cookies for regular sessions should not display the toast.
+    blockThirdPartyIncognito().click();
+    await flushTasks();
+    assertEquals(
+        page.getPref('profile.cookie_controls_mode.value'),
+        CookieControlsMode.INCOGNITO_ONLY);
+    assertFalse(page.$.toast.open);
+
+    // The toast should not be displayed if the user has all privacy sandbox
+    // APIs disabled.
+    page.set('prefs.privacy_sandbox.m1.topics_enabled.value', false);
+    blockThirdParty().click();
+    await flushTasks();
+    assertEquals(
+        page.getPref('profile.cookie_controls_mode.value'),
+        CookieControlsMode.BLOCK_THIRD_PARTY);
+    assertFalse(page.$.toast.open);
+
+    // Reset the state to show the toast.
+    page.set('prefs.privacy_sandbox.m1.topics_enabled.value', true);
+    page.set(
+        'prefs.profile.cookie_controls_mode.value',
+        CookieControlsMode.INCOGNITO_ONLY);
+    blockThirdParty().click();
+    await flushTasks();
+    assertEquals(
+        page.getPref('profile.cookie_controls_mode.value'),
+        CookieControlsMode.BLOCK_THIRD_PARTY);
+    // TODO(crbug.com/1378703): Check historgrams.
+    assertTrue(page.$.toast.open);
+
+    // Reselecting a non-3P cookie blocking setting should hide the toast.
+    allowThirdParty().click();
+    await flushTasks();
+    assertEquals(
+        page.getPref('profile.cookie_controls_mode.value'),
+        CookieControlsMode.OFF);
+    assertFalse(page.$.toast.open);
+
+    // Navigating away from the page should hide the toast, even if navigated
+    // back to.
+    blockThirdParty().click();
+    await flushTasks();
+    assertEquals(
+        page.getPref('profile.cookie_controls_mode.value'),
+        CookieControlsMode.BLOCK_THIRD_PARTY);
+    assertTrue(page.$.toast.open);
+    Router.getInstance().navigateTo(routes.BASIC);
+    Router.getInstance().navigateTo(routes.COOKIES);
+    await flushTasks();
+    assertFalse(page.$.toast.open);
+  });
+
+  test('privacySandboxToast_restrictedSandbox', async function() {
+    // No toast should be shown if the privacy sandbox is restricted
+    loadTimeData.overrideValues({
+      isPrivacySandboxRestricted: true,
+    });
+    page.set('prefs.privacy_sandbox.m1.topics_enabled.value', true);
+    blockThirdParty().click();
+    assertEquals(
+        'Settings.PrivacySandbox.Block3PCookies',
+        await testMetricsBrowserProxy.whenCalled('recordAction'));
+    testMetricsBrowserProxy.resetResolver('recordAction');
+    assertFalse(page.$.toast.open);
+  });
+});
+
+suite('CrSettingsCookiesPageTest_FirstPartySetsUIEnabled', function() {
+  let page: SettingsCookiesPageElement;
+  let settingsPrefs: SettingsPrefsElement;
+
+  function blockThirdParty(): SettingsCollapseRadioButtonElement {
+    return page.shadowRoot!.querySelector('#blockThirdParty')!;
+  }
+
+  function blockThirdPartyIncognito(): SettingsCollapseRadioButtonElement {
+    return page.shadowRoot!.querySelector('#blockThirdPartyIncognito')!;
+  }
+
+  function allowThirdParty(): SettingsCollapseRadioButtonElement {
+    return page.shadowRoot!.querySelector('#allowThirdParty')!;
+  }
+
+  suiteSetup(function() {
+    loadTimeData.overrideValues({firstPartySetsUIEnabled: true});
+    settingsPrefs = document.createElement('settings-prefs');
+    return CrSettingsPrefs.initialized;
+  });
+
+  setup(function() {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    page = document.createElement('settings-cookies-page');
+    page.prefs = settingsPrefs.prefs!;
+    document.body.appendChild(page);
+    flush();
+  });
+
+  teardown(function() {
+    page.remove();
+  });
+
+  test('Disabled Toggle', function() {
+    // Confirm that when the user has not selected the block 3PC setting, the
+    // FPS toggle is disabled.
+    const firstPartySetsToggle =
+        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
+            '#firstPartySetsToggle')!;
+    blockThirdParty().click();
+    flush();
+    assertEquals(
+        CookieControlsMode.BLOCK_THIRD_PARTY,
+        page.prefs.profile.cookie_controls_mode.value);
+    assertFalse(firstPartySetsToggle.disabled, 'expect toggle to be enabled');
+
+    allowThirdParty().click();
+    flush();
+    assertEquals(
+        CookieControlsMode.OFF, page.prefs.profile.cookie_controls_mode.value);
+    assertTrue(firstPartySetsToggle.disabled, 'expect toggle to be disabled');
+
+    blockThirdPartyIncognito().click();
+    flush();
+    assertEquals(
+        CookieControlsMode.INCOGNITO_ONLY,
+        page.prefs.profile.cookie_controls_mode.value);
+    assertTrue(firstPartySetsToggle.disabled, 'expect toggle to be disabled');
+  });
+});
+
+// TODO(crbug.com/1378703): Remove after crbug/1378703 launched.
+suite('PrivacySandboxSettings4Disabled', function() {
+  let siteSettingsBrowserProxy: TestSiteSettingsPrefsBrowserProxy;
+  let testMetricsBrowserProxy: TestMetricsBrowserProxy;
+  let page: SettingsCookiesPageElement;
+  let settingsPrefs: SettingsPrefsElement;
+
+  function blockAll(): SettingsCollapseRadioButtonElement {
+    return page.shadowRoot!.querySelector('#blockAll')!;
+  }
+
+  function blockThirdParty(): SettingsCollapseRadioButtonElement {
+    return page.shadowRoot!.querySelector('#blockThirdParty')!;
+  }
+
+  function blockThirdPartyIncognito(): SettingsCollapseRadioButtonElement {
+    return page.shadowRoot!.querySelector('#blockThirdPartyIncognito')!;
+  }
+
+  function allowAll(): SettingsCollapseRadioButtonElement {
+    return page.shadowRoot!.querySelector('#allowAll')!;
+  }
+
   suiteSetup(function() {
     loadTimeData.overrideValues({
       // <if expr="chromeos_lacros">
       isSecondaryUser: false,
       // </if>
+      isPrivacySandboxSettings4: false,
     });
     settingsPrefs = document.createElement('settings-prefs');
     return CrSettingsPrefs.initialized;
@@ -66,33 +388,26 @@
     assertTrue(isChildVisible(page, '#blockThirdPartyIncognito'));
   });
 
-  test('NetworkPredictionClickRecorded', async function() {
-    page.shadowRoot!.querySelector<HTMLElement>('#networkPrediction')!.click();
-    const result =
-        await testMetricsBrowserProxy.whenCalled('recordSettingsPageHistogram');
-    assertEquals(PrivacyElementInteractions.NETWORK_PREDICTION, result);
-  });
-
   test('CookiesRadioClicksRecorded', async function() {
-    page.$.blockAll.click();
+    blockAll().click();
     let result =
         await testMetricsBrowserProxy.whenCalled('recordSettingsPageHistogram');
     assertEquals(PrivacyElementInteractions.COOKIES_BLOCK, result);
     testMetricsBrowserProxy.reset();
 
-    page.$.blockThirdParty.click();
+    blockThirdParty().click();
     result =
         await testMetricsBrowserProxy.whenCalled('recordSettingsPageHistogram');
     assertEquals(PrivacyElementInteractions.COOKIES_THIRD, result);
     testMetricsBrowserProxy.reset();
 
-    page.$.blockThirdPartyIncognito.click();
+    blockThirdPartyIncognito().click();
     result =
         await testMetricsBrowserProxy.whenCalled('recordSettingsPageHistogram');
     assertEquals(PrivacyElementInteractions.COOKIES_INCOGNITO, result);
     testMetricsBrowserProxy.reset();
 
-    page.$.allowAll.click();
+    allowAll().click();
     result =
         await testMetricsBrowserProxy.whenCalled('recordSettingsPageHistogram');
     assertEquals(PrivacyElementInteractions.COOKIES_ALL, result);
@@ -186,7 +501,6 @@
   test('BlockAll_ManagementSource', async function() {
     // Test that controlledBy for the blockAll_ preference is set to
     // the same value as the generated.cookie_session_only preference.
-    const blockAll = page.$.blockAll;
     page.set('prefs.generated.cookie_session_only', {
       value: true,
       enforcement: chrome.settingsPrivate.Enforcement.ENFORCED,
@@ -194,7 +508,7 @@
     });
     flush();
     assertEquals(
-        blockAll.pref!.controlledBy,
+        blockAll().pref!.controlledBy,
         chrome.settingsPrivate.ControlledBy.EXTENSION);
 
     page.set('prefs.generated.cookie_session_only', {
@@ -203,7 +517,7 @@
       controlledBy: chrome.settingsPrivate.ControlledBy.DEVICE_POLICY,
     });
     assertEquals(
-        blockAll.pref!.controlledBy,
+        blockAll().pref!.controlledBy,
         chrome.settingsPrivate.ControlledBy.DEVICE_POLICY);
   });
 
@@ -211,7 +525,7 @@
     assertFalse(page.$.toast.open);
 
     // Disabling all cookies should display the privacy sandbox toast.
-    page.$.blockAll.click();
+    blockAll().click();
     assertEquals(
         'Settings.PrivacySandbox.Block3PCookies',
         await testMetricsBrowserProxy.whenCalled('recordAction'));
@@ -228,7 +542,7 @@
     assertFalse(page.$.toast.open);
 
     // Renabling 3P cookies for regular sessions should not display the toast.
-    page.$.blockThirdPartyIncognito.click();
+    blockThirdPartyIncognito().click();
     await flushTasks();
     assertFalse(page.$.toast.open);
     assertEquals(0, testMetricsBrowserProxy.getCallCount('recordAction'));
@@ -236,7 +550,7 @@
     // The toast should not be displayed if the user has the privacy sandbox
     // APIs disabled.
     page.set('prefs.privacy_sandbox.apis_enabled_v2.value', false);
-    page.$.blockAll.click();
+    blockAll().click();
     await flushTasks();
     assertFalse(page.$.toast.open);
     assertEquals(0, testMetricsBrowserProxy.getCallCount('recordAction'));
@@ -246,20 +560,20 @@
     page.set(
         'prefs.generated.cookie_primary_setting.value',
         CookiePrimarySetting.ALLOW_ALL);
-    page.$.blockThirdParty.click();
+    blockThirdParty().click();
     assertEquals(
         'Settings.PrivacySandbox.Block3PCookies',
         await testMetricsBrowserProxy.whenCalled('recordAction'));
     assertTrue(page.$.toast.open);
 
     // Reselecting a non-3P cookie blocking setting should hide the toast.
-    page.$.allowAll.click();
+    allowAll().click();
     await flushTasks();
     assertFalse(page.$.toast.open);
 
     // Navigating away from the page should hide the toast, even if navigated
     // back to.
-    page.$.blockAll.click();
+    blockAll().click();
     await flushTasks();
     assertTrue(page.$.toast.open);
     Router.getInstance().navigateTo(routes.BASIC);
@@ -274,7 +588,7 @@
       isPrivacySandboxRestricted: true,
     });
     page.set('prefs.privacy_sandbox.apis_enabled_v2.value', true);
-    page.$.blockAll.click();
+    blockAll().click();
     assertEquals(
         'Settings.PrivacySandbox.Block3PCookies',
         await testMetricsBrowserProxy.whenCalled('recordAction'));
@@ -284,12 +598,16 @@
 });
 
 // <if expr="chromeos_lacros">
+// TODO(crbug/1378703): Remove after crbug/1378703 launched.
 suite('CrSettingsCookiesPageTest_lacrosSecondaryProfile', function() {
   let page: SettingsCookiesPageElement;
   let settingsPrefs: SettingsPrefsElement;
 
   suiteSetup(function() {
-    loadTimeData.overrideValues({isSecondaryUser: true});
+    loadTimeData.overrideValues({
+      isSecondaryUser: true,
+      isPrivacySandboxSettings4: false,
+    });
     settingsPrefs = document.createElement('settings-prefs');
     return CrSettingsPrefs.initialized;
   });
@@ -315,61 +633,3 @@
   });
 });
 // </if>
-
-suite('CrSettingsCookiesPageTest_FirstPartySetsUIEnabled', function() {
-  let page: SettingsCookiesPageElement;
-  let settingsPrefs: SettingsPrefsElement;
-
-  suiteSetup(function() {
-    loadTimeData.overrideValues({firstPartySetsUIEnabled: true});
-    settingsPrefs = document.createElement('settings-prefs');
-    return CrSettingsPrefs.initialized;
-  });
-
-  setup(function() {
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
-    page = document.createElement('settings-cookies-page');
-    page.prefs = settingsPrefs.prefs!;
-    document.body.appendChild(page);
-    flush();
-  });
-
-  teardown(function() {
-    page.remove();
-  });
-
-  test('Disabled Toggle', function() {
-    // Confirm that when the user has not selected the block 3PC setting, the
-    // FPS toggle is disabled.
-    const firstPartySetsToggle =
-        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
-            '#firstPartySetsToggle')!;
-    page.$.blockThirdParty.click();
-    flush();
-    assertEquals(
-        CookiePrimarySetting.BLOCK_THIRD_PARTY,
-        page.prefs.generated.cookie_primary_setting.value);
-    assertFalse(firstPartySetsToggle.disabled);
-
-    page.$.allowAll.click();
-    flush();
-    assertEquals(
-        CookiePrimarySetting.ALLOW_ALL,
-        page.prefs.generated.cookie_primary_setting.value);
-    assertTrue(firstPartySetsToggle.disabled);
-
-    page.$.blockThirdPartyIncognito.click();
-    flush();
-    assertEquals(
-        CookiePrimarySetting.BLOCK_THIRD_PARTY_INCOGNITO,
-        page.prefs.generated.cookie_primary_setting.value);
-    assertTrue(firstPartySetsToggle.disabled);
-
-    page.$.blockAll.click();
-    flush();
-    assertEquals(
-        CookiePrimarySetting.BLOCK_ALL,
-        page.prefs.generated.cookie_primary_setting.value);
-    assertTrue(firstPartySetsToggle.disabled);
-  });
-});
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index e9cbf0f4..50143ee 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -744,6 +744,15 @@
   get browsePreload() {
     return 'chrome://settings/test_loader.html?module=settings/cookies_page_test.js';
   }
+
+  /** @override */
+  get featureList() {
+    return {
+      enabled: [
+        'privacy_sandbox::kPrivacySandboxSettings4',
+      ],
+    };
+  }
 };
 
 // Flaky on MacOS bots and times out on Linux Dbg: https://crbug.com/1240747
@@ -760,6 +769,23 @@
   runMochaSuite('CrSettingsCookiesPageTest_FirstPartySetsUIEnabled');
 });
 
+GEN('#if BUILDFLAG(IS_CHROMEOS_LACROS)');
+TEST_F('CrSettingsCookiesPageTest', 'LacrosSecondaryProfile', function() {
+  runMochaSuite('CrSettingsCookiesPageTest_lacrosSecondaryProfile');
+});
+GEN('#endif');
+
+GEN('#if (BUILDFLAG(IS_MAC)) || (BUILDFLAG(IS_LINUX) && !defined(NDEBUG))');
+GEN('#define MAYBE_PrivacySandboxSettings4Disabled DISABLED_PrivacySandboxSettings4Disabled');
+GEN('#else');
+GEN('#define MAYBE_PrivacySandboxSettings4Disabled PrivacySandboxSettings4Disabled');
+GEN('#endif');
+TEST_F(
+    'CrSettingsCookiesPageTest', 'MAYBE_PrivacySandboxSettings4Disabled',
+    function() {
+      runMochaSuite('PrivacySandboxSettings4Disabled');
+    });
+
 var CrSettingsRouteTest = class extends CrSettingsBrowserTest {
   /** @override */
   get browsePreload() {
@@ -767,12 +793,6 @@
   }
 };
 
-GEN('#if BUILDFLAG(IS_CHROMEOS_LACROS)');
-TEST_F('CrSettingsCookiesPageTest', 'LacrosSecondaryProfile', function() {
-  runMochaSuite('CrSettingsCookiesPageTest_lacrosSecondaryProfile');
-});
-GEN('#endif');
-
 TEST_F('CrSettingsRouteTest', 'Basic', function() {
   runMochaSuite('route');
 });
diff --git a/chrome/updater/mac/privileged_helper/service.mm b/chrome/updater/mac/privileged_helper/service.mm
index 2d517035..6a87639 100644
--- a/chrome/updater/mac/privileged_helper/service.mm
+++ b/chrome/updater/mac/privileged_helper/service.mm
@@ -9,6 +9,7 @@
 #import <Foundation/Foundation.h>
 #include <Security/Security.h>
 
+#include <pwd.h>
 #include <unistd.h>
 
 #include <string>
@@ -17,6 +18,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/command_line.h"
+#include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
@@ -227,28 +229,29 @@
     return;
   }
 
-  base::CommandLine chown_cmd(base::FilePath("/usr/sbin/chown"));
-  chown_cmd.AppendArg("-hR");
-  chown_cmd.AppendArg("root:wheel");
-  chown_cmd.AppendArg(browser_path);
-
-  int exit_code = 0;
-  std::string output;
-  if (!base::GetAppOutputWithExitCode(chown_cmd, &output, &exit_code)) {
-    VLOG(0) << "Something went wrong with altering the browser ownership: "
-            << browser_path;
+  struct passwd* root = getpwnam("root");
+  if (!root) {
+    PLOG(ERROR) << "Could not find root user.";
     main_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(result), kFailedToAlterBrowserOwnership));
     return;
   }
 
-  if (exit_code) {
-    VLOG(0) << "Output from attempting to alter browser ownership: " << output;
-    VLOG(0) << "Exit code: " << exit_code;
-    main_task_runner_->PostTask(FROM_HERE,
-                                base::BindOnce(std::move(result), exit_code));
-    return;
+  // Recursively change |browser_path| to be owned by root, deliberately not
+  // following symlinks.
+  base::FileEnumerator file_enumerator(
+      base::FilePath(browser_path), true,
+      base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+  for (base::FilePath name = file_enumerator.Next(); !name.empty();
+       name = file_enumerator.Next()) {
+    if (lchown(name.value().c_str(), root->pw_uid, root->pw_gid)) {
+      PLOG(ERROR) << "Could not alter ownership of " << name;
+      main_task_runner_->PostTask(
+          FROM_HERE,
+          base::BindOnce(std::move(result), kFailedToAlterBrowserOwnership));
+      return;
+    }
   }
 
   if (!ConfirmFilePermissions(base::FilePath(browser_path), kPermissionsMask)) {
diff --git a/chromecast/BUILD.gn b/chromecast/BUILD.gn
index 2c3a749..e0c560c 100644
--- a/chromecast/BUILD.gn
+++ b/chromecast/BUILD.gn
@@ -36,7 +36,9 @@
     if (chromecast_branding == "public") {
       deps += [ ":cast_shell_apk" ]
     } else {
-      deps += [ ":cast_browser_bundle_module" ]
+      foreach(device_type, android_device_types) {
+        deps += [ ":cast_browser_${device_type}_bundle_module" ]
+      }
     }
   } else if (!is_fuchsia) {
     data_deps += [
@@ -665,7 +667,7 @@
   generate_jni_registration("cast_shell_jni_registration") {
     targets = [ ":cast_shell_apk" ]
     if (chromecast_branding != "public") {
-      targets += [ ":cast_browser_bundle_module" ]
+      targets += [ ":cast_browser_generic_bundle_module" ]
     }
     manual_jni_registration = true
   }
@@ -728,50 +730,51 @@
   }
 
   if (chromecast_branding != "public") {
-    android_app_bundle_module("cast_browser_bundle_module") {
-      is_base_module = false
-      base_module_target = "//chromecast/internal/service/main/android:cast_service_bundle_module"
+    foreach(device_type, android_device_types) {
+      android_app_bundle_module("cast_browser_${device_type}_bundle_module") {
+        is_base_module = false
+        base_module_target = "//chromecast/internal/service/main/android:cast_service_${device_type}_bundle_module"
 
-      android_manifest =
-          "$root_gen_dir/cast_browser_manifest/AndroidManifest.xml"
-      android_manifest_dep =
-          "//chromecast/browser/android:cast_browser_manifest"
+        android_manifest = "$root_gen_dir/cast_browser_${device_type}_manifest.AndroidManifest.xml"
+        android_manifest_dep =
+            "//chromecast/browser/android:cast_browser_${device_type}_manifest"
 
-      min_sdk_version = 24
-      target_sdk_version = 33
+        min_sdk_version = 24
+        target_sdk_version = 33
 
-      package_name = "cast_browser"
+        package_name = "cast_browser"
 
-      # |package_id| is required because the default one conflicts with the base
-      # modules' ID (0x7f).
-      package_id = 125
+        # |package_id| is required because the default one conflicts with the base
+        # modules' ID (0x7f).
+        package_id = 125
 
-      shared_libraries = [ "//chromecast/android:libcast_browser_android" ]
-      deps = [
-        "//base:base_java",
-        "//base:jni_java",
-        "//build/android:build_java",
-        "//chromecast/browser/android:cast_browser_java",
-        "//components/crash/core/app:chrome_crashpad_handler_named_as_so",
-        "//components/viz/service:service_java",
-        "//third_party/crashpad/crashpad/handler:crashpad_handler_trampoline",
-      ]
+        shared_libraries = [ "//chromecast/android:libcast_browser_android" ]
+        deps = [
+          "//base:base_java",
+          "//base:jni_java",
+          "//build/android:build_java",
+          "//chromecast/browser/android:cast_browser_java",
+          "//components/crash/core/app:chrome_crashpad_handler_named_as_so",
+          "//components/viz/service:service_java",
+          "//third_party/crashpad/crashpad/handler:crashpad_handler_trampoline",
+        ]
 
-      loadable_modules = [
-        "$root_out_dir/libchrome_crashpad_handler.so",
-        "$root_out_dir/libcrashpad_handler_trampoline.so",
-      ]
+        loadable_modules = [
+          "$root_out_dir/libchrome_crashpad_handler.so",
+          "$root_out_dir/libcrashpad_handler_trampoline.so",
+        ]
 
-      # TODO(b/228506503): remove the exclusion rule after cast_service and
-      # cast_browser depend on different resource targets.
-      resource_exclusion_regex = "cast_web_contents_activity.xml"
+        # TODO(b/228506503): remove the exclusion rule after cast_service and
+        # cast_browser depend on different resource targets.
+        resource_exclusion_regex = "cast_web_contents_activity.xml"
 
-      enable_multidex = true
+        enable_multidex = true
 
-      # TODO(b/222780347): Enable when compile errors are resolved on release
-      # builds.
-      proguard_enabled = false
-      generate_native_libraries_java = true
+        # TODO(b/222780347): Enable when compile errors are resolved on release
+        # builds.
+        proguard_enabled = false
+        generate_native_libraries_java = true
+      }
     }
   }
 }
diff --git a/chromecast/browser/android/BUILD.gn b/chromecast/browser/android/BUILD.gn
index cec8563..af19a38 100644
--- a/chromecast/browser/android/BUILD.gn
+++ b/chromecast/browser/android/BUILD.gn
@@ -16,17 +16,17 @@
   output = cast_shell_android_manifest
 }
 
-cast_browser_android_manifest =
-    "$root_gen_dir/cast_browser_manifest/AndroidManifest.xml"
+foreach(device_type, android_device_types) {
+  jinja_template("cast_browser_${device_type}_manifest") {
+    input = "apk/CastBrowserAndroidManifest.xml.jinja2"
+    output =
+        "$root_gen_dir/cast_browser_${device_type}_manifest.AndroidManifest.xml"
 
-jinja_template("cast_browser_manifest") {
-  input = "apk/CastBrowserAndroidManifest.xml.jinja2"
-  output = cast_browser_android_manifest
-
-  variables = [
-    "cast_build_architecture=$target_cpu",
-    "cast_build_incremental=$cast_build_incremental",
-  ]
+    variables = [
+      "cast_build_incremental=$cast_build_incremental",
+      "cast_device_type=${device_type}",
+    ]
+  }
 }
 
 java_cpp_template("cast_shell_build_config_gen") {
@@ -73,8 +73,6 @@
 }
 
 android_resources("cast_browser_android_resources") {
-  android_manifest = cast_browser_android_manifest
-  android_manifest_dep = ":cast_browser_manifest"
   sources = extra_resources
 }
 
@@ -212,10 +210,7 @@
 
   jar_excluded_patterns = [ "*/ProductConfig.class" ]
 
-  deps = common_android_library_deps + [
-           ":cast_browser_android_resources",
-           ":cast_browser_manifest",
-         ]
+  deps = common_android_library_deps + [ ":cast_browser_android_resources" ]
   annotation_processor_deps = [
     "//base/android/jni_generator:jni_processor",
     "//components/module_installer/android:module_interface_processor",
diff --git a/chromecast/browser/android/apk/CastBrowserAndroidManifest.xml.jinja2 b/chromecast/browser/android/apk/CastBrowserAndroidManifest.xml.jinja2
index c2aa42f..c5c8fe8f 100644
--- a/chromecast/browser/android/apk/CastBrowserAndroidManifest.xml.jinja2
+++ b/chromecast/browser/android/apk/CastBrowserAndroidManifest.xml.jinja2
@@ -10,13 +10,13 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="com.google.android.apps.mediashell"
           featureSplit="cast_browser"
-          {% set architecture_digit = {
-            'arm': 1,
-            'x86': 2,
-            'arm64': 3,
-            'x64': 4,
-            }[cast_build_architecture] %}
-          android:versionCode="{{ cast_build_incremental }}{{ architecture_digit }}" >
+          {% set device_type_digit = {
+            'generic': 0,
+            'atv': 1,
+            'tablet': 2,
+            'automotive': 3,
+          }[cast_device_type] %}
+          android:versionCode="{{ cast_build_incremental }}{{ device_type_digit }}" >
     <dist:module
         dist:onDemand="false"
         dist:title="cast_browser">
diff --git a/chromecast/chromecast.gni b/chromecast/chromecast.gni
index 5b000ae..48327b1 100644
--- a/chromecast/chromecast.gni
+++ b/chromecast/chromecast.gni
@@ -150,6 +150,16 @@
   # cast_shared_library targets are normal shared libraries. When false,
   # they become source_sets.
   use_vendor_shlibs = !is_android
+
+  # build APKs/bundles for the listed device types. Each APK has different
+  # features enabled in its manifest to ensure it is only distributed to select
+  # devices.
+  android_device_types = [
+    "generic",
+    "atv",
+    "tablet",
+    "automotive",
+  ]
 }
 
 declare_args() {
diff --git a/chromeos/ash/components/audio/cros_audio_config_impl_unittest.cc b/chromeos/ash/components/audio/cros_audio_config_impl_unittest.cc
index 8d51347dc..01744ea 100644
--- a/chromeos/ash/components/audio/cros_audio_config_impl_unittest.cc
+++ b/chromeos/ash/components/audio/cros_audio_config_impl_unittest.cc
@@ -82,9 +82,10 @@
     scoped_feature_list_.InitAndEnableFeature(features::kAudioSettingsPage);
     CrasAudioClient::InitializeFake();
     fake_cras_audio_client_ = FakeCrasAudioClient::Get();
-    audio_pref_handler_ = base::MakeRefCounted<AudioDevicesPrefHandlerStub>();
-    CrasAudioHandler::Initialize(mojo::NullRemote(), audio_pref_handler_);
+    CrasAudioHandler::InitializeForTesting();
     cras_audio_handler_ = CrasAudioHandler::Get();
+    audio_pref_handler_ = base::MakeRefCounted<AudioDevicesPrefHandlerStub>();
+    cras_audio_handler_->SetPrefHandlerForTesting(audio_pref_handler_);
     cros_audio_config_ = std::make_unique<CrosAudioConfigImpl>();
   }
 
diff --git a/chromeos/ash/components/network/client_cert_resolver_unittest.cc b/chromeos/ash/components/network/client_cert_resolver_unittest.cc
index 3d65f0fa..4c5b3c3 100644
--- a/chromeos/ash/components/network/client_cert_resolver_unittest.cc
+++ b/chromeos/ash/components/network/client_cert_resolver_unittest.cc
@@ -87,7 +87,7 @@
   onc_certificate.SetKey("GUID", base::Value(guid));
   onc_certificate.SetKey("Type", base::Value("Client"));
   onc_certificate.SetKey("PKCS12", base::Value(pkcs12_base64_encoded));
-  base::Value onc_certificates(base::Value::Type::LIST);
+  base::Value::List onc_certificates;
   onc_certificates.Append(std::move(onc_certificate));
   return std::make_unique<chromeos::onc::OncParsedCertificates>(
       onc_certificates);
diff --git a/chromeos/ash/components/network/onc/onc_certificate_importer_impl_unittest.cc b/chromeos/ash/components/network/onc/onc_certificate_importer_impl_unittest.cc
index 0ee7578..af1cca3d 100644
--- a/chromeos/ash/components/network/onc/onc_certificate_importer_impl_unittest.cc
+++ b/chromeos/ash/components/network/onc/onc_certificate_importer_impl_unittest.cc
@@ -78,7 +78,7 @@
         chromeos::onc::test_utils::ReadTestDictionaryValue(filename);
     absl::optional<base::Value> certificates_value =
         onc.ExtractKey(::onc::toplevel_config::kCertificates);
-    onc_certificates_ = std::move(*certificates_value);
+    onc_certificates_ = std::move(*certificates_value).TakeList();
 
     CertificateImporterImpl importer(task_runner_, test_nssdb_.get());
     auto onc_parsed_certificates =
@@ -132,7 +132,7 @@
                 certificate::GetCertType(private_list_[0].get()));
     }
 
-    const base::Value& certificate = onc_certificates_.GetList()[0];
+    const base::Value& certificate = onc_certificates_[0];
     const std::string* guid_value =
         certificate.FindStringKey(::onc::certificate::kGUID);
     *guid = *guid_value;
@@ -147,7 +147,7 @@
   std::unique_ptr<base::SingleThreadTaskRunner::CurrentDefaultHandle>
       thread_task_runner_handle_;
   std::unique_ptr<net::NSSCertDatabaseChromeOS> test_nssdb_;
-  base::Value onc_certificates_;
+  base::Value::List onc_certificates_;
   // List of certs in the nssdb's public slot.
   net::ScopedCERTCertificateList public_list_;
   // List of certs in the nssdb's "private" slot.
@@ -188,7 +188,7 @@
   AddCertificatesFromFile("managed_toplevel2.onc", ImportType::kAllCertificates,
                           true /* expected_parse_success */,
                           true /* expected_import_success */);
-  EXPECT_EQ(onc_certificates_.GetList().size(), public_list_.size());
+  EXPECT_EQ(onc_certificates_.size(), public_list_.size());
   EXPECT_TRUE(private_list_.empty());
   EXPECT_EQ(2ul, public_list_.size());
 }
@@ -208,7 +208,7 @@
   AddCertificatesFromFile(
       "toplevel_partially_invalid.onc", ImportType::kAllCertificates,
       false /* expected_parse_success */, true /* expected_import_success */);
-  EXPECT_EQ(3ul, onc_certificates_.GetList().size());
+  EXPECT_EQ(3ul, onc_certificates_.size());
   EXPECT_EQ(1ul, private_list_.size());
   EXPECT_TRUE(public_list_.empty());
 }
diff --git a/chromeos/ash/components/phonehub/app_stream_launcher_data_model.cc b/chromeos/ash/components/phonehub/app_stream_launcher_data_model.cc
index 0f295de..413c61b 100644
--- a/chromeos/ash/components/phonehub/app_stream_launcher_data_model.cc
+++ b/chromeos/ash/components/phonehub/app_stream_launcher_data_model.cc
@@ -4,6 +4,7 @@
 
 #include "chromeos/ash/components/phonehub/app_stream_launcher_data_model.h"
 
+#include "chromeos/ash/components/multidevice/logging/logging.h"
 #include "chromeos/ash/components/phonehub/notification.h"
 
 namespace ash::phonehub {
@@ -40,6 +41,8 @@
 
 void AppStreamLauncherDataModel::SetAppList(
     const std::vector<Notification::AppMetadata>& streamable_apps) {
+  PA_LOG(INFO) << "App Streaming Launcher data updated with "
+               << streamable_apps.size() << " apps";
   apps_list_ = streamable_apps;
 
   apps_list_sorted_by_name_ = streamable_apps;
diff --git a/chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl.cc b/chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl.cc
index 167abdd1..d0f9393d 100644
--- a/chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl.cc
+++ b/chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl.cc
@@ -77,8 +77,7 @@
 
 // Load the |recent_app_metadata_list_| from |pref_service_| if there is a
 // history of |recent_app_metadata_list_| exist in |pref_service_|. Then add or
-// update |app_metadata| into |recent_app_metadata_list_| and sort
-// |recent_app_metadata_list_| based on |last_accessed_timestamp|. Also update
+// update |app_metadata| into |recent_app_metadata_list_|. Also update
 // this |app_metadata| back to |pref_service_|.
 void RecentAppsInteractionHandlerImpl::NotifyRecentAppAddedOrUpdated(
     const Notification::AppMetadata& app_metadata,
@@ -96,17 +95,8 @@
     }
   }
 
-  recent_app_metadata_list_.emplace_back(app_metadata, last_accessed_timestamp);
-
-  // Sort |recent_app_metadata_list_| from most recently visited to least
-  // recently visited.
-  std::sort(recent_app_metadata_list_.begin(), recent_app_metadata_list_.end(),
-            [](const std::pair<Notification::AppMetadata, base::Time>& a,
-               const std::pair<Notification::AppMetadata, base::Time>& b) {
-              // More recently visited apps should come before earlier visited
-              // apps.
-              return a.second > b.second;
-            });
+  recent_app_metadata_list_.emplace(recent_app_metadata_list_.begin(),
+                                    app_metadata, last_accessed_timestamp);
 
   SaveRecentAppMetadataListToPref();
   ComputeAndUpdateUiState();
diff --git a/chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl_unittest.cc b/chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl_unittest.cc
index f3302ae1..b862e24 100644
--- a/chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl_unittest.cc
+++ b/chromeos/ash/components/phonehub/recent_apps_interaction_handler_impl_unittest.cc
@@ -755,10 +755,10 @@
       handler().FetchRecentAppMetadataList();
 
   EXPECT_EQ(recent_apps_metadata_result.size(), 2u);
-  EXPECT_EQ(1, recent_apps_metadata_result[0].user_id);
-  EXPECT_EQ("com.fakeapp1", recent_apps_metadata_result[0].package_name);
-  EXPECT_EQ(2, recent_apps_metadata_result[1].user_id);
-  EXPECT_EQ("com.fakeapp2", recent_apps_metadata_result[1].package_name);
+  EXPECT_EQ(2, recent_apps_metadata_result[0].user_id);
+  EXPECT_EQ("com.fakeapp2", recent_apps_metadata_result[0].package_name);
+  EXPECT_EQ(1, recent_apps_metadata_result[1].user_id);
+  EXPECT_EQ("com.fakeapp1", recent_apps_metadata_result[1].package_name);
 }
 
 TEST_F(RecentAppsInteractionHandlerTest, ShowRecentAppsOfUserWithQuietModeOn) {
@@ -786,8 +786,9 @@
       handler().FetchRecentAppMetadataList();
 
   EXPECT_EQ(recent_apps_metadata_result.size(), 2u);
-  EXPECT_EQ(1, recent_apps_metadata_result[0].user_id);
-  EXPECT_EQ(2, recent_apps_metadata_result[1].user_id);
+  // LIFO scheme is used, hence app 2 is first as it was pushed last.
+  EXPECT_EQ(2, recent_apps_metadata_result[0].user_id);
+  EXPECT_EQ(1, recent_apps_metadata_result[1].user_id);
 }
 
 TEST_F(RecentAppsInteractionHandlerTest, GetUserIdSet) {
diff --git a/chromeos/ash/services/assistant/public/cpp/features.cc b/chromeos/ash/services/assistant/public/cpp/features.cc
index c5ecff2..eedd46bc 100644
--- a/chromeos/ash/services/assistant/public/cpp/features.cc
+++ b/chromeos/ash/services/assistant/public/cpp/features.cc
@@ -60,7 +60,7 @@
 
 BASE_FEATURE(kEnableLibAssistantDlc,
              "LibAssistantDlc",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 bool IsAppSupportEnabled() {
   return base::FeatureList::IsEnabled(
diff --git a/chromeos/components/onc/onc_parsed_certificates.cc b/chromeos/components/onc/onc_parsed_certificates.cc
index 0e4ffe4d..80e141a 100644
--- a/chromeos/components/onc/onc_parsed_certificates.cc
+++ b/chromeos/components/onc/onc_parsed_certificates.cc
@@ -175,18 +175,12 @@
 }
 
 OncParsedCertificates::OncParsedCertificates()
-    : OncParsedCertificates(base::Value(base::Value::Type::LIST)) {}
+    : OncParsedCertificates(base::Value::List()) {}
 
 OncParsedCertificates::OncParsedCertificates(
-    const base::Value& onc_certificates) {
-  if (!onc_certificates.is_list()) {
-    LOG(WARNING) << "Value is not a list";
-    has_error_ = true;
-    return;
-  }
-
-  for (size_t i = 0; i < onc_certificates.GetList().size(); ++i) {
-    const base::Value& onc_certificate = onc_certificates.GetList()[i];
+    const base::Value::List& onc_certificates) {
+  for (size_t i = 0; i < onc_certificates.size(); ++i) {
+    const base::Value& onc_certificate = onc_certificates[i];
     DCHECK(onc_certificate.is_dict());
 
     VLOG(2) << "Parsing certificate at index " << i << ": " << onc_certificate;
diff --git a/chromeos/components/onc/onc_parsed_certificates.h b/chromeos/components/onc/onc_parsed_certificates.h
index 719bd5a3..9a98fe95 100644
--- a/chromeos/components/onc/onc_parsed_certificates.h
+++ b/chromeos/components/onc/onc_parsed_certificates.h
@@ -10,12 +10,9 @@
 
 #include "base/component_export.h"
 #include "base/memory/ref_counted.h"
+#include "base/values.h"
 #include "chromeos/components/onc/certificate_scope.h"
 
-namespace base {
-class Value;
-}  // namespace base
-
 namespace net {
 class X509Certificate;
 }
@@ -97,9 +94,8 @@
   // certificate lists will be empty.
   OncParsedCertificates();
 
-  // Parses |onc_certificates|. This must be a Value of type LIST, corresponding
-  // to the Certificates part of the ONC specification.
-  explicit OncParsedCertificates(const base::Value& onc_certificates);
+  // Parses |onc_certificates|.
+  explicit OncParsedCertificates(const base::Value::List& onc_certificates);
 
   OncParsedCertificates(const OncParsedCertificates&) = delete;
   OncParsedCertificates& operator=(const OncParsedCertificates&) = delete;
diff --git a/chromeos/components/onc/onc_parsed_certificates_unittest.cc b/chromeos/components/onc/onc_parsed_certificates_unittest.cc
index bde6a01..d10aa91 100644
--- a/chromeos/components/onc/onc_parsed_certificates_unittest.cc
+++ b/chromeos/components/onc/onc_parsed_certificates_unittest.cc
@@ -22,16 +22,14 @@
   ~OncParsedCertificatesTest() override = default;
 
  protected:
-  bool ReadFromJSON(
-      base::StringPiece onc_certificates_json,
-      std::unique_ptr<OncParsedCertificates>* out_onc_parsed_certificates) {
+  std::unique_ptr<OncParsedCertificates> ReadFromJSON(
+      base::StringPiece onc_certificates_json) {
     std::unique_ptr<base::Value> onc_certificates =
         base::JSONReader::ReadDeprecated(onc_certificates_json);
-    if (!onc_certificates)
-      return false;
-    *out_onc_parsed_certificates =
-        std::make_unique<OncParsedCertificates>(*onc_certificates);
-    return true;
+    if (!onc_certificates || !onc_certificates->is_list()) {
+      return nullptr;
+    }
+    return std::make_unique<OncParsedCertificates>(onc_certificates->GetList());
   }
 };
 
@@ -43,8 +41,9 @@
           "Type": "Client" }
       ])";
 
-  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates;
-  ASSERT_TRUE(ReadFromJSON(onc_certificates_json, &onc_parsed_certificates));
+  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates =
+      ReadFromJSON(onc_certificates_json);
+  ASSERT_TRUE(onc_parsed_certificates);
 
   EXPECT_FALSE(onc_parsed_certificates->has_error());
   EXPECT_EQ(0u,
@@ -67,8 +66,9 @@
           "Type": "Client" }
       ])";
 
-  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates;
-  ASSERT_TRUE(ReadFromJSON(onc_certificates_json, &onc_parsed_certificates));
+  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates =
+      ReadFromJSON(onc_certificates_json);
+  ASSERT_TRUE(onc_parsed_certificates);
 
   EXPECT_FALSE(onc_parsed_certificates->has_error());
   EXPECT_EQ(0u,
@@ -94,8 +94,9 @@
           "Type": "Client" }
       ])";
 
-  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates;
-  ASSERT_TRUE(ReadFromJSON(onc_certificates_json, &onc_parsed_certificates));
+  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates =
+      ReadFromJSON(onc_certificates_json);
+  ASSERT_TRUE(onc_parsed_certificates);
 
   EXPECT_TRUE(onc_parsed_certificates->has_error());
   EXPECT_EQ(0u,
@@ -161,8 +162,9 @@
       -----END CERTIFICATE-----" }
       ])";
 
-  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates;
-  ASSERT_TRUE(ReadFromJSON(onc_certificates_json, &onc_parsed_certificates));
+  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates =
+      ReadFromJSON(onc_certificates_json);
+  ASSERT_TRUE(onc_parsed_certificates);
 
   EXPECT_FALSE(onc_parsed_certificates->has_error());
   EXPECT_EQ(2u,
@@ -226,8 +228,9 @@
       -----END CERTIFICATE-----" }
       ])";
 
-  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates;
-  ASSERT_TRUE(ReadFromJSON(onc_certificates_json, &onc_parsed_certificates));
+  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates =
+      ReadFromJSON(onc_certificates_json);
+  ASSERT_TRUE(onc_parsed_certificates);
 
   EXPECT_FALSE(onc_parsed_certificates->has_error());
   ASSERT_EQ(1u,
@@ -276,8 +279,9 @@
       -----END CERTIFICATE-----" }
       ])";
 
-  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates;
-  ASSERT_TRUE(ReadFromJSON(onc_certificates_json, &onc_parsed_certificates));
+  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates =
+      ReadFromJSON(onc_certificates_json);
+  ASSERT_TRUE(onc_parsed_certificates);
 
   EXPECT_FALSE(onc_parsed_certificates->has_error());
   ASSERT_EQ(1u,
@@ -338,8 +342,9 @@
       trailing junk" }
       ])";
 
-  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates;
-  ASSERT_TRUE(ReadFromJSON(onc_certificates_json, &onc_parsed_certificates));
+  std::unique_ptr<OncParsedCertificates> onc_parsed_certificates =
+      ReadFromJSON(onc_certificates_json);
+  ASSERT_TRUE(onc_parsed_certificates);
 
   EXPECT_TRUE(onc_parsed_certificates->has_error());
   EXPECT_EQ(1u,
@@ -389,8 +394,9 @@
   std::unique_ptr<base::Value> onc_certificates =
       base::JSONReader::ReadDeprecated(onc_certificates_json);
   ASSERT_TRUE(onc_certificates);
+  ASSERT_TRUE(onc_certificates->is_list());
 
-  OncParsedCertificates authority_and_client_certs(*onc_certificates);
+  OncParsedCertificates authority_and_client_certs(onc_certificates->GetList());
   EXPECT_EQ(authority_and_client_certs.server_or_authority_certificates(),
             authority_and_client_certs.server_or_authority_certificates());
   EXPECT_EQ(authority_and_client_certs.client_certificates(),
@@ -407,7 +413,7 @@
     trust_bits->GetList()[0] = base::Value("UnknownTrustBit");
 
     OncParsedCertificates parsed_authority_web_trust_mangled(
-        authority_web_trust_mangled);
+        authority_web_trust_mangled.GetList());
     EXPECT_FALSE(parsed_authority_web_trust_mangled.has_error());
     EXPECT_NE(
         authority_and_client_certs.server_or_authority_certificates(),
@@ -422,7 +428,8 @@
     authority_guid_mangled.GetList()[1].SetKey("GUID",
                                                base::Value("otherguid"));
 
-    OncParsedCertificates parsed_authority_guid_mangled(authority_guid_mangled);
+    OncParsedCertificates parsed_authority_guid_mangled(
+        authority_guid_mangled.GetList());
     EXPECT_FALSE(parsed_authority_guid_mangled.has_error());
     EXPECT_NE(authority_and_client_certs.server_or_authority_certificates(),
               parsed_authority_guid_mangled.server_or_authority_certificates());
@@ -435,7 +442,8 @@
     base::Value authority_type_mangled = onc_certificates->Clone();
     authority_type_mangled.GetList()[1].SetKey("Type", base::Value("Server"));
 
-    OncParsedCertificates parsed_authority_type_mangled(authority_type_mangled);
+    OncParsedCertificates parsed_authority_type_mangled(
+        authority_type_mangled.GetList());
     EXPECT_FALSE(parsed_authority_type_mangled.has_error());
     EXPECT_NE(authority_and_client_certs.server_or_authority_certificates(),
               parsed_authority_type_mangled.server_or_authority_certificates());
@@ -474,7 +482,8 @@
                             oKjuSSsg/Q8Wx6cpJmttQz5olGPgstmACRWA==
                             -----END CERTIFICATE-----                    )"));
 
-    OncParsedCertificates parsed_authority_x509_mangled(authority_x509_mangled);
+    OncParsedCertificates parsed_authority_x509_mangled(
+        authority_x509_mangled.GetList());
     EXPECT_FALSE(parsed_authority_x509_mangled.has_error());
     EXPECT_NE(authority_and_client_certs.server_or_authority_certificates(),
               parsed_authority_x509_mangled.server_or_authority_certificates());
@@ -487,7 +496,8 @@
     base::Value client_guid_mangled = onc_certificates->Clone();
     client_guid_mangled.GetList()[0].SetKey("GUID", base::Value("other-guid"));
 
-    OncParsedCertificates parsed_client_guid_mangled(client_guid_mangled);
+    OncParsedCertificates parsed_client_guid_mangled(
+        client_guid_mangled.GetList());
     EXPECT_FALSE(parsed_client_guid_mangled.has_error());
     EXPECT_EQ(authority_and_client_certs.server_or_authority_certificates(),
               parsed_client_guid_mangled.server_or_authority_certificates());
@@ -500,7 +510,8 @@
     base::Value client_pkcs12_mangled = onc_certificates->Clone();
     client_pkcs12_mangled.GetList()[0].SetKey("PKCS12", base::Value("YQ=="));
 
-    OncParsedCertificates parsed_client_pkcs12_mangled(client_pkcs12_mangled);
+    OncParsedCertificates parsed_client_pkcs12_mangled(
+        client_pkcs12_mangled.GetList());
     EXPECT_FALSE(parsed_client_pkcs12_mangled.has_error());
     EXPECT_EQ(authority_and_client_certs.server_or_authority_certificates(),
               parsed_client_pkcs12_mangled.server_or_authority_certificates());
diff --git a/chromeos/system/fake_statistics_provider.cc b/chromeos/system/fake_statistics_provider.cc
index c129689..baf5ad72 100644
--- a/chromeos/system/fake_statistics_provider.cc
+++ b/chromeos/system/fake_statistics_provider.cc
@@ -3,12 +3,13 @@
 // found in the LICENSE file.
 
 #include "chromeos/system/fake_statistics_provider.h"
-#include "base/task/sequenced_task_runner.h"
 
+#include <string>
 #include <utility>
 
-namespace chromeos {
-namespace system {
+#include "base/task/sequenced_task_runner.h"
+
+namespace chromeos::system {
 
 FakeStatisticsProvider::FakeStatisticsProvider() = default;
 
@@ -84,5 +85,4 @@
   StatisticsProvider::SetTestProvider(nullptr);
 }
 
-}  // namespace system
-}  // namespace chromeos
+}  // namespace chromeos::system
diff --git a/chromeos/system/fake_statistics_provider.h b/chromeos/system/fake_statistics_provider.h
index f75cf8f..5493a548 100644
--- a/chromeos/system/fake_statistics_provider.h
+++ b/chromeos/system/fake_statistics_provider.h
@@ -13,8 +13,7 @@
 #include "base/strings/string_piece.h"
 #include "chromeos/system/statistics_provider.h"
 
-namespace chromeos {
-namespace system {
+namespace chromeos::system {
 
 // A fake StatisticsProvider implementation that is useful in tests.
 class COMPONENT_EXPORT(CHROMEOS_SYSTEM) FakeStatisticsProvider
@@ -37,10 +36,6 @@
   bool IsRunningOnVm() override;
   VpdStatus GetVpdStatus() const override;
 
-  // TODO(b/213325251): Remove old getters once migration is completed.
-  using StatisticsProvider::GetMachineFlag;
-  using StatisticsProvider::GetMachineStatistic;
-
   void SetMachineStatistic(const std::string& key, const std::string& value);
   void ClearMachineStatistic(base::StringPiece key);
   void SetMachineFlag(const std::string& key, bool value);
@@ -68,15 +63,12 @@
   ~ScopedFakeStatisticsProvider() override;
 };
 
-}  // namespace system
-}  // namespace chromeos
+}  // namespace chromeos::system
 
 // TODO(https://crbug.com/1164001): remove after the //chrome/browser/chromeos
 // source migration is finished.
-namespace ash {
-namespace system {
+namespace ash::system {
 using ::chromeos::system::ScopedFakeStatisticsProvider;
-}
-}  // namespace ash
+}  // namespace ash::system
 
 #endif  // CHROMEOS_SYSTEM_FAKE_STATISTICS_PROVIDER_H_
diff --git a/chromeos/system/statistics_provider.cc b/chromeos/system/statistics_provider.cc
index 9a13856..ddcf59a 100644
--- a/chromeos/system/statistics_provider.cc
+++ b/chromeos/system/statistics_provider.cc
@@ -4,9 +4,6 @@
 
 #include "chromeos/system/statistics_provider.h"
 
-#include <memory>
-#include <vector>
-
 #include "base/memory/singleton.h"
 #include "chromeos/system/statistics_provider_impl.h"
 
@@ -113,38 +110,6 @@
   }
 }
 
-bool StatisticsProvider::GetMachineStatistic(const std::string& name,
-                                             std::string* result) {
-  auto statistic = GetMachineStatistic(name);
-
-  if (!statistic)
-    return false;
-
-  if (result)
-    *result = std::string(statistic.value());
-  return true;
-}
-
-bool StatisticsProvider::GetMachineFlag(const std::string& name, bool* result) {
-  FlagValue flag = GetMachineFlag(name);
-
-  if (flag == FlagValue::kUnset)
-    return false;
-
-  if (result)
-    *result = flag == FlagValue::kTrue;
-
-  return true;
-}
-
-std::string StatisticsProvider::GetEnterpriseMachineID() {
-  if (auto machine_id = GetMachineID()) {
-    return std::string(machine_id.value());
-  }
-
-  return "";
-}
-
 absl::optional<base::StringPiece> StatisticsProvider::GetMachineID() {
   for (const char* key : kMachineInfoSerialNumberKeys) {
     auto machine_id = GetMachineStatistic(key);
diff --git a/chromeos/system/statistics_provider.h b/chromeos/system/statistics_provider.h
index 9a0996b..d3f455e 100644
--- a/chromeos/system/statistics_provider.h
+++ b/chromeos/system/statistics_provider.h
@@ -5,8 +5,6 @@
 #ifndef CHROMEOS_SYSTEM_STATISTICS_PROVIDER_H_
 #define CHROMEOS_SYSTEM_STATISTICS_PROVIDER_H_
 
-#include <string>
-
 #include "base/callback.h"
 #include "base/component_export.h"
 #include "base/strings/string_piece.h"
@@ -176,21 +174,11 @@
   // not work safely together, returns custom tribool value.
   virtual FlagValue GetMachineFlag(base::StringPiece name) = 0;
 
-  // Old versions of machine statistic getters. Sets output
-  // to `result` if provided. Return false if statistic is not found.
-  // TODO(b/213325251): Remove old getters once migration is completed.
-  bool GetMachineStatistic(const std::string& name, std::string* result);
-  bool GetMachineFlag(const std::string& name, bool* result);
-
   // Returns the machine serial number after examining a set of well-known
   // keys. In case no serial is found nullopt is returned.
   // Caveat: On older Samsung devices, the last letter is omitted from the
   // serial number for historical reasons. This is fine.
   absl::optional<base::StringPiece> GetMachineID();
-  // Old version of `GetMachineID`. Returns an empty string if no serial is
-  // found. Soon to be removed.
-  // TODO(b/213325251): Remove old getters once migration is completed.
-  std::string GetEnterpriseMachineID();
 
   // Cancels any pending file operations.
   virtual void Shutdown() = 0;
@@ -215,8 +203,7 @@
 
 // TODO(https://crbug.com/1164001): remove after the //chrome/browser/chromeos
 // source migration is finished.
-namespace ash {
-namespace system {
+namespace ash::system {
 using ::chromeos::system::kActivateDateKey;
 using ::chromeos::system::kBlockDevModeKey;
 using ::chromeos::system::kCheckEnrollmentKey;
@@ -229,7 +216,6 @@
 using ::chromeos::system::kRlzBrandCodeKey;
 using ::chromeos::system::kSerialNumberKeyForTest;
 using ::chromeos::system::StatisticsProvider;
-}  // namespace system
-}  // namespace ash
+}  // namespace ash::system
 
 #endif  // CHROMEOS_SYSTEM_STATISTICS_PROVIDER_H_
diff --git a/chromeos/system/statistics_provider_impl.cc b/chromeos/system/statistics_provider_impl.cc
index 9dbee46..ad740f58 100644
--- a/chromeos/system/statistics_provider_impl.cc
+++ b/chromeos/system/statistics_provider_impl.cc
@@ -4,8 +4,10 @@
 
 #include "chromeos/system/statistics_provider_impl.h"
 
+#include <memory>
 #include <string>
 #include <type_traits>
+#include <utility>
 
 #include "ash/constants/ash_paths.h"
 #include "ash/constants/ash_switches.h"
diff --git a/chromeos/system/statistics_provider_impl.h b/chromeos/system/statistics_provider_impl.h
index f75f110..8cea040 100644
--- a/chromeos/system/statistics_provider_impl.h
+++ b/chromeos/system/statistics_provider_impl.h
@@ -5,6 +5,7 @@
 #ifndef CHROMEOS_SYSTEM_STATISTICS_PROVIDER_IMPL_H_
 #define CHROMEOS_SYSTEM_STATISTICS_PROVIDER_IMPL_H_
 
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
diff --git a/components/attribution_reporting/BUILD.gn b/components/attribution_reporting/BUILD.gn
index 56eb465..7c5c22f53 100644
--- a/components/attribution_reporting/BUILD.gn
+++ b/components/attribution_reporting/BUILD.gn
@@ -7,6 +7,7 @@
 
 mojom("mojom") {
   sources = [
+    "os_support.mojom",
     "source_registration_error.mojom",
     "trigger_registration_error.mojom",
   ]
@@ -133,3 +134,116 @@
   seed_corpus =
       "//components/attribution_reporting/trigger_registration_fuzzer_corpus"
 }
+
+mojom("registration_mojom") {
+  sources = [ "registration.mojom" ]
+
+  generate_java = true
+
+  deps = [
+    "//components/aggregation_service:mojom",
+    "//mojo/public/mojom/base",
+    "//url/mojom:url_mojom_origin",
+  ]
+
+  export_class_attribute_blink = "PLATFORM_EXPORT"
+  export_define_blink = "BLINK_PLATFORM_IMPLEMENTATION=1"
+  export_header_blink = "third_party/blink/renderer/platform/platform_export.h"
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "attribution_reporting.mojom.DebugKey"
+          cpp = "uint64_t"
+        },
+        {
+          mojom = "attribution_reporting.mojom.TriggerDedupKey"
+          cpp = "uint64_t"
+        },
+        {
+          mojom = "attribution_reporting.mojom.SuitableOrigin"
+          cpp = "::attribution_reporting::SuitableOrigin"
+          forward_declaration =
+              "namespace attribution_reporting { class SuitableOrigin; }"
+
+          # Avoid expensive copies by forcing Mojo methods to take the type by
+          # value, not const ref
+          move_only = true
+        },
+        {
+          mojom = "attribution_reporting.mojom.FilterData"
+          cpp = "::attribution_reporting::FilterData"
+          forward_declaration =
+              "namespace attribution_reporting { class FilterData; }"
+        },
+        {
+          mojom = "attribution_reporting.mojom.AggregationKeys"
+          cpp = "::attribution_reporting::AggregationKeys"
+          forward_declaration =
+              "namespace attribution_reporting { class AggregationKeys; }"
+        },
+        {
+          mojom = "attribution_reporting.mojom.SourceRegistration"
+          cpp = "::attribution_reporting::SourceRegistration"
+          forward_declaration =
+              "namespace attribution_reporting { struct SourceRegistration; }"
+
+          # Avoid expensive copies by forcing Mojo methods to take the type by
+          # value, not const ref
+          move_only = true
+        },
+        {
+          mojom = "attribution_reporting.mojom.Filters"
+          cpp = "::attribution_reporting::Filters"
+          forward_declaration =
+              "namespace attribution_reporting { class Filters; }"
+        },
+        {
+          mojom = "attribution_reporting.mojom.EventTriggerData"
+          cpp = "::attribution_reporting::EventTriggerData"
+          forward_declaration =
+              "namespace attribution_reporting { struct EventTriggerData; }"
+        },
+        {
+          mojom = "attribution_reporting.mojom.AggregatableTriggerData"
+          cpp = "::attribution_reporting::AggregatableTriggerData"
+          forward_declaration = "namespace attribution_reporting { struct AggregatableTriggerData; }"
+        },
+        {
+          mojom = "attribution_reporting.mojom.TriggerRegistration"
+          cpp = "::attribution_reporting::TriggerRegistration"
+          forward_declaration =
+              "namespace attribution_reporting { struct TriggerRegistration; }"
+
+          # Avoid expensive copies by forcing Mojo methods to take the type by
+          # value, not const ref
+          move_only = true
+        },
+      ]
+      traits_headers = [ "registration_mojom_traits.h" ]
+      traits_public_deps = [ ":registration_mojom_traits" ]
+    },
+  ]
+
+  blink_cpp_typemaps = cpp_typemaps
+}
+
+component("registration_mojom_traits") {
+  sources = [
+    "registration_mojom_traits.cc",
+    "registration_mojom_traits.h",
+  ]
+
+  defines = [ "IS_ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS_IMPL" ]
+
+  public_deps = [
+    ":attribution_reporting",
+    ":registration_mojom_shared",
+    "//base",
+    "//components/aggregation_service:mojom_shared",
+    "//mojo/public/cpp/base:shared_typemap_traits",
+    "//url",
+    "//url/mojom:mojom_traits",
+  ]
+}
diff --git a/components/attribution_reporting/DEPS b/components/attribution_reporting/DEPS
index 10466c85..a4ca995 100644
--- a/components/attribution_reporting/DEPS
+++ b/components/attribution_reporting/DEPS
@@ -3,3 +3,10 @@
   "+net/http/structured_headers.h",
   "+services/network/public/cpp",
 ]
+
+specific_include_rules = {
+  "registration_mojom_traits\.(h|cc)": [
+    "+mojo/public/cpp/base/int128_mojom_traits.h",
+    "+mojo/public/cpp/base/time_mojom_traits.h",
+  ]
+}
diff --git a/components/attribution_reporting/OWNERS b/components/attribution_reporting/OWNERS
index 3233056..c23628c2 100644
--- a/components/attribution_reporting/OWNERS
+++ b/components/attribution_reporting/OWNERS
@@ -2,3 +2,6 @@
 
 per-file *.mojom=set noparent
 per-file *.mojom=file://ipc/SECURITY_OWNERS
+
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/components/attribution_reporting/os_registration.cc b/components/attribution_reporting/os_registration.cc
index bf7bdd34..57a89afc 100644
--- a/components/attribution_reporting/os_registration.cc
+++ b/components/attribution_reporting/os_registration.cc
@@ -5,6 +5,7 @@
 #include "components/attribution_reporting/os_registration.h"
 
 #include "base/strings/string_piece.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "net/http/structured_headers.h"
 #include "url/gurl.h"
 
@@ -18,4 +19,13 @@
   return GURL(item->item.GetString());
 }
 
+base::StringPiece GetSupportHeader(mojom::OsSupport os_support) {
+  switch (os_support) {
+    case mojom::OsSupport::kDisabled:
+      return "web";
+    case mojom::OsSupport::kEnabled:
+      return "web, os";
+  }
+}
+
 }  // namespace attribution_reporting
diff --git a/components/attribution_reporting/os_registration.h b/components/attribution_reporting/os_registration.h
index 51975eec..3b40631a 100644
--- a/components/attribution_reporting/os_registration.h
+++ b/components/attribution_reporting/os_registration.h
@@ -7,6 +7,7 @@
 
 #include "base/component_export.h"
 #include "base/strings/string_piece_forward.h"
+#include "components/attribution_reporting/os_support.mojom-forward.h"
 #include "url/gurl.h"
 
 namespace attribution_reporting {
@@ -25,6 +26,9 @@
 COMPONENT_EXPORT(ATTRIBUTION_REPORTING)
 GURL ParseOsSourceOrTriggerHeader(base::StringPiece);
 
+COMPONENT_EXPORT(ATTRIBUTION_REPORTING)
+base::StringPiece GetSupportHeader(mojom::OsSupport);
+
 }  // namespace attribution_reporting
 
 #endif  // COMPONENTS_ATTRIBUTION_REPORTING_OS_REGISTRATION_H_
diff --git a/components/attribution_reporting/os_registration_unittest.cc b/components/attribution_reporting/os_registration_unittest.cc
index 763dddb..d0878a6 100644
--- a/components/attribution_reporting/os_registration_unittest.cc
+++ b/components/attribution_reporting/os_registration_unittest.cc
@@ -5,6 +5,7 @@
 #include "components/attribution_reporting/os_registration.h"
 
 #include "base/strings/string_piece.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
@@ -51,5 +52,19 @@
   }
 }
 
+TEST(OsSupport, GetSupportHeader) {
+  const struct {
+    mojom::OsSupport os_support;
+    const char* expected;
+  } kTestCases[] = {
+      {mojom::OsSupport::kDisabled, "web"},
+      {mojom::OsSupport::kEnabled, "web, os"},
+  };
+
+  for (const auto& test_case : kTestCases) {
+    EXPECT_EQ(GetSupportHeader(test_case.os_support), test_case.expected);
+  }
+}
+
 }  // namespace
 }  // namespace attribution_reporting
diff --git a/components/attribution_reporting/os_support.mojom b/components/attribution_reporting/os_support.mojom
new file mode 100644
index 0000000..5b1b1ab8
--- /dev/null
+++ b/components/attribution_reporting/os_support.mojom
@@ -0,0 +1,12 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module attribution_reporting.mojom;
+
+// Indicates whether OS-level attribution is enabled.
+// See https://github.com/WICG/attribution-reporting-api/blob/main/app_to_web.md.
+enum OsSupport {
+  kDisabled,
+  kEnabled,
+};
diff --git a/components/attribution_reporting/registration.mojom b/components/attribution_reporting/registration.mojom
new file mode 100644
index 0000000..b69857b
--- /dev/null
+++ b/components/attribution_reporting/registration.mojom
@@ -0,0 +1,160 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module attribution_reporting.mojom;
+
+import "components/aggregation_service/aggregation_service.mojom";
+import "mojo/public/mojom/base/int128.mojom";
+import "mojo/public/mojom/base/time.mojom";
+import "url/mojom/origin.mojom";
+
+struct DebugKey {
+  uint64 value;
+};
+
+// Encapsulates a potentially trustworthy origin. Equivalent to
+// attribution_reporting::SuitableOrigin.
+struct SuitableOrigin {
+  url.mojom.Origin origin;
+};
+
+// Filter data for selectively matching attribution sources and triggers.
+// See https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#optional-attribution-filters
+// for details.
+struct FilterData {
+  // Map of filter name to a possibly empty set of values. Must not contain a
+  // `source_type` key.
+  map<string, array<string>> filter_values;
+};
+
+// Filters for selectively matching attribution sources and triggers.
+// See https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#optional-attribution-filters
+// for details.
+struct Filters {
+  // Map of filter name to a possibly empty set of values. May contain a
+  // `source_type` key.
+  map<string, array<string>> filter_values;
+};
+
+// See https://github.com/WICG/attribution-reporting-api/blob/main/AGGREGATE.md#attribution-source-registration
+// for details.
+struct AggregationKeys {
+  map<string, mojo_base.mojom.Uint128> keys;
+};
+
+// Struct containing the trigger-side aggregatable data.
+struct AggregatableTriggerData {
+  mojo_base.mojom.Uint128 key_piece;
+  array<string> source_keys;
+  Filters filters;
+  Filters not_filters;
+};
+
+struct SourceRegistration {
+  // Target site where this source will be triggered.
+  //
+  // For sources associated with a navigation, the destination site must be
+  // same-site with the final committed url of the navigation. If they are not
+  // same-site, this source will be ignored by the browser.
+  SuitableOrigin destination;
+
+  // Data that will be sent in attribution reports to identify this source.
+  uint64 source_event_id = 0;
+
+  // Specifies how long this source is eligible for attribution.
+  mojo_base.mojom.TimeDelta? expiry;
+
+  // Optionally specifies how long after source registration an event-level report
+  // can be generated with this source.
+  mojo_base.mojom.TimeDelta? event_report_window;
+
+  // Optionally specifies how long after source registration an aggregatable
+  // report can be generated with this source.
+  mojo_base.mojom.TimeDelta? aggregatable_report_window;
+
+  // Priority for this source.
+  int64 priority = 0;
+
+  // A key that is propagated through the Attribution Reporting API for
+  // debugging purposes.
+  DebugKey? debug_key;
+
+  FilterData filter_data;
+
+  AggregationKeys aggregation_keys;
+
+  // Specifies whether to enable verbose debug reporting.
+  bool debug_reporting = false;
+};
+
+// Deduplication key set by a reporting origin which prevents duplicate triggers
+// from generating multiple attribution reports for a given source.
+struct TriggerDedupKey {
+  // Arbitrary value for deduplication set by the reporting origin.
+  uint64 value;
+};
+
+// Mojo representation of the trigger configuration provided by a reporting
+// origin. This data is provided arbitrarily by certain subresources on a
+// page which invoke Attribution Reporting.
+struct EventTriggerData {
+  // Value which identifies this trigger in attribution reports, determined by
+  // reporting origin.
+  uint64 data = 0;
+
+  // Priority of this trigger relative to other attributed triggers for a
+  // source. Reports created with high priority triggers will be reported over
+  // lower priority ones.
+  int64 priority = 0;
+
+  // Key which allows deduplication against existing attributions for the same
+  // source.
+  TriggerDedupKey? dedup_key;
+
+  // If non-empty, this trigger will be ignored unless the attributed source's
+  // filter data matches.
+  Filters filters;
+
+  // If non-empty, this trigger will be ignored unless the attributed source's
+  // filter data does *NOT* match.
+  Filters not_filters;
+};
+
+// Represents a request from a reporting origin to trigger attribution on a
+// given site. See:
+// https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#triggering-attribution
+struct TriggerRegistration {
+  // List of all event trigger data objects declared by the event trigger
+  // header. This data is arbitrarily set by the reporting origin.
+  array<EventTriggerData> event_triggers;
+
+  // If non-empty, this trigger will be ignored unless the attributed source's
+  // filter data matches.
+  Filters filters;
+
+  // If non-empty, this trigger will be ignored unless the attributed source's
+  // filter data does *NOT* match.
+  Filters not_filters;
+
+  // List of all aggregatable trigger data objects declared by the trigger
+  // header.
+  array<AggregatableTriggerData> aggregatable_trigger_data;
+
+  // A map of aggregation key identifier and the corresponding value.
+  map<string, uint32> aggregatable_values;
+
+  // A key that is propagated through the Attribution Reporting API for
+  // debugging purposes.
+  DebugKey? debug_key;
+
+  // Key which allows deduplication against existing aggregatable reports for
+  // the same source.
+  TriggerDedupKey? aggregatable_dedup_key;
+
+  // Specifies whether to enable verbose debug reporting.
+  bool debug_reporting = false;
+
+  // Specifies the deployment option for the aggregation service.
+  aggregation_service.mojom.AggregationCoordinator aggregation_coordinator;
+};
diff --git a/components/attribution_reporting/registration_mojom_traits.cc b/components/attribution_reporting/registration_mojom_traits.cc
new file mode 100644
index 0000000..19c8f59
--- /dev/null
+++ b/components/attribution_reporting/registration_mojom_traits.cc
@@ -0,0 +1,278 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/attribution_reporting/registration_mojom_traits.h"
+
+#include <stdint.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/time/time.h"
+#include "components/aggregation_service/aggregation_service.mojom-shared.h"
+#include "components/attribution_reporting/aggregatable_trigger_data.h"
+#include "components/attribution_reporting/aggregatable_values.h"
+#include "components/attribution_reporting/aggregation_keys.h"
+#include "components/attribution_reporting/event_trigger_data.h"
+#include "components/attribution_reporting/filters.h"
+#include "components/attribution_reporting/registration.mojom-shared.h"
+#include "components/attribution_reporting/source_registration.h"
+#include "components/attribution_reporting/suitable_origin.h"
+#include "components/attribution_reporting/trigger_registration.h"
+#include "mojo/public/cpp/base/int128_mojom_traits.h"
+#include "mojo/public/cpp/base/time_mojom_traits.h"
+#include "third_party/abseil-cpp/absl/numeric/int128.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/mojom/origin_mojom_traits.h"
+#include "url/origin.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<attribution_reporting::mojom::SuitableOriginDataView,
+                  attribution_reporting::SuitableOrigin>::
+    Read(attribution_reporting::mojom::SuitableOriginDataView data,
+         attribution_reporting::SuitableOrigin* out) {
+  url::Origin origin;
+  if (!data.ReadOrigin(&origin)) {
+    return false;
+  }
+
+  auto suitable_origin =
+      attribution_reporting::SuitableOrigin::Create(std::move(origin));
+  if (!suitable_origin) {
+    return false;
+  }
+
+  *out = std::move(*suitable_origin);
+  return true;
+}
+
+// static
+bool StructTraits<attribution_reporting::mojom::FilterDataDataView,
+                  attribution_reporting::FilterData>::
+    Read(attribution_reporting::mojom::FilterDataDataView data,
+         attribution_reporting::FilterData* out) {
+  attribution_reporting::FilterValues filter_values;
+  if (!data.ReadFilterValues(&filter_values)) {
+    return false;
+  }
+
+  absl::optional<attribution_reporting::FilterData> filter_data =
+      attribution_reporting::FilterData::Create(std::move(filter_values));
+  if (!filter_data.has_value()) {
+    return false;
+  }
+
+  *out = std::move(*filter_data);
+  return true;
+}
+
+// static
+bool StructTraits<attribution_reporting::mojom::AggregationKeysDataView,
+                  attribution_reporting::AggregationKeys>::
+    Read(attribution_reporting::mojom::AggregationKeysDataView data,
+         attribution_reporting::AggregationKeys* out) {
+  attribution_reporting::AggregationKeys::Keys keys;
+  if (!data.ReadKeys(&keys)) {
+    return false;
+  }
+
+  absl::optional<attribution_reporting::AggregationKeys> aggregation_keys =
+      attribution_reporting::AggregationKeys::FromKeys(std::move(keys));
+  if (!aggregation_keys.has_value()) {
+    return false;
+  }
+
+  *out = std::move(*aggregation_keys);
+  return true;
+}
+
+// static
+bool StructTraits<attribution_reporting::mojom::SourceRegistrationDataView,
+                  attribution_reporting::SourceRegistration>::
+    Read(attribution_reporting::mojom::SourceRegistrationDataView data,
+         attribution_reporting::SourceRegistration* out) {
+  if (!data.ReadDestination(&out->destination)) {
+    return false;
+  }
+
+  if (!data.ReadExpiry(&out->expiry)) {
+    return false;
+  }
+
+  if (!data.ReadEventReportWindow(&out->event_report_window)) {
+    return false;
+  }
+
+  if (!data.ReadAggregatableReportWindow(&out->aggregatable_report_window)) {
+    return false;
+  }
+
+  if (!data.ReadDebugKey(&out->debug_key)) {
+    return false;
+  }
+
+  if (!data.ReadFilterData(&out->filter_data)) {
+    return false;
+  }
+
+  if (!data.ReadAggregationKeys(&out->aggregation_keys)) {
+    return false;
+  }
+
+  out->source_event_id = data.source_event_id();
+  out->priority = data.priority();
+  out->debug_reporting = data.debug_reporting();
+  return true;
+}
+
+// static
+bool StructTraits<attribution_reporting::mojom::FiltersDataView,
+                  attribution_reporting::Filters>::
+    Read(attribution_reporting::mojom::FiltersDataView data,
+         attribution_reporting::Filters* out) {
+  attribution_reporting::FilterValues filter_values;
+  if (!data.ReadFilterValues(&filter_values)) {
+    return false;
+  }
+
+  absl::optional<attribution_reporting::Filters> filters =
+      attribution_reporting::Filters::Create(std::move(filter_values));
+  if (!filters.has_value()) {
+    return false;
+  }
+
+  *out = std::move(*filters);
+  return true;
+}
+
+// static
+bool StructTraits<attribution_reporting::mojom::EventTriggerDataDataView,
+                  attribution_reporting::EventTriggerData>::
+    Read(attribution_reporting::mojom::EventTriggerDataDataView data,
+         attribution_reporting::EventTriggerData* out) {
+  if (!data.ReadDedupKey(&out->dedup_key)) {
+    return false;
+  }
+
+  if (!data.ReadFilters(&out->filters)) {
+    return false;
+  }
+
+  if (!data.ReadNotFilters(&out->not_filters)) {
+    return false;
+  }
+
+  out->data = data.data();
+  out->priority = data.priority();
+  return true;
+}
+
+// static
+bool StructTraits<attribution_reporting::mojom::AggregatableTriggerDataDataView,
+                  attribution_reporting::AggregatableTriggerData>::
+    Read(attribution_reporting::mojom::AggregatableTriggerDataDataView data,
+         attribution_reporting::AggregatableTriggerData* out) {
+  absl::uint128 key_piece;
+  if (!data.ReadKeyPiece(&key_piece)) {
+    return false;
+  }
+
+  attribution_reporting::AggregatableTriggerData::Keys source_keys;
+  if (!data.ReadSourceKeys(&source_keys)) {
+    return false;
+  }
+
+  attribution_reporting::Filters filters;
+  if (!data.ReadFilters(&filters)) {
+    return false;
+  }
+
+  attribution_reporting::Filters not_filters;
+  if (!data.ReadNotFilters(&not_filters)) {
+    return false;
+  }
+
+  auto aggregatable_trigger_data =
+      attribution_reporting::AggregatableTriggerData::Create(
+          key_piece, std::move(source_keys), std::move(filters),
+          std::move(not_filters));
+  if (!aggregatable_trigger_data) {
+    return false;
+  }
+
+  *out = std::move(*aggregatable_trigger_data);
+  return true;
+}
+
+// static
+bool StructTraits<attribution_reporting::mojom::TriggerRegistrationDataView,
+                  attribution_reporting::TriggerRegistration>::
+    Read(attribution_reporting::mojom::TriggerRegistrationDataView data,
+         attribution_reporting::TriggerRegistration* out) {
+  std::vector<attribution_reporting::EventTriggerData> event_triggers;
+  if (!data.ReadEventTriggers(&event_triggers)) {
+    return false;
+  }
+
+  auto event_triggers_list =
+      attribution_reporting::EventTriggerDataList::Create(
+          std::move(event_triggers));
+  if (!event_triggers_list) {
+    return false;
+  }
+
+  out->event_triggers = std::move(*event_triggers_list);
+
+  if (!data.ReadFilters(&out->filters)) {
+    return false;
+  }
+
+  if (!data.ReadNotFilters(&out->not_filters)) {
+    return false;
+  }
+
+  std::vector<attribution_reporting::AggregatableTriggerData>
+      aggregatable_trigger_data;
+  if (!data.ReadAggregatableTriggerData(&aggregatable_trigger_data)) {
+    return false;
+  }
+
+  auto aggregatable_trigger_data_list =
+      attribution_reporting::AggregatableTriggerDataList::Create(
+          std::move(aggregatable_trigger_data));
+  if (!aggregatable_trigger_data_list) {
+    return false;
+  }
+
+  out->aggregatable_trigger_data = std::move(*aggregatable_trigger_data_list);
+
+  attribution_reporting::AggregatableValues::Values values;
+  if (!data.ReadAggregatableValues(&values)) {
+    return false;
+  }
+
+  auto aggregatable_values =
+      attribution_reporting::AggregatableValues::Create(std::move(values));
+  if (!aggregatable_values) {
+    return false;
+  }
+
+  out->aggregatable_values = std::move(*aggregatable_values);
+
+  if (!data.ReadDebugKey(&out->debug_key)) {
+    return false;
+  }
+
+  if (!data.ReadAggregatableDedupKey(&out->aggregatable_dedup_key)) {
+    return false;
+  }
+
+  out->debug_reporting = data.debug_reporting();
+  out->aggregation_coordinator = data.aggregation_coordinator();
+  return true;
+}
+
+}  // namespace mojo
diff --git a/components/attribution_reporting/registration_mojom_traits.h b/components/attribution_reporting/registration_mojom_traits.h
new file mode 100644
index 0000000..28d60d09
--- /dev/null
+++ b/components/attribution_reporting/registration_mojom_traits.h
@@ -0,0 +1,288 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS_H_
+#define COMPONENTS_ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/time/time.h"
+#include "components/aggregation_service/aggregation_service.mojom-shared.h"
+#include "components/attribution_reporting/aggregatable_trigger_data.h"
+#include "components/attribution_reporting/aggregatable_values.h"
+#include "components/attribution_reporting/aggregation_keys.h"
+#include "components/attribution_reporting/event_trigger_data.h"
+#include "components/attribution_reporting/filters.h"
+#include "components/attribution_reporting/registration.mojom-shared.h"
+#include "components/attribution_reporting/source_registration.h"
+#include "components/attribution_reporting/suitable_origin.h"
+#include "components/attribution_reporting/trigger_registration.h"
+#include "mojo/public/cpp/base/int128_mojom_traits.h"
+#include "mojo/public/cpp/base/time_mojom_traits.h"
+#include "third_party/abseil-cpp/absl/numeric/int128.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/mojom/origin_mojom_traits.h"
+#include "url/origin.h"
+
+namespace mojo {
+
+template <>
+struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS)
+    StructTraits<attribution_reporting::mojom::DebugKeyDataView, uint64_t> {
+  static uint64_t value(uint64_t debug_key) { return debug_key; }
+
+  static bool Read(attribution_reporting::mojom::DebugKeyDataView data,
+                   uint64_t* out) {
+    *out = data.value();
+    return true;
+  }
+};
+
+template <>
+struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS)
+    StructTraits<attribution_reporting::mojom::SuitableOriginDataView,
+                 attribution_reporting::SuitableOrigin> {
+  static const url::Origin& origin(
+      const attribution_reporting::SuitableOrigin& origin) {
+    return *origin;
+  }
+
+  static bool Read(attribution_reporting::mojom::SuitableOriginDataView data,
+                   attribution_reporting::SuitableOrigin* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS)
+    StructTraits<attribution_reporting::mojom::TriggerDedupKeyDataView,
+                 uint64_t> {
+  static uint64_t value(uint64_t debug_key) { return debug_key; }
+
+  static bool Read(attribution_reporting::mojom::TriggerDedupKeyDataView data,
+                   uint64_t* out) {
+    *out = data.value();
+    return true;
+  }
+};
+
+template <>
+struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS)
+    StructTraits<attribution_reporting::mojom::FilterDataDataView,
+                 attribution_reporting::FilterData> {
+  static const attribution_reporting::FilterValues& filter_values(
+      const attribution_reporting::FilterData& filter_data) {
+    return filter_data.filter_values();
+  }
+
+  static bool Read(attribution_reporting::mojom::FilterDataDataView data,
+                   attribution_reporting::FilterData* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS)
+    StructTraits<attribution_reporting::mojom::AggregationKeysDataView,
+                 attribution_reporting::AggregationKeys> {
+  static const attribution_reporting::AggregationKeys::Keys& keys(
+      const attribution_reporting::AggregationKeys& aggregation_keys) {
+    return aggregation_keys.keys();
+  }
+
+  static bool Read(attribution_reporting::mojom::AggregationKeysDataView data,
+                   attribution_reporting::AggregationKeys* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS)
+    StructTraits<attribution_reporting::mojom::SourceRegistrationDataView,
+                 attribution_reporting::SourceRegistration> {
+  static const attribution_reporting::SuitableOrigin& destination(
+      const attribution_reporting::SourceRegistration& source) {
+    return source.destination;
+  }
+
+  static uint64_t source_event_id(
+      const attribution_reporting::SourceRegistration& source) {
+    return source.source_event_id;
+  }
+
+  static absl::optional<base::TimeDelta> expiry(
+      const attribution_reporting::SourceRegistration& source) {
+    return source.expiry;
+  }
+
+  static absl::optional<base::TimeDelta> event_report_window(
+      const attribution_reporting::SourceRegistration& source) {
+    return source.event_report_window;
+  }
+
+  static absl::optional<base::TimeDelta> aggregatable_report_window(
+      const attribution_reporting::SourceRegistration& source) {
+    return source.aggregatable_report_window;
+  }
+
+  static int64_t priority(
+      const attribution_reporting::SourceRegistration& source) {
+    return source.priority;
+  }
+
+  static absl::optional<uint64_t> debug_key(
+      const attribution_reporting::SourceRegistration& source) {
+    return source.debug_key;
+  }
+
+  static const attribution_reporting::FilterData& filter_data(
+      const attribution_reporting::SourceRegistration& source) {
+    return source.filter_data;
+  }
+
+  static const attribution_reporting::AggregationKeys& aggregation_keys(
+      const attribution_reporting::SourceRegistration& source) {
+    return source.aggregation_keys;
+  }
+
+  static bool debug_reporting(
+      const attribution_reporting::SourceRegistration& source) {
+    return source.debug_reporting;
+  }
+
+  static bool Read(
+      attribution_reporting::mojom::SourceRegistrationDataView data,
+      attribution_reporting::SourceRegistration* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS)
+    StructTraits<attribution_reporting::mojom::FiltersDataView,
+                 attribution_reporting::Filters> {
+  static const attribution_reporting::FilterValues& filter_values(
+      const attribution_reporting::Filters& filters) {
+    return filters.filter_values();
+  }
+
+  static bool Read(attribution_reporting::mojom::FiltersDataView data,
+                   attribution_reporting::Filters* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS)
+    StructTraits<attribution_reporting::mojom::EventTriggerDataDataView,
+                 attribution_reporting::EventTriggerData> {
+  static uint64_t data(const attribution_reporting::EventTriggerData& data) {
+    return data.data;
+  }
+
+  static int64_t priority(const attribution_reporting::EventTriggerData& data) {
+    return data.priority;
+  }
+
+  static absl::optional<uint64_t> dedup_key(
+      const attribution_reporting::EventTriggerData& data) {
+    return data.dedup_key;
+  }
+
+  static const attribution_reporting::Filters& filters(
+      const attribution_reporting::EventTriggerData& data) {
+    return data.filters;
+  }
+
+  static const attribution_reporting::Filters& not_filters(
+      const attribution_reporting::EventTriggerData& data) {
+    return data.not_filters;
+  }
+
+  static bool Read(attribution_reporting::mojom::EventTriggerDataDataView data,
+                   attribution_reporting::EventTriggerData* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS)
+    StructTraits<attribution_reporting::mojom::AggregatableTriggerDataDataView,
+                 attribution_reporting::AggregatableTriggerData> {
+  static absl::uint128 key_piece(
+      const attribution_reporting::AggregatableTriggerData& data) {
+    return data.key_piece();
+  }
+
+  static const attribution_reporting::AggregatableTriggerData::Keys&
+  source_keys(const attribution_reporting::AggregatableTriggerData& data) {
+    return data.source_keys();
+  }
+
+  static const attribution_reporting::Filters& filters(
+      const attribution_reporting::AggregatableTriggerData& data) {
+    return data.filters();
+  }
+
+  static const attribution_reporting::Filters& not_filters(
+      const attribution_reporting::AggregatableTriggerData& data) {
+    return data.not_filters();
+  }
+
+  static bool Read(
+      attribution_reporting::mojom::AggregatableTriggerDataDataView data,
+      attribution_reporting::AggregatableTriggerData* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS)
+    StructTraits<attribution_reporting::mojom::TriggerRegistrationDataView,
+                 attribution_reporting::TriggerRegistration> {
+  static const std::vector<attribution_reporting::EventTriggerData>&
+  event_triggers(const attribution_reporting::TriggerRegistration& trigger) {
+    return trigger.event_triggers.vec();
+  }
+
+  static const attribution_reporting::Filters& filters(
+      const attribution_reporting::TriggerRegistration& trigger) {
+    return trigger.filters;
+  }
+
+  static const attribution_reporting::Filters& not_filters(
+      const attribution_reporting::TriggerRegistration& trigger) {
+    return trigger.not_filters;
+  }
+
+  static const std::vector<attribution_reporting::AggregatableTriggerData>&
+  aggregatable_trigger_data(
+      const attribution_reporting::TriggerRegistration& trigger) {
+    return trigger.aggregatable_trigger_data.vec();
+  }
+
+  static const attribution_reporting::AggregatableValues::Values&
+  aggregatable_values(
+      const attribution_reporting::TriggerRegistration& trigger) {
+    return trigger.aggregatable_values.values();
+  }
+
+  static absl::optional<uint64_t> debug_key(
+      const attribution_reporting::TriggerRegistration& trigger) {
+    return trigger.debug_key;
+  }
+
+  static absl::optional<uint64_t> aggregatable_dedup_key(
+      const attribution_reporting::TriggerRegistration& trigger) {
+    return trigger.aggregatable_dedup_key;
+  }
+
+  static bool debug_reporting(
+      const attribution_reporting::TriggerRegistration& trigger) {
+    return trigger.debug_reporting;
+  }
+
+  static aggregation_service::mojom::AggregationCoordinator
+  aggregation_coordinator(
+      const attribution_reporting::TriggerRegistration& trigger) {
+    return trigger.aggregation_coordinator;
+  }
+
+  static bool Read(
+      attribution_reporting::mojom::TriggerRegistrationDataView data,
+      attribution_reporting::TriggerRegistration* out);
+};
+
+}  // namespace mojo
+
+#endif  // COMPONENTS_ATTRIBUTION_REPORTING_REGISTRATION_MOJOM_TRAITS_H_
diff --git a/components/exo/wayland/protocol/aura-shell.xml b/components/exo/wayland/protocol/aura-shell.xml
index d45ed6b..52ee2902 100644
--- a/components/exo/wayland/protocol/aura-shell.xml
+++ b/components/exo/wayland/protocol/aura-shell.xml
@@ -24,7 +24,7 @@
     DEALINGS IN THE SOFTWARE.
   </copyright>
 
-  <interface name="zaura_shell" version="48">
+  <interface name="zaura_shell" version="49">
     <description summary="aura_shell">
       The global interface exposing aura shell capabilities is used to
       instantiate an interface extension for a wl_surface object.
@@ -353,6 +353,7 @@
 
     <request name="intent_to_snap" since="16">
       <description summary="client intents to snap the surface.">
+        [Deprecated] Use intent_to_snap on zaura_toplevel.
         Notify (or inform) the server the client's intent to snap the window.
         To inform it's no longer willing to snap, send 'none'.
       </description>
@@ -375,6 +376,7 @@
 
     <request name="unset_snap" since="16">
       <description summary="Unset the surface snap.">
+        [Deprecated] Use unset_snap on zaura_toplevel.
         Request that surface resets snapping.
       </description>
     </request>
@@ -753,7 +755,7 @@
     </event>
   </interface>
 
-  <interface name="zaura_toplevel" version="48">
+  <interface name="zaura_toplevel" version="49">
     <description summary="aura shell interface to the toplevel shell">
       An interface to the toplevel shell, which allows the
       client to access shell specific functionality.
@@ -1070,6 +1072,32 @@
       </description>
       <arg name="snap_ratio_as_uint" type="uint"/>
     </request>
+
+    <!-- Version 49 additions-->
+    <enum name="snap_direction">
+      <description summary="window snap directions">
+        Window snap directions.
+      </description>
+      <entry name="none" value="0" summary="unsnap the window"/>
+      <entry name="primary" value="1" summary="snap the window to the left or
+        top in primary layout, right or bottom in secondary layout"/>
+      <entry name="secondary" value="2" summary="snap the window to the right
+        or bottom in primary layout, top or left in secondary layout"/>
+    </enum>
+
+    <request name="intent_to_snap" since="49">
+      <description summary="client intents to snap the surface.">
+        Notify (or inform) the server the client's intent to snap the window.
+        To inform it's no longer willing to snap, send 'none'.
+      </description>
+      <arg name="direction" type="uint" enum="snap_direction"/>
+    </request>
+
+    <request name="unset_snap" since="49">
+      <description summary="Unset the window snap.">
+        Request that window unsets snapping.
+      </description>
+    </request>
   </interface>
 
   <interface name="zaura_popup" version="46">
diff --git a/components/exo/wayland/zaura_shell.cc b/components/exo/wayland/zaura_shell.cc
index 1eb1b11..1007657 100644
--- a/components/exo/wayland/zaura_shell.cc
+++ b/components/exo/wayland/zaura_shell.cc
@@ -204,9 +204,9 @@
   GetUserDataAs<AuraSurface>(resource)->SetServerStartResize();
 }
 
-void aura_surface_intent_to_snap(wl_client* client,
-                                 wl_resource* resource,
-                                 uint32_t snap_direction) {
+void aura_surface_intent_to_snap_deprecated(wl_client* client,
+                                            wl_resource* resource,
+                                            uint32_t snap_direction) {
   GetUserDataAs<AuraSurface>(resource)->IntentToSnap(snap_direction);
 }
 
@@ -220,7 +220,8 @@
   GetUserDataAs<AuraSurface>(resource)->SetSnapSecondary();
 }
 
-void aura_surface_unset_snap(wl_client* client, wl_resource* resource) {
+void aura_surface_unset_snap_deprecated(wl_client* client,
+                                        wl_resource* resource) {
   GetUserDataAs<AuraSurface>(resource)->UnsetSnap();
 }
 
@@ -311,10 +312,10 @@
     aura_surface_set_fullscreen_mode_deprecated,
     aura_surface_set_client_surface_str_id,
     aura_surface_set_server_start_resize,
-    aura_surface_intent_to_snap,
+    aura_surface_intent_to_snap_deprecated,
     aura_surface_set_snap_left_deprecated,
     aura_surface_set_snap_right_deprecated,
-    aura_surface_unset_snap,
+    aura_surface_unset_snap_deprecated,
     aura_surface_set_window_session_id,
     aura_surface_set_can_go_back,
     aura_surface_unset_can_go_back,
@@ -853,6 +854,24 @@
   shell_surface_->SetSnapSecondary(snap_ratio);
 }
 
+void AuraToplevel::IntentToSnap(uint32_t snap_direction) {
+  switch (snap_direction) {
+    case ZAURA_SURFACE_SNAP_DIRECTION_NONE:
+      shell_surface_->HideSnapPreview();
+      break;
+    case ZAURA_SURFACE_SNAP_DIRECTION_LEFT:
+      shell_surface_->ShowSnapPreviewToPrimary();
+      break;
+    case ZAURA_SURFACE_SNAP_DIRECTION_RIGHT:
+      shell_surface_->ShowSnapPreviewToSecondary();
+      break;
+  }
+}
+
+void AuraToplevel::UnsetSnap() {
+  shell_surface_->UnsetSnap();
+}
+
 template <class T>
 void AddState(wl_array* states, T state) {
   T* value = static_cast<T*>(wl_array_add(states, sizeof(T)));
@@ -1304,6 +1323,16 @@
   GetUserDataAs<AuraToplevel>(resource)->SetSnapSecondary(snap_ratio);
 }
 
+void aura_toplevel_intent_to_snap(wl_client* client,
+                                  wl_resource* resource,
+                                  uint32_t snap_direction) {
+  GetUserDataAs<AuraToplevel>(resource)->IntentToSnap(snap_direction);
+}
+
+void aura_toplevel_unset_snap(wl_client* client, wl_resource* resource) {
+  GetUserDataAs<AuraToplevel>(resource)->UnsetSnap();
+}
+
 void aura_toplevel_set_restore_info_with_window_id_source(
     wl_client* client,
     wl_resource* resource,
@@ -1405,6 +1434,8 @@
     aura_toplevel_set_scale_factor,
     aura_toplevel_set_snap_primary,
     aura_toplevel_set_snap_secondary,
+    aura_toplevel_intent_to_snap,
+    aura_toplevel_unset_snap,
 };
 
 void aura_popup_surface_submission_in_pixel_coordinates(wl_client* client,
diff --git a/components/exo/wayland/zaura_shell.h b/components/exo/wayland/zaura_shell.h
index 85f95d1..028c6f5 100644
--- a/components/exo/wayland/zaura_shell.h
+++ b/components/exo/wayland/zaura_shell.h
@@ -147,6 +147,8 @@
   void UnsetFloat();
   void SetSnapPrimary(float snap_ratio);
   void SetSnapSecondary(float snap_ratio);
+  void IntentToSnap(uint32_t snap_direction);
+  void UnsetSnap();
 
   void OnConfigure(const gfx::Rect& bounds,
                    chromeos::WindowStateType state_type,
diff --git a/components/feature_engagement/public/BUILD.gn b/components/feature_engagement/public/BUILD.gn
index 5c66e19..01a489f 100644
--- a/components/feature_engagement/public/BUILD.gn
+++ b/components/feature_engagement/public/BUILD.gn
@@ -19,6 +19,12 @@
     "feature_constants.h",
     "feature_list.cc",
     "feature_list.h",
+    "group_configurations.cc",
+    "group_configurations.h",
+    "group_constants.cc",
+    "group_constants.h",
+    "group_list.cc",
+    "group_list.h",
     "tracker.cc",
     "tracker.h",
   ]
diff --git a/components/feature_engagement/public/feature_configurations.cc b/components/feature_engagement/public/feature_configurations.cc
index dd70083..87cf1f627 100644
--- a/components/feature_engagement/public/feature_configurations.cc
+++ b/components/feature_engagement/public/feature_configurations.cc
@@ -1097,6 +1097,24 @@
         EventConfig("whats_new_used", Comparator(EQUAL, 0), 360, 360);
     return config;
   }
+
+  if (kIPHPriceNotificationsWhileBrowsingFeature.name == feature->name) {
+    // A config that allows a user education bubble to be shown for the bottom
+    // toolbar.
+
+    // TODO(crbug.com/1382913): Set the trigger policy to the desired occurrence
+    // frequency threshold. Currently, the threshold is set to an arbitrary
+    // value.
+    absl::optional<FeatureConfig> config = FeatureConfig();
+    config->valid = true;
+    config->availability = Comparator(ANY, 0);
+    config->session_rate = Comparator(EQUAL, 0);
+    config->trigger =
+        EventConfig("price_notifications_trigger", Comparator(EQUAL, 0), 7, 7);
+    config->used =
+        EventConfig("price_notifications_used", Comparator(EQUAL, 0), 7, 7);
+    return config;
+  }
 #endif  // BUILDFLAG(IS_IOS)
 
   if (kIPHDummyFeature.name == feature->name) {
diff --git a/components/feature_engagement/public/feature_constants.cc b/components/feature_engagement/public/feature_constants.cc
index 44862831..42acabb 100644
--- a/components/feature_engagement/public/feature_constants.cc
+++ b/components/feature_engagement/public/feature_constants.cc
@@ -421,6 +421,9 @@
 BASE_FEATURE(kIPHOverflowMenuTipFeature,
              "IPH_OverflowMenuTip",
              base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kIPHPriceNotificationsWhileBrowsingFeature,
+             "IPH_PriceNotificationsWhileBrowsing",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_IOS)
 
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_LINUX) || \
diff --git a/components/feature_engagement/public/feature_constants.h b/components/feature_engagement/public/feature_constants.h
index 986a2ec..c910daddc 100644
--- a/components/feature_engagement/public/feature_constants.h
+++ b/components/feature_engagement/public/feature_constants.h
@@ -182,6 +182,7 @@
 BASE_DECLARE_FEATURE(kIPHPasswordSuggestionsFeature);
 BASE_DECLARE_FEATURE(kIPHFollowWhileBrowsingFeature);
 BASE_DECLARE_FEATURE(kIPHOverflowMenuTipFeature);
+BASE_DECLARE_FEATURE(kIPHPriceNotificationsWhileBrowsingFeature);
 #endif  // BUILDFLAG(IS_IOS)
 
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_LINUX) || \
diff --git a/components/feature_engagement/public/feature_list.cc b/components/feature_engagement/public/feature_list.cc
index 9d7d6d9..e041dd4 100644
--- a/components/feature_engagement/public/feature_list.cc
+++ b/components/feature_engagement/public/feature_list.cc
@@ -120,6 +120,7 @@
     &kIPHPasswordSuggestionsFeature,
     &kIPHFollowWhileBrowsingFeature,
     &kIPHOverflowMenuTipFeature,
+    &kIPHPriceNotificationsWhileBrowsingFeature,
 #endif  // BUILDFLAG(IS_IOS)
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_LINUX) || \
     BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
diff --git a/components/feature_engagement/public/feature_list.h b/components/feature_engagement/public/feature_list.h
index 5a82090f..3a79b1f 100644
--- a/components/feature_engagement/public/feature_list.h
+++ b/components/feature_engagement/public/feature_list.h
@@ -219,6 +219,8 @@
 DEFINE_VARIATION_PARAM(kIPHFollowWhileBrowsingFeature,
                        "IPH_FollowWhileBrowsing");
 DEFINE_VARIATION_PARAM(kIPHOverflowMenuTipFeature, "IPH_OverflowMenuTip");
+DEFINE_VARIATION_PARAM(kIPHPriceNotificationsWhileBrowsingFeature,
+                       "IPHPriceNotificationsWhileBrowsing");
 #endif  // BUILDFLAG(IS_IOS)
 
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_LINUX) || \
@@ -375,6 +377,7 @@
         VARIATION_ENTRY(kIPHPasswordSuggestionsFeature),
         VARIATION_ENTRY(kIPHFollowWhileBrowsingFeature),
         VARIATION_ENTRY(kIPHOverflowMenuTipFeature),
+        VARIATION_ENTRY(kIPHPriceNotificationsWhileBrowsingFeature),
 #elif BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
     BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
         VARIATION_ENTRY(kIPHBatterySaverModeFeature),
diff --git a/components/feature_engagement/public/group_configurations.cc b/components/feature_engagement/public/group_configurations.cc
new file mode 100644
index 0000000..4cf23d87
--- /dev/null
+++ b/components/feature_engagement/public/group_configurations.cc
@@ -0,0 +1,30 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/feature_engagement/public/group_configurations.h"
+
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "components/feature_engagement/public/configuration.h"
+#include "components/feature_engagement/public/group_constants.h"
+
+namespace feature_engagement {
+
+absl::optional<GroupConfig> GetClientSideGroupConfig(
+    const base::Feature* group) {
+  if (kIPHDummyGroup.name == group->name) {
+    // Only used for tests. Various magic tricks are used below to ensure this
+    // config is invalid and unusable.
+    absl::optional<GroupConfig> config = GroupConfig();
+    config->valid = false;
+    config->session_rate = Comparator(LESS_THAN, 0);
+    config->trigger =
+        EventConfig("dummy_group_iph_trigger", Comparator(LESS_THAN, 0), 1, 1);
+    return config;
+  }
+
+  return absl::nullopt;
+}
+
+}  // namespace feature_engagement
diff --git a/components/feature_engagement/public/group_configurations.h b/components/feature_engagement/public/group_configurations.h
new file mode 100644
index 0000000..045a939
--- /dev/null
+++ b/components/feature_engagement/public/group_configurations.h
@@ -0,0 +1,23 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_FEATURE_ENGAGEMENT_PUBLIC_GROUP_CONFIGURATIONS_H_
+#define COMPONENTS_FEATURE_ENGAGEMENT_PUBLIC_GROUP_CONFIGURATIONS_H_
+
+#include "base/feature_list.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace feature_engagement {
+struct GroupConfig;
+
+// Returns client-side specified GroupConfig if it exists, else an empty
+// optional. For this GroupConfig to be usable, the feature also needs to
+// be enabled by default. As GroupConfigs can only be client-side, this
+// function should return a non-empty optional for all supported Groups.
+absl::optional<GroupConfig> GetClientSideGroupConfig(
+    const base::Feature* feature);
+
+}  // namespace feature_engagement
+
+#endif  // COMPONENTS_FEATURE_ENGAGEMENT_PUBLIC_GROUP_CONFIGURATIONS_H_
diff --git a/components/feature_engagement/public/group_constants.cc b/components/feature_engagement/public/group_constants.cc
new file mode 100644
index 0000000..29e0c8f
--- /dev/null
+++ b/components/feature_engagement/public/group_constants.cc
@@ -0,0 +1,23 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/feature_engagement/public/group_constants.h"
+
+#include "base/feature_list.h"
+#include "build/build_config.h"
+
+namespace feature_engagement {
+
+// Group-related features used by the In-Product Help system.
+
+BASE_FEATURE(kIPHGroups, "IPHGroups", base::FEATURE_DISABLED_BY_DEFAULT);
+
+// Group features used by various clients to control their In-Product Help
+// groups.
+
+BASE_FEATURE(kIPHDummyGroup,
+             "IPH_DummyGroup",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+}  // namespace feature_engagement
diff --git a/components/feature_engagement/public/group_constants.h b/components/feature_engagement/public/group_constants.h
new file mode 100644
index 0000000..67120b7
--- /dev/null
+++ b/components/feature_engagement/public/group_constants.h
@@ -0,0 +1,21 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_FEATURE_ENGAGEMENT_PUBLIC_GROUP_CONSTANTS_H_
+#define COMPONENTS_FEATURE_ENGAGEMENT_PUBLIC_GROUP_CONSTANTS_H_
+
+#include "base/feature_list.h"
+#include "build/build_config.h"
+
+namespace feature_engagement {
+
+// Overall feature controlling whether Groups are enabled.
+BASE_DECLARE_FEATURE(kIPHGroups);
+
+// A feature to ensure all arrays can contain at least one group.
+BASE_DECLARE_FEATURE(kIPHDummyGroup);
+
+}  // namespace feature_engagement
+
+#endif  // COMPONENTS_FEATURE_ENGAGEMENT_PUBLIC_GROUP_CONSTANTS_H_
diff --git a/components/feature_engagement/public/group_list.cc b/components/feature_engagement/public/group_list.cc
new file mode 100644
index 0000000..4aec9fb
--- /dev/null
+++ b/components/feature_engagement/public/group_list.cc
@@ -0,0 +1,23 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/feature_engagement/public/group_list.h"
+
+#include "build/build_config.h"
+#include "components/feature_engagement/public/group_constants.h"
+
+namespace feature_engagement {
+
+namespace {
+const base::Feature* const kAllGroups[] = {
+    &kIPHDummyGroup,
+};
+}  // namespace
+
+std::vector<const base::Feature*> GetAllGroups() {
+  return std::vector<const base::Feature*>(kAllGroups,
+                                           kAllGroups + std::size(kAllGroups));
+}
+
+}  // namespace feature_engagement
diff --git a/components/feature_engagement/public/group_list.h b/components/feature_engagement/public/group_list.h
new file mode 100644
index 0000000..668d4fe
--- /dev/null
+++ b/components/feature_engagement/public/group_list.h
@@ -0,0 +1,21 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_FEATURE_ENGAGEMENT_PUBLIC_GROUP_LIST_H_
+#define COMPONENTS_FEATURE_ENGAGEMENT_PUBLIC_GROUP_LIST_H_
+
+#include "base/feature_list.h"
+#include "build/build_config.h"
+#include "components/feature_engagement/public/group_constants.h"
+#include "components/flags_ui/feature_entry.h"
+
+namespace feature_engagement {
+using GroupVector = std::vector<const base::Feature*>;
+
+// Returns all the features that are in use for engagement tracking.
+GroupVector GetAllGroups();
+
+}  // namespace feature_engagement
+
+#endif  // COMPONENTS_FEATURE_ENGAGEMENT_PUBLIC_GROUP_LIST_H_
diff --git a/components/messages/android/messages_feature.cc b/components/messages/android/messages_feature.cc
index a8cf7e1..4193644 100644
--- a/components/messages/android/messages_feature.cc
+++ b/components/messages/android/messages_feature.cc
@@ -56,15 +56,6 @@
              "MessagesForAndroidOfferNotification",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kMessagesForAndroidPasswords,
-             "MessagesForAndroidPasswords",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-constexpr base::FeatureParam<int>
-    kMessagesForAndroidPasswords_MessageDismissDurationMs{
-        &kMessagesForAndroidPasswords,
-        "save_password_message_dismiss_duration_ms", 20000};
-
 BASE_FEATURE(kMessagesForAndroidPermissionUpdate,
              "MessagesForAndroidPermissionUpdate",
              base::FEATURE_ENABLED_BY_DEFAULT);
@@ -85,14 +76,6 @@
              "MessagesForAndroidStackingAnimation",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kMessagesForAndroidUpdatePassword,
-             "MessagesForAndroidUpdatePassword",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-constexpr base::FeatureParam<bool>
-    kMessagesForAndroidUpdatePassword_UseFollowupButtonText{
-        &kMessagesForAndroidUpdatePassword, "use_followup_button_text", false};
-
 bool IsAdsBlockedMessagesUiEnabled() {
   return base::FeatureList::IsEnabled(kMessagesForAndroidInfrastructure) &&
          base::FeatureList::IsEnabled(kMessagesForAndroidAdsBlocked);
@@ -108,11 +91,6 @@
          base::FeatureList::IsEnabled(kMessagesForAndroidOfferNotification);
 }
 
-bool IsPasswordMessagesUiEnabled() {
-  return base::FeatureList::IsEnabled(kMessagesForAndroidInfrastructure) &&
-         base::FeatureList::IsEnabled(kMessagesForAndroidPasswords);
-}
-
 bool IsPopupBlockedMessagesUiEnabled() {
   return base::FeatureList::IsEnabled(kMessagesForAndroidInfrastructure) &&
          base::FeatureList::IsEnabled(kMessagesForAndroidPopupBlocked);
@@ -123,15 +101,6 @@
          base::FeatureList::IsEnabled(kMessagesForAndroidSaveCard);
 }
 
-bool IsUpdatePasswordMessagesUiEnabled() {
-  return base::FeatureList::IsEnabled(kMessagesForAndroidInfrastructure) &&
-         base::FeatureList::IsEnabled(kMessagesForAndroidUpdatePassword);
-}
-
-bool UseFollowupButtonTextForUpdatePasswordButton() {
-  return kMessagesForAndroidUpdatePassword_UseFollowupButtonText.Get();
-}
-
 bool IsNotificationBlockedMessagesUiEnabled() {
   return base::FeatureList::IsEnabled(kMessagesForAndroidInfrastructure) &&
          base::FeatureList::IsEnabled(kMessagesForAndroidNotificationBlocked);
@@ -142,10 +111,6 @@
          base::FeatureList::IsEnabled(kMessagesForAndroidPermissionUpdate);
 }
 
-int GetSavePasswordMessageDismissDurationMs() {
-  return kMessagesForAndroidPasswords_MessageDismissDurationMs.Get();
-}
-
 static jboolean JNI_MessageFeatureList_IsEnabled(
     JNIEnv* env,
     const JavaParamRef<jstring>& jfeature_name) {
diff --git a/components/messages/android/messages_feature.h b/components/messages/android/messages_feature.h
index 3246b1c..b3ad135 100644
--- a/components/messages/android/messages_feature.h
+++ b/components/messages/android/messages_feature.h
@@ -34,10 +34,6 @@
 // infrastructure.
 BASE_DECLARE_FEATURE(kMessagesForAndroidOfferNotification);
 
-// Feature that controls whether "save password" and "saved password
-// confirmation" prompts use Messages or Infobars infrastructure.
-BASE_DECLARE_FEATURE(kMessagesForAndroidPasswords);
-
 // Feature that controls whether permission update prompts use Messages or
 // Infobars infrastructure.
 BASE_DECLARE_FEATURE(kMessagesForAndroidPermissionUpdate);
@@ -58,10 +54,6 @@
 // new Stacking Animation.
 BASE_DECLARE_FEATURE(kMessagesForAndroidStackingAnimation);
 
-// Feature that controls whether "update password" prompt uses Messages or
-// Infobars infrastructure.
-BASE_DECLARE_FEATURE(kMessagesForAndroidUpdatePassword);
-
 bool IsAdsBlockedMessagesUiEnabled();
 
 bool IsNearOomReductionMessagesUiEnabled();
@@ -70,8 +62,6 @@
 
 bool IsOfferNotificationMessagesUiEnabled();
 
-bool IsPasswordMessagesUiEnabled();
-
 bool IsPermissionUpdateMessagesUiEnabled();
 
 bool IsPopupBlockedMessagesUiEnabled();
@@ -80,12 +70,6 @@
 
 bool IsSaveCardMessagesUiEnabled();
 
-bool IsUpdatePasswordMessagesUiEnabled();
-
-int GetSavePasswordMessageDismissDurationMs();
-
-bool UseFollowupButtonTextForUpdatePasswordButton();
-
 bool UseFollowupButtonTextForSaveCardMessage();
 
 bool UseGPayIconForSaveCardMessage();
diff --git a/components/metrics/debug/app.html b/components/metrics/debug/app.html
index c4bc114..0619c95 100644
--- a/components/metrics/debug/app.html
+++ b/components/metrics/debug/app.html
@@ -82,9 +82,7 @@
       <tbody id="uma-summary-body"></tbody>
     </table>
     <h2>Logs</h2>
-    <div class="uma-callout">
-      List of UMA logs closed since opening this page.
-    </div>
+    <div id="uma-table-caption" class="uma-callout"></div>
     <div class="uma-callout">Proto data is available by exporting.</div>
     <table>
       <thead>
diff --git a/components/metrics/debug/app.ts b/components/metrics/debug/app.ts
index 586008a..a6af3992 100644
--- a/components/metrics/debug/app.ts
+++ b/components/metrics/debug/app.ts
@@ -70,6 +70,16 @@
     await this.updateUmaSummary_();
     setInterval(() => this.updateUmaSummary_(), 3000);
 
+    // Set up the UMA table caption.
+    const umaTableCaption = this.$('#uma-table-caption') as HTMLElement;
+    const isUsingMetricsServiceObserver =
+        await this.browserProxy_.isUsingMetricsServiceObserver();
+    umaTableCaption.textContent = isUsingMetricsServiceObserver ?
+        'List of all UMA logs closed since browser startup.' :
+        'List of UMA logs closed since opening this page. Starting the browser \
+        with the --export-uma-logs-to-file command line flag will instead show \
+        all logs closed since browser startup.';
+
     // Set up a listener for UMA logs. Also update UMA log data immediately in
     // case there are logs that we already have data on.
     addWebUiListener(
diff --git a/components/metrics/debug/browser_proxy.ts b/components/metrics/debug/browser_proxy.ts
index 55f67ea..e5bb3477 100644
--- a/components/metrics/debug/browser_proxy.ts
+++ b/components/metrics/debug/browser_proxy.ts
@@ -56,11 +56,6 @@
 
 export interface MetricsInternalsBrowserProxy {
   /**
-   * Signals to the browser that the page is ready to receive messages.
-   */
-  ready(): Promise<void>;
-
-  /**
    * Gets UMA log data. |includeLogProtoData| determines whether or not the
    * fetched data should also include the protos of the logs.
    */
@@ -75,14 +70,16 @@
    * Fetches a summary of UMA info.
    */
   fetchUmaSummary(): Promise<KeyValue[]>;
+
+  /**
+   * Fetches whether the logs observer being used is owned by the metrics
+   * service or is owned by the page.
+   */
+  isUsingMetricsServiceObserver(): Promise<boolean>;
 }
 
 export class MetricsInternalsBrowserProxyImpl implements
     MetricsInternalsBrowserProxy {
-  ready(): Promise<void> {
-    return sendWithPromise('ready');
-  }
-
   getUmaLogData(includeLogProtoData: boolean): Promise<string> {
     return sendWithPromise('fetchUmaLogsData', includeLogProtoData);
   }
@@ -95,6 +92,10 @@
     return sendWithPromise('fetchUmaSummary');
   }
 
+  isUsingMetricsServiceObserver(): Promise<boolean> {
+    return sendWithPromise('isUsingMetricsServiceObserver');
+  }
+
   static getInstance(): MetricsInternalsBrowserProxy {
     return instance || (instance = new MetricsInternalsBrowserProxyImpl());
   }
diff --git a/components/metrics/metrics_service.cc b/components/metrics/metrics_service.cc
index 5a81727..6760e7c 100644
--- a/components/metrics/metrics_service.cc
+++ b/components/metrics/metrics_service.cc
@@ -157,7 +157,9 @@
 #include "components/metrics/metrics_pref_names.h"
 #include "components/metrics/metrics_rotation_scheduler.h"
 #include "components/metrics/metrics_service_client.h"
+#include "components/metrics/metrics_service_observer.h"
 #include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/metrics_switches.h"
 #include "components/metrics/persistent_system_profile.h"
 #include "components/metrics/stability_metrics_provider.h"
 #include "components/metrics/url_constants.h"
@@ -269,6 +271,27 @@
   DCHECK(client_);
   DCHECK(local_state_);
 
+  bool create_logs_event_observer;
+#ifdef NDEBUG
+  // For non-debug builds, we only create |logs_event_observer_| if the
+  // |kExportUmaLogsToFile| command line flag is passed. This is mostly for
+  // performance reasons: 1) we don't want to have to notify an observer in
+  // non-debug circumstances (there may be heavy work like copying large
+  // strings), and 2) we don't want logs to be lingering in memory.
+  create_logs_event_observer =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kExportUmaLogsToFile);
+#else
+  // For debug builds, always create |logs_event_observer_|.
+  create_logs_event_observer = true;
+#endif  // NDEBUG
+
+  if (create_logs_event_observer) {
+    logs_event_observer_ = std::make_unique<MetricsServiceObserver>(
+        MetricsServiceObserver::MetricsServiceType::UMA);
+    logs_event_manager_.AddObserver(logs_event_observer_.get());
+  }
+
   RegisterMetricsProvider(
       std::make_unique<StabilityMetricsProvider>(local_state_));
 
@@ -277,6 +300,19 @@
 
 MetricsService::~MetricsService() {
   DisableRecording();
+
+  if (logs_event_observer_) {
+    logs_event_manager_.RemoveObserver(logs_event_observer_.get());
+    const base::CommandLine* command_line =
+        base::CommandLine::ForCurrentProcess();
+    if (command_line->HasSwitch(switches::kExportUmaLogsToFile)) {
+      // We should typically not write to files on the main thread, but since
+      // this only happens when |kExportUmaLogsToFile| is passed (which
+      // indicates debugging), this should be fine.
+      logs_event_observer_->ExportLogsToFile(
+          command_line->GetSwitchValuePath(switches::kExportUmaLogsToFile));
+    }
+  }
 }
 
 void MetricsService::InitializeMetricsRecordingState() {
diff --git a/components/metrics/metrics_service.h b/components/metrics/metrics_service.h
index 6802a861f..75ec20e 100644
--- a/components/metrics/metrics_service.h
+++ b/components/metrics/metrics_service.h
@@ -58,6 +58,7 @@
 
 class MetricsRotationScheduler;
 class MetricsServiceClient;
+class MetricsServiceObserver;
 class MetricsStateManager;
 
 // See metrics_service.cc for a detailed description.
@@ -257,6 +258,10 @@
   void AddLogsObserver(MetricsLogsEventManager::Observer* observer);
   void RemoveLogsObserver(MetricsLogsEventManager::Observer* observer);
 
+  MetricsServiceObserver* logs_event_observer() {
+    return logs_event_observer_.get();
+  }
+
   // Observers will be notified when the enablement state changes. The callback
   // should accept one boolean argument, which will signal whether or not the
   // metrics collection has been enabled.
@@ -620,6 +625,13 @@
   // to various objects that are owned by this class.
   MetricsLogsEventManager logs_event_manager_;
 
+  // An observer that observes all events notified through |logs_event_manager_|
+  // since the creation of this MetricsService instance. This is only created
+  // if this is a debug build, or the |kExportUmaLogsToFile| command line flag
+  // is passed. This is primarily used by the chrome://metrics-internals debug
+  // page.
+  std::unique_ptr<MetricsServiceObserver> logs_event_observer_;
+
   // A set of observers that keeps track of the metrics reporting state.
   base::RepeatingCallbackList<void(bool)> enablement_observers_;
 
diff --git a/components/metrics/metrics_service_observer.cc b/components/metrics/metrics_service_observer.cc
index 49620bd6..2764e108 100644
--- a/components/metrics/metrics_service_observer.cc
+++ b/components/metrics/metrics_service_observer.cc
@@ -6,7 +6,9 @@
 
 #include "base/base64.h"
 #include "base/callback_list.h"
+#include "base/files/file_util.h"
 #include "base/json/json_string_value_serializer.h"
+#include "base/logging.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/time/time.h"
 #include "base/values.h"
@@ -154,6 +156,15 @@
   return serializer.Serialize(dict);
 }
 
+void MetricsServiceObserver::ExportLogsToFile(const base::FilePath& path) {
+  std::string logs_data;
+  bool success = ExportLogsAsJson(/*include_log_proto_data=*/true, &logs_data);
+  DCHECK(success);
+  if (!base::WriteFile(path, logs_data)) {
+    LOG(ERROR) << "Failed to export logs to " << path << ": " << logs_data;
+  }
+}
+
 base::CallbackListSubscription MetricsServiceObserver::AddNotifiedCallback(
     base::RepeatingClosure callback) {
   return notified_callbacks_.Add(callback);
diff --git a/components/metrics/metrics_service_observer.h b/components/metrics/metrics_service_observer.h
index 602d447..092ab2fb 100644
--- a/components/metrics/metrics_service_observer.h
+++ b/components/metrics/metrics_service_observer.h
@@ -11,6 +11,7 @@
 
 #include "base/callback_list.h"
 #include "base/containers/flat_map.h"
+#include "base/files/file_path.h"
 #include "base/strings/string_piece.h"
 #include "components/metrics/metrics_logs_event_manager.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -126,6 +127,11 @@
   // serialized protobuf. The "size" field is the size (in bytes) of the log.
   bool ExportLogsAsJson(bool include_log_proto_data, std::string* json_output);
 
+  // Exports logs data (see ExportLogsAsJson() above) to the passed |path|. If
+  // the file pointed by |path| does not exist, it will be created. If it
+  // already exists, its contents will be overwritten.
+  void ExportLogsToFile(const base::FilePath& path);
+
   // Registers a callback. This callback will be run every time this observer is
   // notified through OnLogCreated() or OnLogEvent(). When the returned
   // CallbackListSubscription is destroyed, the callback is automatically
diff --git a/components/metrics/metrics_switches.cc b/components/metrics/metrics_switches.cc
index 650e2f37..6cdfcf47 100644
--- a/components/metrics/metrics_switches.cc
+++ b/components/metrics/metrics_switches.cc
@@ -9,6 +9,14 @@
 namespace metrics {
 namespace switches {
 
+// Enables the observing of all UMA logs created during the session and
+// automatically exports them to the passed file path on shutdown (the file is
+// created if it does not already exist). This also enables viewing all UMA logs
+// in the chrome://metrics-internals debug page. The format of the exported file
+// is outlined in MetricsServiceObserver::ExportLogsAsJson().
+// Example usage: --export-uma-logs-to-file=/tmp/logs.json
+const char kExportUmaLogsToFile[] = "export-uma-logs-to-file";
+
 // Forces metrics reporting to be enabled. Should not be used for tests as it
 // will send data to servers.
 const char kForceEnableMetricsReporting[] = "force-enable-metrics-reporting";
diff --git a/components/metrics/metrics_switches.h b/components/metrics/metrics_switches.h
index 1a808ed..79d84e2 100644
--- a/components/metrics/metrics_switches.h
+++ b/components/metrics/metrics_switches.h
@@ -13,6 +13,7 @@
 // Alphabetical list of switches specific to the metrics component. Document
 // each in the .cc file.
 
+extern const char kExportUmaLogsToFile[];
 extern const char kForceEnableMetricsReporting[];
 extern const char kMetricsRecordingOnly[];
 extern const char kMetricsUploadIntervalSec[];
diff --git a/components/omnibox/browser/omnibox_view.cc b/components/omnibox/browser/omnibox_view.cc
index bdc88da..28ba370 100644
--- a/components/omnibox/browser/omnibox_view.cc
+++ b/components/omnibox/browser/omnibox_view.cc
@@ -20,6 +20,7 @@
 #include "components/omnibox/browser/autocomplete_controller.h"
 #include "components/omnibox/browser/autocomplete_input.h"
 #include "components/omnibox/browser/autocomplete_match.h"
+#include "components/omnibox/browser/autocomplete_match_type.h"
 #include "components/omnibox/browser/location_bar_model.h"
 #include "components/omnibox/browser/omnibox_edit_controller.h"
 #include "components/omnibox/browser/omnibox_edit_model.h"
@@ -31,6 +32,7 @@
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
 
+#include "components/omnibox/browser/vector_icons.h"  // nogncheck
 #include "ui/gfx/paint_vector_icon.h"
 
 #endif
@@ -176,7 +178,7 @@
 
 bool OmniboxView::IsEditingOrEmpty() const {
   return (model_.get() && model_->user_input_in_progress()) ||
-      (GetOmniboxTextLength() == 0);
+         (GetOmniboxTextLength() == 0);
 }
 
 // TODO (manukh) OmniboxView::GetIcon is very similar to
@@ -186,7 +188,9 @@
 // provider icons. It's possible they have other inconsistencies as well. We may
 // want to consider reusing the same code for both the popup and omnibox icons.
 ui::ImageModel OmniboxView::GetIcon(int dip_size,
-                                    SkColor color,
+                                    SkColor color_current_page_icon,
+                                    SkColor color_vectors,
+                                    SkColor color_bright_vectors,
                                     IconFetchedCallback on_icon_fetched) const {
 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
   // This is used on desktop only.
@@ -199,13 +203,14 @@
     AutocompleteMatch fake_match;
     fake_match.type = AutocompleteMatchType::URL_WHAT_YOU_TYPED;
     const gfx::VectorIcon& vector_icon = fake_match.GetVectorIcon(false);
-    return ui::ImageModel::FromVectorIcon(vector_icon, color, dip_size);
+    return ui::ImageModel::FromVectorIcon(vector_icon, color_current_page_icon,
+                                          dip_size);
   }
 
   if (model_->ShouldShowCurrentPageIcon()) {
     LocationBarModel* location_bar_model = controller_->GetLocationBarModel();
     return ui::ImageModel::FromVectorIcon(location_bar_model->GetVectorIcon(),
-                                          color, dip_size);
+                                          color_current_page_icon, dip_size);
   }
 
   gfx::Image favicon;
@@ -215,7 +220,7 @@
     favicon = model_->client()->GetFaviconForDefaultSearchProvider(
         std::move(on_icon_fetched));
 
-  } else {
+  } else if (match.type != AutocompleteMatchType::HISTORY_CLUSTER) {
     // For site suggestions, display site's favicon.
     favicon = model_->client()->GetFaviconForPageUrl(
         match.destination_url, std::move(on_icon_fetched));
@@ -234,7 +239,9 @@
       bookmark_model && bookmark_model->IsBookmarked(match.destination_url);
 
   const gfx::VectorIcon& vector_icon = match.GetVectorIcon(is_bookmarked);
-
+  const auto& color = match.type == AutocompleteMatchType::HISTORY_CLUSTER
+                          ? color_bright_vectors
+                          : color_vectors;
   return ui::ImageModel::FromVectorIcon(vector_icon, color, dip_size);
 #endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
 }
diff --git a/components/omnibox/browser/omnibox_view.h b/components/omnibox/browser/omnibox_view.h
index 2aef519e..eff5d30 100644
--- a/components/omnibox/browser/omnibox_view.h
+++ b/components/omnibox/browser/omnibox_view.h
@@ -92,9 +92,17 @@
   bool IsEditingOrEmpty() const;
 
   // Returns the icon to display as the location icon. If a favicon is
-  // available, |on_icon_fetched| may be called later asynchronously.
+  // available, `on_icon_fetched` may be called later asynchronously.
+  // `color_current_page_icon` is used for the page icon (i.e. when the popup is
+  // closed, there is no input in progress, and there's a URL displayed) (e.g.
+  // the secure page lock). `color_vectors` is used for vector icons e.g. the
+  // history clock or bookmark star. `color_bright_vectors` is used for special
+  // vector icons e.g. the history cluster squiggle. Favicons aren't
+  // custom-colored.
   ui::ImageModel GetIcon(int dip_size,
-                         SkColor color,
+                         SkColor color_current_page_icon,
+                         SkColor color_vectors,
+                         SkColor color_bright_vectors,
                          IconFetchedCallback on_icon_fetched) const;
 
   // The user text is the text the user has manually keyed in.  When present,
@@ -281,8 +289,7 @@
   void GetState(State* state);
 
   // Returns the delta between |before| and |after|.
-  StateChanges GetStateChanges(const State& before,
-                                          const State& after);
+  StateChanges GetStateChanges(const State& before, const State& after);
 
   // Internally invoked whenever the text changes in some way.
   virtual void TextChanged();
diff --git a/components/omnibox/browser/omnibox_view_unittest.cc b/components/omnibox/browser/omnibox_view_unittest.cc
index 02684c30..bc81da48 100644
--- a/components/omnibox/browser/omnibox_view_unittest.cc
+++ b/components/omnibox/browser/omnibox_view_unittest.cc
@@ -171,7 +171,8 @@
       vector_icons::kSearchIcon, gfx::kPlaceholderColor, gfx::kFaviconSize);
 
   ui::ImageModel icon = view()->GetIcon(
-      gfx::kFaviconSize, gfx::kPlaceholderColor, base::DoNothing());
+      gfx::kFaviconSize, gfx::kPlaceholderColor, gfx::kPlaceholderColor,
+      gfx::kPlaceholderColor, base::DoNothing());
 
   EXPECT_EQ(expected_icon, icon);
 }
@@ -191,7 +192,8 @@
       omnibox::kBookmarkIcon, gfx::kPlaceholderColor, gfx::kFaviconSize);
 
   ui::ImageModel icon = view()->GetIcon(
-      gfx::kFaviconSize, gfx::kPlaceholderColor, base::DoNothing());
+      gfx::kFaviconSize, gfx::kPlaceholderColor, gfx::kPlaceholderColor,
+      gfx::kPlaceholderColor, base::DoNothing());
 
   EXPECT_EQ(expected_icon, icon);
 }
@@ -205,7 +207,9 @@
   match.destination_url = kUrl;
   model()->SetCurrentMatchForTest(match);
 
-  view()->GetIcon(gfx::kFaviconSize, gfx::kPlaceholderColor, base::DoNothing());
+  view()->GetIcon(gfx::kFaviconSize, gfx::kPlaceholderColor,
+                  gfx::kPlaceholderColor, gfx::kPlaceholderColor,
+                  base::DoNothing());
 
   EXPECT_EQ(client()->GetPageUrlForLastFaviconRequest(), kUrl);
 }
diff --git a/components/policy/resources/templates/policy_definitions/ActiveDirectoryManagement/CloudAPAuthEnabled.yaml b/components/policy/resources/templates/policy_definitions/ActiveDirectoryManagement/CloudAPAuthEnabled.yaml
index 8fb2b243..95c78e84 100644
--- a/components/policy/resources/templates/policy_definitions/ActiveDirectoryManagement/CloudAPAuthEnabled.yaml
+++ b/components/policy/resources/templates/policy_definitions/ActiveDirectoryManagement/CloudAPAuthEnabled.yaml
@@ -9,7 +9,7 @@
 
   This feature is available starting in <ph name="WIN_NAME">Microsoft® Windows®</ph> 10.
 
-  Note: This policy doesn't apply to Incognito or Guest modes unless <ph name="POLICY_AMBIENTAUTHENTICATIONINPRIVATEMODESENABLED">AmbientAuthenticationInPrivateModesEnabled</ph> is configured.
+  Note: This policy doesn't apply to Incognito or Guest modes.
 example_value: 1
 features:
   dynamic_refresh: true
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml
index 56e08c4..028f22d 100644
--- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml
+++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml
@@ -16,9 +16,10 @@
 
 
   Extensions availability are still controlled by other policies.
+supported_on:
+- chrome.*:110-
+- chrome_os:110-
 future_on:
-- chrome.*
-- chrome_os
 - fuchsia
 features:
   dynamic_refresh: true
diff --git a/components/reading_list/core/fake_reading_list_model.cc b/components/reading_list/core/fake_reading_list_model.cc
index 5799fd1..7f23878 100644
--- a/components/reading_list/core/fake_reading_list_model.cc
+++ b/components/reading_list/core/fake_reading_list_model.cc
@@ -30,7 +30,7 @@
   return nullptr;
 }
 
-const std::vector<GURL> FakeReadingListModel::Keys() const {
+base::flat_set<GURL> FakeReadingListModel::GetKeys() const {
   NOTREACHED();
   return std::vector<GURL>();
 }
@@ -69,26 +69,12 @@
   return nullptr;
 }
 
-const ReadingListEntry* FakeReadingListModel::GetFirstUnreadEntry(
-    bool distilled) const {
-  NOTREACHED();
-  return nullptr;
-}
-
 bool FakeReadingListModel::IsUrlSupported(const GURL& url) {
   NOTREACHED();
   return false;
 }
 
-const ReadingListEntry& FakeReadingListModel::AddEntry(
-    const GURL& url,
-    const std::string& title,
-    reading_list::EntrySource source) {
-  NOTREACHED();
-  return *entry_;
-}
-
-const ReadingListEntry& FakeReadingListModel::AddEntry(
+const ReadingListEntry& FakeReadingListModel::AddOrReplaceEntry(
     const GURL& url,
     const std::string& title,
     reading_list::EntrySource source,
@@ -101,36 +87,36 @@
   NOTREACHED();
 }
 
-void FakeReadingListModel::SetReadStatus(const GURL& url, bool read) {
+void FakeReadingListModel::SetReadStatusIfExists(const GURL& url, bool read) {
   DCHECK(entry_);
   if (entry_->URL() == url) {
     entry_->SetRead(true, base::Time());
   }
 }
 
-void FakeReadingListModel::SetEntryTitle(const GURL& url,
-                                         const std::string& title) {
+void FakeReadingListModel::SetEntryTitleIfExists(const GURL& url,
+                                                 const std::string& title) {
   NOTREACHED();
 }
 
-void FakeReadingListModel::SetEntryDistilledState(
+void FakeReadingListModel::SetEntryDistilledStateIfExists(
     const GURL& url,
     ReadingListEntry::DistillationState state) {
   NOTREACHED();
 }
 
-void FakeReadingListModel::SetEstimatedReadTime(
+void FakeReadingListModel::SetEstimatedReadTimeIfExists(
     const GURL& url,
     base::TimeDelta estimated_read_time) {
   NOTREACHED();
 }
 
-void FakeReadingListModel::SetEntryDistilledInfo(
+void FakeReadingListModel::SetEntryDistilledInfoIfExists(
     const GURL& url,
     const base::FilePath& distilled_path,
     const GURL& distilled_url,
     int64_t distilation_size,
-    const base::Time& distilation_time) {
+    base::Time distilation_time) {
   NOTREACHED();
 }
 
diff --git a/components/reading_list/core/fake_reading_list_model.h b/components/reading_list/core/fake_reading_list_model.h
index f48a1609..94e9a6f 100644
--- a/components/reading_list/core/fake_reading_list_model.h
+++ b/components/reading_list/core/fake_reading_list_model.h
@@ -24,7 +24,7 @@
 
   std::unique_ptr<ScopedReadingListBatchUpdate> BeginBatchUpdates() override;
 
-  const std::vector<GURL> Keys() const override;
+  base::flat_set<GURL> GetKeys() const override;
 
   size_t size() const override;
 
@@ -38,15 +38,9 @@
 
   const ReadingListEntry* GetEntryByURL(const GURL& gurl) const override;
 
-  const ReadingListEntry* GetFirstUnreadEntry(bool distilled) const override;
-
   bool IsUrlSupported(const GURL& url) override;
 
-  const ReadingListEntry& AddEntry(const GURL& url,
-                                   const std::string& title,
-                                   reading_list::EntrySource source) override;
-
-  const ReadingListEntry& AddEntry(
+  const ReadingListEntry& AddOrReplaceEntry(
       const GURL& url,
       const std::string& title,
       reading_list::EntrySource source,
@@ -54,22 +48,24 @@
 
   void RemoveEntryByURL(const GURL& url) override;
 
-  void SetReadStatus(const GURL& url, bool read) override;
+  void SetReadStatusIfExists(const GURL& url, bool read) override;
 
-  void SetEntryTitle(const GURL& url, const std::string& title) override;
+  void SetEntryTitleIfExists(const GURL& url,
+                             const std::string& title) override;
 
-  void SetEntryDistilledState(
+  void SetEntryDistilledStateIfExists(
       const GURL& url,
       ReadingListEntry::DistillationState state) override;
 
-  void SetEstimatedReadTime(const GURL& url,
-                            base::TimeDelta estimated_read_time) override;
+  void SetEstimatedReadTimeIfExists(
+      const GURL& url,
+      base::TimeDelta estimated_read_time) override;
 
-  void SetEntryDistilledInfo(const GURL& url,
-                             const base::FilePath& distilled_path,
-                             const GURL& distilled_url,
-                             int64_t distilation_size,
-                             const base::Time& distilation_time) override;
+  void SetEntryDistilledInfoIfExists(const GURL& url,
+                                     const base::FilePath& distilled_path,
+                                     const GURL& distilled_url,
+                                     int64_t distilation_size,
+                                     base::Time distilation_time) override;
 
   void AddObserver(ReadingListModelObserver* observer) override;
   void RemoveObserver(ReadingListModelObserver* observer) override;
diff --git a/components/reading_list/core/reading_list_model.h b/components/reading_list/core/reading_list_model.h
index d664ace..f1b7cdf2 100644
--- a/components/reading_list/core/reading_list_model.h
+++ b/components/reading_list/core/reading_list_model.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/callback.h"
+#include "base/containers/flat_set.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/reading_list/core/reading_list_entry.h"
 
@@ -52,9 +53,8 @@
   // the batch update has completed.
   virtual std::unique_ptr<ScopedReadingListBatchUpdate> BeginBatchUpdates() = 0;
 
-  // Returns a vector of URLs in the model. The order of the URL is not
-  // specified and can vary on successive calls.
-  virtual const std::vector<GURL> Keys() const = 0;
+  // Returns the set of URLs in the model.
+  virtual base::flat_set<GURL> GetKeys() const = 0;
 
   // Returns the total number of entries in the model.
   virtual size_t size() const = 0;
@@ -76,10 +76,6 @@
   // Returns a specific entry. Returns null if the entry does not exist.
   virtual const ReadingListEntry* GetEntryByURL(const GURL& gurl) const = 0;
 
-  // Returns the first unread entry. If |distilled| is true, prioritize the
-  // entries available offline.
-  virtual const ReadingListEntry* GetFirstUnreadEntry(bool distilled) const = 0;
-
   // Returns true if |url| can be added to the reading list.
   virtual bool IsUrlSupported(const GURL& url) = 0;
 
@@ -88,15 +84,11 @@
   // trimmed copy of |title|. |time_to_read_minutes| is the estimated time to
   // read the page. The addition may be asynchronous, and the data will be
   // available only once the observers are notified.
-  virtual const ReadingListEntry& AddEntry(
+  virtual const ReadingListEntry& AddOrReplaceEntry(
       const GURL& url,
       const std::string& title,
       reading_list::EntrySource source,
       base::TimeDelta estimated_read_time) = 0;
-  virtual const ReadingListEntry& AddEntry(
-      const GURL& url,
-      const std::string& title,
-      reading_list::EntrySource source) = 0;
 
   // Removes an entry. The removal may be asynchronous, and not happen
   // immediately.
@@ -105,14 +97,16 @@
   // If the |url| is in the reading list and entry(|url|).read != |read|, sets
   // the read state of the URL to read. This will also update the update time of
   // the entry.
-  virtual void SetReadStatus(const GURL& url, bool read) = 0;
+  virtual void SetReadStatusIfExists(const GURL& url, bool read) = 0;
 
   // Methods to mutate an entry. Will locate the relevant entry by URL. Does
   // nothing if the entry is not found.
-  virtual void SetEntryTitle(const GURL& url, const std::string& title) = 0;
-  virtual void SetEstimatedReadTime(const GURL& url,
-                                    base::TimeDelta estimated_read_time) = 0;
-  virtual void SetEntryDistilledState(
+  virtual void SetEntryTitleIfExists(const GURL& url,
+                                     const std::string& title) = 0;
+  virtual void SetEstimatedReadTimeIfExists(
+      const GURL& url,
+      base::TimeDelta estimated_read_time) = 0;
+  virtual void SetEntryDistilledStateIfExists(
       const GURL& url,
       ReadingListEntry::DistillationState state) = 0;
 
@@ -122,11 +116,12 @@
   // was distilled, the |distillation_size| (the size of the offline data) and
   // the |distillation_date| (date of distillation in microseconds since Jan 1st
   // 1970.
-  virtual void SetEntryDistilledInfo(const GURL& url,
-                                     const base::FilePath& distilled_path,
-                                     const GURL& distilled_url,
-                                     int64_t distilation_size,
-                                     const base::Time& distilation_time) = 0;
+  virtual void SetEntryDistilledInfoIfExists(
+      const GURL& url,
+      const base::FilePath& distilled_path,
+      const GURL& distilled_url,
+      int64_t distilation_size,
+      base::Time distilation_time) = 0;
 
   // Observer registration methods. The model will remove all observers upon
   // destruction automatically.
diff --git a/components/reading_list/core/reading_list_model_impl.cc b/components/reading_list/core/reading_list_model_impl.cc
index b2361b0..cc1fb1d 100644
--- a/components/reading_list/core/reading_list_model_impl.cc
+++ b/components/reading_list/core/reading_list_model_impl.cc
@@ -181,7 +181,7 @@
     return false;
   }
   auto scoped_model_batch_updates = BeginBatchUpdates();
-  for (const auto& url : Keys()) {
+  for (const auto& url : GetKeys()) {
     RemoveEntryByURL(url);
   }
   return entries_.empty();
@@ -211,12 +211,13 @@
   }
 }
 
-const std::vector<GURL> ReadingListModelImpl::Keys() const {
+base::flat_set<GURL> ReadingListModelImpl::GetKeys() const {
   std::vector<GURL> keys;
-  for (const auto& iterator : entries_) {
-    keys.push_back(iterator.first);
+  keys.reserve(entries_.size());
+  for (const auto& url_and_entry : entries_) {
+    keys.push_back(url_and_entry.first);
   }
-  return keys;
+  return base::flat_set<GURL>(base::sorted_unique, std::move(keys));
 }
 
 const ReadingListEntry* ReadingListModelImpl::GetEntryByURL(
@@ -226,40 +227,6 @@
   return const_cast<ReadingListModelImpl*>(this)->GetMutableEntryFromURL(gurl);
 }
 
-const ReadingListEntry* ReadingListModelImpl::GetFirstUnreadEntry(
-    bool distilled) const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(loaded());
-  if (unread_entry_count_ == 0) {
-    return nullptr;
-  }
-  int64_t update_time_all = 0;
-  const ReadingListEntry* first_entry_all = nullptr;
-  int64_t update_time_distilled = 0;
-  const ReadingListEntry* first_entry_distilled = nullptr;
-  for (auto& iterator : entries_) {
-    const ReadingListEntry& entry = iterator.second;
-    if (entry.IsRead()) {
-      continue;
-    }
-    if (entry.UpdateTime() > update_time_all) {
-      update_time_all = entry.UpdateTime();
-      first_entry_all = &entry;
-    }
-    if (entry.DistilledState() == ReadingListEntry::PROCESSED &&
-        entry.UpdateTime() > update_time_distilled) {
-      update_time_distilled = entry.UpdateTime();
-      first_entry_distilled = &entry;
-    }
-  }
-  DCHECK(first_entry_all);
-  DCHECK_GT(update_time_all, 0);
-  if (distilled && first_entry_distilled) {
-    return first_entry_distilled;
-  }
-  return first_entry_all;
-}
-
 ReadingListEntry* ReadingListModelImpl::GetMutableEntryFromURL(
     const GURL& url) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -355,7 +322,7 @@
   return url.SchemeIsHTTPOrHTTPS();
 }
 
-const ReadingListEntry& ReadingListModelImpl::AddEntry(
+const ReadingListEntry& ReadingListModelImpl::AddOrReplaceEntry(
     const GURL& url,
     const std::string& title,
     reading_list::EntrySource source,
@@ -384,14 +351,7 @@
   return entries_.at(url);
 }
 
-const ReadingListEntry& ReadingListModelImpl::AddEntry(
-    const GURL& url,
-    const std::string& title,
-    reading_list::EntrySource source) {
-  return AddEntry(url, title, source, base::TimeDelta());
-}
-
-void ReadingListModelImpl::SetReadStatus(const GURL& url, bool read) {
+void ReadingListModelImpl::SetReadStatusIfExists(const GURL& url, bool read) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(loaded());
   auto iterator = entries_.find(url);
@@ -423,8 +383,8 @@
   }
 }
 
-void ReadingListModelImpl::SetEntryTitle(const GURL& url,
-                                         const std::string& title) {
+void ReadingListModelImpl::SetEntryTitleIfExists(const GURL& url,
+                                                 const std::string& title) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(loaded());
   auto iterator = entries_.find(url);
@@ -456,7 +416,7 @@
   }
 }
 
-void ReadingListModelImpl::SetEstimatedReadTime(
+void ReadingListModelImpl::SetEstimatedReadTimeIfExists(
     const GURL& url,
     base::TimeDelta estimated_read_time) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -488,12 +448,12 @@
   }
 }
 
-void ReadingListModelImpl::SetEntryDistilledInfo(
+void ReadingListModelImpl::SetEntryDistilledInfoIfExists(
     const GURL& url,
     const base::FilePath& distilled_path,
     const GURL& distilled_url,
     int64_t distillation_size,
-    const base::Time& distillation_date) {
+    base::Time distillation_date) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(loaded());
   auto iterator = entries_.find(url);
@@ -525,7 +485,7 @@
   }
 }
 
-void ReadingListModelImpl::SetEntryDistilledState(
+void ReadingListModelImpl::SetEntryDistilledStateIfExists(
     const GURL& url,
     ReadingListEntry::DistillationState state) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/components/reading_list/core/reading_list_model_impl.h b/components/reading_list/core/reading_list_model_impl.h
index e9b9064..01a33ec 100644
--- a/components/reading_list/core/reading_list_model_impl.h
+++ b/components/reading_list/core/reading_list_model_impl.h
@@ -30,8 +30,6 @@
 // Concrete implementation of a reading list model using in memory lists.
 class ReadingListModelImpl : public ReadingListModel {
  public:
-  using ReadingListEntries = std::map<GURL, ReadingListEntry>;
-
   // Initialize a ReadingListModelImpl to load and save data in
   // |storage_layer|. Passing null to |storage_layer| will create a
   // ReadingListModelImpl without persistence. Data will not be persistent
@@ -51,36 +49,34 @@
   bool IsPerformingBatchUpdates() const override;
   ReadingListSyncBridge* GetModelTypeSyncBridge() override;
   std::unique_ptr<ScopedReadingListBatchUpdate> BeginBatchUpdates() override;
-  const std::vector<GURL> Keys() const override;
+  base::flat_set<GURL> GetKeys() const override;
   size_t size() const override;
   size_t unread_size() const override;
   size_t unseen_size() const override;
   void MarkAllSeen() override;
   bool DeleteAllEntries() override;
   const ReadingListEntry* GetEntryByURL(const GURL& gurl) const override;
-  const ReadingListEntry* GetFirstUnreadEntry(bool distilled) const override;
   bool IsUrlSupported(const GURL& url) override;
-  const ReadingListEntry& AddEntry(
+  const ReadingListEntry& AddOrReplaceEntry(
       const GURL& url,
       const std::string& title,
       reading_list::EntrySource source,
       base::TimeDelta estimated_read_time) override;
-  const ReadingListEntry& AddEntry(const GURL& url,
-                                   const std::string& title,
-                                   reading_list::EntrySource source) override;
   void RemoveEntryByURL(const GURL& url) override;
-  void SetReadStatus(const GURL& url, bool read) override;
-  void SetEntryTitle(const GURL& url, const std::string& title) override;
-  void SetEstimatedReadTime(const GURL& url,
-                            base::TimeDelta estimated_read_time) override;
-  void SetEntryDistilledState(
+  void SetReadStatusIfExists(const GURL& url, bool read) override;
+  void SetEntryTitleIfExists(const GURL& url,
+                             const std::string& title) override;
+  void SetEstimatedReadTimeIfExists(
+      const GURL& url,
+      base::TimeDelta estimated_read_time) override;
+  void SetEntryDistilledStateIfExists(
       const GURL& url,
       ReadingListEntry::DistillationState state) override;
-  void SetEntryDistilledInfo(const GURL& url,
-                             const base::FilePath& distilled_path,
-                             const GURL& distilled_url,
-                             int64_t distilation_size,
-                             const base::Time& distilation_time) override;
+  void SetEntryDistilledInfoIfExists(const GURL& url,
+                                     const base::FilePath& distilled_path,
+                                     const GURL& distilled_url,
+                                     int64_t distilation_size,
+                                     base::Time distilation_time) override;
   void AddObserver(ReadingListModelObserver* observer) override;
   void RemoveObserver(ReadingListModelObserver* observer) override;
 
@@ -159,7 +155,7 @@
 
   bool loaded_ = false;
 
-  ReadingListEntries entries_;
+  std::map<GURL, ReadingListEntry> entries_;
   size_t unread_entry_count_ = 0;
   size_t read_entry_count_ = 0;
   size_t unseen_entry_count_ = 0;
diff --git a/components/reading_list/core/reading_list_model_unittest.cc b/components/reading_list/core/reading_list_model_unittest.cc
index 58a67bf7..9290b3c 100644
--- a/components/reading_list/core/reading_list_model_unittest.cc
+++ b/components/reading_list/core/reading_list_model_unittest.cc
@@ -169,7 +169,7 @@
 
   size_t UnreadSize() {
     size_t size = 0;
-    for (const auto& url : model_->Keys()) {
+    for (const auto& url : model_->GetKeys()) {
       const ReadingListEntry* entry = model_->GetEntryByURL(url);
       if (!entry->IsRead()) {
         size++;
@@ -181,7 +181,7 @@
 
   size_t ReadSize() {
     size_t size = 0;
-    for (const auto& url : model_->Keys()) {
+    for (const auto& url : model_->GetKeys()) {
       const ReadingListEntry* entry = model_->GetEntryByURL(url);
       if (entry->IsRead()) {
         size++;
@@ -231,7 +231,7 @@
   AssertObserverCount(1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
   std::map<GURL, std::string> loaded_entries;
   int size = 0;
-  for (const auto& url : model_->Keys()) {
+  for (const auto& url : model_->GetKeys()) {
     size++;
     const ReadingListEntry* entry = model_->GetEntryByURL(url);
     loaded_entries[url] = entry->Title();
@@ -263,9 +263,10 @@
 TEST_F(ReadingListModelTest, AddEntry) {
   ClearCounts();
 
-  const ReadingListEntry& entry =
-      model_->AddEntry(GURL("http://example.com"), "\n  \tsample Test ",
-                       reading_list::ADDED_VIA_CURRENT_APP);
+  const ReadingListEntry& entry = model_->AddOrReplaceEntry(
+      GURL("http://example.com"), "\n  \tsample Test ",
+      reading_list::ADDED_VIA_CURRENT_APP,
+      /*estimated_read_time=*/base::TimeDelta());
   EXPECT_EQ(GURL("http://example.com"), entry.URL());
   EXPECT_EQ("sample Test", entry.Title());
 
@@ -286,11 +287,13 @@
 TEST_F(ReadingListModelTest, AddExistingEntry) {
   GURL url = GURL("http://example.com");
   std::string title = "\n  \tsample Test ";
-  model_->AddEntry(url, title, reading_list::ADDED_VIA_CURRENT_APP);
+  model_->AddOrReplaceEntry(url, title, reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
   ClearCounts();
 
   const ReadingListEntry& entry =
-      model_->AddEntry(url, title, reading_list::ADDED_VIA_CURRENT_APP);
+      model_->AddOrReplaceEntry(url, title, reading_list::ADDED_VIA_CURRENT_APP,
+                                /*estimated_read_time=*/base::TimeDelta());
   EXPECT_EQ(GURL("http://example.com"), entry.URL());
   EXPECT_EQ("sample Test", entry.Title());
 
@@ -327,15 +330,16 @@
 
 // Tests updating entry from sync.
 TEST_F(ReadingListModelTest, SyncMergeEntry) {
-  model_->AddEntry(GURL("http://example.com"), "sample",
-                   reading_list::ADDED_VIA_CURRENT_APP);
+  model_->AddOrReplaceEntry(GURL("http://example.com"), "sample",
+                            reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
   const base::FilePath distilled_path(FILE_PATH_LITERAL("distilled/page.html"));
   const GURL distilled_url("http://example.com/distilled");
   int64_t size = 50;
   int64_t time = 100;
-  model_->SetEntryDistilledInfo(GURL("http://example.com"), distilled_path,
-                                distilled_url, size,
-                                base::Time::FromTimeT(time));
+  model_->SetEntryDistilledInfoIfExists(GURL("http://example.com"),
+                                        distilled_path, distilled_url, size,
+                                        base::Time::FromTimeT(time));
   const ReadingListEntry* local_entry =
       model_->GetEntryByURL(GURL("http://example.com"));
   int64_t local_update_time = local_entry->UpdateTime();
@@ -367,8 +371,9 @@
 
 // Tests deleting entry.
 TEST_F(ReadingListModelTest, RemoveEntryByUrl) {
-  model_->AddEntry(GURL("http://example.com"), "sample",
-                   reading_list::ADDED_VIA_CURRENT_APP);
+  model_->AddOrReplaceEntry(GURL("http://example.com"), "sample",
+                            reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
   ClearCounts();
   EXPECT_NE(model_->GetEntryByURL(GURL("http://example.com")), nullptr);
   EXPECT_EQ(1ul, UnreadSize());
@@ -380,9 +385,10 @@
   EXPECT_EQ(0ul, ReadSize());
   EXPECT_EQ(model_->GetEntryByURL(GURL("http://example.com")), nullptr);
 
-  model_->AddEntry(GURL("http://example.com"), "sample",
-                   reading_list::ADDED_VIA_CURRENT_APP);
-  model_->SetReadStatus(GURL("http://example.com"), true);
+  model_->AddOrReplaceEntry(GURL("http://example.com"), "sample",
+                            reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
+  model_->SetReadStatusIfExists(GURL("http://example.com"), true);
   ClearCounts();
   EXPECT_NE(model_->GetEntryByURL(GURL("http://example.com")), nullptr);
   EXPECT_EQ(0ul, UnreadSize());
@@ -399,8 +405,9 @@
 TEST_F(ReadingListModelTest, RemoveSyncEntryByUrl) {
   // DCHECKs verify that sync updates are issued as batch updates.
   auto token = model_->BeginBatchUpdates();
-  model_->AddEntry(GURL("http://example.com"), "sample",
-                   reading_list::ADDED_VIA_CURRENT_APP);
+  model_->AddOrReplaceEntry(GURL("http://example.com"), "sample",
+                            reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
   ClearCounts();
   EXPECT_NE(model_->GetEntryByURL(GURL("http://example.com")), nullptr);
   EXPECT_EQ(1ul, UnreadSize());
@@ -412,9 +419,10 @@
   EXPECT_EQ(0ul, ReadSize());
   EXPECT_EQ(model_->GetEntryByURL(GURL("http://example.com")), nullptr);
 
-  model_->AddEntry(GURL("http://example.com"), "sample",
-                   reading_list::ADDED_VIA_CURRENT_APP);
-  model_->SetReadStatus(GURL("http://example.com"), true);
+  model_->AddOrReplaceEntry(GURL("http://example.com"), "sample",
+                            reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
+  model_->SetReadStatusIfExists(GURL("http://example.com"), true);
   ClearCounts();
   EXPECT_NE(model_->GetEntryByURL(GURL("http://example.com")), nullptr);
   EXPECT_EQ(0ul, UnreadSize());
@@ -429,11 +437,12 @@
 
 // Tests marking entry read.
 TEST_F(ReadingListModelTest, ReadEntry) {
-  model_->AddEntry(GURL("http://example.com"), "sample",
-                   reading_list::ADDED_VIA_CURRENT_APP);
+  model_->AddOrReplaceEntry(GURL("http://example.com"), "sample",
+                            reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
 
   ClearCounts();
-  model_->SetReadStatus(GURL("http://example.com"), true);
+  model_->SetReadStatusIfExists(GURL("http://example.com"), true);
   AssertObserverCount(0, 0, 0, 0, 0, 1, 0, 0, 0, 1);
   EXPECT_EQ(0ul, UnreadSize());
   EXPECT_EQ(1ul, ReadSize());
@@ -452,7 +461,9 @@
   GURL url1("http://example.com");
   GURL url2("http://example2.com");
   std::string entry1_title = "foo bar qux";
-  model_->AddEntry(url1, entry1_title, reading_list::ADDED_VIA_CURRENT_APP);
+  model_->AddOrReplaceEntry(url1, entry1_title,
+                            reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
 
   // Check call with nullptr |read| parameter.
   const ReadingListEntry* entry1 = model_->GetEntryByURL(url1);
@@ -463,7 +474,7 @@
   EXPECT_NE(nullptr, entry1);
   EXPECT_EQ(entry1_title, entry1->Title());
   EXPECT_EQ(entry1->IsRead(), false);
-  model_->SetReadStatus(url1, true);
+  model_->SetReadStatusIfExists(url1, true);
   entry1 = model_->GetEntryByURL(url1);
   EXPECT_NE(nullptr, entry1);
   EXPECT_EQ(entry1_title, entry1->Title());
@@ -476,15 +487,16 @@
 // Tests mark entry unread.
 TEST_F(ReadingListModelTest, UnreadEntry) {
   // Setup.
-  model_->AddEntry(GURL("http://example.com"), "sample",
-                   reading_list::ADDED_VIA_CURRENT_APP);
-  model_->SetReadStatus(GURL("http://example.com"), true);
+  model_->AddOrReplaceEntry(GURL("http://example.com"), "sample",
+                            reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
+  model_->SetReadStatusIfExists(GURL("http://example.com"), true);
   ClearCounts();
   EXPECT_EQ(0ul, UnreadSize());
   EXPECT_EQ(1ul, ReadSize());
 
   // Action.
-  model_->SetReadStatus(GURL("http://example.com"), false);
+  model_->SetReadStatusIfExists(GURL("http://example.com"), false);
 
   // Tests.
   AssertObserverCount(0, 0, 0, 0, 0, 1, 0, 0, 0, 1);
@@ -545,22 +557,24 @@
 // Tests setting title on unread entry.
 TEST_F(ReadingListModelTest, UpdateEntryTitle) {
   const GURL gurl("http://example.com");
-  const ReadingListEntry& entry =
-      model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP);
+  const ReadingListEntry& entry = model_->AddOrReplaceEntry(
+      gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP,
+      /*estimated_read_time=*/base::TimeDelta());
   ClearCounts();
 
-  model_->SetEntryTitle(gurl, "ping");
+  model_->SetEntryTitleIfExists(gurl, "ping");
   AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1, 1);
   EXPECT_EQ("ping", entry.Title());
 }
 // Tests setting distillation state on unread entry.
 TEST_F(ReadingListModelTest, UpdateEntryDistilledState) {
   const GURL gurl("http://example.com");
-  const ReadingListEntry& entry =
-      model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP);
+  const ReadingListEntry& entry = model_->AddOrReplaceEntry(
+      gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP,
+      /*estimated_read_time=*/base::TimeDelta());
   ClearCounts();
 
-  model_->SetEntryDistilledState(gurl, ReadingListEntry::PROCESSING);
+  model_->SetEntryDistilledStateIfExists(gurl, ReadingListEntry::PROCESSING);
   AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1, 1);
   EXPECT_EQ(ReadingListEntry::PROCESSING, entry.DistilledState());
 }
@@ -568,17 +582,18 @@
 // Tests setting distillation info on unread entry.
 TEST_F(ReadingListModelTest, UpdateDistilledInfo) {
   const GURL gurl("http://example.com");
-  const ReadingListEntry& entry =
-      model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP);
+  const ReadingListEntry& entry = model_->AddOrReplaceEntry(
+      gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP,
+      /*estimated_read_time=*/base::TimeDelta());
   ClearCounts();
 
   const base::FilePath distilled_path(FILE_PATH_LITERAL("distilled/page.html"));
   const GURL distilled_url("http://example.com/distilled");
   int64_t size = 50;
   int64_t time = 100;
-  model_->SetEntryDistilledInfo(GURL("http://example.com"), distilled_path,
-                                distilled_url, size,
-                                base::Time::FromTimeT(time));
+  model_->SetEntryDistilledInfoIfExists(GURL("http://example.com"),
+                                        distilled_path, distilled_url, size,
+                                        base::Time::FromTimeT(time));
   AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1, 1);
   EXPECT_EQ(ReadingListEntry::PROCESSED, entry.DistilledState());
   EXPECT_EQ(distilled_path, entry.DistilledPath());
@@ -591,12 +606,13 @@
 // Tests setting title on read entry.
 TEST_F(ReadingListModelTest, UpdateReadEntryTitle) {
   const GURL gurl("http://example.com");
-  model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP);
-  model_->SetReadStatus(gurl, true);
+  model_->AddOrReplaceEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
+  model_->SetReadStatusIfExists(gurl, true);
   const ReadingListEntry* entry = model_->GetEntryByURL(gurl);
   ClearCounts();
 
-  model_->SetEntryTitle(gurl, "ping");
+  model_->SetEntryTitleIfExists(gurl, "ping");
   AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1, 1);
   EXPECT_EQ("ping", entry->Title());
 }
@@ -604,12 +620,13 @@
 // Tests setting distillation state on read entry.
 TEST_F(ReadingListModelTest, UpdateReadEntryState) {
   const GURL gurl("http://example.com");
-  model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP);
-  model_->SetReadStatus(gurl, true);
+  model_->AddOrReplaceEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
+  model_->SetReadStatusIfExists(gurl, true);
   const ReadingListEntry* entry = model_->GetEntryByURL(gurl);
   ClearCounts();
 
-  model_->SetEntryDistilledState(gurl, ReadingListEntry::PROCESSING);
+  model_->SetEntryDistilledStateIfExists(gurl, ReadingListEntry::PROCESSING);
   AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1, 1);
   EXPECT_EQ(ReadingListEntry::PROCESSING, entry->DistilledState());
 }
@@ -617,8 +634,9 @@
 // Tests setting distillation info on read entry.
 TEST_F(ReadingListModelTest, UpdateReadDistilledInfo) {
   const GURL gurl("http://example.com");
-  model_->AddEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP);
-  model_->SetReadStatus(gurl, true);
+  model_->AddOrReplaceEntry(gurl, "sample", reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
+  model_->SetReadStatusIfExists(gurl, true);
   const ReadingListEntry* entry = model_->GetEntryByURL(gurl);
   ClearCounts();
 
@@ -626,9 +644,9 @@
   const GURL distilled_url("http://example.com/distilled");
   int64_t size = 50;
   int64_t time = 100;
-  model_->SetEntryDistilledInfo(GURL("http://example.com"), distilled_path,
-                                distilled_url, size,
-                                base::Time::FromTimeT(time));
+  model_->SetEntryDistilledInfoIfExists(GURL("http://example.com"),
+                                        distilled_path, distilled_url, size,
+                                        base::Time::FromTimeT(time));
   AssertObserverCount(0, 0, 0, 0, 0, 0, 0, 1, 1, 1);
   EXPECT_EQ(ReadingListEntry::PROCESSED, entry->DistilledState());
   EXPECT_EQ(distilled_path, entry->DistilledPath());
@@ -649,13 +667,14 @@
 TEST_F(ReadingListModelTest, TestTrimmingTitle) {
   const GURL gurl("http://example.com");
   std::string title = "\n  This\ttitle \n contains new     line \n characters ";
-  model_->AddEntry(gurl, title, reading_list::ADDED_VIA_CURRENT_APP);
-  model_->SetReadStatus(gurl, true);
+  model_->AddOrReplaceEntry(gurl, title, reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
+  model_->SetReadStatusIfExists(gurl, true);
   const ReadingListEntry* entry = model_->GetEntryByURL(gurl);
   EXPECT_EQ(entry->Title(), "This title contains new line characters");
-  model_->SetEntryTitle(gurl, "test");
+  model_->SetEntryTitleIfExists(gurl, "test");
   EXPECT_EQ(entry->Title(), "test");
-  model_->SetEntryTitle(gurl, title);
+  model_->SetEntryTitleIfExists(gurl, title);
   EXPECT_EQ(entry->Title(), "This title contains new line characters");
 }
 
diff --git a/components/reading_list/core/reading_list_sync_bridge.cc b/components/reading_list/core/reading_list_sync_bridge.cc
index 4095eec9..3d827677 100644
--- a/components/reading_list/core/reading_list_sync_bridge.cc
+++ b/components/reading_list/core/reading_list_sync_bridge.cc
@@ -139,7 +139,7 @@
   }
 
   // Commit local only entries to server.
-  for (const auto& url : model_->Keys()) {
+  for (const auto& url : model_->GetKeys()) {
     const ReadingListEntry* entry = model_->GetEntryByURL(url);
     if (synced_entries.count(url.spec())) {
       // Entry already exists and has been merged above.
@@ -230,7 +230,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto batch = std::make_unique<syncer::MutableDataBatch>();
 
-  for (const auto& url : model_->Keys()) {
+  for (const auto& url : model_->GetKeys()) {
     const ReadingListEntry* entry = model_->GetEntryByURL(GURL(url));
     AddEntryToBatch(batch.get(), *entry);
   }
diff --git a/components/reading_list/core/reading_list_sync_bridge_unittest.cc b/components/reading_list/core/reading_list_sync_bridge_unittest.cc
index d7c81ea..29df16e 100644
--- a/components/reading_list/core/reading_list_sync_bridge_unittest.cc
+++ b/components/reading_list/core/reading_list_sync_bridge_unittest.cc
@@ -208,8 +208,9 @@
 
 TEST_F(ReadingListSyncBridgeTest, ApplySyncChangesOneMerge) {
   AdvanceAndGetTime(&clock_);
-  model_->AddEntry(GURL("http://unread.example.com/"), "unread title",
-                   reading_list::ADDED_VIA_CURRENT_APP);
+  model_->AddOrReplaceEntry(GURL("http://unread.example.com/"), "unread title",
+                            reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
 
   ReadingListEntry new_entry(GURL("http://unread.example.com/"), "unread title",
                              AdvanceAndGetTime(&clock_));
@@ -243,8 +244,10 @@
   old_entry.SetRead(true, AdvanceAndGetTime(&clock_));
 
   AdvanceAndGetTime(&clock_);
-  model_->AddEntry(GURL("http://unread.example.com/"), "new unread title",
-                   reading_list::ADDED_VIA_CURRENT_APP);
+  model_->AddOrReplaceEntry(GURL("http://unread.example.com/"),
+                            "new unread title",
+                            reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
 
   std::unique_ptr<sync_pb::ReadingListSpecifics> specifics =
       old_entry.AsReadingListSpecifics();
@@ -269,8 +272,9 @@
 }
 
 TEST_F(ReadingListSyncBridgeTest, ApplySyncChangesOneRemove) {
-  model_->AddEntry(GURL("http://read.example.com/"), "read title",
-                   reading_list::ADDED_VIA_CURRENT_APP);
+  model_->AddOrReplaceEntry(GURL("http://read.example.com/"), "read title",
+                            reading_list::ADDED_VIA_CURRENT_APP,
+                            /*estimated_read_time=*/base::TimeDelta());
 
   syncer::EntityChangeList delete_changes;
   delete_changes.push_back(
diff --git a/components/remote_cocoa/app_shim/bridged_content_view.mm b/components/remote_cocoa/app_shim/bridged_content_view.mm
index 69aad30..5dda9a9 100644
--- a/components/remote_cocoa/app_shim/bridged_content_view.mm
+++ b/components/remote_cocoa/app_shim/bridged_content_view.mm
@@ -59,6 +59,26 @@
                     NSHeight(content_rect) - point_in_window.y);
 }
 
+// Convert a |point| in |source_window|'s AppKit coordinate system (origin at
+// the bottom left of the window) to |target_view|'s coordinate, with the
+// origin at the top left of the view.
+// If |source_window| is nil, |point| will be treated as screen coordinates.
+gfx::Point MovePointToView(const NSPoint& point,
+                           NSWindow* source_window,
+                           NSView* target_view) {
+  NSPoint point_in_screen =
+      source_window ? ui::ConvertPointFromWindowToScreen(source_window, point)
+                    : point;
+
+  NSWindow* target_window = [target_view window];
+  NSPoint point_in_window =
+      ui::ConvertPointFromScreenToWindow(target_window, point_in_screen);
+  NSPoint point_in_view = [target_view convertPoint:point_in_window
+                                           fromView:nil];
+  return gfx::Point(point_in_window.x,
+                    NSHeight([target_view frame]) - point_in_view.y);
+}
+
 // Some keys are silently consumed by -[NSView interpretKeyEvents:]
 // They should not be processed as accelerators.
 // See comments at |keyDown:| for details.
@@ -305,8 +325,20 @@
     return;
   }
 
-  gfx::Point event_location =
-      MovePointToWindow([theEvent locationInWindow], source, target);
+  gfx::Point event_location;
+  if (remote_cocoa::IsNSToolbarFullScreenWindow(target)) {
+    // We are in immersive fullscreen. This event is generated from the overlay
+    // window which sits atop the toolbar. Convert the event location to the
+    // content view coordiate, which should have the same bounds as the overlay
+    // window.
+    // This is to handle the case that `target` may contain the titlebar which
+    // the overlay window does not contain. Without this, buttons in the toolbar
+    // are not clickable when the titlebar is revealed.
+    event_location = MovePointToView([theEvent locationInWindow], source, self);
+  } else {
+    event_location =
+        MovePointToWindow([theEvent locationInWindow], source, target);
+  }
   [self updateTooltipIfRequiredAt:event_location];
 
   if (isScrollEvent) {
diff --git a/components/remote_cocoa/app_shim/immersive_mode_controller.h b/components/remote_cocoa/app_shim/immersive_mode_controller.h
index 13ac014..940775a1 100644
--- a/components/remote_cocoa/app_shim/immersive_mode_controller.h
+++ b/components/remote_cocoa/app_shim/immersive_mode_controller.h
@@ -68,6 +68,9 @@
   // Start observing child windows of overlay_widget_.
   void ObserveOverlayChildWindows();
 
+  // Reparent children of `source` to `target`.
+  void ReparentChildWindows(NSWindow* source, NSWindow* target);
+
   bool enabled_ = false;
 
   NSWindow* const browser_widget_;
diff --git a/components/remote_cocoa/app_shim/immersive_mode_controller.mm b/components/remote_cocoa/app_shim/immersive_mode_controller.mm
index d31fc0b..d9cf7a8 100644
--- a/components/remote_cocoa/app_shim/immersive_mode_controller.mm
+++ b/components/remote_cocoa/app_shim/immersive_mode_controller.mm
@@ -10,6 +10,7 @@
 #import "components/remote_cocoa/app_shim/bridged_content_view.h"
 #import "components/remote_cocoa/app_shim/immersive_mode_delegate_mac.h"
 #import "components/remote_cocoa/app_shim/native_widget_mac_nswindow.h"
+#import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
 #include "ui/gfx/geometry/rect.h"
 
 namespace {
@@ -103,15 +104,18 @@
   base::OnceClosure _view_will_appear_callback;
   base::scoped_nsobject<ImmersiveModeTitlebarObserver>
       _immersive_mode_titlebar_observer;
+  NSWindow* _overlay_window;
 }
 @end
 
 @implementation ImmersiveModeTitlebarViewController
 
-- (instancetype)initWithViewWillAppearCallback:
-    (base::OnceClosure)viewWillAppearCallback {
+- (instancetype)initWithOverlayWindow:(NSWindow*)overlay_window
+               viewWillAppearCallback:
+                   (base::OnceClosure)view_will_appear_callback {
   if ((self = [super init])) {
-    _view_will_appear_callback = std::move(viewWillAppearCallback);
+    _overlay_window = overlay_window;
+    _view_will_appear_callback = std::move(view_will_appear_callback);
   }
   return self;
 }
@@ -139,6 +143,15 @@
     return;
   }
 
+  // Attach overlay_widget to NSToolbarFullScreen so that children are placed on
+  // top of the toolbar.
+  // When exitting fullscreeen, we don't re-parent the overlay window back to
+  // the browser window because it seems to trigger re-entrancy in AppKit and
+  // cause crash.  This is safe because sub-widgets will be re-parented to the
+  // browser window and therefore the overlay window won't have any observable
+  // effect.
+  [self.view.window addChildWindow:_overlay_window ordered:NSWindowAbove];
+
   NSView* view = GetNSTitlebarContainerViewFromWindow(self.view.window);
   DCHECK(view);
   [view addObserver:_immersive_mode_titlebar_observer
@@ -289,7 +302,8 @@
   // overlay_view_.
   immersive_mode_titlebar_view_controller_.reset(
       [[ImmersiveModeTitlebarViewController alloc]
-          initWithViewWillAppearCallback:std::move(callback)]);
+           initWithOverlayWindow:overlay_widget_
+          viewWillAppearCallback:std::move(callback)]);
 
   // Create a NSWindow delegate that will be used to map the AppKit created
   // NSWindow to the overlay view widget's NSWindow.
@@ -341,7 +355,10 @@
   thin_titlebar_view_controller_.get().fullScreenMinHeight =
       kThinControllerHeight;
 
+  // Move sub-widgets from the browser widget to the overlay widget so that
+  // they are rendered above the toolbar.
   ObserveOverlayChildWindows();
+  ReparentChildWindows(browser_widget_, overlay_widget_);
 }
 
 ImmersiveModeController::~ImmersiveModeController() {
@@ -361,6 +378,9 @@
   if (@available(macOS 11.0, *)) {
     browser_widget_.titlebarSeparatorStyle = NSTitlebarSeparatorStyleAutomatic;
   }
+
+  // Move sub-widgets back to the browser widget.
+  ReparentChildWindows(overlay_widget_, browser_widget_);
 }
 
 void ImmersiveModeController::Enable() {
@@ -458,6 +478,13 @@
 
 void ImmersiveModeController::ObserveOverlayChildWindows() {
   // Watch the overlay Widget for new child Widgets.
+  auto observe_window = [this](NSWindow* window) {
+    [window addObserver:immersive_mode_window_observer_
+             forKeyPath:@"visible"
+                options:NSKeyValueObservingOptionInitial |
+                        NSKeyValueObservingOptionNew
+                context:nullptr];
+  };
   NativeWidgetMacNSWindow* overlay_window =
       base::mac::ObjCCastStrict<NativeWidgetMacNSWindow>(overlay_widget_);
   overlay_window.childWindowAddedHandler = ^(NSWindow* child) {
@@ -465,14 +492,24 @@
     if (!child.visible) {
       return;
     }
-    [child addObserver:immersive_mode_window_observer_
-            forKeyPath:@"visible"
-               options:NSKeyValueObservingOptionInitial |
-                       NSKeyValueObservingOptionNew
-               context:nullptr];
+    observe_window(child);
   };
 }
 
+void ImmersiveModeController::ReparentChildWindows(NSWindow* source,
+                                                   NSWindow* target) {
+  NativeWidgetNSWindowBridge* source_bridge =
+      NativeWidgetNSWindowBridge::GetFromNativeWindow(source);
+  NativeWidgetNSWindowBridge* target_bridge =
+      NativeWidgetNSWindowBridge::GetFromNativeWindow(target);
+
+  // TODO(kerenzhu): DCHECK(source_bridge && target_bridge)
+  // Only in unittests the associated bridges might not exist.
+  if (source_bridge && target_bridge) {
+    source_bridge->MoveChildrenTo(target_bridge);
+  }
+}
+
 void ImmersiveModeController::TitlebarLock() {
   titlebar_lock_count_++;
   SetTitlebarPinned(true);
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h
index a09d15a0..40a75c3 100644
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h
@@ -305,6 +305,9 @@
   // update widget and compositor size.
   void UpdateWindowGeometry();
 
+  // Move `child_windows_` to `target`.
+  void MoveChildrenTo(NativeWidgetNSWindowBridge* target);
+
  private:
   friend class views::test::BridgedNativeWidgetTestApi;
 
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
index 41e73ce..5085d15 100644
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
@@ -1590,6 +1590,10 @@
     } else {
       if (child_window.parentWindow == window)
         continue;
+      if (immersive_mode_controller_ &&
+          immersive_mode_controller_->overlay_widget() == child_window) {
+        continue;
+      }
       [window addChildWindow:child_window ordered:NSWindowAbove];
     }
   }
@@ -1664,6 +1668,18 @@
     invalidate_shadow_on_frame_swap_ = true;
 }
 
+void NativeWidgetNSWindowBridge::MoveChildrenTo(
+    NativeWidgetNSWindowBridge* target) {
+  // Make a copy of `child_windows_` because it will be updated during the loop.
+  std::vector<NativeWidgetNSWindowBridge*> child_windows(child_windows_);
+  for (NativeWidgetNSWindowBridge* child : child_windows) {
+    if (child != target) {
+      child->SetParent(target->id_);
+      child->host()->OnWindowParentChanged(target->id_);
+    }
+  }
+}
+
 void NativeWidgetNSWindowBridge::UpdateWindowDisplay() {
   if (fullscreen_controller_.IsInFullscreenTransition())
     return;
diff --git a/components/remote_cocoa/common/native_widget_ns_window_host.mojom b/components/remote_cocoa/common/native_widget_ns_window_host.mojom
index 354a3b71..e29d17d 100644
--- a/components/remote_cocoa/common/native_widget_ns_window_host.mojom
+++ b/components/remote_cocoa/common/native_widget_ns_window_host.mojom
@@ -164,6 +164,9 @@
   // state has changed.
   OnWindowStateRestorationDataChanged(array<uint8> data);
 
+  // Called when this widget is reparented to another window.
+  OnWindowParentChanged(uint64 new_parent_id);
+
   // Accept or cancel the current dialog window (depending on the value of
   // |button|), if a current dialog exists.
   DoDialogButtonAction(ui.mojom.DialogButton button);
diff --git a/components/reporting/storage/storage_queue.cc b/components/reporting/storage/storage_queue.cc
index 5862711..9b0550b7 100644
--- a/components/reporting/storage/storage_queue.cc
+++ b/components/reporting/storage/storage_queue.cc
@@ -575,13 +575,9 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(storage_queue_sequence_checker_);
 
   // Test only: Simulate failure if requested
-  if (test_injected_failures_.count(
-          test::StorageQueueOperationKind::kWriteBlock) > 0 &&
-      test_injected_failures_[test::StorageQueueOperationKind::kWriteBlock]
-          .count(next_sequencing_id_)) {
-    return Status(error::INTERNAL,
-                  base::StrCat({"Simulated failure, seq=",
-                                base::NumberToString(next_sequencing_id_)}));
+  if (test_injection_handler_) {
+    RETURN_IF_ERROR(test_injection_handler_.Run(
+        test::StorageQueueOperationKind::kWriteBlock, next_sequencing_id_));
   }
 
   // Prepare header.
@@ -646,13 +642,9 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(storage_queue_sequence_checker_);
 
   // Test only: Simulate failure if requested
-  if (test_injected_failures_.count(
-          test::StorageQueueOperationKind::kWriteMetadata) > 0 &&
-      test_injected_failures_[test::StorageQueueOperationKind::kWriteMetadata]
-          .count(next_sequencing_id_)) {
-    return Status(error::INTERNAL,
-                  base::StrCat({"Simulated failure, seq=",
-                                base::NumberToString(next_sequencing_id_)}));
+  if (test_injection_handler_) {
+    RETURN_IF_ERROR(test_injection_handler_.Run(
+        test::StorageQueueOperationKind::kWriteMetadata, next_sequencing_id_));
   }
 
   // Synchronously write the metafile.
@@ -1229,15 +1221,9 @@
         storage_queue_->storage_queue_sequence_checker_);
 
     // Test only: simulate error, if requested.
-    if (storage_queue_->test_injected_failures_.count(
-            test::StorageQueueOperationKind::kReadBlock) > 0 &&
-        storage_queue_
-                ->test_injected_failures_
-                    [test::StorageQueueOperationKind::kReadBlock]
-                .count(sequencing_id) > 0) {
-      return Status(error::INTERNAL,
-                    base::StrCat({"Simulated failure, seq=",
-                                  base::NumberToString(sequencing_id)}));
+    if (storage_queue_->test_injection_handler_) {
+      RETURN_IF_ERROR(storage_queue_->test_injection_handler_.Run(
+          test::StorageQueueOperationKind::kReadBlock, sequencing_id));
     }
 
     // Read from the current file at the current offset.
@@ -1560,20 +1546,21 @@
         << "Unusually large timestamp (in milliseconds): "
         << wrapped_record.record().timestamp_us();
 
+    // Serialize wrapped record into a string.
     std::string buffer;
     if (!wrapped_record.SerializeToString(&buffer)) {
       Schedule(&ReadContext::Response, base::Unretained(this),
                Status(error::DATA_LOSS, "Cannot serialize record"));
       return;
     }
-    // Release wrapped record memory, so scoped reservation may act.
+    // Release wrapped record memory, so `scoped_reservation` may act.
     wrapped_record.Clear();
     CompressWrappedRecord(std::move(buffer), std::move(scoped_reservation));
   }
 
   void CompressWrappedRecord(std::string serialized_record,
                              ScopedReservation scoped_reservation) {
-    // Compress the string.
+    // Compress the string. If memory is insufficient, compression is skipped.
     storage_queue_->compression_module_->CompressRecord(
         std::move(serialized_record),
         storage_queue_->options().memory_resource(),
@@ -2077,9 +2064,10 @@
 }
 
 void StorageQueue::TestInjectErrorsForOperation(
-    const test::StorageQueueOperationKind operation_kind,
-    std::initializer_list<int64_t> sequencing_ids) {
-  test_injected_failures_[operation_kind] = sequencing_ids;
+    base::RepeatingCallback<
+        Status(test::StorageQueueOperationKind operation_kind, int64_t)>
+        handler) {
+  test_injection_handler_ = handler;
 }
 
 //
diff --git a/components/reporting/storage/storage_queue.h b/components/reporting/storage/storage_queue.h
index ef18f72..a2c93c06 100644
--- a/components/reporting/storage/storage_queue.h
+++ b/components/reporting/storage/storage_queue.h
@@ -46,7 +46,7 @@
 enum class StorageQueueOperationKind {
   kReadBlock,
   kWriteBlock,
-  kWriteMetadata
+  kWriteMetadata,
 };
 
 }  // namespace test
@@ -134,10 +134,13 @@
   // to its completion.
   void RegisterCompletionCallback(base::OnceClosure callback);
 
-  // Test only: makes specified records fail on specified operation kind.
+  // Test only: provides an injection handler that would receive operation kind
+  // and seq id, and then return Status. Non-OK Status injects the error and
+  // can be returned as a resulting operation status too.
+  // If `handler` is null, error injections is disabled.
   void TestInjectErrorsForOperation(
-      const test::StorageQueueOperationKind operation_kind,
-      std::initializer_list<int64_t> sequencing_ids);
+      base::RepeatingCallback<Status(test::StorageQueueOperationKind, int64_t)>
+          handler = decltype(handler)());
 
   // Access queue options.
   const QueueOptions& options() const { return options_; }
@@ -483,9 +486,11 @@
   // Compression module.
   scoped_refptr<CompressionModule> compression_module_;
 
-  // Test only: records specified to fail for a given operation kind.
-  base::flat_map<test::StorageQueueOperationKind, base::flat_set<int64_t>>
-      test_injected_failures_;
+  // Test only: records callback to be invoked. It will be called with operation
+  // kind and seq id, and will return Status (non-OK status indicates the
+  // failure to be injected). In production code must be null.
+  base::RepeatingCallback<Status(test::StorageQueueOperationKind, int64_t)>
+      test_injection_handler_;
 
   // Weak pointer factory (must be last member in class).
   base::WeakPtrFactory<StorageQueue> weakptr_factory_{this};
diff --git a/components/reporting/storage/storage_queue_unittest.cc b/components/reporting/storage/storage_queue_unittest.cc
index 32e7d76..dbaa07e6 100644
--- a/components/reporting/storage/storage_queue_unittest.cc
+++ b/components/reporting/storage/storage_queue_unittest.cc
@@ -40,6 +40,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 using ::testing::_;
+using ::testing::AnyOf;
 using ::testing::AtMost;
 using ::testing::Between;
 using ::testing::DoAll;
@@ -629,10 +630,19 @@
     EXPECT_THAT(options_.disk_space_resource()->GetUsed(), Eq(0u));
   }
 
-  void InjectFailures(const test::StorageQueueOperationKind operation_kind,
-                      std::initializer_list<int64_t> sequencing_ids) {
-    storage_queue_->TestInjectErrorsForOperation(operation_kind,
-                                                 sequencing_ids);
+  std::unique_ptr<
+      ::testing::MockFunction<Status(test::StorageQueueOperationKind, int64_t)>>
+  InjectFailures() {
+    auto inject = std::make_unique<::testing::MockFunction<Status(
+        test::StorageQueueOperationKind, int64_t)>>();
+    // By default return OK status - no error injected.
+    EXPECT_CALL(*inject, Call(_, _))
+        .WillRepeatedly(WithoutArgs(Return(Status::StatusOK())));
+    storage_queue_->TestInjectErrorsForOperation(base::BindRepeating(
+        &::testing::MockFunction<Status(test::StorageQueueOperationKind,
+                                        int64_t)>::Call,
+        base::Unretained(inject.get())));
+    return inject;
   }
 
   QueueOptions BuildStorageQueueOptionsImmediate() const {
@@ -872,7 +882,14 @@
   WriteStringOrDie(kData[2]);
 
   // Inject simulated failures.
-  InjectFailures(test::StorageQueueOperationKind::kReadBlock, {1});
+  auto inject = InjectFailures();
+  EXPECT_CALL(*inject,
+              Call(Eq(test::StorageQueueOperationKind::kReadBlock), Eq(1)))
+      .WillRepeatedly(WithArg<1>(Invoke([](int64_t seq_id) {
+        return Status(error::INTERNAL,
+                      base::StrCat({"Simulated read failure, seq=",
+                                    base::NumberToString(seq_id)}));
+      })));
 
   // Set uploader expectations.
   test::TestCallbackAutoWaiter waiter;
@@ -1659,7 +1676,14 @@
   WriteStringOrDie(kMoreData[2]);
 
   // Inject simulated failures.
-  InjectFailures(test::StorageQueueOperationKind::kReadBlock, {4, 5});
+  auto inject = InjectFailures();
+  EXPECT_CALL(*inject, Call(Eq(test::StorageQueueOperationKind::kReadBlock),
+                            AnyOf(4, 5)))
+      .WillRepeatedly(WithArg<1>(Invoke([](int64_t seq_id) {
+        return Status(error::INTERNAL,
+                      base::StrCat({"Simulated read failure, seq=",
+                                    base::NumberToString(seq_id)}));
+      })));
 
   {
     // Set uploader expectations.
@@ -1687,8 +1711,8 @@
   // Confirm #2 and forward time again, removing record #2
   ConfirmOrDie(/*sequencing_id=*/2);
 
-  // Reset simulated failures.
-  InjectFailures(test::StorageQueueOperationKind::kReadBlock, {});
+  // Reset error injection.
+  storage_queue_->TestInjectErrorsForOperation();
 
   {
     // Set uploader expectations.
@@ -2098,7 +2122,16 @@
 
 TEST_P(StorageQueueTest, WriteRecordWithWriteMetadataFailures) {
   CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
-  InjectFailures(test::StorageQueueOperationKind::kWriteMetadata, {0});
+
+  auto inject = InjectFailures();
+  EXPECT_CALL(*inject,
+              Call(Eq(test::StorageQueueOperationKind::kWriteMetadata), Eq(0)))
+      .WillOnce(WithArg<1>(Invoke([](int64_t seq_id) {
+        return Status(error::INTERNAL,
+                      base::StrCat({"Simulated metadata write failure, seq=",
+                                    base::NumberToString(seq_id)}));
+      })));
+
   Status write_result = WriteString(kData[0]);
   EXPECT_FALSE(write_result.ok());
   EXPECT_EQ(write_result.error_code(), error::INTERNAL);
@@ -2106,7 +2139,16 @@
 
 TEST_P(StorageQueueTest, WriteRecordWithWriteBlockFailures) {
   CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
-  InjectFailures(test::StorageQueueOperationKind::kWriteBlock, {0});
+
+  auto inject = InjectFailures();
+  EXPECT_CALL(*inject,
+              Call(Eq(test::StorageQueueOperationKind::kWriteBlock), Eq(0)))
+      .WillOnce(WithArg<1>(Invoke([](int64_t seq_id) {
+        return Status(error::INTERNAL,
+                      base::StrCat({"Simulated write failure, seq=",
+                                    base::NumberToString(seq_id)}));
+      })));
+
   Status write_result = WriteString(kData[0]);
   EXPECT_FALSE(write_result.ok());
   EXPECT_EQ(write_result.error_code(), error::INTERNAL);
diff --git a/components/security_interstitials/content/stateful_ssl_host_state_delegate.cc b/components/security_interstitials/content/stateful_ssl_host_state_delegate.cc
index ee93e5c3..5cf9e3d 100644
--- a/components/security_interstitials/content/stateful_ssl_host_state_delegate.cc
+++ b/components/security_interstitials/content/stateful_ssl_host_state_delegate.cc
@@ -86,9 +86,9 @@
                                      int threshold) {
   double now = clock->Now().ToJsTime();
 
-  DictionaryPrefUpdate pref_update(pref_service,
+  ScopedDictPrefUpdate pref_update(pref_service,
                                    prefs::kRecurrentSSLInterstitial);
-  base::Value::Dict& dict = pref_update->GetDict();
+  base::Value::Dict& dict = pref_update.Get();
   base::Value::List* list = dict.FindList(net::ErrorToShortString(error));
   if (list) {
     // Check that the values are in increasing order and wipe out the list if
@@ -454,9 +454,9 @@
 
 void StatefulSSLHostStateDelegate::ResetRecurrentErrorCountForTesting() {
   recurrent_errors_.clear();
-  DictionaryPrefUpdate pref_update(pref_service_,
+  ScopedDictPrefUpdate pref_update(pref_service_,
                                    prefs::kRecurrentSSLInterstitial);
-  pref_update->GetDict().clear();
+  pref_update->clear();
 }
 
 void StatefulSSLHostStateDelegate::SetClockForTesting(
diff --git a/components/segmentation_platform/internal/dummy_segmentation_platform_service.cc b/components/segmentation_platform/internal/dummy_segmentation_platform_service.cc
index 58a1527..1e26d48 100644
--- a/components/segmentation_platform/internal/dummy_segmentation_platform_service.cc
+++ b/components/segmentation_platform/internal/dummy_segmentation_platform_service.cc
@@ -23,6 +23,12 @@
       FROM_HERE, base::BindOnce(std::move(callback), SegmentSelectionResult()));
 }
 
+void DummySegmentationPlatformService::GetClassificationResult(
+    const std::string& segmentation_key,
+    const PredictionOptions& prediction_options,
+    scoped_refptr<InputContext> input_context,
+    ClassificationResultCallback callback) {}
+
 SegmentSelectionResult DummySegmentationPlatformService::GetCachedSegmentResult(
     const std::string& segmentation_key) {
   return SegmentSelectionResult();
diff --git a/components/segmentation_platform/internal/dummy_segmentation_platform_service.h b/components/segmentation_platform/internal/dummy_segmentation_platform_service.h
index e1a94c3..96666df 100644
--- a/components/segmentation_platform/internal/dummy_segmentation_platform_service.h
+++ b/components/segmentation_platform/internal/dummy_segmentation_platform_service.h
@@ -27,6 +27,10 @@
   // SegmentationPlatformService overrides.
   void GetSelectedSegment(const std::string& segmentation_key,
                           SegmentSelectionCallback callback) override;
+  void GetClassificationResult(const std::string& segmentation_key,
+                               const PredictionOptions& prediction_options,
+                               scoped_refptr<InputContext> input_context,
+                               ClassificationResultCallback callback) override;
   SegmentSelectionResult GetCachedSegmentResult(
       const std::string& segmentation_key) override;
   void GetSelectedSegmentOnDemand(const std::string& segmentation_key,
diff --git a/components/segmentation_platform/internal/metadata/metadata_writer.cc b/components/segmentation_platform/internal/metadata/metadata_writer.cc
index 545f222..3c9c9bce 100644
--- a/components/segmentation_platform/internal/metadata/metadata_writer.cc
+++ b/components/segmentation_platform/internal/metadata/metadata_writer.cc
@@ -144,7 +144,8 @@
 
 void MetadataWriter::AddOutputConfigForMultiClassClassifier(
     const std::vector<std::string>& class_labels,
-    int top_k_outputs) {
+    int top_k_outputs,
+    absl::optional<float> threshold) {
   proto::Predictor_MultiClassClassifier* multi_class_classifier =
       metadata_->mutable_output_config()
           ->mutable_predictor()
@@ -153,6 +154,9 @@
   multi_class_classifier->set_top_k_outputs(top_k_outputs);
   multi_class_classifier->mutable_class_labels()->Assign(class_labels.begin(),
                                                          class_labels.end());
+  if (threshold.has_value()) {
+    multi_class_classifier->set_threshold(threshold.value());
+  }
 }
 
 void MetadataWriter::AddOutputConfigForBinnedClassifier(
diff --git a/components/segmentation_platform/internal/metadata/metadata_writer.h b/components/segmentation_platform/internal/metadata/metadata_writer.h
index 9d9e3b5..7f5418c 100644
--- a/components/segmentation_platform/internal/metadata/metadata_writer.h
+++ b/components/segmentation_platform/internal/metadata/metadata_writer.h
@@ -11,6 +11,7 @@
 #include "base/memory/raw_ptr.h"
 #include "components/segmentation_platform/internal/database/ukm_types.h"
 #include "components/segmentation_platform/public/proto/model_metadata.pb.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace segmentation_platform {
 
@@ -148,7 +149,8 @@
   // Adds a MultiClassClassifier.
   void AddOutputConfigForMultiClassClassifier(
       const std::vector<std::string>& class_labels,
-      int top_k_outputs);
+      int top_k_outputs,
+      absl::optional<float> threshold);
 
   // Adds a BinnedClassifier.
   void AddOutputConfigForBinnedClassifier(
diff --git a/components/segmentation_platform/internal/post_processor/post_processor.cc b/components/segmentation_platform/internal/post_processor/post_processor.cc
index a0e821fb..e51812d 100644
--- a/components/segmentation_platform/internal/post_processor/post_processor.cc
+++ b/components/segmentation_platform/internal/post_processor/post_processor.cc
@@ -61,10 +61,14 @@
                const std::pair<std::string, float>& b) {
               return a.second > b.second;
             });
-
+  float threshold = multi_class_classifier.threshold();
   int top_k_outputs = multi_class_classifier.top_k_outputs();
+
   std::vector<std::string> top_k_output_labels;
   for (int index = 0; index < top_k_outputs; index++) {
+    if (labeled_results[index].second < threshold) {
+      break;
+    }
     top_k_output_labels.emplace_back(labeled_results[index].first);
   }
   return top_k_output_labels;
diff --git a/components/segmentation_platform/internal/post_processor/post_processor_unittest.cc b/components/segmentation_platform/internal/post_processor/post_processor_unittest.cc
index 2729f9b5d..8c95664 100644
--- a/components/segmentation_platform/internal/post_processor/post_processor_unittest.cc
+++ b/components/segmentation_platform/internal/post_processor/post_processor_unittest.cc
@@ -42,13 +42,14 @@
 }
 
 proto::OutputConfig GetTestOutputConfigForMultiClassClassifier(
-    int top_k_outputs) {
+    int top_k_outputs,
+    absl::optional<float> threshold) {
   proto::SegmentationModelMetadata model_metadata;
   MetadataWriter writer(&model_metadata);
 
   writer.AddOutputConfigForMultiClassClassifier(
       /*class_labels=*/{kShareUser, kNewTabUser, kVoiceUser, kShoppingUser},
-      top_k_outputs);
+      top_k_outputs, threshold);
   return model_metadata.output_config();
 }
 
@@ -95,7 +96,9 @@
   std::vector<std::string> top_k_labels = post_processor.GetClassifierResults(
       metadata_utils::CreatePredictionResult(
           /*model_scores=*/{0.5, 0.2, 0.4, 0.7},
-          GetTestOutputConfigForMultiClassClassifier(/*top_k-outputs=*/2),
+          GetTestOutputConfigForMultiClassClassifier(
+              /*top_k-outputs=*/2,
+              /*threshold=*/absl::nullopt),
           /*timestamp=*/base::Time::Now()));
   EXPECT_THAT(top_k_labels, testing::ElementsAre(kShoppingUser, kShareUser));
 }
@@ -105,12 +108,50 @@
   std::vector<std::string> top_k_labels = post_processor.GetClassifierResults(
       metadata_utils::CreatePredictionResult(
           /*model_scores=*/{0.5, 0.2, 0.4, 0.7},
-          GetTestOutputConfigForMultiClassClassifier(/*top_k-outputs=*/4),
+          GetTestOutputConfigForMultiClassClassifier(
+              /*top_k-outputs=*/4,
+              /*threshold=*/absl::nullopt),
           /*timestamp=*/base::Time::Now()));
   EXPECT_THAT(top_k_labels, testing::ElementsAre(kShoppingUser, kShareUser,
                                                  kVoiceUser, kNewTabUser));
 }
 
+TEST(PostProcessorTest, MultiClassClassifierWithThresholdBetweenModelResult) {
+  PostProcessor post_processor;
+  std::vector<std::string> top_k_labels = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.5, 0.2, 0.4, 0.7},
+          GetTestOutputConfigForMultiClassClassifier(/*top_k-outputs=*/4,
+                                                     /*threshold=*/0.4),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_THAT(top_k_labels,
+              testing::ElementsAre(kShoppingUser, kShareUser, kVoiceUser));
+}
+
+TEST(PostProcessorTest,
+     MultiClassClassifierWithThresholdGreaterThanModelResult) {
+  PostProcessor post_processor;
+  std::vector<std::string> top_k_labels = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.5, 0.2, 0.4, 0.7},
+          GetTestOutputConfigForMultiClassClassifier(/*top_k-outputs=*/4,
+                                                     /*threshold=*/0.8),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_TRUE(top_k_labels.empty());
+}
+
+TEST(PostProcessorTest,
+     MultiClassClassifierWithThresholdLesserThanModelResult) {
+  PostProcessor post_processor;
+  std::vector<std::string> top_k_labels = post_processor.GetClassifierResults(
+      metadata_utils::CreatePredictionResult(
+          /*model_scores=*/{0.5, 0.2, 0.4, 0.7},
+          GetTestOutputConfigForMultiClassClassifier(/*top_k-outputs=*/2,
+                                                     /*threshold=*/0.1),
+          /*timestamp=*/base::Time::Now()));
+  EXPECT_THAT(top_k_labels, testing::ElementsAre(kShoppingUser, kShareUser));
+}
+
 TEST(PostProcessorTest, BinnedClassifierScoreGreaterThanHighUserThreshold) {
   PostProcessor post_processor;
   std::vector<std::string> winning_label = post_processor.GetClassifierResults(
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_impl.cc b/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
index 65a3caf..79a4a5e4 100644
--- a/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
+++ b/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
@@ -136,6 +136,14 @@
   selector->GetSelectedSegment(std::move(callback));
 }
 
+void SegmentationPlatformServiceImpl::GetClassificationResult(
+    const std::string& segmentation_key,
+    const PredictionOptions& prediction_options,
+    scoped_refptr<InputContext> input_context,
+    ClassificationResultCallback callback) {
+  // TODO(ritikagup@) : Implement logic.
+}
+
 SegmentSelectionResult SegmentationPlatformServiceImpl::GetCachedSegmentResult(
     const std::string& segmentation_key) {
   CHECK(segment_selectors_.find(segmentation_key) != segment_selectors_.end());
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_impl.h b/components/segmentation_platform/internal/segmentation_platform_service_impl.h
index 36abc6f..11a73d9 100644
--- a/components/segmentation_platform/internal/segmentation_platform_service_impl.h
+++ b/components/segmentation_platform/internal/segmentation_platform_service_impl.h
@@ -106,6 +106,10 @@
   // SegmentationPlatformService overrides.
   void GetSelectedSegment(const std::string& segmentation_key,
                           SegmentSelectionCallback callback) override;
+  void GetClassificationResult(const std::string& segmentation_key,
+                               const PredictionOptions& prediction_options,
+                               scoped_refptr<InputContext> input_context,
+                               ClassificationResultCallback callback) override;
   SegmentSelectionResult GetCachedSegmentResult(
       const std::string& segmentation_key) override;
   void GetSelectedSegmentOnDemand(const std::string& segmentation_key,
diff --git a/components/segmentation_platform/public/proto/output_config.proto b/components/segmentation_platform/public/proto/output_config.proto
index b64ba2ec..96e51b0 100644
--- a/components/segmentation_platform/public/proto/output_config.proto
+++ b/components/segmentation_platform/public/proto/output_config.proto
@@ -37,6 +37,10 @@
     // model must be equal to the length of this list. Each label maps to the
     // corresponding output index.
     repeated string class_labels = 2;
+
+    // Threshold to get `class_labels` with score above it. Only labels with
+    // score above or equal to threshold are returned. 
+    optional float threshold = 3;
   }
 
   // A post-processor that converts a continuous model score into discrete
diff --git a/components/segmentation_platform/public/segmentation_platform_service.h b/components/segmentation_platform/public/segmentation_platform_service.h
index e6c6d35..9f1302e 100644
--- a/components/segmentation_platform/public/segmentation_platform_service.h
+++ b/components/segmentation_platform/public/segmentation_platform_service.h
@@ -13,6 +13,7 @@
 #include "base/types/id_type.h"
 #include "build/build_config.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/segmentation_platform/public/result.h"
 
 #if BUILDFLAG(IS_ANDROID)
 #include "base/android/jni_android.h"
@@ -75,6 +76,16 @@
   virtual void GetSelectedSegment(const std::string& segmentation_key,
                                   SegmentSelectionCallback callback) = 0;
 
+  // Called to get the classification results for a given client. The
+  // classification config must be defined in the associated model metadata.
+  // Depending on the options and client config, it either runs the associated
+  // model or uses unexpired cached results.
+  virtual void GetClassificationResult(
+      const std::string& segmentation_key,
+      const PredictionOptions& prediction_options,
+      scoped_refptr<InputContext> input_context,
+      ClassificationResultCallback callback) = 0;
+
   // Called to get the selected segment synchronously. If none, returns empty
   // result.
   virtual SegmentSelectionResult GetCachedSegmentResult(
diff --git a/components/test/data/payments/payment_handler.js b/components/test/data/payments/payment_handler.js
index 7aeaddb..74452a2 100644
--- a/components/test/data/payments/payment_handler.js
+++ b/components/test/data/payments/payment_handler.js
@@ -42,12 +42,15 @@
 
 /**
  * Uninstalls the payment handler.
+ * @param {string} swSrcUrlOverride - Optional service worker JavaScript file
+ * URL.
  * @return {Promise<string>} - 'success' or error message on failure.
  */
-async function uninstall() {
+async function uninstall(swSrcUrlOverride) {
+  let swSrcUrl =
+      (swSrcUrlOverride !== undefined) ? swSrcUrlOverride : SW_SRC_URL;
   try {
-    let registration =
-        await navigator.serviceWorker.getRegistration(SW_SRC_URL);
+    let registration = await navigator.serviceWorker.getRegistration(swSrcUrl);
     if (!registration) {
       return 'The Payment handler has not been installed yet.';
     }
@@ -87,11 +90,14 @@
 
 /**
  * Launches the payment handler.
+ * @param {string} methodNameOverride - Optional payment method identifier.
  * @return {Promise<string>} - 'success' or error message on failure.
  */
-async function launch() {
+async function launch(methodNameOverride) {
+  let method =
+      (methodNameOverride !== undefined) ? methodNameOverride : methodName;
   try {
-    const request = new PaymentRequest([{supportedMethods: methodName}], {
+    const request = new PaymentRequest([{supportedMethods: method}], {
       total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
     });
     const response = await request.show();
diff --git a/components/test/data/payments/show_promise/app.json b/components/test/data/payments/show_promise/app.json
deleted file mode 100644
index 2d5b5798..0000000
--- a/components/test/data/payments/show_promise/app.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "name": "AppPay",
-  "icons": [{
-    "src": "icon.png",
-    "sizes": "48x48",
-    "type": "image/png"
-  }]
-}
\ No newline at end of file
diff --git a/components/test/data/payments/show_promise/app_installer.js b/components/test/data/payments/show_promise/app_installer.js
deleted file mode 100644
index fd91ed0..0000000
--- a/components/test/data/payments/show_promise/app_installer.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2019 The Chromium Authors
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**
- * Installs the payment handler.
- */
-function install() {
-  if (!navigator.serviceWorker) {
-    output('install()', 'ServiceWorker API not found.');
-    return;
-  }
-
-  navigator.serviceWorker.getRegistration('app.js')
-      .then((registration) => {
-        if (registration) {
-          output(
-              'serviceWorker.getRegistration()',
-              'The ServiceWorker is already installed.');
-          return;
-        }
-        navigator.serviceWorker.register('app.js')
-            .then(() => {
-              return navigator.serviceWorker.ready;
-            })
-            .then((registration) => {
-              if (!registration.paymentManager) {
-                output(
-                    'serviceWorker.register()',
-                    'PaymentManager API not found.');
-                return;
-              }
-
-              registration.paymentManager.instruments
-                  .set(
-                      '123456',
-                      {name: 'Echo Pay', method: window.location.href})
-                  .then(() => {
-                    output('instruments.set()', 'Payment handler installed.');
-                  })
-                  .catch((error) => {
-                    output('instruments.set()', error);
-                  });
-            })
-            .catch((error) => {
-              output('serviceWorker.register()', error);
-            });
-      })
-      .catch((error) => {
-        output('serviceWorker.getRegistration()', error);
-      });
-}
diff --git a/components/test/data/payments/show_promise/digital_goods.html b/components/test/data/payments/show_promise/digital_goods.html
index 5df8c1ce8..510a9f2 100644
--- a/components/test/data/payments/show_promise/digital_goods.html
+++ b/components/test/data/payments/show_promise/digital_goods.html
@@ -7,16 +7,13 @@
 <html lang="en">
 <head>
   <script src="../util.js"></script>
-  <script src="app_installer.js"></script>
   <script src="digital_goods.js"></script>
   <meta charset="utf-8">
-  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5"> 
+  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
   <title>Show Promise Test For Digital Goods</title>
   <link rel="stylesheet" type="text/css" href="../style.css">
-  <link rel="manifest" href="app.json">
 </head>
 <body>
-  <div><button onclick="create()" id="create">Create</button></div>
   <div><button onclick="buy()" id="buy">Buy</button></div>
   <pre id="result"></pre>
 </body>
diff --git a/components/test/data/payments/show_promise/digital_goods.js b/components/test/data/payments/show_promise/digital_goods.js
index c29e50d..41137562 100644
--- a/components/test/data/payments/show_promise/digital_goods.js
+++ b/components/test/data/payments/show_promise/digital_goods.js
@@ -8,12 +8,12 @@
 
 /**
  * Create an instance of PaymentRequest.
- * @param {DOMString} supportedMethods - The payment method name. If absent,
- * then the page URL is used instead.
+ * @param {DOMString} supportedMethods - The payment method name.
  */
 function create(supportedMethods) {
   if (!supportedMethods) {
-    supportedMethods = window.location.href;
+    print('supportedMethods required');
+    return;
   }
   try {
     request = new PaymentRequest([{supportedMethods}], {
diff --git a/components/test/data/payments/show_promise/helper.js b/components/test/data/payments/show_promise/helper.js
deleted file mode 100644
index fae5888..0000000
--- a/components/test/data/payments/show_promise/helper.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2021 The Chromium Authors
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-/**
- * Launch PaymentRequest with a show promise that resolves with an empty
- * dictionary. The payment method to be used is 'basic-card'.
- */
-function buy() {
-  buyWithMethods('basic-card');
-}
-
-/**
- * Launch PaymentRequest with a show promise that resolves with an empty
- * dictionary. The payment method to be used is the current url of the page.
- * @return {string} - The error message, if any.
- */
-async function buyWithCurrentUrlMethod() {
-  return buyWithMethods(window.location.href);
-}
-
-/**
- * Launch PaymentRequest with a show promise that resolves with an empty
- * dictionary. The payment method to be used is 'https://bobpay.test'.
- */
-function buyWithUrlMethod() {
-  buyWithMethods('https://bobpay.test');
-}
diff --git a/components/test/data/payments/show_promise/icon.png b/components/test/data/payments/show_promise/icon.png
deleted file mode 100644
index f10ab54..0000000
--- a/components/test/data/payments/show_promise/icon.png
+++ /dev/null
Binary files differ
diff --git a/components/test/data/payments/show_promise/invalid_details.html b/components/test/data/payments/show_promise/invalid_details.html
index 554e448..f4a2c35 100644
--- a/components/test/data/payments/show_promise/invalid_details.html
+++ b/components/test/data/payments/show_promise/invalid_details.html
@@ -7,17 +7,14 @@
 <html lang="en">
 <head>
   <script src="../util.js"></script>
-  <script src="helper.js"></script>
-  <script src="app_installer.js"></script>
   <script src="invalid_details.js"></script>
   <meta charset="utf-8">
-  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5"> 
+  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
   <title>Test for Show Promise with Invalid Details</title>
   <link rel="stylesheet" type="text/css" href="../style.css">
-  <link rel="manifest" href="app.json">
 </head>
 <body>
-  <div><button onclick="buy()" id="buy">Buy</button></div>
+  <div><button onclick="buy('https://example.test')" id="buy">Buy</button></div>
   <pre id="result"></pre>
 </body>
 </html>
\ No newline at end of file
diff --git a/components/test/data/payments/show_promise/invalid_details.js b/components/test/data/payments/show_promise/invalid_details.js
index 7b62efce..f516ae1 100644
--- a/components/test/data/payments/show_promise/invalid_details.js
+++ b/components/test/data/payments/show_promise/invalid_details.js
@@ -5,12 +5,16 @@
  */
 
 /**
- * Launch PaymentRequest with a show promise that resolve with invalid details.
- * @param {string} supportedMethods The payment method that is supported by this
- *        request.
+ * Launch PaymentRequest with a show promise that resolve with invalid details
+ * (a negative total amount).
+ * @param {string} supportedMethods The payment method identifier.
  * @return {string} - The error message, if any.
  */
-async function buyWithMethods(supportedMethods) {
+async function buy(supportedMethods) {
+  if (!supportedMethods) {
+    print('supportedMethods required');
+    return 'supportedMethods required';
+  }
   try {
     await new PaymentRequest([{supportedMethods}], {
       total: {
@@ -22,7 +26,7 @@
           resolve({
             total: {
               label: 'Total',
-              amount: {currency: 'USD', value: '-1.00'},
+              amount: {currency: 'USD', value: '-1.00'}, // -1.00 is not valid.
             },
           });
         }));
diff --git a/components/test/data/payments/show_promise/reject.html b/components/test/data/payments/show_promise/reject.html
index 74c83e3b..c9d8fc1 100644
--- a/components/test/data/payments/show_promise/reject.html
+++ b/components/test/data/payments/show_promise/reject.html
@@ -7,16 +7,14 @@
 <html lang="en">
 <head>
   <script src="../util.js"></script>
-  <script src="app_installer.js"></script>
   <script src="reject.js"></script>
   <meta charset="utf-8">
-  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5"> 
+  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
   <title>Test for Rejecting the Show Promise</title>
   <link rel="stylesheet" type="text/css" href="../style.css">
-  <link rel="manifest" href="app.json">
 </head>
 <body>
-  <div><button onclick="buy()" id="buy">Buy</button></div>
+  <div><button onclick="buy('https://example.test')" id="buy">Buy</button></div>
   <pre id="result"></pre>
 </body>
 </html>
\ No newline at end of file
diff --git a/components/test/data/payments/show_promise/reject.js b/components/test/data/payments/show_promise/reject.js
index 662bae9..9015d92e 100644
--- a/components/test/data/payments/show_promise/reject.js
+++ b/components/test/data/payments/show_promise/reject.js
@@ -6,17 +6,15 @@
 
 /**
  * Launch PaymentRequest with a show promise and reject that promise.
- * @param {boolean} useUrlPaymentMethod - Whether URL payment method should be
- * used. Useful for payment handlers, which cannot use basic-card payment
- * method. By default, basic-card payment method is used.
+ * @param {string} supportedMethods - The payment method identifier.
  * @return {string} - The error message, if any.
  */
-async function buy(useUrlPaymentMethod) {
+async function buy(supportedMethods) {
+  if (!supportedMethods) {
+    print('supportedMethods required');
+    return 'supportedMethods required';
+  }
   try {
-    let supportedMethods = 'basic-card';
-    if (useUrlPaymentMethod) {
-      supportedMethods = window.location.href;
-    }
     await new PaymentRequest(
         [{supportedMethods}],
         {total: {label: 'Total', amount: {currency: 'USD', value: '1.00'}}})
diff --git a/components/test/data/payments/show_promise/resolve_with_empty_dictionary.html b/components/test/data/payments/show_promise/resolve_with_empty_dictionary.html
index 25a3177f..44b69db 100644
--- a/components/test/data/payments/show_promise/resolve_with_empty_dictionary.html
+++ b/components/test/data/payments/show_promise/resolve_with_empty_dictionary.html
@@ -7,19 +7,14 @@
 <html lang="en">
 <head>
   <script src="../util.js"></script>
-  <script src="helper.js"></script>
-  <script src="app_installer.js"></script>
   <script src="resolve_with_empty_dictionary.js"></script>
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
   <title>Test For Show Promise That Resolves With Empty Dictionary</title>
   <link rel="stylesheet" type="text/css" href="../style.css">
-  <link rel="manifest" href="app.json">
 </head>
 <body>
-  <div><button onclick="buy()" id="buy">Buy</button></div>
-  <div><button onclick="buyWithCurrentUrlMethod()" id="buyWithCurrentUrlMethod">Buy using the current URL as the method</button></div>
-  <div><button onclick="buyWithUrlMethod()" id="buyWithUrlMethod">Buy with URL method</button></div>
+  <div><button onclick="buy('https://example.test')" id="buy">Buy</button></div>
   <pre id="result"></pre>
 </body>
 </html>
\ No newline at end of file
diff --git a/components/test/data/payments/show_promise/resolve_with_empty_dictionary.js b/components/test/data/payments/show_promise/resolve_with_empty_dictionary.js
index a948525..7374449 100644
--- a/components/test/data/payments/show_promise/resolve_with_empty_dictionary.js
+++ b/components/test/data/payments/show_promise/resolve_with_empty_dictionary.js
@@ -7,10 +7,13 @@
 /**
  * Launch PaymentRequest with a show promise that resolves with an empty
  * dictionary.
- * @param {string} supportedMethods The payment method that is supported by this
- *        request.
+ * @param {string} supportedMethods - The payment method identifier.
  */
-function buyWithMethods(supportedMethods) {
+function buy(supportedMethods) {
+  if (!supportedMethods) {
+    print('supportedMethods required');
+    return;
+  }
   try {
     var request = new PaymentRequest(
         [{supportedMethods}], {
diff --git a/components/test/data/payments/show_promise/resolve_with_empty_lists.html b/components/test/data/payments/show_promise/resolve_with_empty_lists.html
index 04b0006..2073cb43 100644
--- a/components/test/data/payments/show_promise/resolve_with_empty_lists.html
+++ b/components/test/data/payments/show_promise/resolve_with_empty_lists.html
@@ -7,17 +7,14 @@
 <html lang="en">
 <head>
   <script src="../util.js"></script>
-  <script src="helper.js"></script>
-  <script src="app_installer.js"></script>
   <script src="resolve_with_empty_lists.js"></script>
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
   <title>Test For Resolving The Show Promise With Empty Lists Items</title>
   <link rel="stylesheet" type="text/css" href="../style.css">
-  <link rel="manifest" href="app.json">
 </head>
 <body>
-  <div><button onclick="buy()" id="buy">Buy</button></div>
+  <div><button onclick="buy('https://example.test')" id="buy">Buy</button></div>
   <pre id="result"></pre>
 </body>
 </html>
\ No newline at end of file
diff --git a/components/test/data/payments/show_promise/resolve_with_empty_lists.js b/components/test/data/payments/show_promise/resolve_with_empty_lists.js
index 6fedd85..9da1940 100644
--- a/components/test/data/payments/show_promise/resolve_with_empty_lists.js
+++ b/components/test/data/payments/show_promise/resolve_with_empty_lists.js
@@ -5,14 +5,17 @@
  */
 
 /**
- * Launch PaymentRequest by resolving the promised passed into the shoe() method
+ * Launch PaymentRequest by resolving the promised passed into the show() method
  * with empty lists of display items, modifiers, and shipping options.
- * @param {string} supportedMethods The payment method that is supported by this
- *        request.
+ * @param {string} supportedMethods - The payment method identifier.
  */
-function buyWithMethods(supportedMethods) {
+function buy(supportedMethods) {
+  if (!supportedMethods) {
+    print('supportedMethods required');
+    return;
+  }
   try {
-    var request = new PaymentRequest(
+    const request = new PaymentRequest(
         [{supportedMethods}], {
           total: {label: 'Total', amount: {currency: 'USD', value: '1.00'}},
           displayItems: [{
@@ -21,7 +24,7 @@
             amount: {currency: 'USD', value: '99.99'},
           }],
           modifiers: [{
-            supportedMethods: 'basic-card',
+            supportedMethods,
             additionalDisplayItems: [{
               label: 'PENDING ADDITIONAL DISPLAY ITEM',
               pending: true,
diff --git a/components/test/data/payments/show_promise/single_option_shipping.html b/components/test/data/payments/show_promise/single_option_shipping.html
index 691af84..d7aac7a 100644
--- a/components/test/data/payments/show_promise/single_option_shipping.html
+++ b/components/test/data/payments/show_promise/single_option_shipping.html
@@ -7,17 +7,14 @@
 <html lang="en">
 <head>
   <script src="../util.js"></script>
-  <script src="helper.js"></script>
-  <script src="app_installer.js"></script>
   <script src="single_option_shipping.js"></script>
   <meta charset="utf-8">
-  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5"> 
+  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
   <title>Show Promise Test For Single-Option Shipping</title>
   <link rel="stylesheet" type="text/css" href="../style.css">
-  <link rel="manifest" href="app.json">
 </head>
 <body>
-  <div><button onclick="buy()" id="buy">Buy</button></div>
+  <div><button onclick="buy('https://example.test')" id="buy">Buy</button></div>
   <pre id="result"></pre>
 </body>
 </html>
\ No newline at end of file
diff --git a/components/test/data/payments/show_promise/single_option_shipping.js b/components/test/data/payments/show_promise/single_option_shipping.js
index f9b7d2fc..2320d1e9 100644
--- a/components/test/data/payments/show_promise/single_option_shipping.js
+++ b/components/test/data/payments/show_promise/single_option_shipping.js
@@ -7,10 +7,13 @@
 /**
  * Launch PaymentRequest with a show promise and a single pre-selected option
  * for shipping worldwide.
- * @param {string} supportedMethods The payment method that is supported by this
- *        request.
+ * @param {string} supportedMethods - The payment method identifier.
  */
-function buyWithMethods(supportedMethods) {
+function buy(supportedMethods) {
+  if (!supportedMethods) {
+    print('supportedMethods required');
+    return;
+  }
   try {
     new PaymentRequest(
         [{supportedMethods}],
diff --git a/components/test/data/payments/show_promise/single_option_shipping_with_update.html b/components/test/data/payments/show_promise/single_option_shipping_with_update.html
index b01e6f3..2233f88 100644
--- a/components/test/data/payments/show_promise/single_option_shipping_with_update.html
+++ b/components/test/data/payments/show_promise/single_option_shipping_with_update.html
@@ -7,17 +7,14 @@
 <html lang="en">
 <head>
   <script src="../util.js"></script>
-  <script src="helper.js"></script>
-  <script src="app_installer.js"></script>
   <script src="single_option_shipping_with_update.js"></script>
   <meta charset="utf-8">
-  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5"> 
+  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
   <title>Show Promise Test For Single-Option Shipping With Update</title>
   <link rel="stylesheet" type="text/css" href="../style.css">
-  <link rel="manifest" href="app.json">
 </head>
 <body>
-  <div><button onclick="buy()" id="buy">Buy</button></div>
+  <div><button onclick="buy('https://example.test')" id="buy">Buy</button></div>
   <pre id="result"></pre>
 </body>
-</html> 
\ No newline at end of file
+</html>
\ No newline at end of file
diff --git a/components/test/data/payments/show_promise/single_option_shipping_with_update.js b/components/test/data/payments/show_promise/single_option_shipping_with_update.js
index d8040aff..7fd9ed6 100644
--- a/components/test/data/payments/show_promise/single_option_shipping_with_update.js
+++ b/components/test/data/payments/show_promise/single_option_shipping_with_update.js
@@ -8,10 +8,9 @@
  * Launch PaymentRequest with a show promise and a single pre-selected option
  * for shipping worldwide and a handler for shipping address change events that
  * does not change anything.
- * @param {string} supportedMethods The payment method that is supported by this
- *        request.
+ * @param {string} supportedMethods - The payment method identifier.
  */
-function buyWithMethods(supportedMethods) {
+function buy(supportedMethods) {
   var finalizedDetails = {
     total: {label: 'Total', amount: {currency: 'USD', value: '1.00'}},
     shippingOptions: [{
diff --git a/components/test/data/payments/show_promise/timeout.html b/components/test/data/payments/show_promise/timeout.html
index 10b25f78..17b50af4 100644
--- a/components/test/data/payments/show_promise/timeout.html
+++ b/components/test/data/payments/show_promise/timeout.html
@@ -7,16 +7,14 @@
 <html lang="en">
 <head>
   <script src="../util.js"></script>
-  <script src="app_installer.js"></script>
   <script src="timeout.js"></script>
   <meta charset="utf-8">
-  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5"> 
+  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
   <title>Test for Timing Out the Show Promise</title>
   <link rel="stylesheet" type="text/css" href="../style.css">
-  <link rel="manifest" href="app.json">
 </head>
 <body>
-  <div><button onclick="buy()" id="buy">Buy</button></div>
+  <div><button onclick="buy('https://example.test')" id="buy">Buy</button></div>
   <pre id="result"></pre>
 </body>
 </html>
\ No newline at end of file
diff --git a/components/test/data/payments/show_promise/timeout.js b/components/test/data/payments/show_promise/timeout.js
index edb35535..640acce 100644
--- a/components/test/data/payments/show_promise/timeout.js
+++ b/components/test/data/payments/show_promise/timeout.js
@@ -6,12 +6,17 @@
 
 /**
  * Launch PaymentRequest with a show promise and don't resolve or reject it.
+ * @param {string} supportedMethods - The payment method identifier.
  * @return {string} - The error message, if any.
  */
-async function buy() {
+async function buy(supportedMethods) {
+  if (!supportedMethods) {
+    print('supportedMethods required');
+    return 'supportedMethods required';
+  }
   try {
     await new PaymentRequest(
-        [{supportedMethods: window.location.href}],
+        [{supportedMethods: window.location.origin}],
         {total: {label: 'Total', amount: {currency: 'USD', value: '1.00'}}})
         .show(new Promise(function(resolve) { /* Intentionally empty. */ }));
   } catch (error) {
diff --git a/components/test/data/payments/show_promise/unsupported.html b/components/test/data/payments/show_promise/unsupported.html
index 5b374ccf..54bc3eb 100644
--- a/components/test/data/payments/show_promise/unsupported.html
+++ b/components/test/data/payments/show_promise/unsupported.html
@@ -9,10 +9,9 @@
   <script src="../util.js"></script>
   <script src="unsupported.js"></script>
   <meta charset="utf-8">
-  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5"> 
+  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
   <title>Test for Show Promise with Unsupported Payment Method Identifier</title>
   <link rel="stylesheet" type="text/css" href="../style.css">
-  <link rel="manifest" href="app.json">
 </head>
 <body>
   <div><button onclick="buy()" id="buy">Buy</button></div>
diff --git a/components/test/data/payments/show_promise/us_only_shipping.html b/components/test/data/payments/show_promise/us_only_shipping.html
index e36040a..63956e2 100644
--- a/components/test/data/payments/show_promise/us_only_shipping.html
+++ b/components/test/data/payments/show_promise/us_only_shipping.html
@@ -7,17 +7,14 @@
 <html lang="en">
 <head>
   <script src="../util.js"></script>
-  <script src="helper.js"></script>
-  <script src="app_installer.js"></script>
   <script src="us_only_shipping.js"></script>
   <meta charset="utf-8">
-  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5"> 
+  <meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
   <title>Show Promise Test For US-Only Shipping</title>
   <link rel="stylesheet" type="text/css" href="../style.css">
-  <link rel="manifest" href="app.json">
 </head>
 <body>
-  <div><button onclick="buy()" id="buy">Buy</button></div>
+  <div><button onclick="buy('https://example.test')" id="buy">Buy</button></div>
   <pre id="result"></pre>
 </body>
 </html>
\ No newline at end of file
diff --git a/components/test/data/payments/show_promise/us_only_shipping.js b/components/test/data/payments/show_promise/us_only_shipping.js
index 4979bd74..05aa70a4 100644
--- a/components/test/data/payments/show_promise/us_only_shipping.js
+++ b/components/test/data/payments/show_promise/us_only_shipping.js
@@ -6,10 +6,13 @@
 
 /**
  * Launch PaymentRequest with a show promise and US-only shipping.
- * @param {string} supportedMethods The payment method that is supported by this
- *        request.
+ * @param {string} supportedMethods - The payment method identifier.
  */
-function buyWithMethods(supportedMethods) {
+function buy(supportedMethods) {
+  if (!supportedMethods) {
+    print('supportedMethods required');
+    return;
+  }
   var detailsForUSAddress = {
     shippingOptions: [{
       id: '1',
diff --git a/components/viz/common/resources/shared_image_format.h b/components/viz/common/resources/shared_image_format.h
index c9988bd..347ca1f 100644
--- a/components/viz/common/resources/shared_image_format.h
+++ b/components/viz/common/resources/shared_image_format.h
@@ -63,6 +63,7 @@
   static const SharedImageFormat kRGBA_8888;
   static const SharedImageFormat kBGRA_8888;
   static const SharedImageFormat kRGBA_F16;
+  static const SharedImageFormat kBGR_565;
 
   SharedImageFormat() = default;
   static constexpr SharedImageFormat SinglePlane(
@@ -200,6 +201,8 @@
     SharedImageFormat::SinglePlane(ResourceFormat::BGRA_8888);
 constexpr SharedImageFormat SharedImageFormat::kRGBA_F16 =
     SharedImageFormat::SinglePlane(ResourceFormat::RGBA_F16);
+constexpr SharedImageFormat SharedImageFormat::kBGR_565 =
+    SharedImageFormat::SinglePlane(ResourceFormat::BGR_565);
 
 }  // namespace viz
 
diff --git a/components/viz/service/display_embedder/skia_output_device_gl.cc b/components/viz/service/display_embedder/skia_output_device_gl.cc
index ded60f6..4d778a1 100644
--- a/components/viz/service/display_embedder/skia_output_device_gl.cc
+++ b/components/viz/service/display_embedder/skia_output_device_gl.cc
@@ -310,23 +310,6 @@
   FinishSwapBuffers(std::move(result), size, std::move(frame));
 }
 
-bool SkiaOutputDeviceGL::SetDrawRectangle(const gfx::Rect& draw_rectangle) {
-  return gl_surface_->SetDrawRectangle(draw_rectangle);
-}
-
-void SkiaOutputDeviceGL::SetGpuVSyncEnabled(bool enabled) {
-  gl_surface_->SetGpuVSyncEnabled(enabled);
-}
-
-void SkiaOutputDeviceGL::SetEnableDCLayers(bool enable) {
-  gl_surface_->SetEnableDCLayers(enable);
-}
-
-void SkiaOutputDeviceGL::ScheduleOverlays(
-    SkiaOutputSurface::OverlayList overlays) {
-  NOTREACHED();
-}
-
 void SkiaOutputDeviceGL::EnsureBackbuffer() {
   gl_surface_->SetBackbufferAllocation(true);
 }
diff --git a/components/viz/service/display_embedder/skia_output_device_gl.h b/components/viz/service/display_embedder/skia_output_device_gl.h
index 0f75abb..c4abfef5 100644
--- a/components/viz/service/display_embedder/skia_output_device_gl.h
+++ b/components/viz/service/display_embedder/skia_output_device_gl.h
@@ -54,10 +54,6 @@
                      OutputSurfaceFrame frame) override;
   void CommitOverlayPlanes(BufferPresentedCallback feedback,
                            OutputSurfaceFrame frame) override;
-  bool SetDrawRectangle(const gfx::Rect& draw_rectangle) override;
-  void SetGpuVSyncEnabled(bool enabled) override;
-  void SetEnableDCLayers(bool enable) override;
-  void ScheduleOverlays(SkiaOutputSurface::OverlayList overlays) override;
   void EnsureBackbuffer() override;
   void DiscardBackbuffer() override;
   SkSurface* BeginPaint(
@@ -67,8 +63,7 @@
  private:
   class OverlayData;
 
-  // Use instead of calling FinishSwapBuffers() directly. On Windows this cleans
-  // up old entries in |overlays_|.
+  // Use instead of calling FinishSwapBuffers() directly.
   void DoFinishSwapBuffers(const gfx::Size& size,
                            OutputSurfaceFrame frame,
                            gfx::SwapCompletionResult result);
@@ -78,9 +73,6 @@
                                 OutputSurfaceFrame frame,
                                 gfx::SwapCompletionResult result);
 
-  gpu::OverlayImageRepresentation::ScopedReadAccess* BeginOverlayAccess(
-      const gpu::Mailbox& mailbox);
-
   void CreateSkSurface();
 
   const raw_ptr<gpu::SharedContextState> context_state_;
diff --git a/components/viz/test/test_raster_interface.h b/components/viz/test/test_raster_interface.h
index b31d8f7b..f51d2be6 100644
--- a/components/viz/test/test_raster_interface.h
+++ b/components/viz/test/test_raster_interface.h
@@ -115,8 +115,7 @@
                       const gfx::Vector2dF& post_translate,
                       const gfx::Vector2dF& post_scale,
                       bool requires_clear,
-                      size_t* max_op_size_hint,
-                      bool preserve_recording = true) override {}
+                      size_t* max_op_size_hint) override {}
   void EndRasterCHROMIUM() override {}
   gpu::SyncToken ScheduleImageDecode(base::span<const uint8_t> encoded_data,
                                      const gfx::Size& output_size,
diff --git a/content/DEPS b/content/DEPS
index 303257d..9083535a 100644
--- a/content/DEPS
+++ b/content/DEPS
@@ -24,6 +24,7 @@
   # as autofill or extensions, and chrome implementation details such as
   # settings, packaging details, installation or crash reporting.
 
+  "+components/attribution_reporting",
   "+components/browsing_topics/common",
   "+components/memory_pressure",
   "+components/ml/mojom",
diff --git a/content/browser/attribution_reporting/DEPS b/content/browser/attribution_reporting/DEPS
index 88b90fbe..4437dd3f9 100644
--- a/content/browser/attribution_reporting/DEPS
+++ b/content/browser/attribution_reporting/DEPS
@@ -1,4 +1,3 @@
 include_rules = [
   "+components/aggregation_service",
-  "+components/attribution_reporting",
 ]
diff --git a/content/browser/attribution_reporting/attribution_manager.cc b/content/browser/attribution_reporting/attribution_manager.cc
index e73f1ec..afb7d30 100644
--- a/content/browser/attribution_reporting/attribution_manager.cc
+++ b/content/browser/attribution_reporting/attribution_manager.cc
@@ -5,12 +5,12 @@
 #include "content/browser/attribution_reporting/attribution_manager.h"
 
 #include "base/check.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "content/browser/attribution_reporting/attribution_manager_impl.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom.h"
 
 namespace content {
 
@@ -24,7 +24,7 @@
 }
 
 // static
-blink::mojom::AttributionOsSupport AttributionManager::GetOsSupport() {
+attribution_reporting::mojom::OsSupport AttributionManager::GetOsSupport() {
   return AttributionManagerImpl::GetOsSupport();
 }
 
diff --git a/content/browser/attribution_reporting/attribution_manager.h b/content/browser/attribution_reporting/attribution_manager.h
index ffce3c87..e981453 100644
--- a/content/browser/attribution_reporting/attribution_manager.h
+++ b/content/browser/attribution_reporting/attribution_manager.h
@@ -9,10 +9,10 @@
 #include <vector>
 
 #include "base/callback_forward.h"
+#include "components/attribution_reporting/os_support.mojom-forward.h"
 #include "components/attribution_reporting/source_registration_error.mojom-forward.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/public/browser/storage_partition.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom-forward.h"
 
 namespace attribution_reporting {
 class SuitableOrigin;
@@ -38,7 +38,7 @@
  public:
   static AttributionManager* FromWebContents(WebContents* web_contents);
 
-  static blink::mojom::AttributionOsSupport GetOsSupport();
+  static attribution_reporting::mojom::OsSupport GetOsSupport();
 
   virtual ~AttributionManager() = default;
 
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc
index 230fd641..367e189 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl.cc
@@ -29,6 +29,7 @@
 #include "base/threading/sequence_bound.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "components/attribution_reporting/source_registration_error.mojom.h"
 #include "components/attribution_reporting/suitable_origin.h"
 #include "components/attribution_reporting/trigger_registration.h"
@@ -68,7 +69,6 @@
 #include "storage/browser/quota/special_storage_policy.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom.h"
 #include "url/gurl.h"
 
 namespace content {
@@ -347,7 +347,7 @@
 }
 
 ScopedOsSupportForTesting::ScopedOsSupportForTesting(
-    blink::mojom::AttributionOsSupport os_support)
+    attribution_reporting::mojom::OsSupport os_support)
     : previous_(AttributionManagerImpl::g_os_support_) {
   AttributionManagerImpl::SetOsSupportForTesting(os_support);
 }
@@ -357,8 +357,8 @@
 }
 
 // static
-blink::mojom::AttributionOsSupport AttributionManagerImpl::g_os_support_ =
-    blink::mojom::AttributionOsSupport::kDisabled;
+attribution_reporting::mojom::OsSupport AttributionManagerImpl::g_os_support_ =
+    attribution_reporting::mojom::OsSupport::kDisabled;
 
 // static
 std::unique_ptr<AttributionManagerImpl>
@@ -408,7 +408,7 @@
 
 // static
 void AttributionManagerImpl::SetOsSupportForTesting(
-    blink::mojom::AttributionOsSupport os_support) {
+    attribution_reporting::mojom::OsSupport os_support) {
   g_os_support_ = os_support;
 
   for (RenderProcessHost::iterator it = RenderProcessHost::AllHostsIterator();
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.h b/content/browser/attribution_reporting/attribution_manager_impl.h
index bec5d8a..d348cdb5 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.h
+++ b/content/browser/attribution_reporting/attribution_manager_impl.h
@@ -18,6 +18,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/threading/sequence_bound.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "components/attribution_reporting/source_registration_error.mojom-forward.h"
 #include "content/browser/aggregation_service/aggregation_service.h"
 #include "content/browser/aggregation_service/report_scheduler_timer.h"
@@ -29,7 +30,6 @@
 #include "content/public/browser/storage_partition.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom.h"
 
 namespace attribution_reporting {
 class SuitableOrigin;
@@ -91,7 +91,7 @@
 
   class CONTENT_EXPORT ScopedOsSupportForTesting {
    public:
-    explicit ScopedOsSupportForTesting(blink::mojom::AttributionOsSupport);
+    explicit ScopedOsSupportForTesting(attribution_reporting::mojom::OsSupport);
     ~ScopedOsSupportForTesting();
 
     ScopedOsSupportForTesting(const ScopedOsSupportForTesting&) = delete;
@@ -102,7 +102,7 @@
     ScopedOsSupportForTesting& operator=(ScopedOsSupportForTesting&&) = delete;
 
    private:
-    const blink::mojom::AttributionOsSupport previous_;
+    const attribution_reporting::mojom::OsSupport previous_;
   };
 
   static std::unique_ptr<AttributionManagerImpl> CreateForTesting(
@@ -122,7 +122,7 @@
 
   // Returns whether OS-level attribution is enabled. `kDisabled` is returned
   // before the result is returned from the underlying platform (e.g. Android).
-  static blink::mojom::AttributionOsSupport GetOsSupport() {
+  static attribution_reporting::mojom::OsSupport GetOsSupport() {
     return g_os_support_;
   }
 
@@ -166,11 +166,11 @@
   friend class AttributionManagerImplTest;
 
   static void SetOsSupportForTesting(
-      blink::mojom::AttributionOsSupport os_support);
+      attribution_reporting::mojom::OsSupport os_support);
 
   // TODO(crbug.com/1373536): The OS-level support should be derived from the
   // underlying platform (e.g. Android).
-  static blink::mojom::AttributionOsSupport g_os_support_;
+  static attribution_reporting::mojom::OsSupport g_os_support_;
 
   using ReportSentCallback = AttributionReportSender::ReportSentCallback;
   using SourceOrTrigger = absl::variant<StorableSource, AttributionTrigger>;
diff --git a/content/browser/attribution_reporting/attribution_src_browsertest.cc b/content/browser/attribution_reporting/attribution_src_browsertest.cc
index 3811e795..4927c613 100644
--- a/content/browser/attribution_reporting/attribution_src_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_src_browsertest.cc
@@ -14,6 +14,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "components/aggregation_service/aggregation_service.mojom.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "components/attribution_reporting/source_registration.h"
 #include "components/attribution_reporting/suitable_origin.h"
 #include "components/attribution_reporting/test_utils.h"
@@ -41,7 +42,6 @@
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/navigation/impression.h"
 #include "third_party/blink/public/mojom/conversions/attribution_data_host.mojom.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom.h"
 #include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom.h"
 #include "url/gurl.h"
 
@@ -1341,7 +1341,7 @@
   ASSERT_TRUE(https_server->Start());
 
   AttributionManagerImpl::ScopedOsSupportForTesting scoped_os_support_setting(
-      blink::mojom::AttributionOsSupport::kEnabled);
+      attribution_reporting::mojom::OsSupport::kEnabled);
 
   GURL page_url =
       https_server->GetURL("b.test", "/page_with_impression_creator.html");
@@ -1395,7 +1395,7 @@
   ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
 
   AttributionManagerImpl::ScopedOsSupportForTesting scoped_os_support_setting(
-      blink::mojom::AttributionOsSupport::kEnabled);
+      attribution_reporting::mojom::OsSupport::kEnabled);
 
   GURL register_url = https_server->GetURL("d.test", "/register_source1");
   ASSERT_TRUE(ExecJs(web_contents(),
diff --git a/content/browser/attribution_reporting/attributions_browsertest.cc b/content/browser/attribution_reporting/attributions_browsertest.cc
index 42e311e..7fc4cef9 100644
--- a/content/browser/attribution_reporting/attributions_browsertest.cc
+++ b/content/browser/attribution_reporting/attributions_browsertest.cc
@@ -17,6 +17,7 @@
 #include "base/time/time.h"
 #include "base/values.h"
 #include "build/buildflag.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "content/browser/attribution_reporting/attribution_manager.h"
 #include "content/browser/attribution_reporting/attribution_manager_impl.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
@@ -52,7 +53,6 @@
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_registration_options.mojom.h"
 #include "url/gurl.h"
 
@@ -1573,7 +1573,7 @@
   EXPECT_TRUE(NavigateToURL(web_contents(), impression_url));
 
   AttributionManagerImpl::ScopedOsSupportForTesting scoped_os_support_setting(
-      blink::mojom::AttributionOsSupport::kEnabled);
+      attribution_reporting::mojom::OsSupport::kEnabled);
 
   GURL register_source_url =
       https_server()->GetURL("d.test", "/register_source_redirect");
diff --git a/content/browser/preloading/prefetch/prefetch_container.cc b/content/browser/preloading/prefetch/prefetch_container.cc
index 945144c..fc27861 100644
--- a/content/browser/preloading/prefetch/prefetch_container.cc
+++ b/content/browser/preloading/prefetch/prefetch_container.cc
@@ -6,8 +6,8 @@
 
 #include <memory>
 
-#include "base/callback.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/notreached.h"
 #include "base/time/time.h"
 #include "content/browser/preloading/prefetch/prefetch_cookie_listener.h"
 #include "content/browser/preloading/prefetch/prefetch_document_manager.h"
@@ -21,7 +21,10 @@
 #include "content/browser/preloading/prefetch/prefetch_type.h"
 #include "content/browser/preloading/prefetch/prefetched_mainframe_response_container.h"
 #include "content/browser/preloading/prefetch/proxy_lookup_client_impl.h"
+#include "content/browser/preloading/preloading.h"
+#include "content/browser/preloading/preloading_data_impl.h"
 #include "content/public/browser/global_routing_id.h"
+#include "content/public/browser/web_contents.h"
 #include "services/metrics/public/cpp/metrics_utils.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
@@ -50,6 +53,118 @@
       base::Seconds(5), 50);
 }
 
+static_assert(
+    static_cast<int>(PrefetchStatus::kMaxValue) +
+        static_cast<int>(
+            PreloadingEligibility::kPreloadingEligibilityCommonEnd) <=
+    static_cast<int>(PreloadingEligibility::kPreloadingEligibilityContentEnd));
+
+PreloadingEligibility ToPreloadingEligibility(PrefetchStatus status) {
+  if (status == PrefetchStatus::kPrefetchNotEligibleDataSaverEnabled)
+    return PreloadingEligibility::kDataSaverEnabled;
+  return static_cast<PreloadingEligibility>(
+      static_cast<int>(status) +
+      static_cast<int>(PreloadingEligibility::kPreloadingEligibilityCommonEnd));
+}
+
+// Please follow go/preloading-dashboard-updates if a new eligibility is added.
+void SetIneligibilityFromStatus(PreloadingAttempt* attempt,
+                                PrefetchStatus status) {
+  if (attempt) {
+    switch (status) {
+      case PrefetchStatus::kPrefetchNotEligibleBrowserContextOffTheRecord:
+      case PrefetchStatus::kPrefetchNotEligibleDataSaverEnabled:
+      case PrefetchStatus::kPrefetchNotEligibleHostIsNonUnique:
+      case PrefetchStatus::kPrefetchNotEligibleSchemeIsNotHttps:
+      case PrefetchStatus::kPrefetchProxyNotAvailable:
+      case PrefetchStatus::kPrefetchNotEligibleNonDefaultStoragePartition:
+      case PrefetchStatus::kPrefetchIneligibleRetryAfter:
+      case PrefetchStatus::kPrefetchNotEligibleUserHasServiceWorker:
+      case PrefetchStatus::kPrefetchNotEligibleUserHasCookies:
+      case PrefetchStatus::kPrefetchNotEligibleExistingProxy:
+        attempt->SetEligibility(ToPreloadingEligibility(status));
+        break;
+      default:
+        NOTIMPLEMENTED();
+    }
+  }
+}
+
+static_assert(
+    static_cast<int>(PrefetchStatus::kMaxValue) +
+        static_cast<int>(
+            PreloadingFailureReason::kPreloadingFailureReasonCommonEnd) <=
+    static_cast<int>(
+        PreloadingFailureReason::kPreloadingFailureReasonContentEnd));
+
+PreloadingFailureReason ToPreloadingFailureReason(PrefetchStatus status) {
+  return static_cast<PreloadingFailureReason>(
+      static_cast<int>(status) +
+      static_cast<int>(
+          PreloadingFailureReason::kPreloadingFailureReasonCommonEnd));
+}
+
+// Please follow go/preloading-dashboard-updates if a new outcome enum or a
+// failure reason enum is added.
+void SetTriggeringOutcomeAndFailureReasonFromStatus(PreloadingAttempt* attempt,
+                                                    PrefetchStatus status) {
+  if (attempt) {
+    switch (status) {
+      case PrefetchStatus::kPrefetchNotFinishedInTime:
+        attempt->SetTriggeringOutcome(PreloadingTriggeringOutcome::kRunning);
+        break;
+      case PrefetchStatus::kPrefetchSuccessful:
+        // A successful prefetch means the response is ready to be used for the
+        // next navigation.
+        attempt->SetTriggeringOutcome(PreloadingTriggeringOutcome::kReady);
+        break;
+      case PrefetchStatus::kPrefetchResponseUsed:
+        attempt->SetTriggeringOutcome(PreloadingTriggeringOutcome::kSuccess);
+        break;
+      // A decoy is considered eligible because a network request is made for
+      // it. It is considered as a failure as the final response is never
+      // served.
+      case PrefetchStatus::kPrefetchIsPrivacyDecoy:
+      case PrefetchStatus::kPrefetchFailedRedirectsDisabled:
+      case PrefetchStatus::kPrefetchFailedNetError:
+      case PrefetchStatus::kPrefetchFailedNon2XX:
+      case PrefetchStatus::kPrefetchFailedMIMENotSupported:
+        attempt->SetFailureReason(ToPreloadingFailureReason(status));
+        break;
+      case PrefetchStatus::kPrefetchHeldback:
+      // `kPrefetchAllowed` will soon transition into `kPrefetchNotStarted`.
+      case PrefetchStatus::kPrefetchAllowed:
+      case PrefetchStatus::kPrefetchNotStarted:
+        // `kPrefetchNotStarted` is set in
+        // `PrefetchService::OnGotEligibilityResult` when the container is
+        // pushed onto the prefetch queue, which occurs before the holdback
+        // status is determined in `PrefetchService::StartSinglePrefetch`.
+        // After the container is queued and before it is sent for prefetch, the
+        // only status change is when the container is popped from the queue but
+        // heldback. This is covered by attempt's holdback status. For these two
+        // reasons this PrefetchStatus does not fire a `SetTriggeringOutcome`.
+        break;
+      default:
+        NOTIMPLEMENTED();
+    }
+  }
+}
+
+void SetHoldbackFromStatus(PreloadingAttempt* attempt, PrefetchStatus status) {
+  if (attempt) {
+    switch (status) {
+      case PrefetchStatus::kPrefetchAllowed:
+        attempt->SetHoldbackStatus(PreloadingHoldbackStatus::kAllowed);
+        break;
+      case PrefetchStatus::kPrefetchHeldback:
+        attempt->SetHoldbackStatus(PreloadingHoldbackStatus::kHoldback);
+        break;
+      default:
+        break;
+    }
+  }
+}
+
 }  // namespace
 
 PrefetchContainer::PrefetchContainer(
@@ -67,7 +182,20 @@
                          ? prefetch_document_manager_->render_frame_host()
                                .GetPageUkmSourceId()
                          : ukm::kInvalidSourceId),
-      request_id_(base::UnguessableToken::Create().ToString()) {}
+      request_id_(base::UnguessableToken::Create().ToString()) {
+  auto* rfh = RenderFrameHost::FromID(referring_render_frame_host_id_);
+  if (rfh) {
+    auto* preloading_data = PreloadingData::GetOrCreateForWebContents(
+        WebContents::FromRenderFrameHost(rfh));
+    auto* attempt = preloading_data->AddPreloadingAttempt(
+        ToPreloadingPredictor(ContentPreloadingPredictor::kSpeculationRules),
+        PreloadingType::kPrefetch,
+        PreloadingDataImpl::GetSameURLAndNoVarySearchURLMatcher(
+            prefetch_document_manager_, url_));
+    attempt_ = attempt->GetWeakPtr();
+    // `PreloadingPrediction` is added in `PreloadingDecider`.
+  }
+}
 
 PrefetchContainer::~PrefetchContainer() {
   ukm::builders::PrefetchProxy_PrefetchedResource builder(ukm_source_id_);
@@ -96,6 +224,13 @@
   builder.Record(ukm::UkmRecorder::Get());
 }
 
+void PrefetchContainer::SetPrefetchStatus(PrefetchStatus prefetch_status) {
+  prefetch_status_ = prefetch_status;
+  SetHoldbackFromStatus(attempt_.get(), prefetch_status);
+  SetTriggeringOutcomeAndFailureReasonFromStatus(attempt_.get(),
+                                                 prefetch_status);
+}
+
 PrefetchStatus PrefetchContainer::GetPrefetchStatus() const {
   DCHECK(prefetch_status_);
   return prefetch_status_.value();
@@ -127,8 +262,18 @@
   return prefetch_document_manager_.get();
 }
 
-void PrefetchContainer::OnEligibilityCheckComplete(bool is_eligible) {
+void PrefetchContainer::OnEligibilityCheckComplete(
+    bool is_eligible,
+    absl::optional<PrefetchStatus> status) {
   is_eligible_ = is_eligible;
+  if (!is_eligible_) {
+    // Expect a reason (status) if not eligible.
+    DCHECK(status.has_value());
+    prefetch_status_ = status;
+    SetIneligibilityFromStatus(attempt_.get(), prefetch_status_.value());
+  } else if (attempt_) {
+    attempt_->SetEligibility(PreloadingEligibility::kEligible);
+  }
   prefetch_document_manager_->OnEligibilityCheckComplete(is_eligible);
 }
 
@@ -257,12 +402,14 @@
       break;
     case PrefetchProbeResult::kDNSProbeSuccess:
     case PrefetchProbeResult::kTLSProbeSuccess:
-      prefetch_status_ = PrefetchStatus::kPrefetchUsedProbeSuccess;
+      SetPrefetchStatus(PrefetchStatus::kPrefetchResponseUsed);
       break;
     case PrefetchProbeResult::kDNSProbeFailure:
     case PrefetchProbeResult::kTLSProbeFailure:
       prefetch_status_ = PrefetchStatus::kPrefetchNotUsedProbeFailed;
       break;
+    default:
+      NOTIMPLEMENTED();
   }
 }
 
@@ -378,4 +525,11 @@
   }
 }
 
+void PrefetchContainer::SimulateAttemptAtInterceptorForTest() {
+  if (attempt_)
+    attempt_->SetEligibility(PreloadingEligibility::kEligible);
+  SetPrefetchStatus(PrefetchStatus::kPrefetchAllowed);
+  SetPrefetchStatus(PrefetchStatus::kPrefetchSuccessful);
+}
+
 }  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_container.h b/content/browser/preloading/prefetch/prefetch_container.h
index f5b9686..ae9b270 100644
--- a/content/browser/preloading/prefetch/prefetch_container.h
+++ b/content/browser/preloading/prefetch/prefetch_container.h
@@ -7,7 +7,6 @@
 
 #include <utility>
 
-#include "base/callback.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "content/browser/preloading/prefetch/prefetch_probe_result.h"
@@ -36,6 +35,7 @@
 class PrefetchServingPageMetricsContainer;
 class PrefetchStreamingURLLoader;
 class PrefetchedMainframeResponseContainer;
+class PreloadingAttempt;
 class ProxyLookupClientImpl;
 
 // Holds the relevant size information of the prefetched response. The struct is
@@ -87,10 +87,11 @@
   }
 
   // The status of the current prefetch. Note that |HasPrefetchStatus| will be
-  // initially false until |SetPrefetchStatus| is called.
-  void SetPrefetchStatus(PrefetchStatus prefetch_status) {
-    prefetch_status_ = prefetch_status;
-  }
+  // initially false until |SetPrefetchStatus| is called. |SetPrefetchStatus|
+  // also sets |attempt_| PreloadingHoldbackStatus, PreloadingTriggeringOutcome
+  // and PreloadingFailureReason. It is only safe to call after
+  // `OnEligibilityCheckComplete`.
+  void SetPrefetchStatus(PrefetchStatus prefetch_status);
   bool HasPrefetchStatus() const { return prefetch_status_.has_value(); }
   PrefetchStatus GetPrefetchStatus() const;
 
@@ -100,8 +101,9 @@
       std::unique_ptr<ProxyLookupClientImpl> proxy_lookup_client);
   std::unique_ptr<ProxyLookupClientImpl> ReleaseProxyLookupClient();
 
-  // Whether or not the prefetch was determined to be eligibile
-  void OnEligibilityCheckComplete(bool is_eligible);
+  // Whether or not the prefetch was determined to be eligibile.
+  void OnEligibilityCheckComplete(bool is_eligible,
+                                  absl::optional<PrefetchStatus> status);
   bool IsEligible() const { return is_eligible_; }
 
   // Whether this prefetch is a decoy. Decoy prefetches will not store the
@@ -225,6 +227,15 @@
     return prefetch_response_sizes_;
   }
 
+  bool HasPreloadingAttempt() { return !!attempt_; }
+
+  // Simulates a prefetch container that reaches the interceptor. It sets the
+  // `attempt_` to the correct state: `PreloadingEligibility::kEligible`,
+  // `PreloadingHoldbackStatus::kAllowed` and
+  // `PreloadingTriggeringOutcome::kReady`.
+  void SimulateAttemptAtInterceptorForTest();
+  void DisablePrecogLoggingForTest() { attempt_ = nullptr; }
+
  protected:
   friend class PrefetchContainerTest;
 
@@ -339,6 +350,14 @@
   // Weak pointer to DevTools observer
   base::WeakPtr<SpeculationHostDevToolsObserver> devtools_observer_;
 
+  // `PreloadingAttempt` is used to track the lifecycle of the preloading event,
+  // and reports various statuses to UKM dashboard. It is initialised along with
+  // `this`, and destroyed when `WCO::DidFinishNavigation` is fired.
+  // `attempt_`'s eligibility is set in `OnEligibilityCheckComplete`, and its
+  // holdback status, triggering outcome and failure reason are set in
+  // `SetPrefetchStatus`.
+  base::WeakPtr<PreloadingAttempt> attempt_;
+
   base::WeakPtrFactory<PrefetchContainer> weak_method_factory_{this};
 };
 
diff --git a/content/browser/preloading/prefetch/prefetch_container_unittest.cc b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
index 4c0fa6a..bfa4820 100644
--- a/content/browser/preloading/prefetch/prefetch_container_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
@@ -307,7 +307,7 @@
       ukm_metrics.end());
   EXPECT_EQ(ukm_metrics.at(
                 ukm::builders::PrefetchProxy_PrefetchedResource::kStatusName),
-            static_cast<int>(PrefetchStatus::kPrefetchUsedProbeSuccess));
+            static_cast<int>(PrefetchStatus::kPrefetchResponseUsed));
 
   ASSERT_TRUE(
       ukm_metrics.find(
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager.cc b/content/browser/preloading/prefetch/prefetch_document_manager.cc
index a713c15..15e248e 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager.cc
@@ -226,6 +226,7 @@
 
   switch (container->GetPrefetchStatus()) {
     case PrefetchStatus::kPrefetchSuccessful:
+    case PrefetchStatus::kPrefetchResponseUsed:
       return false;
     case PrefetchStatus::kPrefetchNotEligibleUserHasCookies:
     case PrefetchStatus::kPrefetchNotEligibleUserHasServiceWorker:
@@ -239,7 +240,6 @@
     case PrefetchStatus::kPrefetchNotEligibleDataSaverEnabled:
     case PrefetchStatus::kPrefetchNotEligibleExistingProxy:
     case PrefetchStatus::kPrefetchUsedNoProbe:
-    case PrefetchStatus::kPrefetchUsedProbeSuccess:
     case PrefetchStatus::kPrefetchNotUsedProbeFailed:
     case PrefetchStatus::kPrefetchNotStarted:
     case PrefetchStatus::kPrefetchNotFinishedInTime:
@@ -264,6 +264,9 @@
     case PrefetchStatus::kPrefetchIsStaleNSPNotStarted:
     case PrefetchStatus::kPrefetchNotUsedCookiesChanged:
     case PrefetchStatus::kPrefetchFailedRedirectsDisabled:
+    case PrefetchStatus::kPrefetchNotEligibleBrowserContextOffTheRecord:
+    case PrefetchStatus::kPrefetchHeldback:
+    case PrefetchStatus::kPrefetchAllowed:
       return true;
   }
 }
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc b/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
index 4a9905b..718b3fbd 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
@@ -36,6 +36,7 @@
 
   void PrefetchUrl(
       base::WeakPtr<PrefetchContainer> prefetch_container) override {
+    prefetch_container->DisablePrecogLoggingForTest();
     prefetches_.push_back(prefetch_container);
   }
 
diff --git a/content/browser/preloading/prefetch/prefetch_params.cc b/content/browser/preloading/prefetch/prefetch_params.cc
index b7decd4..acbc289 100644
--- a/content/browser/preloading/prefetch/prefetch_params.cc
+++ b/content/browser/preloading/prefetch/prefetch_params.cc
@@ -222,4 +222,8 @@
       features::kPrefetchUseContentRefactor, "use_streaming_url_loader", true);
 }
 
+bool IsContentPrefetchHoldback() {
+  return base::GetFieldTrialParamByFeatureAsBool(
+      features::kPrefetchUseContentRefactor, "prefetch_holdback", false);
+}
 }  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_params.h b/content/browser/preloading/prefetch/prefetch_params.h
index 00b0e9b4..ef1371c 100644
--- a/content/browser/preloading/prefetch/prefetch_params.h
+++ b/content/browser/preloading/prefetch/prefetch_params.h
@@ -111,6 +111,9 @@
 // prefetches.
 bool PrefetchUseStreamingURLLoader();
 
+// Returns whether the client is involved in the Holdback Finch
+// experiment group.
+bool IsContentPrefetchHoldback();
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_PARAMS_H_
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index 99a73c6..1f5c162 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -21,6 +21,7 @@
 #include "content/browser/preloading/prefetch/prefetch_origin_prober.h"
 #include "content/browser/preloading/prefetch/prefetch_params.h"
 #include "content/browser/preloading/prefetch/prefetch_proxy_configurator.h"
+#include "content/browser/preloading/prefetch/prefetch_status.h"
 #include "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h"
 #include "content/browser/preloading/prefetch/prefetched_mainframe_response_container.h"
 #include "content/browser/preloading/prefetch/proxy_lookup_client_impl.h"
@@ -50,8 +51,6 @@
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/simple_url_loader.h"
 #include "services/network/public/mojom/cookie_manager.mojom.h"
-#include "services/network/public/mojom/cookie_partition_key.mojom.h"
-#include "services/network/public/mojom/fetch_api.mojom.h"
 #include "services/network/public/mojom/url_response_head.mojom-shared.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "url/gurl.h"
@@ -88,11 +87,11 @@
     case PrefetchStatus::kPrefetchNotEligibleHostIsNonUnique:
     case PrefetchStatus::kPrefetchNotEligibleDataSaverEnabled:
     case PrefetchStatus::kPrefetchNotEligibleExistingProxy:
+    case PrefetchStatus::kPrefetchNotEligibleBrowserContextOffTheRecord:
       // These statuses don't relate to any user state, so don't send a decoy
       // request.
       return false;
     case PrefetchStatus::kPrefetchUsedNoProbe:
-    case PrefetchStatus::kPrefetchUsedProbeSuccess:
     case PrefetchStatus::kPrefetchNotUsedProbeFailed:
     case PrefetchStatus::kPrefetchNotStarted:
     case PrefetchStatus::kPrefetchNotFinishedInTime:
@@ -118,6 +117,9 @@
     case PrefetchStatus::kPrefetchIsStaleNSPNotStarted:
     case PrefetchStatus::kPrefetchNotUsedCookiesChanged:
     case PrefetchStatus::kPrefetchFailedRedirectsDisabled:
+    case PrefetchStatus::kPrefetchResponseUsed:
+    case PrefetchStatus::kPrefetchHeldback:
+    case PrefetchStatus::kPrefetchAllowed:
       // These statuses should not be returned by the eligibility checks, and
       // thus not be passed in here.
       NOTREACHED();
@@ -197,6 +199,21 @@
   closure.Run();
 }
 
+// Returns true if the prefetch is heldback, and set the holdback status
+// correspondingly.
+bool CheckAndSetPrefetchHoldbackStatus(
+    base::WeakPtr<PrefetchContainer> prefetch_container) {
+  if (!prefetch_container->HasPreloadingAttempt())
+    return false;
+  if (!IsContentPrefetchHoldback()) {
+    prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchAllowed);
+    return false;
+  } else {
+    prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchHeldback);
+    return true;
+  }
+}
+
 }  // namespace
 
 // static
@@ -293,7 +310,9 @@
   // checks provide a PrefetchStatus related to the check.
 
   if (browser_context_->IsOffTheRecord()) {
-    std::move(result_callback).Run(prefetch_container, false, absl::nullopt);
+    std::move(result_callback)
+        .Run(prefetch_container, false,
+             PrefetchStatus::kPrefetchNotEligibleBrowserContextOffTheRecord);
     return;
   }
 
@@ -509,43 +528,49 @@
     base::WeakPtr<PrefetchContainer> prefetch_container,
     bool eligible,
     absl::optional<PrefetchStatus> status) {
-  if (prefetch_container)
-    prefetch_container->OnEligibilityCheckComplete(eligible);
-
-  if (!eligible || !prefetch_container) {
-    if (status && prefetch_container) {
-      prefetch_container->SetPrefetchStatus(status.value());
-
-      if (prefetch_container->GetPrefetchType().IsProxyRequired() &&
-          ShouldConsiderDecoyRequestForStatus(
-              prefetch_container->GetPrefetchStatus()) &&
-          PrefetchServiceSendDecoyRequestForIneligblePrefetch(
-              delegate_ ? delegate_->DisableDecoysBasedOnUserSettings()
-                        : false)) {
-        prefetch_container->SetIsDecoy(true);
-        prefetch_queue_.push_back(prefetch_container);
-        prefetch_container->SetPrefetchStatus(
-            PrefetchStatus::kPrefetchIsPrivacyDecoy);
-        Prefetch();
-      }
-    }
+  if (!prefetch_container)
     return;
+
+  bool is_decoy = false;
+  if (!eligible) {
+    // Expect a status if the container is alive but prefetch not eligible.
+    DCHECK(status.has_value());
+    is_decoy =
+        prefetch_container->GetPrefetchType().IsProxyRequired() &&
+        ShouldConsiderDecoyRequestForStatus(status.value()) &&
+        PrefetchServiceSendDecoyRequestForIneligblePrefetch(
+            delegate_ ? delegate_->DisableDecoysBasedOnUserSettings() : false);
+  }
+  // The prefetch decoy is pushed onto the queue and the network request will be
+  // dispatched, but the response will not be used. Thus it is eligible but a
+  // failure.
+  prefetch_container->SetIsDecoy(is_decoy);
+  if (is_decoy) {
+    prefetch_container->OnEligibilityCheckComplete(true, absl::nullopt);
+  } else {
+    prefetch_container->OnEligibilityCheckComplete(eligible, status);
   }
 
-  prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchNotStarted);
+  if (!eligible && !is_decoy)
+    return;
+
+  if (!is_decoy)
+    prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchNotStarted);
   prefetch_queue_.push_back(prefetch_container);
 
   Prefetch();
 
-  // Registers a cookie listener for this prefetch if it is using an isolated
-  // network context. If the cookies in the default partition associated with
-  // this URL change after this point, then the prefetched resources should not
-  // be served.
-  if (prefetch_container->GetPrefetchType()
-          .IsIsolatedNetworkContextRequired()) {
-    prefetch_container->RegisterCookieListener(
-        browser_context_->GetDefaultStoragePartition()
-            ->GetCookieManagerForBrowserProcess());
+  if (!is_decoy) {
+    // Registers a cookie listener for this prefetch if it is using an isolated
+    // network context. If the cookies in the default partition associated with
+    // this URL change after this point, then the prefetched resources should
+    // not be served.
+    if (prefetch_container->GetPrefetchType()
+            .IsIsolatedNetworkContextRequired()) {
+      prefetch_container->RegisterCookieListener(
+          browser_context_->GetDefaultStoragePartition()
+              ->GetCookieManagerForBrowserProcess());
+    }
   }
 }
 
@@ -692,6 +717,12 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(prefetch_container);
 
+  // Do not prefetch for a Holdback control group. Called after the checks in
+  // `PopNextPrefetchContainer` because we want to compare against the
+  // prefetches that would have been dispatched.
+  if (CheckAndSetPrefetchHoldbackStatus(prefetch_container))
+    return;
+
   TakeOwnershipOfPrefetch(prefetch_container);
 
   if (!prefetch_container->IsDecoy()) {
@@ -902,6 +933,8 @@
   prefetch_container->OnPrefetchComplete();
 
   if (prefetch_container->IsDecoy()) {
+    prefetch_container->SetPrefetchStatus(
+        PrefetchStatus::kPrefetchIsPrivacyDecoy);
     // Since this prefetch was a decoy, we don't cache the response.
     prefetch_container->ResetURLLoader();
     Prefetch();
@@ -1085,6 +1118,8 @@
   prefetch_container->OnPrefetchComplete();
 
   if (prefetch_container->IsDecoy()) {
+    prefetch_container->SetPrefetchStatus(
+        PrefetchStatus::kPrefetchIsPrivacyDecoy);
     prefetch_container->ResetStreamingLoader();
     Prefetch();
     return;
@@ -1105,10 +1140,11 @@
   RecordPrefetchProxyPrefetchMainframeNetError(net_error);
 
   // Updates the prefetch's status if it hasn't been updated since the request
-  // first started.
-  if (!prefetch_container->HasPrefetchStatus() ||
-      prefetch_container->GetPrefetchStatus() ==
-          PrefetchStatus::kPrefetchNotFinishedInTime) {
+  // first started. For the prefetch to reach the network stack, it must have
+  // `PrefetchStatus::kPrefetchAllowed` or beyond.
+  DCHECK(prefetch_container->HasPrefetchStatus());
+  if (prefetch_container->GetPrefetchStatus() ==
+      PrefetchStatus::kPrefetchNotFinishedInTime) {
     prefetch_container->SetPrefetchStatus(
         net_error == net::OK ? PrefetchStatus::kPrefetchSuccessful
                              : PrefetchStatus::kPrefetchFailedNetError);
diff --git a/content/browser/preloading/prefetch/prefetch_service_unittest.cc b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
index ef0fb01f..6ae34239 100644
--- a/content/browser/preloading/prefetch/prefetch_service_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
@@ -7,24 +7,31 @@
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "components/ukm/test_ukm_recorder.h"
 #include "content/browser/preloading/prefetch/prefetch_container.h"
 #include "content/browser/preloading/prefetch/prefetch_document_manager.h"
 #include "content/browser/preloading/prefetch/prefetch_features.h"
 #include "content/browser/preloading/prefetch/prefetch_params.h"
 #include "content/browser/preloading/prefetch/prefetch_status.h"
 #include "content/browser/preloading/prefetch/prefetched_mainframe_response_container.h"
+#include "content/browser/preloading/preloading.h"
+#include "content/browser/preloading/preloading_data_impl.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/frame_accept_header.h"
 #include "content/public/browser/prefetch_service_delegate.h"
+#include "content/public/browser/preloading.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/common/content_client.h"
 #include "content/public/test/fake_service_worker_context.h"
 #include "content/public/test/mock_navigation_handle.h"
+#include "content/public/test/preloading_test_util.h"
+#include "content/public/test/test_browser_context.h"
 #include "content/public/test/test_renderer_host.h"
-#include "content/public/test/test_utils.h"
 #include "content/test/test_content_browser_client.h"
 #include "net/base/load_flags.h"
 #include "net/base/proxy_server.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/parsed_headers.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
@@ -61,6 +68,21 @@
         <body></body>
       </html>)";
 
+PreloadingEligibility ToPreloadingEligibility(PrefetchStatus status) {
+  if (status == PrefetchStatus::kPrefetchNotEligibleDataSaverEnabled)
+    return PreloadingEligibility::kDataSaverEnabled;
+  return static_cast<PreloadingEligibility>(
+      static_cast<int>(status) +
+      static_cast<int>(PreloadingEligibility::kPreloadingEligibilityCommonEnd));
+}
+
+PreloadingFailureReason ToPreloadingFailureReason(PrefetchStatus status) {
+  return static_cast<PreloadingFailureReason>(
+      static_cast<int>(status) +
+      static_cast<int>(
+          PreloadingFailureReason::kPreloadingFailureReasonCommonEnd));
+}
+
 class MockPrefetchServiceDelegate : public PrefetchServiceDelegate {
  public:
   explicit MockPrefetchServiceDelegate(int num_on_prefetch_likely_calls = 1) {
@@ -118,6 +140,8 @@
       : mock_prefetch_service_delegate_(
             std::move(mock_prefetch_service_delegate)) {
     old_browser_client_ = SetBrowserClientForTesting(this);
+    off_the_record_context_ = std::make_unique<TestBrowserContext>();
+    off_the_record_context_->set_is_off_the_record(true);
   }
 
   ~ScopedPrefetchServiceContentBrowserClient() override {
@@ -143,10 +167,30 @@
     prefs->data_saver_enabled = data_saver_enabled_;
   }
 
+  void UseOffTheRecordContextForStoragePartition(bool use) {
+    use_off_the_record_context_for_storage_paritition_ = use;
+  }
+  // `BrowserContext::GetStoragePartitionForUrl` eventually calls this method
+  // on the browser client to get the config. Overwrite it so the prefetch can
+  // be rejected due to a non-default storage partition.
+  StoragePartitionConfig GetStoragePartitionConfigForSite(
+      BrowserContext* browser_context,
+      const GURL& site) override {
+    if (use_off_the_record_context_for_storage_paritition_) {
+      return StoragePartitionConfig::CreateDefault(
+          off_the_record_context_.get());
+    }
+    return TestContentBrowserClient::GetStoragePartitionConfigForSite(
+        browser_context, site);
+  }
+
  private:
   raw_ptr<ContentBrowserClient> old_browser_client_;
   std::unique_ptr<MockPrefetchServiceDelegate> mock_prefetch_service_delegate_;
-
+  // This browser context is used to generate a different storage partition if
+  // `use_off_the_record_context_for_storage_paritition_` is set to true.
+  std::unique_ptr<TestBrowserContext> off_the_record_context_;
+  bool use_off_the_record_context_for_storage_paritition_{false};
   bool data_saver_enabled_{false};
 };
 
@@ -194,6 +238,12 @@
         [](base::StringPiece) { return false; });
     PrefetchService::SetServiceWorkerContextForTesting(
         &service_worker_context_);
+
+    test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
+    attempt_entry_builder_ =
+        std::make_unique<content::test::PreloadingAttemptUkmEntryBuilder>(
+            ToPreloadingPredictor(
+                ContentPreloadingPredictor::kSpeculationRules));
   }
 
   void TearDown() override {
@@ -333,7 +383,11 @@
       const std::string mime_type,
       bool use_prefetch_proxy,
       std::vector<std::pair<std::string, std::string>> headers,
-      const std::string& body) {
+      const std::string& body,
+      network::TestURLLoaderFactory::Redirects redirects =
+          network::TestURLLoaderFactory::Redirects(),
+      network::TestURLLoaderFactory::ResponseProduceFlags rp_flags =
+          network::TestURLLoaderFactory::kResponseDefault) {
     network::TestURLLoaderFactory::PendingRequest* request =
         test_url_loader_factory_.GetPendingRequest(0);
     ASSERT_TRUE(request);
@@ -343,7 +397,8 @@
                                                  request->request.url);
     network::URLLoaderCompletionStatus status(net_error);
     test_url_loader_factory_.AddResponse(request->request.url, std::move(head),
-                                         body, status);
+                                         body, status, std::move(redirects),
+                                         rp_flags);
     task_environment()->RunUntilIdle();
     // Clear responses in the network service so we can inspect the next request
     // that comes in before it is responded to.
@@ -463,6 +518,48 @@
     return test_content_browser_client_.get();
   }
 
+  ukm::TestAutoSetUkmRecorder* test_ukm_recorder() {
+    return test_ukm_recorder_.get();
+  }
+
+  const test::PreloadingAttemptUkmEntryBuilder* attempt_entry_builder() {
+    return attempt_entry_builder_.get();
+  }
+
+  ukm::SourceId ForceLogsUploadAndGetUkmId() {
+    MockNavigationHandle mock_handle;
+    mock_handle.set_is_in_primary_main_frame(true);
+    mock_handle.set_is_same_document(false);
+    mock_handle.set_has_committed(true);
+    auto* preloading_data =
+        PreloadingData::GetOrCreateForWebContents(web_contents());
+    static_cast<PreloadingDataImpl*>(preloading_data)
+        ->DidFinishNavigation(&mock_handle);
+    return mock_handle.GetNextPageUkmSourceId();
+  }
+
+  void ExpectCorrectUkmLogs(PreloadingEligibility eligibility,
+                            PreloadingHoldbackStatus holdback,
+                            PreloadingTriggeringOutcome outcome,
+                            PreloadingFailureReason failure) {
+    const auto source_id = ForceLogsUploadAndGetUkmId();
+    auto actual_attempts = test_ukm_recorder()->GetEntries(
+        ukm::builders::Preloading_Attempt::kEntryName,
+        test::kPreloadingAttemptUkmMetrics);
+    EXPECT_EQ(actual_attempts.size(), 1u);
+
+    auto expected_attempts = {attempt_entry_builder()->BuildEntry(
+        source_id, PreloadingType::kPrefetch, eligibility, holdback, outcome,
+        failure,
+        /*accurate=*/false)};
+    EXPECT_THAT(actual_attempts,
+                testing::UnorderedElementsAreArray(expected_attempts))
+        << test::ActualVsExpectedUkmEntriesToString(actual_attempts,
+                                                    expected_attempts);
+    // We do not test the `PreloadingPrediction` as it is added in
+    // `PreloadingDecider`.
+  }
+
  protected:
   FakeServiceWorkerContext service_worker_context_;
   mojo::Remote<network::mojom::CookieManager> cookie_manager_;
@@ -481,6 +578,9 @@
       test_content_browser_client_;
 
   mojo::ScopedDataPipeProducerHandle producer_handle_;
+  std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
+  std::unique_ptr<test::PreloadingAttemptUkmEntryBuilder>
+      attempt_entry_builder_;
 };
 
 TEST_F(PrefetchServiceTest, CreateServiceWhenFeatureEnabled) {
@@ -563,6 +663,11 @@
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
   ASSERT_TRUE(serveable_prefetch_container->GetHead());
   EXPECT_TRUE(serveable_prefetch_container->GetHead()->was_in_prefetch_cache);
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, NoPrefetchingPreloadingDisabled) {
@@ -614,6 +719,14 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  // We expect one entry because the PreloadingAttempt is created when the
+  // container is created, but since `IsSomePreloadingEnabled()` is false
+  // we did not reach to the eligibility check.
+  ExpectCorrectUkmLogs(PreloadingEligibility::kUnspecified,
+                       PreloadingHoldbackStatus::kUnspecified,
+                       PreloadingTriggeringOutcome::kUnspecified,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, NoPrefetchingDomainNotInAllowList) {
@@ -666,6 +779,13 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  // `IsDomainInPrefetchAllowList` returns false so we did not reach the
+  // eligibility check.
+  ExpectCorrectUkmLogs(PreloadingEligibility::kUnspecified,
+                       PreloadingHoldbackStatus::kUnspecified,
+                       PreloadingTriggeringOutcome::kUnspecified,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 class PrefetchServiceAllowAllDomainsTest : public PrefetchServiceTest {
@@ -743,6 +863,11 @@
             PrefetchStatus::kPrefetchSuccessful);
   EXPECT_TRUE(
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 class PrefetchServiceAllowAllDomainsForExtendedPreloadingTest
@@ -824,6 +949,11 @@
             PrefetchStatus::kPrefetchSuccessful);
   EXPECT_TRUE(
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceAllowAllDomainsForExtendedPreloadingTest,
@@ -879,6 +1009,11 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kUnspecified,
+                       PreloadingHoldbackStatus::kUnspecified,
+                       PreloadingTriggeringOutcome::kUnspecified,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, NonProxiedPrefetchDoesNotRequireAllowList) {
@@ -946,6 +1081,11 @@
             PrefetchStatus::kPrefetchSuccessful);
   EXPECT_TRUE(
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleHostnameNonUnique) {
@@ -997,6 +1137,12 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(ToPreloadingEligibility(
+                           PrefetchStatus::kPrefetchNotEligibleHostIsNonUnique),
+                       PreloadingHoldbackStatus::kUnspecified,
+                       PreloadingTriggeringOutcome::kUnspecified,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleDataSaverEnabled) {
@@ -1046,6 +1192,11 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kDataSaverEnabled,
+                       PreloadingHoldbackStatus::kUnspecified,
+                       PreloadingTriggeringOutcome::kUnspecified,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleNonHttps) {
@@ -1094,6 +1245,13 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(
+      ToPreloadingEligibility(
+          PrefetchStatus::kPrefetchNotEligibleSchemeIsNotHttps),
+      PreloadingHoldbackStatus::kUnspecified,
+      PreloadingTriggeringOutcome::kUnspecified,
+      PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, NotEligiblePrefetchProxyNotAvailable) {
@@ -1149,6 +1307,12 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(
+      ToPreloadingEligibility(PrefetchStatus::kPrefetchProxyNotAvailable),
+      PreloadingHoldbackStatus::kUnspecified,
+      PreloadingTriggeringOutcome::kUnspecified,
+      PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest,
@@ -1216,6 +1380,11 @@
             PrefetchStatus::kPrefetchSuccessful);
   EXPECT_TRUE(
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleOriginWithinRetryAfterWindow) {
@@ -1270,6 +1439,12 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(
+      ToPreloadingEligibility(PrefetchStatus::kPrefetchIneligibleRetryAfter),
+      PreloadingHoldbackStatus::kUnspecified,
+      PreloadingTriggeringOutcome::kUnspecified,
+      PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, EligibleNonHttpsNonProxiedPotentiallyTrustworthy) {
@@ -1328,6 +1503,11 @@
             PrefetchStatus::kPrefetchSuccessful);
   EXPECT_TRUE(
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleServiceWorkerRegistered) {
@@ -1379,6 +1559,13 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(
+      ToPreloadingEligibility(
+          PrefetchStatus::kPrefetchNotEligibleUserHasServiceWorker),
+      PreloadingHoldbackStatus::kUnspecified,
+      PreloadingTriggeringOutcome::kUnspecified,
+      PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, EligibleServiceWorkerNotRegistered) {
@@ -1440,6 +1627,11 @@
             PrefetchStatus::kPrefetchSuccessful);
   EXPECT_TRUE(
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleUserHasCookies) {
@@ -1490,6 +1682,12 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(ToPreloadingEligibility(
+                           PrefetchStatus::kPrefetchNotEligibleUserHasCookies),
+                       PreloadingHoldbackStatus::kUnspecified,
+                       PreloadingTriggeringOutcome::kUnspecified,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, EligibleUserHasCookiesForDifferentUrl) {
@@ -1550,6 +1748,11 @@
             PrefetchStatus::kPrefetchSuccessful);
   EXPECT_TRUE(
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, EligibleSameOriginPrefetchCanHaveExistingCookies) {
@@ -1610,6 +1813,11 @@
             PrefetchStatus::kPrefetchSuccessful);
   EXPECT_TRUE(
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, NotEligibleExistingConnectProxy) {
@@ -1665,6 +1873,12 @@
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
 
+  ExpectCorrectUkmLogs(ToPreloadingEligibility(
+                           PrefetchStatus::kPrefetchNotEligibleExistingProxy),
+                       PreloadingHoldbackStatus::kUnspecified,
+                       PreloadingTriggeringOutcome::kUnspecified,
+                       PreloadingFailureReason::kUnspecified);
+
   PrefetchService::SetNetworkContextForProxyLookupForTesting(nullptr);
 }
 
@@ -1733,6 +1947,11 @@
   EXPECT_TRUE(
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
 
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
+
   PrefetchService::SetNetworkContextForProxyLookupForTesting(nullptr);
 }
 
@@ -1787,6 +2006,11 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(
+      PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
+      PreloadingTriggeringOutcome::kFailure,
+      ToPreloadingFailureReason(PrefetchStatus::kPrefetchFailedNon2XX));
 }
 
 TEST_F(PrefetchServiceTest, FailedNetError) {
@@ -1839,6 +2063,11 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(
+      PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
+      PreloadingTriggeringOutcome::kFailure,
+      ToPreloadingFailureReason(PrefetchStatus::kPrefetchFailedNetError));
 }
 
 TEST_F(PrefetchServiceTest, HandleRetryAfterResponse) {
@@ -1903,6 +2132,11 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(
+      PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
+      PreloadingTriggeringOutcome::kFailure,
+      ToPreloadingFailureReason(PrefetchStatus::kPrefetchFailedNon2XX));
 }
 
 TEST_F(PrefetchServiceTest, SuccessNonHTML) {
@@ -1963,6 +2197,11 @@
             PrefetchStatus::kPrefetchSuccessful);
   EXPECT_TRUE(
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 TEST_F(PrefetchServiceTest, NotServeableNavigationInDifferentRenderFrameHost) {
@@ -2014,6 +2253,11 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
 }
 
 class PrefetchServiceLimitedPrefetchesTest : public PrefetchServiceTest {
@@ -2141,6 +2385,41 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container3 =
       prefetch_service_->GetPrefetchToServe(GURL("https://example3.com"));
   EXPECT_FALSE(serveable_prefetch_container3);
+  {
+    const auto source_id = ForceLogsUploadAndGetUkmId();
+    auto actual_attempts = test_ukm_recorder()->GetEntries(
+        ukm::builders::Preloading_Attempt::kEntryName,
+        test::kPreloadingAttemptUkmMetrics);
+    EXPECT_EQ(actual_attempts.size(), 3u);
+
+    // The third entry never reaches the holdback status check.
+    std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> expected_attempts =
+        {attempt_entry_builder()->BuildEntry(
+             source_id, PreloadingType::kPrefetch,
+             PreloadingEligibility::kEligible,
+             PreloadingHoldbackStatus::kAllowed,
+             PreloadingTriggeringOutcome::kReady,
+             PreloadingFailureReason::kUnspecified,
+             /*accurate=*/false),
+         attempt_entry_builder()->BuildEntry(
+             source_id, PreloadingType::kPrefetch,
+             PreloadingEligibility::kEligible,
+             PreloadingHoldbackStatus::kAllowed,
+             PreloadingTriggeringOutcome::kReady,
+             PreloadingFailureReason::kUnspecified,
+             /*accurate=*/false),
+         attempt_entry_builder()->BuildEntry(
+             source_id, PreloadingType::kPrefetch,
+             PreloadingEligibility::kEligible,
+             PreloadingHoldbackStatus::kUnspecified,
+             PreloadingTriggeringOutcome::kUnspecified,
+             PreloadingFailureReason::kUnspecified,
+             /*accurate=*/false)};
+    EXPECT_THAT(actual_attempts,
+                testing::UnorderedElementsAreArray(expected_attempts))
+        << test::ActualVsExpectedUkmEntriesToString(actual_attempts,
+                                                    expected_attempts);
+  }
 }
 
 class PrefetchServiceWithHTMLOnlyTest : public PrefetchServiceTest {
@@ -2207,6 +2486,12 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kFailure,
+                       ToPreloadingFailureReason(
+                           PrefetchStatus::kPrefetchFailedMIMENotSupported));
 }
 
 class PrefetchServiceAlwaysMakeDecoyRequestTest : public PrefetchServiceTest {
@@ -2254,7 +2539,7 @@
   absl::optional<PrefetchReferringPageMetrics> referring_page_metrics =
       PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
   EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
-  EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 0);
+  EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 1);
   EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 0);
 
   absl::optional<PrefetchServingPageMetrics> serving_page_metrics =
@@ -2272,6 +2557,11 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+  // A decoy is considered a failure.
+  ExpectCorrectUkmLogs(
+      PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
+      PreloadingTriggeringOutcome::kFailure,
+      ToPreloadingFailureReason(PrefetchStatus::kPrefetchIsPrivacyDecoy));
 }
 
 TEST_F(PrefetchServiceAlwaysMakeDecoyRequestTest,
@@ -2329,8 +2619,208 @@
   base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
       prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
   EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(ToPreloadingEligibility(
+                           PrefetchStatus::kPrefetchNotEligibleUserHasCookies),
+                       PreloadingHoldbackStatus::kUnspecified,
+                       PreloadingTriggeringOutcome::kUnspecified,
+                       PreloadingFailureReason::kUnspecified);
 }
 
+class PrefetchServiceHoldbackTest : public PrefetchServiceTest {
+ public:
+  void InitScopedFeatureList() override {
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        content::features::kPrefetchUseContentRefactor,
+        {{"prefetch_holdback", "true"}});
+  }
+};
+
+TEST_F(PrefetchServiceHoldbackTest, PrefetchHeldback) {
+  base::HistogramTester histogram_tester;
+
+  MakePrefetchService(
+      std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
+
+  MakePrefetchOnMainFrame(GURL("https://example.com"),
+                          PrefetchType(/*use_isolated_network_context=*/true,
+                                       /*use_prefetch_proxy=*/true));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(RequestCount(), 0);
+
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode",
+                                    0);
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
+                                    0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.BodyLength", 0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.TotalTime", 0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.ConnectTime", 0);
+
+  absl::optional<PrefetchReferringPageMetrics> referring_page_metrics =
+      PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
+  EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
+  // Holdback is checked and set after eligibility.
+  EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 1);
+  EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 0);
+
+  absl::optional<PrefetchServingPageMetrics> serving_page_metrics =
+      GetMetricsForMostRecentNavigation();
+  ASSERT_TRUE(serving_page_metrics);
+  EXPECT_TRUE(serving_page_metrics->prefetch_status);
+  EXPECT_EQ(serving_page_metrics->prefetch_status.value(),
+            static_cast<int>(PrefetchStatus::kPrefetchHeldback));
+  EXPECT_TRUE(serving_page_metrics->required_private_prefetch_proxy);
+  EXPECT_TRUE(serving_page_metrics->same_tab_as_prefetching_tab);
+  EXPECT_FALSE(serving_page_metrics->prefetch_header_latency);
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kHoldback,
+                       PreloadingTriggeringOutcome::kUnspecified,
+                       PreloadingFailureReason::kUnspecified);
+}
+
+class PrefetchServiceIncognitoTest : public PrefetchServiceTest {
+ protected:
+  std::unique_ptr<BrowserContext> CreateBrowserContext() override {
+    auto browser_context = std::make_unique<TestBrowserContext>();
+    browser_context->set_is_off_the_record(true);
+    return browser_context;
+  }
+};
+
+TEST_F(PrefetchServiceIncognitoTest, OffTheRecordIneligible) {
+  base::HistogramTester histogram_tester;
+
+  MakePrefetchService(
+      std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
+
+  MakePrefetchOnMainFrame(GURL("https://example.com"),
+                          PrefetchType(/*use_isolated_network_context=*/true,
+                                       /*use_prefetch_proxy=*/true));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(RequestCount(), 0);
+
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode",
+                                    0);
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
+                                    0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.BodyLength", 0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.TotalTime", 0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.ConnectTime", 0);
+
+  absl::optional<PrefetchReferringPageMetrics> referring_page_metrics =
+      PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
+  EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
+  EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 0);
+  EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 0);
+
+  absl::optional<PrefetchServingPageMetrics> serving_page_metrics =
+      GetMetricsForMostRecentNavigation();
+  ASSERT_TRUE(serving_page_metrics);
+  EXPECT_TRUE(serving_page_metrics->prefetch_status);
+  EXPECT_EQ(
+      serving_page_metrics->prefetch_status.value(),
+      static_cast<int>(
+          PrefetchStatus::kPrefetchNotEligibleBrowserContextOffTheRecord));
+  EXPECT_TRUE(serving_page_metrics->required_private_prefetch_proxy);
+  EXPECT_TRUE(serving_page_metrics->same_tab_as_prefetching_tab);
+  EXPECT_FALSE(serving_page_metrics->prefetch_header_latency);
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(
+      ToPreloadingEligibility(
+          PrefetchStatus::kPrefetchNotEligibleBrowserContextOffTheRecord),
+      PreloadingHoldbackStatus::kUnspecified,
+      PreloadingTriggeringOutcome::kUnspecified,
+      PreloadingFailureReason::kUnspecified);
+}
+
+TEST_F(PrefetchServiceTest, NonDefaultStoragePartition) {
+  base::HistogramTester histogram_tester;
+
+  MakePrefetchService(
+      std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
+  test_content_browser_client_->UseOffTheRecordContextForStoragePartition(true);
+
+  MakePrefetchOnMainFrame(GURL("https://example.com"),
+                          PrefetchType(/*use_isolated_network_context=*/true,
+                                       /*use_prefetch_proxy=*/true));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(RequestCount(), 0);
+
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode",
+                                    0);
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
+                                    0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.BodyLength", 0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.TotalTime", 0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.ConnectTime", 0);
+
+  absl::optional<PrefetchReferringPageMetrics> referring_page_metrics =
+      PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
+  EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
+  EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 0);
+  EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 0);
+
+  absl::optional<PrefetchServingPageMetrics> serving_page_metrics =
+      GetMetricsForMostRecentNavigation();
+  ASSERT_TRUE(serving_page_metrics);
+  EXPECT_TRUE(serving_page_metrics->prefetch_status);
+  EXPECT_EQ(
+      serving_page_metrics->prefetch_status.value(),
+      static_cast<int>(
+          PrefetchStatus::kPrefetchNotEligibleNonDefaultStoragePartition));
+  EXPECT_TRUE(serving_page_metrics->required_private_prefetch_proxy);
+  EXPECT_TRUE(serving_page_metrics->same_tab_as_prefetching_tab);
+  EXPECT_FALSE(serving_page_metrics->prefetch_header_latency);
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(
+      ToPreloadingEligibility(
+          PrefetchStatus::kPrefetchNotEligibleNonDefaultStoragePartition),
+      PreloadingHoldbackStatus::kUnspecified,
+      PreloadingTriggeringOutcome::kUnspecified,
+      PreloadingFailureReason::kUnspecified);
+}
+
+class PrefetchServiceNoVarySearchTest : public PrefetchServiceTest {
+ public:
+  void InitScopedFeatureList() override {
+    scoped_feature_list_.InitWithFeatures(
+        {content::features::kPrefetchUseContentRefactor,
+         network::features::kPrefetchNoVarySearch},
+        {});
+  }
+};
+
 class PrefetchServiceStreamingURLLoaderTest : public PrefetchServiceTest {
  public:
   void InitScopedFeatureList() override {
@@ -2460,20 +2950,13 @@
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
   ASSERT_TRUE(serveable_prefetch_container->GetHead());
   EXPECT_TRUE(serveable_prefetch_container->GetHead()->was_in_prefetch_cache);
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
 }
 
-// TODO(https://crbug.com/1299059): Add test for incognito mode.
-
-class PrefetchServiceNoVarySearchTest : public PrefetchServiceTest {
- public:
-  void InitScopedFeatureList() override {
-    scoped_feature_list_.InitWithFeatures(
-        {content::features::kPrefetchUseContentRefactor,
-         network::features::kPrefetchNoVarySearch},
-        {});
-  }
-};
-
 // TODO(crbug.com/1396460): Test flaky on lacros trybots.
 #if BUILDFLAG(IS_CHROMEOS)
 #define MAYBE_NoVarySearchSuccessCase DISABLED_NoVarySearchSuccessCase
@@ -2481,6 +2964,8 @@
 #define MAYBE_NoVarySearchSuccessCase NoVarySearchSuccessCase
 #endif
 TEST_F(PrefetchServiceNoVarySearchTest, MAYBE_NoVarySearchSuccessCase) {
+  base::HistogramTester histogram_tester;
+
   MakePrefetchService(
       std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
 
@@ -2509,6 +2994,104 @@
             PrefetchStatus::kPrefetchSuccessful);
   EXPECT_TRUE(
       serveable_prefetch_container->IsPrefetchServable(base::TimeDelta::Max()));
+
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.ExistingPrefetchWithMatchingURL", false, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.BodyLength", std::size(kHTMLBody), 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1);
+
+  absl::optional<PrefetchReferringPageMetrics> referring_page_metrics =
+      PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
+  EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
+  EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 1);
+  EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 1);
+
+  absl::optional<PrefetchServingPageMetrics> serving_page_metrics =
+      GetMetricsForMostRecentNavigation();
+  ASSERT_TRUE(serving_page_metrics);
+  EXPECT_TRUE(serving_page_metrics->prefetch_status);
+  EXPECT_EQ(serving_page_metrics->prefetch_status.value(),
+            static_cast<int>(PrefetchStatus::kPrefetchSuccessful));
+  EXPECT_TRUE(serving_page_metrics->required_private_prefetch_proxy);
+  EXPECT_TRUE(serving_page_metrics->same_tab_as_prefetching_tab);
+  EXPECT_TRUE(serving_page_metrics->prefetch_header_latency);
+  EXPECT_EQ(serving_page_metrics->prefetch_header_latency.value(),
+            base::Milliseconds(kHeaderLatency));
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kReady,
+                       PreloadingFailureReason::kUnspecified);
+}
+
+TEST_F(PrefetchServiceTest, PrefetchFailedForRedirect) {
+  base::HistogramTester histogram_tester;
+
+  MakePrefetchService(
+      std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>());
+
+  MakePrefetchOnMainFrame(GURL("https://example.com"),
+                          PrefetchType(/*use_isolated_network_context=*/true,
+                                       /*use_prefetch_proxy=*/true));
+  base::RunLoop().RunUntilIdle();
+
+  VerifyCommonRequestState(GURL("https://example.com"),
+                           /*use_prefetch_proxy=*/true);
+
+  network::TestURLLoaderFactory::Redirects redirects;
+  redirects.emplace_back(net::RedirectInfo(),
+                         network::mojom::URLResponseHead::New());
+  MakeResponseAndWait(
+      net::HTTP_OK, net::OK, kHTMLMimeType,
+      /*use_prefetch_proxy=*/true, {{"X-Testing", "Hello World"}}, kHTMLBody,
+      std::move(redirects), network::TestURLLoaderFactory::kResponseDefault);
+
+  Navigate(GURL("https://example.com"), main_rfh()->GetGlobalId());
+
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode",
+                                    0);
+  histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError",
+                                    0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.BodyLength", 0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.TotalTime", 0);
+  histogram_tester.ExpectTotalCount(
+      "PrefetchProxy.Prefetch.Mainframe.ConnectTime", 0);
+
+  absl::optional<PrefetchReferringPageMetrics> referring_page_metrics =
+      PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh());
+  EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1);
+  EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 1);
+  EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 0);
+
+  absl::optional<PrefetchServingPageMetrics> serving_page_metrics =
+      GetMetricsForMostRecentNavigation();
+  ASSERT_TRUE(serving_page_metrics);
+  EXPECT_TRUE(serving_page_metrics->prefetch_status);
+  EXPECT_EQ(serving_page_metrics->prefetch_status.value(),
+            static_cast<int>(PrefetchStatus::kPrefetchFailedRedirectsDisabled));
+  EXPECT_TRUE(serving_page_metrics->required_private_prefetch_proxy);
+  EXPECT_TRUE(serving_page_metrics->same_tab_as_prefetching_tab);
+  EXPECT_FALSE(serving_page_metrics->prefetch_header_latency);
+
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      prefetch_service_->GetPrefetchToServe(GURL("https://example.com"));
+  EXPECT_FALSE(serveable_prefetch_container);
+
+  ExpectCorrectUkmLogs(PreloadingEligibility::kEligible,
+                       PreloadingHoldbackStatus::kAllowed,
+                       PreloadingTriggeringOutcome::kFailure,
+                       ToPreloadingFailureReason(
+                           PrefetchStatus::kPrefetchFailedRedirectsDisabled));
 }
 
 }  // namespace
diff --git a/content/browser/preloading/prefetch/prefetch_status.h b/content/browser/preloading/prefetch/prefetch_status.h
index 9b270b7..16e3859 100644
--- a/content/browser/preloading/prefetch/prefetch_status.h
+++ b/content/browser/preloading/prefetch/prefetch_status.h
@@ -15,8 +15,11 @@
   // The interceptor used a prefetch.
   kPrefetchUsedNoProbe = 0,
 
+  // Deprecated. Probe success implies the response is used. Thus replaced
+  // by `kPrefetchResponseUsed`.
+  //
   // The interceptor used a prefetch after successfully probing the origin.
-  kPrefetchUsedProbeSuccess = 1,
+  // kPrefetchUsedProbeSuccess = 1,
 
   // The interceptor was not able to use an available prefetch because the
   // origin probe failed.
@@ -137,6 +140,20 @@
   // The URL is not eligible to be prefetched, because in the default network
   // context it is configured to use a proxy server.
   kPrefetchNotEligibleExistingProxy = 38,
+
+  // Prefetch not supported in Guest or Incognito mode.
+  kPrefetchNotEligibleBrowserContextOffTheRecord = 39,
+
+  // Whether this prefetch is heldback for counterfactual logging.
+  kPrefetchHeldback = 40,
+  kPrefetchAllowed = 41,
+
+  // The response of the prefetch is used for the next navigation. This is the
+  // final successful state.
+  kPrefetchResponseUsed = 42,
+
+  // The max value of the PrefetchStatus. Update this when new enums are added.
+  kMaxValue = kPrefetchResponseUsed,
 };
 
 }  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
index 26a87c9..1c37655 100644
--- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
+++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
@@ -233,6 +233,16 @@
     return;
   }
 
+  // This can only happen when probing is required and probing is successful.
+  // `PrefetchURLLoaderInterceptor::InterceptPrefetchedNavigation` is reached
+  // from a different code path, see
+  // `PrefetchURLLoaderInterceptor::MaybeCreateLoader`.
+  if (const auto status = prefetch_container->GetPrefetchStatus();
+      status != PrefetchStatus::kPrefetchResponseUsed) {
+    prefetch_container->SetPrefetchStatus(
+        PrefetchStatus::kPrefetchResponseUsed);
+  }
+
   // Set up URL loader that will serve the prefetched data, and URL loader
   // factory that will "create" this loader.
   scoped_refptr<network::SingleRequestURLLoaderFactory>
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
index 698bee95..487a93dd 100644
--- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "components/ukm/test_ukm_recorder.h"
 #include "content/browser/preloading/prefetch/prefetch_container.h"
 #include "content/browser/preloading/prefetch/prefetch_features.h"
 #include "content/browser/preloading/prefetch/prefetch_origin_prober.h"
@@ -18,17 +19,23 @@
 #include "content/browser/preloading/prefetch/prefetch_probe_result.h"
 #include "content/browser/preloading/prefetch/prefetch_type.h"
 #include "content/browser/preloading/prefetch/prefetched_mainframe_response_container.h"
+#include "content/browser/preloading/preloading.h"
+#include "content/browser/preloading/preloading_data_impl.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/navigation_request.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/preloading.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_client.h"
+#include "content/public/test/mock_navigation_handle.h"
 #include "content/public/test/navigation_simulator.h"
+#include "content/public/test/preloading_test_util.h"
 #include "content/public/test/test_renderer_host.h"
 #include "content/test/test_content_browser_client.h"
 #include "net/base/isolation_info.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/network/public/mojom/cookie_manager.mojom.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
@@ -182,6 +189,12 @@
 
     interceptor_ = std::make_unique<TestPrefetchURLLoaderInterceptor>(
         web_contents()->GetPrimaryMainFrame()->GetFrameTreeNodeId());
+
+    test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
+    attempt_entry_builder_ =
+        std::make_unique<content::test::PreloadingAttemptUkmEntryBuilder>(
+            ToPreloadingPredictor(
+                ContentPreloadingPredictor::kSpeculationRules));
   }
 
   void TearDown() override {
@@ -261,6 +274,47 @@
     return test_content_browser_client_.get();
   }
 
+  ukm::TestAutoSetUkmRecorder* test_ukm_recorder() {
+    return test_ukm_recorder_.get();
+  }
+
+  const test::PreloadingAttemptUkmEntryBuilder* attempt_entry_builder() {
+    return attempt_entry_builder_.get();
+  }
+
+  void ExpectCorrectUkmLogs(const GURL& url,
+                            PreloadingTriggeringOutcome outcome) {
+    MockNavigationHandle mock_handle;
+    mock_handle.set_is_in_primary_main_frame(true);
+    mock_handle.set_is_same_document(false);
+    mock_handle.set_has_committed(true);
+    mock_handle.set_url(url);
+    auto* preloading_data =
+        PreloadingData::GetOrCreateForWebContents(web_contents());
+    static_cast<PreloadingDataImpl*>(preloading_data)
+        ->DidStartNavigation(&mock_handle);
+    static_cast<PreloadingDataImpl*>(preloading_data)
+        ->DidFinishNavigation(&mock_handle);
+
+    auto actual_attempts = test_ukm_recorder()->GetEntries(
+        ukm::builders::Preloading_Attempt::kEntryName,
+        test::kPreloadingAttemptUkmMetrics);
+    EXPECT_EQ(actual_attempts.size(), 1u);
+
+    auto expected_attempts = {attempt_entry_builder()->BuildEntry(
+        mock_handle.GetNextPageUkmSourceId(), PreloadingType::kPrefetch,
+        PreloadingEligibility::kEligible, PreloadingHoldbackStatus::kAllowed,
+        outcome, PreloadingFailureReason::kUnspecified,
+        /*accurate=*/true)};
+
+    EXPECT_THAT(actual_attempts,
+                testing::UnorderedElementsAreArray(expected_attempts))
+        << test::ActualVsExpectedUkmEntriesToString(actual_attempts,
+                                                    expected_attempts);
+    // We do not test the `PreloadingPrediction` as it is added in
+    // `PreloadingDecider`.
+  }
+
  private:
   std::unique_ptr<TestPrefetchURLLoaderInterceptor> interceptor_;
 
@@ -271,6 +325,10 @@
 
   mojo::Remote<network::mojom::CookieManager> cookie_manager_;
   std::unique_ptr<ScopedMockContentBrowserClient> test_content_browser_client_;
+
+  std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
+  std::unique_ptr<test::PreloadingAttemptUkmEntryBuilder>
+      attempt_entry_builder_;
 };
 
 TEST_F(PrefetchURLLoaderInterceptorTest,
@@ -300,6 +358,7 @@
           PrefetchType(/*use_isolated_network_context=*/true,
                        /*use_prefetch_proxy=*/true),
           blink::mojom::Referrer(), nullptr);
+  prefetch_container->SimulateAttemptAtInterceptorForTest();
 
   prefetch_container->TakePrefetchedResponse(
       std::make_unique<PrefetchedMainframeResponseContainer>(
@@ -340,6 +399,9 @@
       1);
 
   EXPECT_EQ(interceptor()->num_probes(), 0);
+  EXPECT_EQ(prefetch_container->GetPrefetchStatus(),
+            PrefetchStatus::kPrefetchResponseUsed);
+  ExpectCorrectUkmLogs(kTestUrl, PreloadingTriggeringOutcome::kSuccess);
 }
 
 TEST_F(PrefetchURLLoaderInterceptorTest,
@@ -369,6 +431,7 @@
           PrefetchType(/*use_isolated_network_context=*/true,
                        /*use_prefetch_proxy=*/true),
           blink::mojom::Referrer(), nullptr);
+  prefetch_container->SimulateAttemptAtInterceptorForTest();
 
   prefetch_container->TakePrefetchedResponse(
       std::make_unique<PrefetchedMainframeResponseContainer>(
@@ -416,6 +479,7 @@
       base::Milliseconds(20), 1);
 
   EXPECT_EQ(interceptor()->num_probes(), 0);
+  ExpectCorrectUkmLogs(kTestUrl, PreloadingTriggeringOutcome::kSuccess);
 }
 
 TEST_F(PrefetchURLLoaderInterceptorTest,
@@ -447,6 +511,7 @@
           PrefetchType(/*use_isolated_network_context=*/false,
                        /*use_prefetch_proxy=*/false),
           blink::mojom::Referrer(), nullptr);
+  prefetch_container->SimulateAttemptAtInterceptorForTest();
 
   prefetch_container->TakePrefetchedResponse(
       std::make_unique<PrefetchedMainframeResponseContainer>(
@@ -481,6 +546,7 @@
       1);
 
   EXPECT_EQ(interceptor()->num_probes(), 0);
+  ExpectCorrectUkmLogs(kTestUrl, PreloadingTriggeringOutcome::kSuccess);
 }
 
 TEST_F(PrefetchURLLoaderInterceptorTest,
@@ -517,6 +583,11 @@
       "PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", 0);
 
   EXPECT_EQ(interceptor()->num_probes(), 0);
+
+  auto actual = test_ukm_recorder()->GetEntries(
+      ukm::builders::Preloading_Attempt::kEntryName,
+      test::kPreloadingAttemptUkmMetrics);
+  EXPECT_EQ(actual.size(), 0u);
 }
 
 TEST_F(PrefetchURLLoaderInterceptorTest,
@@ -533,6 +604,7 @@
           PrefetchType(/*use_isolated_network_context=*/true,
                        /*use_prefetch_proxy=*/true),
           blink::mojom::Referrer(), nullptr);
+  prefetch_container->SimulateAttemptAtInterceptorForTest();
 
   interceptor()->AddPrefetch(prefetch_container->GetWeakPtr());
 
@@ -563,6 +635,7 @@
       "PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", 0);
 
   EXPECT_EQ(interceptor()->num_probes(), 0);
+  ExpectCorrectUkmLogs(kTestUrl, PreloadingTriggeringOutcome::kReady);
 }
 
 TEST_F(PrefetchURLLoaderInterceptorTest,
@@ -578,6 +651,7 @@
           PrefetchType(/*use_isolated_network_context=*/true,
                        /*use_prefetch_proxy=*/true),
           blink::mojom::Referrer(), nullptr);
+  prefetch_container->SimulateAttemptAtInterceptorForTest();
 
   prefetch_container->TakePrefetchedResponse(
       std::make_unique<PrefetchedMainframeResponseContainer>(
@@ -614,6 +688,7 @@
       "PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", 0);
 
   EXPECT_EQ(interceptor()->num_probes(), 0);
+  ExpectCorrectUkmLogs(kTestUrl, PreloadingTriggeringOutcome::kReady);
 }
 
 TEST_F(PrefetchURLLoaderInterceptorTest,
@@ -629,6 +704,7 @@
           PrefetchType(/*use_isolated_network_context=*/true,
                        /*use_prefetch_proxy=*/true),
           blink::mojom::Referrer(), nullptr);
+  prefetch_container->SimulateAttemptAtInterceptorForTest();
 
   prefetch_container->TakePrefetchedResponse(
       std::make_unique<PrefetchedMainframeResponseContainer>(
@@ -667,6 +743,7 @@
       "PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", 0);
 
   EXPECT_EQ(interceptor()->num_probes(), 0);
+  ExpectCorrectUkmLogs(kTestUrl, PreloadingTriggeringOutcome::kReady);
 }
 
 TEST_F(PrefetchURLLoaderInterceptorTest, DISABLE_ASAN(ProbeSuccess)) {
@@ -695,6 +772,7 @@
           PrefetchType(/*use_isolated_network_context=*/true,
                        /*use_prefetch_proxy=*/true),
           blink::mojom::Referrer(), nullptr);
+  prefetch_container->SimulateAttemptAtInterceptorForTest();
 
   prefetch_container->TakePrefetchedResponse(
       std::make_unique<PrefetchedMainframeResponseContainer>(
@@ -730,6 +808,7 @@
   EXPECT_TRUE(was_intercepted().value());
 
   EXPECT_EQ(interceptor()->num_probes(), 1);
+  ExpectCorrectUkmLogs(kTestUrl, PreloadingTriggeringOutcome::kSuccess);
 }
 
 TEST_F(PrefetchURLLoaderInterceptorTest, DISABLE_ASAN(ProbeFailure)) {
@@ -744,6 +823,7 @@
           PrefetchType(/*use_isolated_network_context=*/true,
                        /*use_prefetch_proxy=*/true),
           blink::mojom::Referrer(), nullptr);
+  prefetch_container->SimulateAttemptAtInterceptorForTest();
 
   prefetch_container->TakePrefetchedResponse(
       std::make_unique<PrefetchedMainframeResponseContainer>(
@@ -779,6 +859,7 @@
   EXPECT_FALSE(was_intercepted().value());
 
   EXPECT_EQ(interceptor()->num_probes(), 1);
+  ExpectCorrectUkmLogs(kTestUrl, PreloadingTriggeringOutcome::kReady);
 }
 
 }  // namespace
diff --git a/content/browser/preloading/prefetcher_unittest.cc b/content/browser/preloading/prefetcher_unittest.cc
index 467ce3f..82b7d62 100644
--- a/content/browser/preloading/prefetcher_unittest.cc
+++ b/content/browser/preloading/prefetcher_unittest.cc
@@ -51,6 +51,7 @@
 
   void PrefetchUrl(
       base::WeakPtr<PrefetchContainer> prefetch_container) override {
+    prefetch_container->DisablePrecogLoggingForTest();
     prefetches_.push_back(prefetch_container);
 
     const auto& devtools_observer = prefetch_container->GetDevToolsObserver();
diff --git a/content/browser/preloading/preloading_data_impl.cc b/content/browser/preloading/preloading_data_impl.cc
index 20b9bd1..e0e03231 100644
--- a/content/browser/preloading/preloading_data_impl.cc
+++ b/content/browser/preloading/preloading_data_impl.cc
@@ -4,6 +4,8 @@
 
 #include "content/browser/preloading/preloading_data_impl.h"
 
+#include "content/browser/preloading/prefetch/no_vary_search_helper.h"
+#include "content/browser/preloading/prefetch/prefetch_document_manager.h"
 #include "content/browser/preloading/preloading_attempt_impl.h"
 #include "content/browser/preloading/preloading_prediction.h"
 #include "content/browser/renderer_host/navigation_request.h"
@@ -24,6 +26,28 @@
 }
 
 // static
+PreloadingURLMatchCallback
+PreloadingDataImpl::GetSameURLAndNoVarySearchURLMatcher(
+    base::WeakPtr<PrefetchDocumentManager> manager,
+    const GURL& destination_url) {
+  return base::BindRepeating(
+      [](base::WeakPtr<PrefetchDocumentManager> prefetch_doc_manager,
+         const GURL& predicted_url, const GURL& navigated_url) {
+        if (!prefetch_doc_manager)
+          return predicted_url == navigated_url;
+
+        if (predicted_url == navigated_url)
+          return true;
+
+        const absl::optional<GURL> match_url =
+            prefetch_doc_manager->GetNoVarySearchHelper().MatchUrl(
+                navigated_url);
+        return match_url == predicted_url;
+      },
+      manager, destination_url);
+}
+
+// static
 PreloadingData* PreloadingData::GetOrCreateForWebContents(
     WebContents* web_contents) {
   return PreloadingDataImpl::GetOrCreateForWebContents(web_contents);
diff --git a/content/browser/preloading/preloading_data_impl.h b/content/browser/preloading/preloading_data_impl.h
index a431325..fcdba7c6 100644
--- a/content/browser/preloading/preloading_data_impl.h
+++ b/content/browser/preloading/preloading_data_impl.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/memory/weak_ptr.h"
 #include "content/public/browser/preloading_data.h"
 
 #include "content/public/browser/web_contents_observer.h"
@@ -18,6 +19,7 @@
 
 class PreloadingAttemptImpl;
 class PreloadingPrediction;
+class PrefetchDocumentManager;
 
 // The scope of current preloading logging is only limited to the same
 // WebContents navigations. If the predicted URL is opened in a new tab we lose
@@ -34,6 +36,14 @@
   static PreloadingDataImpl* GetOrCreateForWebContents(
       WebContents* web_contents);
 
+  // NoVarySearch is a `/content/browser` feature so is the matcher getter.
+  // The matcher first checks if `destination_url` is the same as the
+  // prediction; if not, the matcher checks if the `destination_url` matches
+  // any NoVarySearch query using `NoVarySearchHelper`.
+  static PreloadingURLMatchCallback GetSameURLAndNoVarySearchURLMatcher(
+      base::WeakPtr<PrefetchDocumentManager> manager,
+      const GURL& destination_url);
+
   // Disallow copy and assign.
   PreloadingDataImpl(const PreloadingDataImpl& other) = delete;
   PreloadingDataImpl& operator=(const PreloadingDataImpl& other) = delete;
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 757b998..90394fd2 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -36,6 +36,8 @@
 #include "base/types/optional_util.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "components/attribution_reporting/os_registration.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "content/browser/attribution_reporting/attribution_manager.h"
 #include "content/browser/blob_storage/chrome_blob_storage_context.h"
 #include "content/browser/child_process_security_policy_impl.h"
@@ -163,7 +165,6 @@
 #include "third_party/blink/public/common/security/address_space_feature.h"
 #include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
 #include "third_party/blink/public/mojom/loader/mixed_content.mojom.h"
 #include "third_party/blink/public/mojom/loader/transferrable_url_loader.mojom.h"
@@ -477,10 +478,9 @@
 
     if (base::FeatureList::IsEnabled(
             blink::features::kAttributionReportingCrossAppWeb)) {
-      bool has_os_support = AttributionManager::GetOsSupport() ==
-                            blink::mojom::AttributionOsSupport::kEnabled;
       headers->SetHeader("Attribution-Reporting-Support",
-                         has_os_support ? "web, os" : "web");
+                         attribution_reporting::GetSupportHeader(
+                             AttributionManager::GetOsSupport()));
     }
   }
 }
@@ -1872,7 +1872,8 @@
   navigation_handle_proxy_ = std::make_unique<NavigationHandleProxy>(this);
 #endif
 
-  if (NeedsUrlLoader() && common_params_->url.SchemeIsHTTPOrHTTPS()) {
+  if (base::FeatureList::IsEnabled(features::kNavigationRequestPreconnect) &&
+      NeedsUrlLoader() && common_params_->url.SchemeIsHTTPOrHTTPS()) {
     BrowserContext* browser_context =
         frame_tree_node_->navigator().controller().GetBrowserContext();
     if (GetContentClient()->browser()->ShouldPreconnectNavigation(
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 962c03c..33c911bf 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -66,6 +66,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "cc/base/switches.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "components/discardable_memory/public/mojom/discardable_shared_memory_manager.mojom.h"
 #include "components/discardable_memory/service/discardable_shared_memory_manager.h"
 #include "components/metrics/single_sample_metrics.h"
@@ -194,7 +195,6 @@
 #include "third_party/blink/public/common/page/launching_process_state.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/common/switches.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom.h"
 #include "third_party/blink/public/mojom/disk_allocator.mojom.h"
 #include "third_party/blink/public/mojom/plugins/plugin_registry.mojom.h"
 #include "third_party/blink/public/public_buildflags.h"
@@ -5385,7 +5385,7 @@
 }
 
 void RenderProcessHostImpl::SetOsSupportForAttributionReporting(
-    blink::mojom::AttributionOsSupport os_support) {
+    attribution_reporting::mojom::OsSupport os_support) {
   GetRendererInterface()->SetOsSupportForAttributionReporting(os_support);
 }
 
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index 8d6fe0d..24063b9e 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -324,7 +324,7 @@
       const GlobalRenderFrameHostId& render_frame_host_id) override;
 
   void SetOsSupportForAttributionReporting(
-      blink::mojom::AttributionOsSupport os_support) override;
+      attribution_reporting::mojom::OsSupport os_support) override;
 
   // IPC::Sender via RenderProcessHost.
   bool Send(IPC::Message* msg) override;
diff --git a/content/browser/resources/attribution_reporting/attribution_internals.ts b/content/browser/resources/attribution_reporting/attribution_internals.ts
index d115e24..9ddde4a 100644
--- a/content/browser/resources/attribution_reporting/attribution_internals.ts
+++ b/content/browser/resources/attribution_reporting/attribution_internals.ts
@@ -33,18 +33,13 @@
 }
 
 class ValueColumn<T, V> implements Column<T> {
-  compare: (a: T, b: T) => number;
-  header: string;
-  protected getValue: (param: T) => V;
+  readonly compare?: (a: T, b: T) => number;
 
   constructor(
-      header: string, getValue: (param: T) => V,
-      compare?: ((a: T, b: T) => number)) {
-    this.header = header;
-    this.getValue = getValue;
-    if (compare) {
-      this.compare = compare;
-    } else {
+      private readonly header: string,
+      protected readonly getValue: (param: T) => V,
+      comparable: boolean = true) {
+    if (comparable) {
       this.compare = (a: T, b: T) => compareDefault(getValue(a), getValue(b));
     }
   }
@@ -70,7 +65,7 @@
 
 class CodeColumn<T> extends ValueColumn<T, string> {
   constructor(header: string, getValue: (p: T) => string) {
-    super(header, getValue);
+    super(header, getValue, /*comparable=*/ false);
   }
 
   override render(td: HTMLElement, row: T) {
@@ -109,8 +104,6 @@
 }
 
 class LogMetadataColumn implements Column<Log> {
-  compare = null;
-
   renderHeader(th: HTMLElement) {
     th.innerText = 'Metadata';
   }
@@ -121,8 +114,6 @@
 }
 
 class LogDescriptionColumn implements Column<Log> {
-  compare = null;
-
   renderHeader(th: HTMLElement) {
     th.innerText = 'Description';
   }
@@ -135,12 +126,12 @@
 const debugPathPattern: RegExp =
     /(?<=\/\.well-known\/attribution-reporting\/)debug(?=\/)/;
 
-class ReportUrlColumn extends ValueColumn<Report, string> {
+class ReportUrlColumn<T extends Report> extends ValueColumn<T, string> {
   constructor() {
     super('Report URL', (e) => e.reportUrl);
   }
 
-  override render(td: HTMLElement, row: Report) {
+  override render(td: HTMLElement, row: T) {
     if (!row.isDebug) {
       td.innerText = row.reportUrl;
       return;
@@ -168,16 +159,11 @@
 }
 
 class SelectionColumn<T extends Selectable> implements Column<T> {
-  compare: ((a: T, b: T) => number)|null;
-  model: TableModel<T>;
-  selectAll: HTMLInputElement;
-  listener: () => void;
-  selectionChangedListeners: Set<(param: boolean) => void>;
+  private readonly selectAll: HTMLInputElement;
+  private readonly listener: () => void;
+  readonly selectionChangedListeners: Set<(param: boolean) => void> = new Set();
 
-  constructor(model: TableModel<T>) {
-    this.compare = null;
-    this.model = model;
-
+  constructor(private readonly model: TableModel<T>) {
     this.selectAll = document.createElement('input');
     this.selectAll.type = 'checkbox';
     this.selectAll.addEventListener('input', () => {
@@ -192,7 +178,6 @@
 
     this.listener = () => this.onChange();
     this.model.rowsChangedListeners.add(this.listener);
-    this.selectionChangedListeners = new Set();
   }
 
   render(td: HTMLElement, row: T) {
@@ -283,48 +268,47 @@
 }
 
 class SourceTableModel extends TableModel<Source> {
-  storedSources: Source[] = [];
-  unstoredSources: Source[] = [];
+  private storedSources: Source[] = [];
+  private unstoredSources: Source[] = [];
 
   constructor() {
-    super();
-
-    this.cols = [
-      new ValueColumn<Source, bigint>(
-          'Source Event ID', (e) => e.sourceEventId),
-      new ValueColumn<Source, string>('Status', (e) => e.status),
-      new ValueColumn<Source, string>('Source Origin', (e) => e.sourceOrigin),
-      new ValueColumn<Source, string>(
-          'Destination', (e) => e.attributionDestination),
-      new ValueColumn<Source, string>(
-          'Reporting Origin', (e) => e.reportingOrigin),
-      new DateColumn<Source>('Source Registration Time', (e) => e.sourceTime),
-      new DateColumn<Source>('Expiry Time', (e) => e.expiryTime),
-      new DateColumn<Source>(
-          'Event Report Window Time', (e) => e.eventReportWindowTime),
-      new DateColumn<Source>(
-          'Aggregatable Report Window Time',
-          (e) => e.aggregatableReportWindowTime),
-      new ValueColumn<Source, string>('Source Type', (e) => e.sourceType),
-      new ValueColumn<Source, bigint>('Priority', (e) => e.priority),
-      new CodeColumn<Source>('Filter Data', (e) => e.filterData),
-      new CodeColumn<Source>('Aggregation Keys', (e) => e.aggregationKeys),
-      new ValueColumn<Source, string>(
-          'Aggregatable Budget Consumed',
-          (e) => `${e.aggregatableBudgetConsumed} / ${BUDGET_PER_SOURCE}`),
-      new ValueColumn<Source, string>('Debug Key', (e) => e.debugKey),
-      new ValueColumn<Source, string>('Dedup Keys', (e) => e.dedupKeys),
-      new ValueColumn<Source, string>(
-          'Aggregatable Dedup Keys', (e) => e.aggregatableDedupKeys),
-      new ValueColumn<Source, string>(
-          'Verbose Debug Reporting',
-          (e) => e.debugReportingEnabled ? 'enabled' : 'disabled'),
-    ];
-
-    this.emptyRowText = 'No sources.';
-
-    // Sort by source registration time by default.
-    this.sortIdx = 5;
+    super(
+        [
+          new ValueColumn<Source, bigint>(
+              'Source Event ID', (e) => e.sourceEventId),
+          new ValueColumn<Source, string>('Status', (e) => e.status),
+          new ValueColumn<Source, string>(
+              'Source Origin', (e) => e.sourceOrigin),
+          new ValueColumn<Source, string>(
+              'Destination', (e) => e.attributionDestination),
+          new ValueColumn<Source, string>(
+              'Reporting Origin', (e) => e.reportingOrigin),
+          new DateColumn<Source>(
+              'Source Registration Time', (e) => e.sourceTime),
+          new DateColumn<Source>('Expiry Time', (e) => e.expiryTime),
+          new DateColumn<Source>(
+              'Event Report Window Time', (e) => e.eventReportWindowTime),
+          new DateColumn<Source>(
+              'Aggregatable Report Window Time',
+              (e) => e.aggregatableReportWindowTime),
+          new ValueColumn<Source, string>('Source Type', (e) => e.sourceType),
+          new ValueColumn<Source, bigint>('Priority', (e) => e.priority),
+          new CodeColumn<Source>('Filter Data', (e) => e.filterData),
+          new CodeColumn<Source>('Aggregation Keys', (e) => e.aggregationKeys),
+          new ValueColumn<Source, string>(
+              'Aggregatable Budget Consumed',
+              (e) => `${e.aggregatableBudgetConsumed} / ${BUDGET_PER_SOURCE}`),
+          new ValueColumn<Source, string>('Debug Key', (e) => e.debugKey),
+          new ValueColumn<Source, string>('Dedup Keys', (e) => e.dedupKeys),
+          new ValueColumn<Source, string>(
+              'Aggregatable Dedup Keys', (e) => e.aggregatableDedupKeys),
+          new ValueColumn<Source, string>(
+              'Verbose Debug Reporting',
+              (e) => e.debugReportingEnabled ? 'enabled' : 'disabled'),
+        ],
+        5,  // Sort by source registration time by default.
+        'No sources.',
+    );
   }
 
   override getRows() {
@@ -373,28 +357,26 @@
 }
 
 class TriggerTableModel extends TableModel<Trigger> {
-  triggers: Trigger[] = [];
+  private triggers: Trigger[] = [];
 
   constructor() {
-    super();
-
-    this.cols = [
-      new DateColumn<Trigger>('Trigger Time', (e) => e.triggerTime),
-      new ValueColumn<Trigger, string>(
-          'Event-Level Status', (e) => e.eventLevelStatus),
-      new ValueColumn<Trigger, string>(
-          'Aggregatable Status', (e) => e.aggregatableStatus),
-      new ValueColumn<Trigger, string>(
-          'Destination', (e) => e.destinationOrigin),
-      new ValueColumn<Trigger, string>(
-          'Reporting Origin', (e) => e.reportingOrigin),
-      new CodeColumn<Trigger>('Registration JSON', (e) => e.registrationJson),
-    ];
-
-    this.emptyRowText = 'No triggers.';
-
-    // Sort by trigger time by default.
-    this.sortIdx = 0;
+    super(
+        [
+          new DateColumn<Trigger>('Trigger Time', (e) => e.triggerTime),
+          new ValueColumn<Trigger, string>(
+              'Event-Level Status', (e) => e.eventLevelStatus),
+          new ValueColumn<Trigger, string>(
+              'Aggregatable Status', (e) => e.aggregatableStatus),
+          new ValueColumn<Trigger, string>(
+              'Destination', (e) => e.destinationOrigin),
+          new ValueColumn<Trigger, string>(
+              'Reporting Origin', (e) => e.reportingOrigin),
+          new CodeColumn<Trigger>(
+              'Registration JSON', (e) => e.registrationJson),
+        ],
+        0,  // Sort by trigger time by default.
+        'No triggers.',
+    );
   }
 
   override getRows() {
@@ -492,19 +474,37 @@
   }
 }
 
-class ReportTableModel extends TableModel<Report> {
-  showDebugReportsCheckbox: HTMLInputElement;
-  hiddenDebugReportsSpan: HTMLSpanElement;
-  sendReportsButton: HTMLButtonElement;
-  selectionColumn: SelectionColumn<Report>;
-  sentOrDroppedReports: Report[] = [];
-  storedReports: Report[] = [];
-  debugReports: Report[] = [];
+function commonReportTableColumns<T extends Report>(): Array<Column<T>> {
+  return [
+    new CodeColumn<T>('Report Body', (e) => e.reportBody),
+    new ValueColumn<T, string>('Status', (e) => e.status),
+    new ReportUrlColumn<T>(),
+    new DateColumn<T>('Trigger Time', (e) => e.triggerTime),
+    new DateColumn<T>('Report Time', (e) => e.reportTime),
+  ];
+}
+
+class ReportTableModel<T extends Report> extends TableModel<T> {
+  private readonly showDebugReportsCheckbox: HTMLInputElement;
+  private readonly hiddenDebugReportsSpan: HTMLSpanElement;
+  private readonly sendReportsButton: HTMLButtonElement;
+  private sentOrDroppedReports: T[] = [];
+  private storedReports: T[] = [];
+  private debugReports: T[] = [];
 
   constructor(
-      showDebugReportsContainer: HTMLElement,
+      cols: Array<Column<T>>, showDebugReportsContainer: HTMLElement,
       sendReportsButton: HTMLButtonElement) {
-    super();
+    super(
+        commonReportTableColumns<T>().concat(cols),
+        5,  // Sort by report time by default; the extra column is added below
+        'No sent or pending reports.',
+    );
+
+    // This can't be included in the super call above, as `this` can't be
+    // accessed until after `super` returns.
+    const selectionColumn = new SelectionColumn<T>(this);
+    this.cols.unshift(selectionColumn);
 
     const showDebugReportsCheckbox =
         showDebugReportsContainer.querySelector<HTMLInputElement>(
@@ -519,18 +519,13 @@
 
     this.sendReportsButton = sendReportsButton;
 
-    this.selectionColumn = new SelectionColumn(this);
-
-    this.emptyRowText = 'No sent or pending reports.';
-
     this.showDebugReportsCheckbox.addEventListener(
         'input', () => this.notifyRowsChanged());
 
     this.sendReportsButton.addEventListener('click', () => this.sendReports_());
-    this.selectionColumn.selectionChangedListeners.add(
-        (anySelected: boolean) => {
-          this.sendReportsButton.disabled = !anySelected;
-        });
+    selectionColumn.selectionChangedListeners.add((anySelected: boolean) => {
+      this.sendReportsButton.disabled = !anySelected;
+    });
 
     this.rowsChangedListeners.add(() => this.updateHiddenDebugReportsSpan_());
   }
@@ -547,12 +542,12 @@
     return rows;
   }
 
-  setStoredReports(storedReports: Report[]) {
+  setStoredReports(storedReports: T[]) {
     this.storedReports = storedReports;
     this.notifyRowsChanged();
   }
 
-  addSentOrDroppedReport(report: Report) {
+  addSentOrDroppedReport(report: T) {
     // Prevent the page from consuming ever more memory if the user leaves the
     // page open for a long time.
     if (this.sentOrDroppedReports.length + this.debugReports.length >= 1000) {
@@ -614,51 +609,37 @@
   }
 }
 
-class EventLevelReportTableModel extends ReportTableModel {
+class EventLevelReportTableModel extends ReportTableModel<EventLevelReport> {
   constructor(
       showDebugReportsContainer: HTMLElement,
       sendReportsButton: HTMLButtonElement) {
-    super(showDebugReportsContainer, sendReportsButton);
-
-    this.cols = [
-      this.selectionColumn,
-      new CodeColumn<Report>('Report Body', (e) => e.reportBody),
-      new ValueColumn<Report, string>('Status', (e) => e.status),
-      new ReportUrlColumn(),
-      new DateColumn<Report>('Trigger Time', (e) => e.triggerTime),
-      new DateColumn<Report>('Report Time', (e) => e.reportTime),
-      new ValueColumn<Report, bigint>(
-          'Report Priority', (e) => (e as EventLevelReport).reportPriority),
-      new ValueColumn<Report, string>(
-          'Randomized Report',
-          (e) => (e as EventLevelReport).attributedTruthfully ? 'no' : 'yes'),
-    ];
-
-    // Sort by report time by default.
-    this.sortIdx = 5;
+    super(
+        [
+          new ValueColumn<EventLevelReport, bigint>(
+              'Report Priority', (e) => e.reportPriority),
+          new ValueColumn<EventLevelReport, string>(
+              'Randomized Report',
+              (e) => e.attributedTruthfully ? 'no' : 'yes'),
+        ],
+        showDebugReportsContainer,
+        sendReportsButton,
+    );
   }
 }
 
-class AggregatableAttributionReportTableModel extends ReportTableModel {
+class AggregatableAttributionReportTableModel extends
+    ReportTableModel<AggregatableAttributionReport> {
   constructor(
       showDebugReportsContainer: HTMLElement,
       sendReportsButton: HTMLButtonElement) {
-    super(showDebugReportsContainer, sendReportsButton);
-
-    this.cols = [
-      this.selectionColumn,
-      new CodeColumn<Report>('Report Body', (e) => e.reportBody),
-      new ValueColumn<Report, string>('Status', (e) => e.status),
-      new ReportUrlColumn(),
-      new DateColumn<Report>('Trigger Time', (e) => e.triggerTime),
-      new DateColumn<Report>('Report Time', (e) => e.reportTime),
-      new CodeColumn<Report>(
-          'Histograms',
-          (e) => (e as AggregatableAttributionReport).contributions),
-    ];
-
-    // Sort by report time by default.
-    this.sortIdx = 5;
+    super(
+        [
+          new CodeColumn<AggregatableAttributionReport>(
+              'Histograms', (e) => e.contributions),
+        ],
+        showDebugReportsContainer,
+        sendReportsButton,
+    );
   }
 }
 
@@ -684,22 +665,19 @@
 }
 
 class DebugReportTableModel extends TableModel<DebugReport> {
-  debugReports: DebugReport[] = [];
+  private debugReports: DebugReport[] = [];
 
   constructor() {
-    super();
-
-    this.cols = [
-      new DateColumn<DebugReport>('Time', (e) => e.time),
-      new ValueColumn<DebugReport, string>('URL', (e) => e.url),
-      new ValueColumn<DebugReport, string>('Status', (e) => e.status),
-      new CodeColumn<DebugReport>('Body', (e) => e.body),
-    ];
-
-    // Sort by report time by default.
-    this.sortIdx = 0;
-
-    this.emptyRowText = 'No verbose debug reports.';
+    super(
+        [
+          new DateColumn<DebugReport>('Time', (e) => e.time),
+          new ValueColumn<DebugReport, string>('URL', (e) => e.url),
+          new ValueColumn<DebugReport, string>('Status', (e) => e.status),
+          new CodeColumn<DebugReport>('Body', (e) => e.body),
+        ],
+        0,  // Sort by report time by default.
+        'No verbose debug reports.',
+    );
   }
 
   // TODO(apaseltiner): Style error rows like `ReportTableModel`
@@ -883,21 +861,18 @@
 }
 
 class LogTableModel extends TableModel<Log> {
-  logs: Log[] = [];
+  private logs: Log[] = [];
 
   constructor() {
-    super();
-
-    this.cols = [
-      new DateColumn<Log>('Timestamp', (e) => e.timestamp),
-      new LogDescriptionColumn(),
-      new LogMetadataColumn(),
-    ];
-
-    this.emptyRowText = 'No logs.';
-
-    // Sort by time by default.
-    this.sortIdx = 0;
+    super(
+        [
+          new DateColumn<Log>('Timestamp', (e) => e.timestamp),
+          new LogDescriptionColumn(),
+          new LogMetadataColumn(),
+        ],
+        0,  // Sort by time by default.
+        'No logs.',
+    );
   }
 
   override getRows() {
@@ -1262,14 +1237,15 @@
   triggerTable.setModel(triggerTableModel!);
 
   const reportTable =
-      document.querySelector<AttributionInternalsTableElement<Report>>(
-          '#reportTable');
+      document
+          .querySelector<AttributionInternalsTableElement<EventLevelReport>>(
+              '#reportTable');
   assert(reportTable);
   reportTable.setModel(eventLevelReportTableModel!);
 
-  const aggregatableReportTable =
-      document.querySelector<AttributionInternalsTableElement<Report>>(
-          '#aggregatableReportTable');
+  const aggregatableReportTable = document.querySelector<
+      AttributionInternalsTableElement<AggregatableAttributionReport>>(
+      '#aggregatableReportTable');
   assert(aggregatableReportTable);
   aggregatableReportTable.setModel(aggregatableAttributionReportTableModel!);
 
diff --git a/content/browser/resources/attribution_reporting/table_model.ts b/content/browser/resources/attribution_reporting/table_model.ts
index b9c03b7..4deba27 100644
--- a/content/browser/resources/attribution_reporting/table_model.ts
+++ b/content/browser/resources/attribution_reporting/table_model.ts
@@ -3,28 +3,25 @@
 // found in the LICENSE file.
 
 export interface Column<T> {
-  compare: ((a: T, b: T) => number)|null;
+  readonly compare?: (a: T, b: T) => number;
 
   render(td: HTMLElement, row: T): void;
 
   renderHeader(th: HTMLElement): void;
 }
 
-export class TableModel<T> {
-  cols: Array<Column<T>> = [];
-  emptyRowText: string = '';
-  sortIdx: number = -1;
-  rowsChangedListeners: Set<() => void>;
+export abstract class TableModel<T> {
+  readonly rowsChangedListeners: Set<() => void> = new Set();
 
-  constructor() {
-    this.rowsChangedListeners = new Set();
-  }
+  constructor(
+      public readonly cols: Array<Column<T>>,
+      public sortIdx: number,
+      public readonly emptyRowText: string,
+  ) {}
 
   styleRow(_tr: Element, _data: T) {}
 
-  getRows(): T[] {
-    return [];
-  }
+  abstract getRows(): T[];
 
   notifyRowsChanged() {
     this.rowsChangedListeners.forEach(f => f());
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 44f90bc..4b0346f8 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -4856,7 +4856,7 @@
       // Instead, we just navigate directly on the relevant frame
       // tree.
       if (frame_tree.type() == FrameTree::Type::kPrerender ||
-          frame_tree.type() == FrameTree::Type::kFencedFrame) {
+          frame_tree_node->IsInFencedFrameTree()) {
         DCHECK_EQ(params.disposition, WindowOpenDisposition::CURRENT_TAB);
         frame_tree.controller().LoadURLWithParams(
             NavigationController::LoadURLParams(params));
@@ -7973,7 +7973,7 @@
   if (inner_contents) {
     // An inner WebContents is not created from Fenced Frames so we
     // shouldn't end up in this branch.
-    DCHECK_NE(FrameTree::Type::kFencedFrame, node->frame_tree().type());
+    DCHECK(!node->IsInFencedFrameTree());
 
     // |this| is an outer WebContents and |node| represents an inner
     // WebContents. Transfer the focus to the inner contents if |this| is
@@ -7989,7 +7989,7 @@
     // A GuestView embedding a fenced frame will have an
     // OuterContentsFrameTreeNode. However, it will not have the same site
     // instance because a FencedFrame creates a new BrowsingInstance.
-    DCHECK_NE(FrameTree::Type::kFencedFrame, node->frame_tree().type());
+    DCHECK(!node->IsInFencedFrameTree());
 
     // |this| is an inner WebContents, |node| is its main FrameTreeNode and
     // the outer WebContents FrameTreeNode is at |source|'s SiteInstance.
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index ff6b2e9..54df36f6 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -608,6 +608,7 @@
 
   public_deps = [
     "//cc/mojom",
+    "//components/attribution_reporting:mojom",
     "//components/services/storage/shared_storage/public/mojom",
     "//components/variations:variations_mojom",
     "//content/public/common:interfaces",
diff --git a/content/common/renderer.mojom b/content/common/renderer.mojom
index 8e229371..48eab92 100644
--- a/content/common/renderer.mojom
+++ b/content/common/renderer.mojom
@@ -4,6 +4,7 @@
 
 module content.mojom;
 
+import "components/attribution_reporting/os_support.mojom";
 import "content/common/agent_scheduling_group.mojom";
 import "content/common/native_types.mojom";
 import "ipc/ipc.mojom";
@@ -13,7 +14,6 @@
 import "skia/public/mojom/skcolor.mojom";
 import "third_party/blink/public/mojom/browser_interface_broker.mojom";
 import "third_party/blink/public/mojom/user_agent/user_agent_metadata.mojom";
-import "third_party/blink/public/mojom/conversions/attribution_reporting.mojom";
 import "ui/color/color_id.mojom";
 
 struct UpdateScrollbarThemeParams {
@@ -165,7 +165,7 @@
   // Set the OS-level support for Attribution Reporting indicating whether
   // OS-level attribution is enabled.
   SetOsSupportForAttributionReporting(
-      blink.mojom.AttributionOsSupport os_support);
+      attribution_reporting.mojom.OsSupport os_support);
 
   // Initialize renderer user agent string, user agent metadata and CORS exempt
   // header list on renderer startup.
@@ -193,5 +193,5 @@
                      string reduced_user_agent,
                      blink.mojom.UserAgentMetadata metadata,
                      array<string> cors_exempt_header_list,
-                     blink.mojom.AttributionOsSupport attribution_os_support);
+                     attribution_reporting.mojom.OsSupport attribution_os_support);
 };
diff --git a/content/public/browser/preloading.h b/content/public/browser/preloading.h
index f7cb32e6..496cccf 100644
--- a/content/public/browser/preloading.h
+++ b/content/public/browser/preloading.h
@@ -157,6 +157,11 @@
   // Preloading was ineligible because the Battery Saver setting was enabled.
   kBatterySaverEnabled = 14,
 
+  // Values between `kPreloadingEligibilityCommonEnd` (inclusive) and
+  // `kPreloadingEligibilityContentEnd` (exclusive) are reserved for enums
+  // defined under `//content`.
+  kPreloadingEligibilityCommonEnd = 50,
+
   // TODO(crbug.com/1309934): Add more specific ineligibility reasons subject to
   // each preloading operation
   // This constant is used to define the value from which embedders can add more
diff --git a/content/public/browser/render_process_host.h b/content/public/browser/render_process_host.h
index ab0913e..cfe4a79 100644
--- a/content/public/browser/render_process_host.h
+++ b/content/public/browser/render_process_host.h
@@ -19,6 +19,7 @@
 #include "base/supports_user_data.h"
 #include "base/tracing/protos/chrome_track_event.pbzero.h"
 #include "build/build_config.h"
+#include "components/attribution_reporting/os_support.mojom-forward.h"
 #include "content/common/content_export.h"
 #include "ipc/ipc_listener.h"
 #include "ipc/ipc_sender.h"
@@ -35,7 +36,6 @@
 #include "third_party/blink/public/mojom/background_sync/background_sync.mojom.h"
 #include "third_party/blink/public/mojom/buckets/bucket_manager_host.mojom-forward.h"
 #include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom-forward.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom-forward.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom.h"
 #include "third_party/blink/public/mojom/filesystem/file_system.mojom-forward.h"
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-forward.h"
@@ -689,7 +689,7 @@
   // See
   // https://github.com/WICG/attribution-reporting-api/blob/main/app_to_web.md.
   virtual void SetOsSupportForAttributionReporting(
-      blink::mojom::AttributionOsSupport os_support) = 0;
+      attribution_reporting::mojom::OsSupport os_support) = 0;
 
   // Static management functions -----------------------------------------------
 
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index eb2c037f..04740b4 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -706,6 +706,11 @@
              "NavigationNetworkResponseQueue",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Preconnects socket at the construction of NavigationRequest.
+BASE_FEATURE(kNavigationRequestPreconnect,
+             "NavigationRequestPreconnect",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // If the network service is enabled, runs it in process.
 BASE_FEATURE(kNetworkServiceInProcess,
              "NetworkServiceInProcess2",
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index ebb46e20f..aa57d09 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -160,6 +160,7 @@
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kMojoVideoCaptureSecondary);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kMouseSubframeNoImplicitCapture);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kNavigationNetworkResponseQueue);
+CONTENT_EXPORT BASE_DECLARE_FEATURE(kNavigationRequestPreconnect);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kNetworkQualityEstimatorWebHoldback);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kNetworkServiceInProcess);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kNeverSlowMode);
diff --git a/content/public/renderer/render_thread.h b/content/public/renderer/render_thread.h
index 1c39dd9..13b13fa6 100644
--- a/content/public/renderer/render_thread.h
+++ b/content/public/renderer/render_thread.h
@@ -12,10 +12,10 @@
 #include "base/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/task/single_thread_task_runner.h"
+#include "components/attribution_reporting/os_support.mojom-forward.h"
 #include "content/common/content_export.h"
 #include "content/public/child/child_thread.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom-forward.h"
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/perfetto/include/perfetto/tracing/traced_proto.h"
 
@@ -122,7 +122,7 @@
   // Returns whether OS-level support is enabled for Attribution Reporting API.
   // See
   // https://github.com/WICG/attribution-reporting-api/blob/main/app_to_web.md.
-  virtual blink::mojom::AttributionOsSupport
+  virtual attribution_reporting::mojom::OsSupport
   GetOsSupportForAttributionReporting() = 0;
 };
 
diff --git a/content/public/test/mock_render_process_host.h b/content/public/test/mock_render_process_host.h
index a76cf79..d7d78cf 100644
--- a/content/public/test/mock_render_process_host.h
+++ b/content/public/test/mock_render_process_host.h
@@ -23,6 +23,7 @@
 #include "base/observer_list.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -34,7 +35,6 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "net/base/network_isolation_key.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom.h"
 
 #if BUILDFLAG(IS_ANDROID)
 #include "base/android/child_process_binding_types.h"
@@ -285,7 +285,7 @@
   std::string GetInfoForBrowserContextDestructionCrashReporting() override;
   void WriteIntoTrace(perfetto::TracedProto<TraceProto> proto) const override;
   void SetOsSupportForAttributionReporting(
-      blink::mojom::AttributionOsSupport os_support) override {}
+      attribution_reporting::mojom::OsSupport os_support) override {}
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   void ReinitializeLogging(uint32_t logging_dest,
diff --git a/content/public/test/mock_render_thread.cc b/content/public/test/mock_render_thread.cc
index 40742784..444b2227 100644
--- a/content/public/test/mock_render_thread.cc
+++ b/content/public/test/mock_render_thread.cc
@@ -12,6 +12,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/unguessable_token.h"
 #include "build/build_config.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "content/common/associated_interfaces.mojom.h"
 #include "content/common/frame.mojom.h"
 #include "content/common/render_message_filter.mojom.h"
@@ -25,7 +26,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/dom_storage/session_storage_namespace_id.h"
 #include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom.h"
 #include "third_party/blink/public/mojom/page/widget.mojom.h"
 #include "third_party/blink/public/mojom/widget/platform_widget.mojom.h"
 
@@ -331,9 +331,9 @@
   page_broadcasts_.clear();
 }
 
-blink::mojom::AttributionOsSupport
+attribution_reporting::mojom::OsSupport
 MockRenderThread::GetOsSupportForAttributionReporting() {
-  return blink::mojom::AttributionOsSupport::kDisabled;
+  return attribution_reporting::mojom::OsSupport::kDisabled;
 }
 
 }  // namespace content
diff --git a/content/public/test/mock_render_thread.h b/content/public/test/mock_render_thread.h
index aeb9ae6..db7b69a 100644
--- a/content/public/test/mock_render_thread.h
+++ b/content/public/test/mock_render_thread.h
@@ -95,7 +95,7 @@
   void WriteIntoTrace(
       perfetto::TracedProto<perfetto::protos::pbzero::RenderProcessHost> proto)
       override;
-  blink::mojom::AttributionOsSupport GetOsSupportForAttributionReporting()
+  attribution_reporting::mojom::OsSupport GetOsSupportForAttributionReporting()
       override;
 
   // Returns a new, unique routing ID that can be assigned to the next view,
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index fbab90b..2ac91dc 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -910,7 +910,7 @@
     const std::string& reduced_user_agent,
     const blink::UserAgentMetadata& user_agent_metadata,
     const std::vector<std::string>& cors_exempt_header_list,
-    blink::mojom::AttributionOsSupport attribution_os_support) {
+    attribution_reporting::mojom::OsSupport attribution_os_support) {
   DCHECK(user_agent_.IsNull());
   DCHECK(reduced_user_agent_.IsNull());
   DCHECK(full_user_agent_.IsNull());
@@ -1829,13 +1829,13 @@
   return rendering_color_space_;
 }
 
-blink::mojom::AttributionOsSupport
+attribution_reporting::mojom::OsSupport
 RenderThreadImpl::GetOsSupportForAttributionReporting() {
   return attribution_os_support_;
 }
 
 void RenderThreadImpl::SetOsSupportForAttributionReporting(
-    blink::mojom::AttributionOsSupport attribution_os_support) {
+    attribution_reporting::mojom::OsSupport attribution_os_support) {
   attribution_os_support_ = attribution_os_support;
 }
 
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index c313e59f..d3dc1320 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -29,6 +29,7 @@
 #include "base/types/pass_key.h"
 #include "build/build_config.h"
 #include "cc/tiles/gpu_image_decode_cache.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "content/child/child_thread_impl.h"
 #include "content/common/agent_scheduling_group.mojom.h"
 #include "content/common/content_export.h"
@@ -55,7 +56,6 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
 #include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom.h"
 #include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h"
 #include "third_party/blink/public/platform/url_loader_throttle_provider.h"
 #include "third_party/blink/public/platform/web_connection_type.h"
@@ -192,7 +192,7 @@
   void WriteIntoTrace(
       perfetto::TracedProto<perfetto::protos::pbzero::RenderProcessHost> proto)
       override;
-  blink::mojom::AttributionOsSupport GetOsSupportForAttributionReporting()
+  attribution_reporting::mojom::OsSupport GetOsSupportForAttributionReporting()
       override;
 
   // IPC::Listener implementation via ChildThreadImpl:
@@ -428,7 +428,7 @@
       const std::string& reduced_user_agent,
       const blink::UserAgentMetadata& user_agent_metadata,
       const std::vector<std::string>& cors_exempt_header_list,
-      blink::mojom::AttributionOsSupport attribution_os_support) override;
+      attribution_reporting::mojom::OsSupport attribution_os_support) override;
   void UpdateScrollbarTheme(
       mojom::UpdateScrollbarThemeParamsPtr params) override;
   void OnSystemColorsChanged(int32_t aqua_color_variant,
@@ -447,7 +447,7 @@
   void SetIsCrossOriginIsolated(bool value) override;
   void SetIsIsolatedContext(bool value) override;
   void SetOsSupportForAttributionReporting(
-      blink::mojom::AttributionOsSupport os_support) override;
+      attribution_reporting::mojom::OsSupport os_support) override;
   void OnMemoryPressure(
       base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
 
@@ -492,7 +492,7 @@
   blink::WebString reduced_user_agent_;
   blink::UserAgentMetadata user_agent_metadata_;
 
-  blink::mojom::AttributionOsSupport attribution_os_support_;
+  attribution_reporting::mojom::OsSupport attribution_os_support_;
 
   // Sticky once true, indicates that compositing is done without Gpu, so
   // resources given to the compositor or to the viz service should be
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
index fa98109..c313231 100644
--- a/content/renderer/renderer_blink_platform_impl.cc
+++ b/content/renderer/renderer_blink_platform_impl.cc
@@ -33,6 +33,7 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "cc/trees/raster_context_provider_wrapper.h"
+#include "components/attribution_reporting/os_support.mojom.h"
 #include "components/url_formatter/url_formatter.h"
 #include "components/viz/common/features.h"
 #include "content/child/child_process.h"
@@ -86,7 +87,6 @@
 #include "third_party/blink/public/common/origin_trials/trial_token_validator.h"
 #include "third_party/blink/public/common/security/protocol_handler_security_level.h"
 #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom.h"
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
 #include "third_party/blink/public/platform/file_path_conversion.h"
@@ -1048,12 +1048,12 @@
   return io_thread_id_;
 }
 
-blink::mojom::AttributionOsSupport
+attribution_reporting::mojom::OsSupport
 RendererBlinkPlatformImpl::GetOsSupportForAttributionReporting() {
   auto* render_thread = RenderThreadImpl::current();
   // RenderThreadImpl is null in some tests.
   if (!render_thread)
-    return blink::mojom::AttributionOsSupport::kDisabled;
+    return attribution_reporting::mojom::OsSupport::kDisabled;
   return render_thread->GetOsSupportForAttributionReporting();
 }
 
diff --git a/content/renderer/renderer_blink_platform_impl.h b/content/renderer/renderer_blink_platform_impl.h
index 18b60216..3ae765e 100644
--- a/content/renderer/renderer_blink_platform_impl.h
+++ b/content/renderer/renderer_blink_platform_impl.h
@@ -228,7 +228,7 @@
       const blink::WebURL& url,
       blink::WebVector<blink::WebContentSecurityPolicyHeader>* csp) override;
   base::PlatformThreadId GetIOThreadId() const override;
-  blink::mojom::AttributionOsSupport GetOsSupportForAttributionReporting()
+  attribution_reporting::mojom::OsSupport GetOsSupportForAttributionReporting()
       override;
   scoped_refptr<base::SingleThreadTaskRunner> VideoFrameCompositorTaskRunner()
       override;
diff --git a/docs/adding_to_third_party.md b/docs/adding_to_third_party.md
index ab88e05..98531ca1 100644
--- a/docs/adding_to_third_party.md
+++ b/docs/adding_to_third_party.md
@@ -22,8 +22,8 @@
 
 To make sure the inclusion of a new third_party project makes sense for the
 Chromium project, you should first obtain
-[Chrome Eng Review](../ENG_REVIEW_OWNERS) approval. Please include the following information in an
-email to chrome-eng-review@google.com:
+[Chrome ATL](../ATL_OWNERS) approval. Please include the following information in an
+email to chrome-atls@google.com:
 * Motivation of your project
 * Design docs
 * Additional checkout size
@@ -31,8 +31,8 @@
 * Binary size increase on Android ([official](https://www.chromium.org/developers/gn-build-configuration)  builds)
 * Binary size increase on Windows
 
-Googlers can access [go/chrome-eng-review](https://goto.google.com/chrome-eng-review) and review
-existing topics in g/chrome-eng-review, and can also come to office hours to ask
+Googlers can access [go/chrome-atls](https://goto.google.com/chrome-atls) and review
+existing topics in g/chrome-atls, and can also come to office hours to ask
 questions.
 
 ### Rust
@@ -50,7 +50,7 @@
 (memory/crashes/undefined behavior) bugs, when compared to the existing
 third-party library and related C++ code required to use the library. We realize
 assessing risk is quite complex and very nuanced. If this is the criteria by
-which the third-party library is being added, chrome-eng-review@google.com and
+which the third-party library is being added, chrome-atls@google.com and
 chrome-rust@google.com may ask for more data.
 
 Support for third-party libraries written in Rust is in active development. If
@@ -223,7 +223,7 @@
 Non-Googlers can email one of the people in
 [//third_party/OWNERS](../third_party/OWNERS) for help.
 
-* Make sure you have the approval from Chrome Eng Review as mentioned
+* Make sure you have the approval from Chrome ATLs as mentioned
   [above](#before-you-start).
 * Get security@chromium.org (or chrome-security@google.com, Google-only)
   approval. Email the list with relevant details and a link to the CL.
@@ -242,8 +242,8 @@
 * Lastly, if all other steps are complete, get a positive code review from a
   member of [//third_party/OWNERS](../third_party/OWNERS) to land the change.
 
-Please send separate emails to the eng review and security@chromium.org.
-You can skip the eng review and security@chromium.org when you are only moving
+Please send separate emails to the ATLs and security@chromium.org.
+You can skip the ATL review and security@chromium.org when you are only moving
 existing directories in Chromium to //third_party/.
 
 Subsequent changes don't normally require third-party-owners or security
diff --git a/docs/code_review_owners.md b/docs/code_review_owners.md
index 7f44b389..451b602 100644
--- a/docs/code_review_owners.md
+++ b/docs/code_review_owners.md
@@ -40,8 +40,7 @@
 only mechanical changes associated with changes in //base/ APIs, //build/ APIs,
 //content/ APIs, //url/ APIs or //third_party/blink/public/APIs, the API owners can set `Owners-Override`.
 
-For other one-off CLs, [Chrome Eng Review members](https://chromium.googlesource.com/chromium/src/+/HEAD/ENG_REVIEW_OWNERS)
-can set `Owners-Override`.
+For other one-off CLs, [Chrome ATLs](../ATL_OWNERS) can set `Owners-Override`.
 
 ### How does Rubber Stamper bot work?
 
diff --git a/docs/code_reviews.md b/docs/code_reviews.md
index 7516ffc..ed4aed3 100644
--- a/docs/code_reviews.md
+++ b/docs/code_reviews.md
@@ -120,7 +120,7 @@
       be escalated to the owners of the parent directory (or directories)
       as necessary to provide enough of votes.
     * If there are objections, then the decision should be escalated to
-      the [../CHROME_ENG_REVIEW](//CHROME_ENG_REVIEW) for resolution.
+      the [../ATL_OWNERS](../ATL_OWNERS) for resolution.
 
 Note: For the purpose of not slowing down code review, Chromium removes
 inactive owners (e.g., those who made no contributions for multiple quarters)
@@ -152,20 +152,20 @@
 
 The text `set noparent` will stop owner propagation from parent directories.
 This should be rarely used. If you want to use `set noparent` except for IPC
-related files, please first reach out to chrome-eng-review@google.com.
+related files, please first reach out to chrome-atls@google.com.
 
 You have to use `set noparent` together with a reference to a file that lists
 the owners for the given use case. Approved use cases are listed in
 `//build/OWNERS.setnoparent`. Owners listed in those files are expected to
-execute special governance functions such as eng review or ipc security review.
+execute special governance functions such as ATL reviews or ipc security review.
 Every set of owners should implement their own means of auditing membership. The
 minimum expectation is that membership in those files is reevaluated on
 project, or affiliation changes.
 
-In this example, only the eng reviewers are owners:
+In this example, only the ATLs are owners:
 ```
 set noparent
-file://ENG_REVIEW_OWNERS
+file://ATL_OWNERS
 ```
 
 The `per-file` directive allows owners to be added that apply only to files
@@ -196,7 +196,7 @@
 [sheriffs](sheriffs.md), Release Program Managers,
 [Large Scale Changes](#large-scale-changes),
 [Global Approvers](#global-approvals) reviewers,
-[Chrome Eng Review members](https://chromium.googlesource.com/chromium/src/+/HEAD/ENG_REVIEW_OWNERS)
+[Chrome ATLs](../ATL_OWNERS)
 have this capability. The power to use Owners-Override should be restricted
 as follows:
 
@@ -209,10 +209,10 @@
     mechanical CLs associated with their API changes. For example,
     //base/OWNERS can set Owners-Override on mechanical CLs associated with
     //base/ API changes.
-  * Chrome Eng Review members can set Owners-Override on any changes to help
-    with cases that cannot be handled by the above groups and expedite CLs
-    when LSC is too heavyweight.. However, please use one of the above groups
-    before asking Chrome Eng Review members.
+  * Chrome ATLs can set Owners-Override on any changes to help with cases that
+    cannot be handled by the above groups and expedite CLs when LSC is too
+    heavyweight. However, please use one of the above groups before asking
+    Chrome ATLs.
 
 When you need Owners-Override on sheriffing CLs, please reach out to the
 Active Sheriffs and Release Program Managers first. If none of them is
@@ -233,7 +233,7 @@
 mechanical updates to the affected directories.
 
 If you are making one-off CLs that touch many directories and cannot be
-handled by the global approvers, you can ask one of Chrome Eng Review members.
+handled by the global approvers, you can ask one of Chrome ATLs.
 
 ### Large Scale Changes
 You can use the [Large Scale Changes](process/lsc/large_scale_changes.md)
diff --git a/docs/process/lsc/large_scale_changes.md b/docs/process/lsc/large_scale_changes.md
index 7134799..e403380 100644
--- a/docs/process/lsc/large_scale_changes.md
+++ b/docs/process/lsc/large_scale_changes.md
@@ -16,7 +16,7 @@
 
 Fixing old mistakes, crufty interfaces, and bad usage requires making many small changes across the codebase. Individually, these changes are tiny, but taken all together, they allow us to deprecate old interfaces and pay down our technical debt. The simpler we can make the code base, the more that developers can focus on their actual problems, instead of coping with artificial complexity because of accumulated cruft.
 
-Although there are many ways that these changes can be handled, most will be handled by an Eng Review reviewer and a domain reviewer.
+Although there are many ways that these changes can be handled, most will be handled by an ATL reviewer and a domain reviewer.
 
 ## What is it NOT good for? {#what-is-it-not-good-for}
 
@@ -32,9 +32,9 @@
 
 ### Why do we have this process? {#why-do-we-have-this-process}
 
-At the highest level, we want to make sure anything going out in tens/hundreds/thousands of CLs is moving the codebase in the right direction and that it's worth the work it takes to get such changes tested, reviewed, and submitted. Eng Review will rarely say "This isn't impactful enough," although it may happen (especially in cases where there is little or no distinct benefit). The more common role of the review is to ensure that the necessary documentation is in place: you will be sending CLs that are seen by dozens or hundreds of engineers who may not have any knowledge about your change. Some CL description updates and documentation can go a long way to making the process smooth.
+At the highest level, we want to make sure anything going out in tens/hundreds/thousands of CLs is moving the codebase in the right direction and that it's worth the work it takes to get such changes tested, reviewed, and submitted. ATLs will rarely say "This isn't impactful enough," although it may happen (especially in cases where there is little or no distinct benefit). The more common role of the review is to ensure that the necessary documentation is in place: you will be sending CLs that are seen by dozens or hundreds of engineers who may not have any knowledge about your change. Some CL description updates and documentation can go a long way to making the process smooth.
 
-In Chrome, we will grant the LSC Requestor the Gerrit Global Owners Approver power for the duration of their change, i.e. a member of Eng Review will approve the LSC Proposal which when then gives the LSC Requester the ability to bypass OWNERS in the interest of efficiency. (For external contributors, a Googler sponsor will act as the Global Owners Approver power holder. If you’re an external contributor, everything else is the same.) Each CL will still need a second human to review it to satisfy “two sets of eyes” code review policy requirements. This reduces the code-review burden on the rest of Chrome reviewers and increases the rate at which your change gets submitted. However, this also means that these kinds of LSCs have a high bar of being low-risk and non-controversial.
+In Chrome, we will grant the LSC Requestor the Gerrit Global Owners Approver power for the duration of their change, i.e. a member of ATLs will approve the LSC Proposal which when then gives the LSC Requester the ability to bypass OWNERS in the interest of efficiency. (For external contributors, a Googler sponsor will act as the Global Owners Approver power holder. If you’re an external contributor, everything else is the same.) Each CL will still need a second human to review it to satisfy “two sets of eyes” code review policy requirements. This reduces the code-review burden on the rest of Chrome reviewers and increases the rate at which your change gets submitted. However, this also means that these kinds of LSCs have a high bar of being low-risk and non-controversial.
 
 As the LSC Requestor, the process of getting the LSC fully submitted looks like:
 
@@ -77,12 +77,12 @@
   * If all sub-CLs affect the same OWNERS, you do not need to follow the LSC process and instead can talk to an area owner for how to proceed with your change.
 * **What do I have to do?** Follow the steps at [Chrome LSC Workflow](lsc_workflow.md).
 * **How long will this process take?** You should expect an initial response from a cleanup approver within two days.
-* **I'm in a hurry; Is there a fast track?** Eng Review may fast-track low-risk changes, with a response estimated between a few hours to one business day. If you'd like to request fast tracking, email [lsc-review](https://groups.google.com/a/chromium.org/d/forum/lsc-review) with a link to the doc that you created end of [Chrome LSC Workflow](lsc_workflow.md) and we'll get to it as quickly as we can.
+* **I'm in a hurry; Is there a fast track?** ATLs may fast-track low-risk changes, with a response estimated between a few hours to one business day. If you'd like to request fast tracking, email [lsc-review](https://groups.google.com/a/chromium.org/d/forum/lsc-review) with a link to the doc that you created end of [Chrome LSC Workflow](lsc_workflow.md) and we'll get to it as quickly as we can.
 * **Why this process?** Changes that operate broadly across the codebase affect many engineers and teams and these changes may generate discussion on the change or the best way to roll it out. This process is in place to make sure that these changes are useful, well-communicated, and to minimize the risk of having to attempt difficult rollbacks.
 * **I have an idea for a change, but I’m not sure who to ask about it?** Definitely email the lsc-review@chromium.org list. We should be able to give you at least a vague idea of whether your idea has merit and who to approach as a domain expert to move ahead.
 * **My LSC touches third_party, do I have to do anything special?** These changes are likely to be rare. If it does occur, please obey the normal rules for third_party changes. Be aware that some parts of third_party have an external codebase as their source-of-truth, and so are mechanically generated (e.g. by Copybara [[external](https://opensource.google/projects/copybara), [internal, Googlers only](http://go/copybara)]). This means that your changes will be overwritten by the next import.
 * **My proposal was accepted for "local approval", now what?** Get your CLs submitted. For changes at this scale, it's usually best to use Find Owners and Auto-Submit. If anything surprising comes up during this process, or you need to ask questions to someone familiar with the process, try your assigned committee reviewer (see your LSC document). As with any wide-scale change, you should also consider announcing it on a mailing list that covers the target audience.
-* **My proposal was accepted for "global approval", now what?** Contact your Eng Review reviewer: they should have granted you (or your Googler sponsor) Global Approver power. The techniques for getting these submitted via global owner approval vary based on what is being changed, your approver will know what's what for your particular LSC. As with any wide-scale change, you should also consider announcing it on a mailing list. Any coworker or contributor can be your second-set of eyes to mass Code-Review +1 all of your CLs.
+* **My proposal was accepted for "global approval", now what?** Contact your ATL reviewer: they should have granted you (or your Googler sponsor) Global Approver power. The techniques for getting these submitted via global owner approval vary based on what is being changed, your approver will know what's what for your particular LSC. As with any wide-scale change, you should also consider announcing it on a mailing list. Any coworker or contributor can be your second-set of eyes to mass Code-Review +1 all of your CLs.
 * **My proposal requires multiple large changes, do I need multiple LSC docs & approvals?** No. You can use a single LSC document to describe your entire series of changes even if you are asking for a mix of local and global approvals. Be sure to break each change out into an easily identifiable step in the document.
 * **How do I deal with backsliding?** Backsliding (others submitting changes which reverse the intended change) is something which cannot be completely avoided in all cases. A useful techniques to reduce them is to use Tricium (see [go/luci/tricium [internal, Googlers only]](https://goto.google.com/luci/tricium) for details) or Presubmit.
 
diff --git a/docs/standards/positions/OWNERS b/docs/standards/positions/OWNERS
index b2326450b..d9a487a 100644
--- a/docs/standards/positions/OWNERS
+++ b/docs/standards/positions/OWNERS
@@ -5,5 +5,5 @@
 
 # Override the OWNERS: * from the parent directory.
 set noparent
-file://ENG_REVIEW_OWNERS
+file://ATL_OWNERS
 file://third_party/blink/API_OWNERS
diff --git a/docs/ui/views/views.png b/docs/ui/views/images/views.png
similarity index 100%
rename from docs/ui/views/views.png
rename to docs/ui/views/images/views.png
Binary files differ
diff --git a/docs/ui/views/images/widget_destruction_NWOW_MacViews.png b/docs/ui/views/images/widget_destruction_NWOW_MacViews.png
new file mode 100644
index 0000000..dbaf484c0
--- /dev/null
+++ b/docs/ui/views/images/widget_destruction_NWOW_MacViews.png
Binary files differ
diff --git a/docs/ui/views/images/widget_destruction_NWOW_desktop.png b/docs/ui/views/images/widget_destruction_NWOW_desktop.png
new file mode 100644
index 0000000..1f60201
--- /dev/null
+++ b/docs/ui/views/images/widget_destruction_NWOW_desktop.png
Binary files differ
diff --git a/docs/ui/views/images/widget_destruction_NWOW_non_desktop.png b/docs/ui/views/images/widget_destruction_NWOW_non_desktop.png
new file mode 100644
index 0000000..b670248
--- /dev/null
+++ b/docs/ui/views/images/widget_destruction_NWOW_non_desktop.png
Binary files differ
diff --git a/docs/ui/views/images/widget_destruction_WONW_MacViews.png b/docs/ui/views/images/widget_destruction_WONW_MacViews.png
new file mode 100644
index 0000000..5eb2365
--- /dev/null
+++ b/docs/ui/views/images/widget_destruction_WONW_MacViews.png
Binary files differ
diff --git a/docs/ui/views/images/widget_destruction_WONW_desktop.png b/docs/ui/views/images/widget_destruction_WONW_desktop.png
new file mode 100644
index 0000000..ea0095e
--- /dev/null
+++ b/docs/ui/views/images/widget_destruction_WONW_desktop.png
Binary files differ
diff --git a/docs/ui/views/images/widget_destruction_WONW_non_desktop.png b/docs/ui/views/images/widget_destruction_WONW_non_desktop.png
new file mode 100644
index 0000000..2b015ef
--- /dev/null
+++ b/docs/ui/views/images/widget_destruction_WONW_non_desktop.png
Binary files differ
diff --git a/docs/ui/views/overview.md b/docs/ui/views/overview.md
index 1a94ae5..e299757 100644
--- a/docs/ui/views/overview.md
+++ b/docs/ui/views/overview.md
@@ -132,7 +132,7 @@
 
 The overall structure of a Widget and its helper Views looks like this:
 
-![views](views.png)
+![views](images/views.png)
 
 ## Dialogs
 
diff --git a/docs/ui/views/widget_destruction.md b/docs/ui/views/widget_destruction.md
new file mode 100644
index 0000000..e446219
--- /dev/null
+++ b/docs/ui/views/widget_destruction.md
@@ -0,0 +1,22 @@
+This document describes the destruction procedure of Widget related classes.
+
+## Desktop Native Widget
+### NativeWidgetOwnsWidget
+![NativeWidgetOwnsWidget - DesktopNativeWidget](images/widget_destruction_NWOW_desktop.png)
+
+### WidgetOwnsNativeWidget
+![WidgetOwnsNativeWidget - DesktopNativeWidget](images/widget_destruction_WONW_desktop.png)
+
+## Native Widget
+### NativeWidgetOwnsWidget
+![NativeWidgetOwnsWidget - NativeWidget](images/widget_destruction_NWOW_non_desktop.png)
+
+### NativeWidgetOwnsWidget
+![WidgetOwnsNativeWidget - NativeWidget](images/widget_destruction_WONW_non_desktop.png)
+
+## MacViews
+### NativeWidgetOwnsWidget
+![NativeWidgetOwnsWidget - MacViews](images/widget_destruction_NWOW_MacViews.png)
+
+### WidgetOwnsNativeWidget
+![WidgetOwnsNativeWidget - MacViews](images/widget_destruction_WONW_MacViews.png)
\ No newline at end of file
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 5fc45e0..56f6267a 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1803,6 +1803,8 @@
   AUTOTESTPRIVATE_REFRESHREMOTECOMMANDS = 1740,
   FILESYSTEMPROVIDERINTERNAL_RESPONDTOMOUNTREQUEST = 1741,
   OS_DIAGNOSTICS_RUNEMMCLIFETIMEROUTINE = 1742,
+  AUTOFILLPRIVATE_SAVEIBAN = 1743,
+  AUTOFILLPRIVATE_GETIBANLIST = 1744,
   // Last entry: Add new entries above, then run:
   // tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/gpu/command_buffer/client/raster_implementation.cc b/gpu/command_buffer/client/raster_implementation.cc
index d6b2d402..bc77b13 100644
--- a/gpu/command_buffer/client/raster_implementation.cc
+++ b/gpu/command_buffer/client/raster_implementation.cc
@@ -1336,8 +1336,7 @@
                                           const gfx::Vector2dF& post_translate,
                                           const gfx::Vector2dF& post_scale,
                                           bool requires_clear,
-                                          size_t* max_op_size_hint,
-                                          bool preserve_recording) {
+                                          size_t* max_op_size_hint) {
   TRACE_EVENT1("gpu", "RasterImplementation::RasterCHROMIUM",
                "raster_chromium_id", ++raster_chromium_id_);
   DCHECK(max_op_size_hint);
@@ -1390,13 +1389,8 @@
           raster_properties_->can_use_lcd_text,
           capabilities().context_supports_distance_field_text,
           capabilities().max_texture_size));
-  if (preserve_recording) {
-    serializer.Serialize(&list->paint_op_buffer_, &temp_raster_offsets_,
-                         preamble);
-  } else {
-    auto* buffer = const_cast<cc::PaintOpBuffer*>(&list->paint_op_buffer_);
-    serializer.SerializeAndDestroy(buffer, &temp_raster_offsets_, preamble);
-  }
+  serializer.Serialize(&list->paint_op_buffer_, &temp_raster_offsets_,
+                       preamble);
   // TODO(piman): raise error if !serializer.valid()?
   op_serializer.SendSerializedData();
 }
diff --git a/gpu/command_buffer/client/raster_implementation.h b/gpu/command_buffer/client/raster_implementation.h
index bc4446e..4730b67 100644
--- a/gpu/command_buffer/client/raster_implementation.h
+++ b/gpu/command_buffer/client/raster_implementation.h
@@ -164,8 +164,7 @@
                       const gfx::Vector2dF& post_translate,
                       const gfx::Vector2dF& post_scale,
                       bool requires_clear,
-                      size_t* max_op_size_hint,
-                      bool preserve_recording = true) override;
+                      size_t* max_op_size_hint) override;
   SyncToken ScheduleImageDecode(base::span<const uint8_t> encoded_data,
                                 const gfx::Size& output_size,
                                 uint32_t transfer_cache_entry_id,
diff --git a/gpu/command_buffer/client/raster_implementation_gles.cc b/gpu/command_buffer/client/raster_implementation_gles.cc
index 7d95c363..f07b1c4 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.cc
+++ b/gpu/command_buffer/client/raster_implementation_gles.cc
@@ -237,8 +237,7 @@
     const gfx::Vector2dF& post_translate,
     const gfx::Vector2dF& post_scale,
     bool requires_clear,
-    size_t* max_op_size_hint,
-    bool preserve_recording) {
+    size_t* max_op_size_hint) {
   NOTREACHED();
 }
 
diff --git a/gpu/command_buffer/client/raster_implementation_gles.h b/gpu/command_buffer/client/raster_implementation_gles.h
index f0426407..7f412ac 100644
--- a/gpu/command_buffer/client/raster_implementation_gles.h
+++ b/gpu/command_buffer/client/raster_implementation_gles.h
@@ -107,8 +107,7 @@
                       const gfx::Vector2dF& post_translate,
                       const gfx::Vector2dF& post_scale,
                       bool requires_clear,
-                      size_t* max_op_size_hint,
-                      bool preserve_recording = true) override;
+                      size_t* max_op_size_hint) override;
   void EndRasterCHROMIUM() override;
 
   // Image decode acceleration.
diff --git a/gpu/command_buffer/client/raster_interface.h b/gpu/command_buffer/client/raster_interface.h
index a9da90f..7302880 100644
--- a/gpu/command_buffer/client/raster_interface.h
+++ b/gpu/command_buffer/client/raster_interface.h
@@ -112,8 +112,7 @@
                               const gfx::Vector2dF& post_translate,
                               const gfx::Vector2dF& post_scale,
                               bool requires_clear,
-                              size_t* max_op_size_hint,
-                              bool preserve_recording = true) = 0;
+                              size_t* max_op_size_hint) = 0;
 
   // Schedules a hardware-accelerated image decode and a sync token that's
   // released when the image decode is complete. If the decode could not be
diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
index c975a32..3dfa5b1b 100644
--- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
@@ -47,6 +47,11 @@
 namespace gpu {
 namespace {
 
+bool IsGLSupported(viz::SharedImageFormat format) {
+  return format.is_single_plane() && !format.IsLegacyMultiplanar() &&
+         format != viz::SharedImageFormat::kBGR_565;
+}
+
 void CreateSharedContext(const GpuDriverBugWorkarounds& workarounds,
                          scoped_refptr<gl::GLSurface>& surface,
                          scoped_refptr<gl::GLContext>& context,
@@ -252,7 +257,7 @@
   for (int i = 0; i <= viz::RESOURCE_FORMAT_MAX; ++i) {
     auto format = viz::SharedImageFormat::SinglePlane(
         static_cast<viz::ResourceFormat>(i));
-    if (!GLSupportsFormat(format) || format.IsCompressed())
+    if (!IsGLSupported(format) || format.IsCompressed())
       continue;
     int storage_format = TextureStorageFormat(
         format, feature_info->feature_flags().angle_rgbx_internal_format);
diff --git a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc
index 21ec633..3146d8f 100644
--- a/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/iosurface_image_backing_factory_unittest.cc
@@ -46,6 +46,14 @@
 using testing::AtLeast;
 
 namespace gpu {
+namespace {
+
+bool IsGLSupported(viz::SharedImageFormat format) {
+  return format.is_single_plane() && !format.IsLegacyMultiplanar() &&
+         format != viz::SharedImageFormat::kBGR_565;
+}
+
+}  // namespace
 
 class IOSurfaceImageBackingFactoryTest : public testing::Test {
  public:
@@ -1032,7 +1040,7 @@
   for (int i = 0; i <= viz::RESOURCE_FORMAT_MAX; ++i) {
     auto format = viz::SharedImageFormat::SinglePlane(
         static_cast<viz::ResourceFormat>(i));
-    if (!GLSupportsFormat(format) || format.IsCompressed())
+    if (!IsGLSupported(format) || format.IsCompressed())
       continue;
     int storage_format = TextureStorageFormat(
         format, feature_info->feature_flags().angle_rgbx_internal_format);
diff --git a/gpu/command_buffer/service/shared_image/shared_image_format_utils.cc b/gpu/command_buffer/service/shared_image/shared_image_format_utils.cc
index 8569bc90..fee06f4 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_format_utils.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_format_utils.cc
@@ -42,13 +42,6 @@
   }
 }
 
-bool GLSupportsFormat(viz::SharedImageFormat format) {
-  if (format.is_single_plane())
-    return viz::GLSupportsFormat(format.resource_format());
-  // No support for multiplanar formats.
-  return false;
-}
-
 GLFormatDesc ToGLFormatDescExternalSampler(viz::SharedImageFormat format) {
   DCHECK(format.is_multi_plane());
   DCHECK(format.PrefersExternalSampler());
diff --git a/gpu/command_buffer/service/shared_image/shared_image_format_utils.h b/gpu/command_buffer/service/shared_image/shared_image_format_utils.h
index 3f15d3f..51d7a4f3 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_format_utils.h
+++ b/gpu/command_buffer/service/shared_image/shared_image_format_utils.h
@@ -56,8 +56,6 @@
 
 // Following functions return the appropriate GL type/format for a
 // SharedImageFormat.
-// Returns true if given `format` is supported by GL.
-GPU_GLES2_EXPORT bool GLSupportsFormat(viz::SharedImageFormat format);
 // Return the GLFormatDesc when using external sampler for a given `format`.
 GPU_GLES2_EXPORT GLFormatDesc
 ToGLFormatDescExternalSampler(viz::SharedImageFormat format);
diff --git a/gpu/vulkan/BUILD.gn b/gpu/vulkan/BUILD.gn
index 903873d..a9e43857 100644
--- a/gpu/vulkan/BUILD.gn
+++ b/gpu/vulkan/BUILD.gn
@@ -96,6 +96,8 @@
       "vulkan_implementation.h",
       "vulkan_instance.cc",
       "vulkan_instance.h",
+      "vulkan_memory.cc",
+      "vulkan_memory.h",
       "vulkan_surface.cc",
       "vulkan_surface.h",
       "vulkan_swap_chain.cc",
diff --git a/gpu/vulkan/vulkan_image.cc b/gpu/vulkan/vulkan_image.cc
index 5bce25e..80cd110 100644
--- a/gpu/vulkan/vulkan_image.cc
+++ b/gpu/vulkan/vulkan_image.cc
@@ -17,28 +17,6 @@
 
 namespace gpu {
 
-namespace {
-
-absl::optional<uint32_t> FindMemoryTypeIndex(
-    VkPhysicalDevice physical_device,
-    const VkMemoryRequirements* requirements,
-    VkMemoryPropertyFlags flags) {
-  VkPhysicalDeviceMemoryProperties properties;
-  vkGetPhysicalDeviceMemoryProperties(physical_device, &properties);
-  constexpr uint32_t kMaxIndex = 31;
-  for (uint32_t i = 0; i <= kMaxIndex; i++) {
-    if (((1u << i) & requirements->memoryTypeBits) == 0)
-      continue;
-    if ((properties.memoryTypes[i].propertyFlags & flags) != flags)
-      continue;
-    return i;
-  }
-  NOTREACHED();
-  return absl::nullopt;
-}
-
-}  // namespace
-
 // static
 std::unique_ptr<VulkanImage> VulkanImage::Create(
     VulkanDeviceQueue* device_queue,
@@ -113,13 +91,12 @@
   auto image = std::make_unique<VulkanImage>(base::PassKey<VulkanImage>());
   image->device_queue_ = device_queue;
   image->image_ = vk_image;
-  image->device_memory_ = vk_device_memory;
+  image->memory_ = VulkanMemory::Create(device_queue, vk_device_memory,
+                                        device_size, memory_type_index);
   image->create_info_.extent = {static_cast<uint32_t>(size.width()),
                                 static_cast<uint32_t>(size.height()), 1};
   image->create_info_.format = format;
   image->create_info_.tiling = image_tiling;
-  image->device_size_ = device_size;
-  image->memory_type_index_ = memory_type_index;
   image->ycbcr_info_ = ycbcr_info;
   image->create_info_.usage = usage;
   image->create_info_.flags = flags;
@@ -131,7 +108,7 @@
 VulkanImage::~VulkanImage() {
   DCHECK(!device_queue_);
   DCHECK(image_ == VK_NULL_HANDLE);
-  DCHECK(device_memory_ == VK_NULL_HANDLE);
+  DCHECK(!memory_);
 }
 
 void VulkanImage::Destroy() {
@@ -142,34 +119,48 @@
     vkDestroyImage(vk_device, image_, nullptr /* pAllocator */);
     image_ = VK_NULL_HANDLE;
   }
-  if (device_memory_ != VK_NULL_HANDLE) {
-    vkFreeMemory(vk_device, device_memory_, nullptr /* pAllocator */);
-    device_memory_ = VK_NULL_HANDLE;
+  if (memory_) {
+    memory_->Destroy();
+    memory_.reset();
   }
   device_queue_ = nullptr;
 }
 
-#if BUILDFLAG(IS_POSIX)
-base::ScopedFD VulkanImage::GetMemoryFd(
-    VkExternalMemoryHandleTypeFlagBits handle_type) {
-  VkMemoryGetFdInfoKHR get_fd_info = {
-      .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
-      .memory = device_memory_,
-      .handleType = handle_type,
+VkMemoryRequirements VulkanImage::GetMemoryRequirements(size_t plane) {
+  DCHECK(device_queue_);
+  DCHECK(image_ != VK_NULL_HANDLE);
+  DCHECK(plane < plane_count_);
 
-  };
-
-  VkDevice device = device_queue_->GetVulkanDevice();
-  int memory_fd = -1;
-  vkGetMemoryFdKHR(device, &get_fd_info, &memory_fd);
-  if (memory_fd < 0) {
-    DLOG(ERROR) << "Unable to extract file descriptor out of external VkImage";
-    return base::ScopedFD();
+  if (disjoint_) {
+    DCHECK_LT(plane, 3u);
+    static const VkImageAspectFlagBits kPlaneAspects[3] = {
+        VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT,
+        VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT,
+        VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT,
+    };
+    VkMemoryRequirements2 requirements2 = {
+        VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2};
+    VkImagePlaneMemoryRequirementsInfo plane_memory_requirements = {
+        .sType = VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO,
+        .pNext = nullptr,
+        .planeAspect = kPlaneAspects[plane],
+    };
+    VkImageMemoryRequirementsInfo2 info = {
+        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2,
+        .pNext = &plane_memory_requirements,
+        .image = image_,
+    };
+    vkGetImageMemoryRequirements2(device_queue_->GetVulkanDevice(), &info,
+                                  &requirements2);
+    return requirements2.memoryRequirements;
   }
 
-  return base::ScopedFD(memory_fd);
+  DCHECK_EQ(plane, 0u);
+  VkMemoryRequirements requirements;
+  vkGetImageMemoryRequirements(device_queue_->GetVulkanDevice(), image_,
+                               &requirements);
+  return requirements;
 }
-#endif  // BUILDFLAG(IS_POSIX)
 
 bool VulkanImage::Initialize(VulkanDeviceQueue* device_queue,
                              const gfx::Size& size,
@@ -182,7 +173,7 @@
                              const VkMemoryRequirements* requirements) {
   DCHECK(!device_queue_);
   DCHECK(image_ == VK_NULL_HANDLE);
-  DCHECK(device_memory_ == VK_NULL_HANDLE);
+  DCHECK(!memory_);
 
   device_queue_ = device_queue;
   create_info_ = {
@@ -216,7 +207,7 @@
 
   VkMemoryRequirements tmp_requirements;
   if (!requirements) {
-    vkGetImageMemoryRequirements(vk_device, image_, &tmp_requirements);
+    tmp_requirements = GetMemoryRequirements(0);
     if (!tmp_requirements.memoryTypeBits) {
       DLOG(ERROR) << "vkGetImageMemoryRequirements failed";
       Destroy();
@@ -225,8 +216,6 @@
     requirements = &tmp_requirements;
   }
 
-  device_size_ = requirements->size;
-
   // Some vulkan implementations require dedicated memory for sharing memory
   // object between vulkan instances.
   VkMemoryDedicatedAllocateInfoKHR dedicated_memory_info = {
@@ -235,32 +224,14 @@
       .image = image_,
   };
 
-  auto index =
-      FindMemoryTypeIndex(device_queue->GetVulkanPhysicalDevice(), requirements,
-                          VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
-  if (!index) {
-    DLOG(ERROR) << "Cannot find validate memory type index.";
+  memory_ =
+      VulkanMemory::Create(device_queue_, requirements, &dedicated_memory_info);
+  if (!memory_) {
     Destroy();
     return false;
   }
 
-  memory_type_index_ = index.value();
-  VkMemoryAllocateInfo memory_allocate_info = {
-      .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
-      .pNext = &dedicated_memory_info,
-      .allocationSize = device_size_,
-      .memoryTypeIndex = memory_type_index_,
-  };
-
-  result = vkAllocateMemory(vk_device, &memory_allocate_info,
-                            nullptr /* pAllocator */, &device_memory_);
-  if (result != VK_SUCCESS) {
-    DLOG(ERROR) << "vkAllocateMemory failed result:" << result;
-    Destroy();
-    return false;
-  }
-
-  result = vkBindImageMemory(vk_device, image_, device_memory_,
+  result = vkBindImageMemory(vk_device, image_, memory_->device_memory(),
                              0 /* memoryOffset */);
   if (result != VK_SUCCESS) {
     DLOG(ERROR) << "Failed to bind memory to external VkImage: " << result;
diff --git a/gpu/vulkan/vulkan_image.h b/gpu/vulkan/vulkan_image.h
index d9dbc47..cd57b71 100644
--- a/gpu/vulkan/vulkan_image.h
+++ b/gpu/vulkan/vulkan_image.h
@@ -16,6 +16,7 @@
 #include "base/types/pass_key.h"
 #include "build/build_config.h"
 #include "gpu/ipc/common/vulkan_ycbcr_info.h"
+#include "gpu/vulkan/vulkan_memory.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/gpu_memory_buffer.h"
@@ -98,19 +99,25 @@
 
   void Destroy();
 
+  VkMemoryRequirements GetMemoryRequirements(size_t plane);
+
 #if BUILDFLAG(IS_POSIX)
   base::ScopedFD GetMemoryFd(VkExternalMemoryHandleTypeFlagBits handle_type =
-                                 VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT);
+                                 VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT) {
+    return memory_->GetMemoryFd(handle_type);
+  }
 #endif
 
 #if BUILDFLAG(IS_WIN)
   base::win::ScopedHandle GetMemoryHandle(
       VkExternalMemoryHandleTypeFlagBits handle_type =
-          VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT);
+          VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT) {
+    return memory_->GetMemoryHandle(handle_type);
+  }
 #endif
 
 #if BUILDFLAG(IS_FUCHSIA)
-  zx::vmo GetMemoryZirconHandle();
+  zx::vmo GetMemoryZirconHandle() { return memory_->GetMemoryZirconHandle(); }
 #endif
 
   VulkanDeviceQueue* device_queue() const { return device_queue_; }
@@ -121,8 +128,8 @@
   VkFormat format() const { return create_info_.format; }
   VkImageCreateFlags flags() const { return create_info_.flags; }
   VkImageUsageFlags usage() const { return create_info_.usage; }
-  VkDeviceSize device_size() const { return device_size_; }
-  uint32_t memory_type_index() const { return memory_type_index_; }
+  VkDeviceSize device_size() const { return memory_->size(); }
+  uint32_t memory_type_index() const { return memory_->type_index(); }
   VkImageTiling image_tiling() const { return create_info_.tiling; }
   uint32_t queue_family_index() const { return queue_family_index_; }
   void set_queue_family_index(uint32_t index) { queue_family_index_ = index; }
@@ -130,7 +137,7 @@
     return ycbcr_info_;
   }
   VkImage image() const { return image_; }
-  VkDeviceMemory device_memory() const { return device_memory_; }
+  VkDeviceMemory device_memory() const { return memory_->device_memory(); }
   VkExternalMemoryHandleTypeFlags handle_types() const { return handle_types_; }
   void set_native_pixmap(scoped_refptr<gfx::NativePixmap> pixmap) {
     native_pixmap_ = std::move(pixmap);
@@ -179,14 +186,13 @@
                                                 VkImageCreateFlags flags);
 #endif
 
+  bool disjoint_ = false;
   raw_ptr<VulkanDeviceQueue> device_queue_ = nullptr;
   VkImageCreateInfo create_info_{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
-  VkDeviceSize device_size_ = 0;
-  uint32_t memory_type_index_ = 0;
   uint32_t queue_family_index_ = VK_QUEUE_FAMILY_IGNORED;
   absl::optional<VulkanYCbCrInfo> ycbcr_info_;
   VkImage image_ = VK_NULL_HANDLE;
-  VkDeviceMemory device_memory_ = VK_NULL_HANDLE;
+  std::unique_ptr<VulkanMemory> memory_;
   VkExternalMemoryHandleTypeFlags handle_types_ = 0;
   scoped_refptr<gfx::NativePixmap> native_pixmap_;
   uint64_t modifier_ = gfx::NativePixmapHandle::kNoModifier;
diff --git a/gpu/vulkan/vulkan_image_fuchsia.cc b/gpu/vulkan/vulkan_image_fuchsia.cc
index 06004ef..14f4e6f 100644
--- a/gpu/vulkan/vulkan_image_fuchsia.cc
+++ b/gpu/vulkan/vulkan_image_fuchsia.cc
@@ -24,24 +24,4 @@
   return false;
 }
 
-zx::vmo VulkanImage::GetMemoryZirconHandle() {
-  DCHECK(handle_types_ & VK_EXTERNAL_MEMORY_HANDLE_TYPE_ZIRCON_VMO_BIT_FUCHSIA);
-  VkMemoryGetZirconHandleInfoFUCHSIA get_handle_info = {
-      .sType = VK_STRUCTURE_TYPE_MEMORY_GET_ZIRCON_HANDLE_INFO_FUCHSIA,
-      .memory = device_memory_,
-      .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ZIRCON_VMO_BIT_FUCHSIA,
-  };
-
-  VkDevice device = device_queue_->GetVulkanDevice();
-  zx::vmo vmo;
-  VkResult result = vkGetMemoryZirconHandleFUCHSIA(device, &get_handle_info,
-                                                   vmo.reset_and_get_address());
-  if (result != VK_SUCCESS) {
-    DLOG(ERROR) << "vkGetMemoryFuchsiaHandleKHR failed: " << result;
-    vmo.reset();
-  }
-
-  return vmo;
-}
-
 }  // namespace gpu
diff --git a/gpu/vulkan/vulkan_image_win.cc b/gpu/vulkan/vulkan_image_win.cc
index d7406c1cf..d3b6472 100644
--- a/gpu/vulkan/vulkan_image_win.cc
+++ b/gpu/vulkan/vulkan_image_win.cc
@@ -23,24 +23,4 @@
   return false;
 }
 
-base::win::ScopedHandle VulkanImage::GetMemoryHandle(
-    VkExternalMemoryHandleTypeFlagBits handle_type) {
-  VkMemoryGetWin32HandleInfoKHR get_handle_info = {
-      .sType = VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR,
-      .memory = device_memory_,
-      .handleType = handle_type,
-  };
-
-  VkDevice device = device_queue_->GetVulkanDevice();
-
-  HANDLE handle = nullptr;
-  vkGetMemoryWin32HandleKHR(device, &get_handle_info, &handle);
-  if (handle == nullptr) {
-    DLOG(ERROR) << "Unable to extract file handle out of external VkImage";
-    return base::win::ScopedHandle();
-  }
-
-  return base::win::ScopedHandle(handle);
-}
-
 }  // namespace gpu
diff --git a/gpu/vulkan/vulkan_memory.cc b/gpu/vulkan/vulkan_memory.cc
new file mode 100644
index 0000000..442571b
--- /dev/null
+++ b/gpu/vulkan/vulkan_memory.cc
@@ -0,0 +1,182 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gpu/vulkan/vulkan_memory.h"
+
+#include <vulkan/vulkan.h>
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "gpu/vulkan/vulkan_device_queue.h"
+#include "gpu/vulkan/vulkan_function_pointers.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace gpu {
+
+namespace {
+
+absl::optional<uint32_t> FindMemoryTypeIndex(
+    VkPhysicalDevice physical_device,
+    const VkMemoryRequirements* requirements,
+    VkMemoryPropertyFlags flags) {
+  VkPhysicalDeviceMemoryProperties properties;
+  vkGetPhysicalDeviceMemoryProperties(physical_device, &properties);
+  constexpr uint32_t kMaxIndex = 31;
+  for (uint32_t i = 0; i <= kMaxIndex; i++) {
+    if (((1u << i) & requirements->memoryTypeBits) == 0)
+      continue;
+    if ((properties.memoryTypes[i].propertyFlags & flags) != flags)
+      continue;
+    return i;
+  }
+  NOTREACHED();
+  return absl::nullopt;
+}
+
+}  // namespace
+
+VulkanMemory::VulkanMemory(base::PassKey<VulkanMemory> pass_key) {}
+
+VulkanMemory::~VulkanMemory() {
+  DCHECK(!device_queue_);
+  DCHECK(device_memory_ == VK_NULL_HANDLE);
+}
+
+// static
+std::unique_ptr<VulkanMemory> VulkanMemory::Create(
+    VulkanDeviceQueue* device_queue,
+    VkDeviceMemory device_memory,
+    VkDeviceSize size,
+    uint32_t type_index) {
+  auto memory = std::make_unique<VulkanMemory>(base::PassKey<VulkanMemory>());
+  memory->device_queue_ = device_queue;
+  memory->device_memory_ = device_memory;
+  memory->size_ = size;
+  memory->type_index_ = type_index;
+  return memory;
+}
+
+// static
+std::unique_ptr<VulkanMemory> VulkanMemory::Create(
+    VulkanDeviceQueue* device_queue,
+    const VkMemoryRequirements* requirements,
+    const void* extra_allocate_info) {
+  auto memory = std::make_unique<VulkanMemory>(base::PassKey<VulkanMemory>());
+  if (!memory->Initialize(device_queue, requirements, extra_allocate_info)) {
+    return nullptr;
+  }
+  return memory;
+}
+
+void VulkanMemory::Destroy() {
+  if (!device_queue_)
+    return;
+  VkDevice vk_device = device_queue_->GetVulkanDevice();
+  if (device_memory_ != VK_NULL_HANDLE) {
+    vkFreeMemory(vk_device, device_memory_, nullptr /* pAllocator */);
+    device_memory_ = VK_NULL_HANDLE;
+  }
+  device_queue_ = nullptr;
+}
+
+bool VulkanMemory::Initialize(VulkanDeviceQueue* device_queue,
+                              const VkMemoryRequirements* requirements,
+                              const void* extra_allocate_info) {
+  auto index =
+      FindMemoryTypeIndex(device_queue->GetVulkanPhysicalDevice(), requirements,
+                          VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+  if (!index) {
+    DLOG(ERROR) << "Cannot find validate memory type index.";
+    return false;
+  }
+
+  VkMemoryAllocateInfo memory_allocate_info = {
+      .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+      .pNext = extra_allocate_info,
+      .allocationSize = requirements->size,
+      .memoryTypeIndex = index.value(),
+  };
+
+  VkDevice vk_device = device_queue->GetVulkanDevice();
+  VkResult result = vkAllocateMemory(vk_device, &memory_allocate_info,
+                                     nullptr /* pAllocator */, &device_memory_);
+  if (result != VK_SUCCESS) {
+    DLOG(ERROR) << "vkAllocateMemory failed result:" << result;
+    return false;
+  }
+
+  device_queue_ = device_queue;
+  size_ = requirements->size;
+  type_index_ = index.value();
+
+  return true;
+}
+
+#if BUILDFLAG(IS_POSIX)
+base::ScopedFD VulkanMemory::GetMemoryFd(
+    VkExternalMemoryHandleTypeFlagBits handle_type) {
+  VkMemoryGetFdInfoKHR get_fd_info = {
+      .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR,
+      .memory = device_memory_,
+      .handleType = handle_type,
+
+  };
+
+  VkDevice device = device_queue_->GetVulkanDevice();
+  int memory_fd = -1;
+  vkGetMemoryFdKHR(device, &get_fd_info, &memory_fd);
+  if (memory_fd < 0) {
+    DLOG(ERROR) << "Unable to extract file descriptor out of external VkImage";
+    return base::ScopedFD();
+  }
+
+  return base::ScopedFD(memory_fd);
+}
+#endif  // BUILDFLAG(IS_POSIX)
+
+#if BUILDFLAG(IS_WIN)
+base::win::ScopedHandle VulkanMemory::GetMemoryHandle(
+    VkExternalMemoryHandleTypeFlagBits handle_type) {
+  VkMemoryGetWin32HandleInfoKHR get_handle_info = {
+      .sType = VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR,
+      .memory = device_memory(),
+      .handleType = handle_type,
+  };
+
+  VkDevice device = device_queue_->GetVulkanDevice();
+
+  HANDLE handle = nullptr;
+  vkGetMemoryWin32HandleKHR(device, &get_handle_info, &handle);
+  if (handle == nullptr) {
+    DLOG(ERROR) << "Unable to extract file handle out of external VkImage";
+    return base::win::ScopedHandle();
+  }
+
+  return base::win::ScopedHandle(handle);
+}
+#endif  // BUILDFLAG(IS_WIN)
+
+#if BUILDFLAG(IS_FUCHSIA)
+zx::vmo VulkanMemory::GetMemoryZirconHandle() {
+  VkMemoryGetZirconHandleInfoFUCHSIA get_handle_info = {
+      .sType = VK_STRUCTURE_TYPE_MEMORY_GET_ZIRCON_HANDLE_INFO_FUCHSIA,
+      .memory = device_memory(),
+      .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ZIRCON_VMO_BIT_FUCHSIA,
+  };
+
+  VkDevice device = device_queue_->GetVulkanDevice();
+  zx::vmo vmo;
+  VkResult result = vkGetMemoryZirconHandleFUCHSIA(device, &get_handle_info,
+                                                   vmo.reset_and_get_address());
+  if (result != VK_SUCCESS) {
+    DLOG(ERROR) << "vkGetMemoryFuchsiaHandleKHR failed: " << result;
+    vmo.reset();
+  }
+
+  return vmo;
+}
+#endif  // BUILDFLAG(IS_FUCHSIA)
+
+}  // namespace gpu
diff --git a/gpu/vulkan/vulkan_memory.h b/gpu/vulkan/vulkan_memory.h
new file mode 100644
index 0000000..47a5d6c
--- /dev/null
+++ b/gpu/vulkan/vulkan_memory.h
@@ -0,0 +1,79 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GPU_VULKAN_VULKAN_MEMORY_H_
+#define GPU_VULKAN_VULKAN_MEMORY_H_
+
+#include <vulkan/vulkan_core.h>
+
+#include "base/component_export.h"
+#include "base/files/scoped_file.h"
+#include "base/memory/raw_ptr.h"
+#include "base/types/pass_key.h"
+#include "build/build_config.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "base/win/scoped_handle.h"
+#endif
+
+#if BUILDFLAG(IS_FUCHSIA)
+#include <lib/zx/vmo.h>
+#endif
+
+namespace gpu {
+
+class VulkanDeviceQueue;
+
+class COMPONENT_EXPORT(VULKAN) VulkanMemory {
+ public:
+  explicit VulkanMemory(base::PassKey<VulkanMemory> pass_key);
+  ~VulkanMemory();
+
+  VulkanMemory(VulkanMemory&) = delete;
+  VulkanMemory& operator=(VulkanMemory&) = delete;
+
+  static std::unique_ptr<VulkanMemory> Create(VulkanDeviceQueue* device_queue,
+                                              VkDeviceMemory device_memory,
+                                              VkDeviceSize size,
+                                              uint32_t type_index);
+
+  static std::unique_ptr<VulkanMemory> Create(
+      VulkanDeviceQueue* device_queue,
+      const VkMemoryRequirements* requirements,
+      const void* extra_allocate_info);
+
+  void Destroy();
+
+#if BUILDFLAG(IS_POSIX)
+  base::ScopedFD GetMemoryFd(VkExternalMemoryHandleTypeFlagBits handle_type);
+#endif  // BUILDFLAG(IS_POSIX)
+
+#if BUILDFLAG(IS_WIN)
+  base::win::ScopedHandle GetMemoryHandle(
+      VkExternalMemoryHandleTypeFlagBits handle_type);
+#endif  // BUILDFLAG(IS_WIN)
+
+#if BUILDFLAG(IS_FUCHSIA)
+  zx::vmo GetMemoryZirconHandle();
+#endif  // BUILDFLAG(IS_FUCHSIA)
+
+  VulkanDeviceQueue* device_queue() const { return device_queue_; }
+  VkDeviceSize size() const { return size_; }
+  uint32_t type_index() const { return type_index_; }
+  VkDeviceMemory device_memory() const { return device_memory_; }
+
+ private:
+  bool Initialize(VulkanDeviceQueue* device_queue,
+                  const VkMemoryRequirements* requirements,
+                  const void* extra_allocate_info);
+
+  raw_ptr<VulkanDeviceQueue> device_queue_ = nullptr;
+  VkDeviceMemory device_memory_ = VK_NULL_HANDLE;
+  VkDeviceSize size_ = 0;
+  uint32_t type_index_ = 0;
+};
+
+}  // namespace gpu
+
+#endif  // GPU_VULKAN_VULKAN_MEMORY_H_
diff --git a/headless/test/headless_protocol_browsertest.cc b/headless/test/headless_protocol_browsertest.cc
index 79ed3b1..d1d1561 100644
--- a/headless/test/headless_protocol_browsertest.cc
+++ b/headless/test/headless_protocol_browsertest.cc
@@ -179,7 +179,7 @@
     ADD_FAILURE() << "Unable to read expectations at " << expectation_path;
   }
 
-  EXPECT_EQ(test_result, expectation);
+  EXPECT_EQ(expectation, test_result);
 }
 
 void HeadlessProtocolBrowserTest::OnConsoleAPICalled(
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index ce6cce6..7c9ce8d 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -5587,12 +5587,15 @@
   manifest_name: "REVISION"
   builders {
     name: "buildbucket/luci.chromium.ci/linux-rel-cft"
+    short_name: "linux-rel-cft"
   }
   builders {
     name: "buildbucket/luci.chromium.ci/mac-rel-cft"
+    short_name: "mac-rel-cft"
   }
   builders {
     name: "buildbucket/luci.chromium.ci/win-rel-cft"
+    short_name: "win-rel-cft"
   }
   header {
     oncalls {
diff --git a/infra/config/subprojects/chromium/ci/chromium.cft.star b/infra/config/subprojects/chromium/ci/chromium.cft.star
index 0ba3cf7..22296356 100644
--- a/infra/config/subprojects/chromium/ci/chromium.cft.star
+++ b/infra/config/subprojects/chromium/ci/chromium.cft.star
@@ -46,7 +46,9 @@
         target_platform = builder_config.target_platform.MAC,
         build_config = builder_config.build_config.RELEASE,
     ),
-    console_view_entry = consoles.console_view_entry(),
+    console_view_entry = consoles.console_view_entry(
+        short_name = "mac-rel-cft",
+    ),
     cores = None,
     os = os.MAC_DEFAULT,
 )
@@ -57,7 +59,9 @@
         target_platform = builder_config.target_platform.LINUX,
         build_config = builder_config.build_config.RELEASE,
     ),
-    console_view_entry = consoles.console_view_entry(),
+    console_view_entry = consoles.console_view_entry(
+        short_name = "linux-rel-cft",
+    ),
     os = os.LINUX_DEFAULT,
 )
 
@@ -67,6 +71,8 @@
         target_platform = builder_config.target_platform.WIN,
         build_config = builder_config.build_config.RELEASE,
     ),
-    console_view_entry = consoles.console_view_entry(),
+    console_view_entry = consoles.console_view_entry(
+        short_name = "win-rel-cft",
+    ),
     os = os.WINDOWS_DEFAULT,
 )
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index d01eab5..2ad98d1 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -1734,6 +1734,9 @@
       <message name="IDS_IOS_PRICE_NOTIFICATIONS_PRICE_TRACK_TITLE" desc="Title for Price Notifications table view.">
         Track Price
       </message>
+      <message name="IDS_IOS_PRICE_NOTIFICATIONS_PRICE_TRACK_TOAST_IPH_TEXT" desc="Text for Price tracking toast IPH.">
+        Track price here
+      </message>
       <message name="IDS_IOS_PRICE_NOTIFICATIONS_PRICE_TRACK_TRACKABLE_SECTION_HEADER" desc="Section header for a Price Notifications table view section that lists items available on the current page to have their prices tracked.">
         Current Site
       </message>
@@ -2213,6 +2216,12 @@
       <message name="IDS_IOS_TOOLS_MENU_NEW_WINDOW" desc="The iOS menu item for opening a new window [iOS only]" meaning="[Length: unlimited]">
         New Window
       </message>
+      <message name="IDS_IOS_TOOLS_MENU_PIN_TAB" desc="The iOS menu item for pinning a tab [iOS only]" meaning="[Length: unlimited]">
+        Pin Tab
+      </message>
+      <message name="IDS_IOS_TOOLS_MENU_UNPIN_TAB" desc="The iOS menu item for unpinning a tab [iOS only]" meaning="[Length: unlimited]">
+        Unpin Tab
+      </message>
       <message name="IDS_IOS_READING_LIST_CONTENT_CONTEXT_OFFLINE" desc="Title of the button to display the Offline version instead of the Online one. [Length: 50em]">
         View Offline Version in New Tab
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PRICE_NOTIFICATIONS_PRICE_TRACK_TOAST_IPH_TEXT.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PRICE_NOTIFICATIONS_PRICE_TRACK_TOAST_IPH_TEXT.png.sha1
new file mode 100644
index 0000000..b632d15
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PRICE_NOTIFICATIONS_PRICE_TRACK_TOAST_IPH_TEXT.png.sha1
@@ -0,0 +1 @@
+d291f6aab986dc390de37137aa6d2419b3266070
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_PIN_TAB.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_PIN_TAB.png.sha1
new file mode 100644
index 0000000..29c4e0f
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_PIN_TAB.png.sha1
@@ -0,0 +1 @@
+60f6d1b829d57688435bac92015fa175fbe261cb
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_UNPIN_TAB.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_UNPIN_TAB.png.sha1
new file mode 100644
index 0000000..b9a1ddd
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TOOLS_MENU_UNPIN_TAB.png.sha1
@@ -0,0 +1 @@
+f148ba0c89dd2dbd88f80ad8d6d2ebbb8b6c5550
\ No newline at end of file
diff --git a/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm b/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm
index c125a68..dc83c10e 100644
--- a/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm
+++ b/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm
@@ -215,7 +215,7 @@
     ReadingListModel* model =
         ReadingListModelFactory::GetForBrowserState(browser_state);
     if (model && model->loaded())
-      model->SetReadStatus(original_pending_url, true);
+      model->SetReadStatusIfExists(original_pending_url, true);
   }
   if (last_committed_url.is_valid() ||
       !web_state_->GetNavigationManager()->GetLastCommittedItem()) {
diff --git a/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm b/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
index ded805f2..967c3b77 100644
--- a/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
+++ b/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
@@ -169,7 +169,9 @@
     ReadingListModel* model = ReadingListModelFactory::GetForBrowserState(
         chrome_browser_state_.get());
     EXPECT_TRUE(model->DeleteAllEntries());
-    model->AddEntry(pending_url, "unread", reading_list::ADDED_VIA_CURRENT_APP);
+    model->AddOrReplaceEntry(pending_url, "unread",
+                             reading_list::ADDED_VIA_CURRENT_APP,
+                             /*estimated_read_time=*/base::TimeDelta());
     abuse_detector_.policy = is_app_blocked ? ExternalAppLaunchPolicyBlock
                                             : ExternalAppLaunchPolicyAllow;
     ui::PageTransition transition_type =
diff --git a/ios/chrome/browser/commerce/price_notifications/BUILD.gn b/ios/chrome/browser/commerce/price_notifications/BUILD.gn
index b1a7b51..7a8cf4c5 100644
--- a/ios/chrome/browser/commerce/price_notifications/BUILD.gn
+++ b/ios/chrome/browser/commerce/price_notifications/BUILD.gn
@@ -5,13 +5,17 @@
 source_set("price_notifications") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
+    "price_notifications_iph_presenter.h",
     "price_notifications_tab_helper.h",
     "price_notifications_tab_helper.mm",
   ]
   deps = [
     "//base",
     "//components/commerce/core:shopping_service",
+    "//components/feature_engagement/public",
+    "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/commerce:shopping_service",
+    "//ios/chrome/browser/feature_engagement",
     "//ios/web/public",
     "//ios/web/public:web_state_observer",
   ]
diff --git a/ios/chrome/browser/commerce/price_notifications/price_notifications_iph_presenter.h b/ios/chrome/browser/commerce/price_notifications/price_notifications_iph_presenter.h
new file mode 100644
index 0000000..7c1dba1
--- /dev/null
+++ b/ios/chrome/browser/commerce/price_notifications/price_notifications_iph_presenter.h
@@ -0,0 +1,18 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_COMMERCE_PRICE_NOTIFICATIONS_PRICE_NOTIFICATIONS_IPH_PRESENTER_H_
+#define IOS_CHROME_BROWSER_COMMERCE_PRICE_NOTIFICATIONS_PRICE_NOTIFICATIONS_IPH_PRESENTER_H_
+
+// Protocol to present in-product help (IPH) related to the price notifications
+// feature.
+@protocol PriceNotificationsIPHPresenter
+
+// Tells receiver to present the in-product help (IPH) to price track the
+// currently browsed site.
+- (void)presentPriceNotificationsWhileBrowsingIPH;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_COMMERCE_PRICE_NOTIFICATIONS_PRICE_NOTIFICATIONS_IPH_PRESENTER_H_
diff --git a/ios/chrome/browser/commerce/price_notifications/price_notifications_tab_helper.h b/ios/chrome/browser/commerce/price_notifications/price_notifications_tab_helper.h
index 224fae0..f8e52876 100644
--- a/ios/chrome/browser/commerce/price_notifications/price_notifications_tab_helper.h
+++ b/ios/chrome/browser/commerce/price_notifications/price_notifications_tab_helper.h
@@ -10,6 +10,8 @@
 #import "ios/web/public/web_state_observer.h"
 #import "ios/web/public/web_state_user_data.h"
 
+@protocol PriceNotificationsIPHPresenter;
+
 namespace commerce {
 class ShoppingService;
 }  // namespace commerce
@@ -27,6 +29,13 @@
 
   ~PriceNotificationsTabHelper() override;
 
+  // Sets the presenter for follow in-product help (IPH). `presenter` is not
+  // retained by this tab helper.
+  void SetPriceNotificationsIPHPresenter(
+      id<PriceNotificationsIPHPresenter> presenter) {
+    price_notifications_iph_presenter_ = presenter;
+  }
+
  private:
   friend class web::WebStateUserData<PriceNotificationsTabHelper>;
 
@@ -45,6 +54,10 @@
   // price tracked.
   commerce::ShoppingService* shopping_service_ = nullptr;
 
+  // The presenter that displays the price tracking bubble IPH.
+  __weak id<PriceNotificationsIPHPresenter> price_notifications_iph_presenter_ =
+      nil;
+
   WEB_STATE_USER_DATA_KEY_DECL();
 };
 #endif  // IOS_CHROME_BROWSER_COMMERCE_PRICE_NOTIFICATIONS_PRICE_NOTIFICATIONS_TAB_HELPER_H_
diff --git a/ios/chrome/browser/commerce/price_notifications/price_notifications_tab_helper.mm b/ios/chrome/browser/commerce/price_notifications/price_notifications_tab_helper.mm
index b63fc940..ee1bfb16 100644
--- a/ios/chrome/browser/commerce/price_notifications/price_notifications_tab_helper.mm
+++ b/ios/chrome/browser/commerce/price_notifications/price_notifications_tab_helper.mm
@@ -5,22 +5,49 @@
 #import "ios/chrome/browser/commerce/price_notifications/price_notifications_tab_helper.h"
 
 #import "components/commerce/core/shopping_service.h"
+#import "components/feature_engagement/public/feature_constants.h"
+#import "components/feature_engagement/public/tracker.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/commerce/price_notifications/price_notifications_iph_presenter.h"
 #import "ios/chrome/browser/commerce/shopping_service_factory.h"
+#import "ios/chrome/browser/feature_engagement/tracker_factory.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+// Helper object to weakly bind `presenter` in the callback.
+@interface WeakPriceNotificationsPresenter : NSObject
+- (instancetype)initWithPresenter:(id<PriceNotificationsIPHPresenter>)presenter
+    NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
+
+@property(nonatomic, weak) id<PriceNotificationsIPHPresenter> presenter;
+@end
+
+@implementation WeakPriceNotificationsPresenter
+- (instancetype)initWithPresenter:
+    (id<PriceNotificationsIPHPresenter>)presenter {
+  if ((self = [super init])) {
+    _presenter = presenter;
+  }
+
+  return self;
+}
+@end
+
 namespace {
 
 void OnProductInfoUrl(
-    const GURL& productURL,
+    WeakPriceNotificationsPresenter* presenter,
+    const GURL& product_url,
     const absl::optional<commerce::ProductInfo>& product_info) {
-  if (!product_info)
+  DCHECK(presenter);
+  if (!product_info) {
     return;
+  }
 
-  // TODO(crbug.com/1362350) Once the PriceNotificationsTabHelper
-  // has landed, this section will display the IPH.
+  [presenter.presenter presentPriceNotificationsWhileBrowsingIPH];
 }
 
 }  // namespace
@@ -37,8 +64,27 @@
 void PriceNotificationsTabHelper::DidFinishNavigation(
     web::WebState* web_state,
     web::NavigationContext* navigation_context) {
-  shopping_service_->GetProductInfoForUrl(web_state->GetVisibleURL(),
-                                          base::BindOnce(&OnProductInfoUrl));
+  // Always show an IPH for eligible websites if the price tracking
+  // experimental setting is enabled.
+  if (!base::FeatureList::IsEnabled(
+          feature_engagement::kIPHPriceNotificationsWhileBrowsingFeature)) {
+    feature_engagement::Tracker* feature_engagement_tracker =
+        feature_engagement::TrackerFactory::GetForBrowserState(
+            ChromeBrowserState::FromBrowserState(web_state->GetBrowserState()));
+    // Do not show price notifications IPH if the feature engagement
+    // conditions are not fulfilled.
+    if (!feature_engagement_tracker->WouldTriggerHelpUI(
+            feature_engagement::kIPHPriceNotificationsWhileBrowsingFeature)) {
+      return;
+    }
+  }
+
+  WeakPriceNotificationsPresenter* weak_presenter =
+      [[WeakPriceNotificationsPresenter alloc]
+          initWithPresenter:price_notifications_iph_presenter_];
+  shopping_service_->GetProductInfoForUrl(
+      web_state->GetVisibleURL(),
+      base::BindOnce(&OnProductInfoUrl, weak_presenter));
 }
 
 void PriceNotificationsTabHelper::WebStateDestroyed(web::WebState* web_state) {
diff --git a/ios/chrome/browser/ui/credential_provider_promo/BUILD.gn b/ios/chrome/browser/credential_provider_promo/BUILD.gn
similarity index 100%
rename from ios/chrome/browser/ui/credential_provider_promo/BUILD.gn
rename to ios/chrome/browser/credential_provider_promo/BUILD.gn
diff --git a/ios/chrome/browser/ui/credential_provider_promo/features.h b/ios/chrome/browser/credential_provider_promo/features.h
similarity index 68%
rename from ios/chrome/browser/ui/credential_provider_promo/features.h
rename to ios/chrome/browser/credential_provider_promo/features.h
index de7af74..97207cfc 100644
--- a/ios/chrome/browser/ui/credential_provider_promo/features.h
+++ b/ios/chrome/browser/credential_provider_promo/features.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_UI_CREDENTIAL_PROVIDER_PROMO_FEATURES_H_
-#define IOS_CHROME_BROWSER_UI_CREDENTIAL_PROVIDER_PROMO_FEATURES_H_
+#ifndef IOS_CHROME_BROWSER_CREDENTIAL_PROVIDER_PROMO_FEATURES_H_
+#define IOS_CHROME_BROWSER_CREDENTIAL_PROVIDER_PROMO_FEATURES_H_
 
 #import "base/feature_list.h"
 
@@ -13,4 +13,4 @@
 // Returns true if Credential Provider Extension Promo feature is enabled.
 bool IsCredentialProviderExtensionPromoEnabled();
 
-#endif  // IOS_CHROME_BROWSER_UI_CREDENTIAL_PROVIDER_PROMO_FEATURES_H_
+#endif  // IOS_CHROME_BROWSER_CREDENTIAL_PROVIDER_PROMO_FEATURES_H_
diff --git a/ios/chrome/browser/ui/credential_provider_promo/features.mm b/ios/chrome/browser/credential_provider_promo/features.mm
similarity index 83%
rename from ios/chrome/browser/ui/credential_provider_promo/features.mm
rename to ios/chrome/browser/credential_provider_promo/features.mm
index 037a902..cb65efe 100644
--- a/ios/chrome/browser/ui/credential_provider_promo/features.mm
+++ b/ios/chrome/browser/credential_provider_promo/features.mm
@@ -2,9 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/credential_provider_promo/features.h"
-
-#import "base/feature_list.h"
+#import "ios/chrome/browser/credential_provider_promo/features.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
diff --git a/ios/chrome/browser/flags/BUILD.gn b/ios/chrome/browser/flags/BUILD.gn
index dbdc6d1d1..5797b647 100644
--- a/ios/chrome/browser/flags/BUILD.gn
+++ b/ios/chrome/browser/flags/BUILD.gn
@@ -54,6 +54,7 @@
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/browsing_data:feature_flags",
     "//ios/chrome/browser/crash_report",
+    "//ios/chrome/browser/credential_provider_promo:features",
     "//ios/chrome/browser/drag_and_drop",
     "//ios/chrome/browser/ntp:features",
     "//ios/chrome/browser/policy",
@@ -68,7 +69,6 @@
     "//ios/chrome/browser/ui/autofill:features",
     "//ios/chrome/browser/ui/bubble:features",
     "//ios/chrome/browser/ui/content_suggestions:feature_flags",
-    "//ios/chrome/browser/ui/credential_provider_promo:features",
     "//ios/chrome/browser/ui/default_promo:utils",
     "//ios/chrome/browser/ui/download:features",
     "//ios/chrome/browser/ui/first_run:field_trial",
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 7217e32..967d563e 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -66,6 +66,7 @@
 #import "ios/chrome/app/background_mode_buildflags.h"
 #import "ios/chrome/browser/browsing_data/browsing_data_features.h"
 #import "ios/chrome/browser/crash_report/features.h"
+#import "ios/chrome/browser/credential_provider_promo/features.h"
 #import "ios/chrome/browser/flags/chrome_switches.h"
 #import "ios/chrome/browser/flags/ios_chrome_flag_descriptions.h"
 #import "ios/chrome/browser/flags/system_flags.h"
@@ -81,7 +82,6 @@
 #import "ios/chrome/browser/ui/autofill/features.h"
 #import "ios/chrome/browser/ui/bubble/bubble_features.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_feature.h"
-#import "ios/chrome/browser/ui/credential_provider_promo/features.h"
 #import "ios/chrome/browser/ui/default_promo/default_browser_utils.h"
 #import "ios/chrome/browser/ui/download/features.h"
 #import "ios/chrome/browser/ui/first_run/field_trial_constants.h"
@@ -1325,6 +1325,12 @@
      flag_descriptions::kIOSForceTranslateEnabledName,
      flag_descriptions::kIOSForceTranslateEnabledDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(translate::kIOSForceTranslateEnabled)},
+    {"iph-price-notifications-while-browsing",
+     flag_descriptions::kIPHPriceNotificationsWhileBrowsingName,
+     flag_descriptions::kIPHPriceNotificationsWhileBrowsingDescription,
+     flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(
+         feature_engagement::kIPHPriceNotificationsWhileBrowsingFeature)},
 };
 
 bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) {
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index 53dadd23..3a3ba81 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -656,6 +656,13 @@
     "Displays warning when user types or pastes a saved password into a "
     "phishing website.";
 
+const char kIPHPriceNotificationsWhileBrowsingName[] =
+    "Price Tracking IPH Display";
+const char kIPHPriceNotificationsWhileBrowsingDescription[] =
+    "Always displays the Price Tracking IPH when the user navigates to a "
+    "product "
+    "webpage that supports price tracking.";
+
 const char kRecordSnapshotSizeName[] =
     "Record the size of image and PDF snapshots in UMA histograms";
 const char kRecordSnapshotSizeDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index fd87580..83839695 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -591,6 +591,11 @@
 extern const char kPasswordReuseDetectionName[];
 extern const char kPasswordReuseDetectionDescription[];
 
+// Title and description for the flag to enable PriceNotifications IPH to be
+// alwayws be displayed.
+extern const char kIPHPriceNotificationsWhileBrowsingName[];
+extern const char kIPHPriceNotificationsWhileBrowsingDescription[];
+
 // Title and description for the flag to native restore web states.
 extern const char kRestoreSessionFromCacheName[];
 extern const char kRestoreSessionFromCacheDescription[];
diff --git a/ios/chrome/browser/providers/lens/chromium_lens.mm b/ios/chrome/browser/providers/lens/chromium_lens.mm
index 72e93155..467926f 100644
--- a/ios/chrome/browser/providers/lens/chromium_lens.mm
+++ b/ios/chrome/browser/providers/lens/chromium_lens.mm
@@ -53,22 +53,6 @@
   return absl::nullopt;
 }
 
-web::NavigationManager::WebLoadParams GenerateLensLoadParamsForImage(
-    UIImage* image,
-    LensEntrypoint entry_point,
-    bool is_incognito) {
-  // Lens is not supported in Chromium; this function will never be called.
-  NOTREACHED() << "Lens is not supported.";
-  return web::NavigationManager::WebLoadParams({});
-}
-
-void GenerateLensLoadParamsForImageAsync(UIImage* image,
-                                         LensEntrypoint entry_point,
-                                         bool is_incognito,
-                                         LensWebParamsCallback completion) {
-  NOTREACHED() << "Lens is not supported.";
-}
-
 void GenerateLensLoadParamsAsync(LensQuery* query,
                                  LensWebParamsCallback completion) {
   NOTREACHED() << "Lens is not supported.";
diff --git a/ios/chrome/browser/reading_list/offline_page_tab_helper.mm b/ios/chrome/browser/reading_list/offline_page_tab_helper.mm
index 618aa4f..6986e46 100644
--- a/ios/chrome/browser/reading_list/offline_page_tab_helper.mm
+++ b/ios/chrome/browser/reading_list/offline_page_tab_helper.mm
@@ -274,7 +274,7 @@
       !reading_list_model_->GetEntryByURL(url)) {
     return;
   }
-  reading_list_model_->SetReadStatus(url, true);
+  reading_list_model_->SetReadStatusIfExists(url, true);
   UMA_HISTOGRAM_BOOLEAN("ReadingList.OfflineVersionDisplayed",
                         presenting_offline_page_);
 }
diff --git a/ios/chrome/browser/reading_list/offline_page_tab_helper_unittest.mm b/ios/chrome/browser/reading_list/offline_page_tab_helper_unittest.mm
index e0149b9..2955e1d 100644
--- a/ios/chrome/browser/reading_list/offline_page_tab_helper_unittest.mm
+++ b/ios/chrome/browser/reading_list/offline_page_tab_helper_unittest.mm
@@ -47,8 +47,10 @@
                   /* storage_layer */ nullptr, /* pref_service */ nullptr,
                   base::DefaultClock::GetInstance());
 
-              model->AddEntry(GURL(kTestURL), kTestTitle,
-                              reading_list::ADDED_VIA_CURRENT_APP);
+              model->AddOrReplaceEntry(
+                  GURL(kTestURL), kTestTitle,
+                  reading_list::ADDED_VIA_CURRENT_APP,
+                  /*estimated_read_time=*/base::TimeDelta());
 
               return model;
             }));
@@ -165,7 +167,7 @@
 TEST_F(OfflinePageTabHelperTest, TestLoadReadingListDistilled) {
   GURL url(kTestURL);
   std::string distilled_path = kTestDistilledPath;
-  reading_list_model()->SetEntryDistilledInfo(
+  reading_list_model()->SetEntryDistilledInfoIfExists(
       url, base::FilePath(distilled_path), GURL(kTestDistilledURL), 50,
       base::Time::FromTimeT(100));
   const ReadingListEntry* entry = reading_list_model()->GetEntryByURL(url);
@@ -232,7 +234,7 @@
       offline_page_tab_helper->HasDistilledVersionForOnlineUrl(second_url));
 
   std::string distilled_path = kTestDistilledPath;
-  reading_list_model()->SetEntryDistilledInfo(
+  reading_list_model()->SetEntryDistilledInfoIfExists(
       url, base::FilePath(distilled_path), GURL(kTestDistilledURL), 50,
       base::Time::FromTimeT(100));
   EXPECT_TRUE(offline_page_tab_helper->HasDistilledVersionForOnlineUrl(url));
diff --git a/ios/chrome/browser/reading_list/reading_list_download_service.mm b/ios/chrome/browser/reading_list/reading_list_download_service.mm
index 100249b..ed4bc74 100644
--- a/ios/chrome/browser/reading_list/reading_list_download_service.mm
+++ b/ios/chrome/browser/reading_list/reading_list_download_service.mm
@@ -153,7 +153,7 @@
   DCHECK(reading_list_model_->loaded());
   std::set<std::string> processed_directories;
   std::set<GURL> unprocessed_entries;
-  for (const auto& url : reading_list_model_->Keys()) {
+  for (const auto& url : reading_list_model_->GetKeys()) {
     const ReadingListEntry* entry = reading_list_model_->GetEntryByURL(url);
     switch (entry->DistilledState()) {
       case ReadingListEntry::PROCESSED:
@@ -220,8 +220,8 @@
   // There is a connection.
   if (entry->FailedDownloadCounter() < kNumberOfFailsBeforeWifiOnly) {
     // Try to download the page, whatever the connection.
-    reading_list_model_->SetEntryDistilledState(entry->URL(),
-                                                ReadingListEntry::PROCESSING);
+    reading_list_model_->SetEntryDistilledStateIfExists(
+        entry->URL(), ReadingListEntry::PROCESSING);
     url_downloader_->DownloadOfflineURL(entry->URL());
 
   } else if (entry->FailedDownloadCounter() < kNumberOfFailsBeforeStop) {
@@ -236,8 +236,8 @@
                        weak_ptr_factory_.GetWeakPtr()));
     if (connection_type == network::mojom::ConnectionType::CONNECTION_WIFI) {
       // The connection is wifi, download the page.
-      reading_list_model_->SetEntryDistilledState(entry->URL(),
-                                                  ReadingListEntry::PROCESSING);
+      reading_list_model_->SetEntryDistilledStateIfExists(
+          entry->URL(), ReadingListEntry::PROCESSING);
       url_downloader_->DownloadOfflineURL(entry->URL());
 
     } else {
@@ -268,12 +268,12 @@
   switch (real_success_value) {
     case URLDownloader::DOWNLOAD_SUCCESS:
     case URLDownloader::DOWNLOAD_EXISTS: {
-      reading_list_model_->SetEntryDistilledInfo(
+      reading_list_model_->SetEntryDistilledInfoIfExists(
           url, distilled_path, distilled_url, size, base::Time::Now());
 
       std::string trimmed_title = base::CollapseWhitespaceASCII(title, false);
       if (!trimmed_title.empty())
-        reading_list_model_->SetEntryTitle(url, trimmed_title);
+        reading_list_model_->SetEntryTitleIfExists(url, trimmed_title);
 
       const ReadingListEntry* entry = reading_list_model_->GetEntryByURL(url);
       if (entry)
@@ -289,7 +289,7 @@
       // Add this failure to the total failure count.
       if (entry && real_success_value == URLDownloader::ERROR &&
           entry->FailedDownloadCounter() + 1 < kNumberOfFailsBeforeStop) {
-        reading_list_model_->SetEntryDistilledState(
+        reading_list_model_->SetEntryDistilledStateIfExists(
             url, ReadingListEntry::WILL_RETRY);
         ScheduleDownloadEntry(url);
         UMA_HISTOGRAM_ENUMERATION("ReadingList.Download.Status", RETRY,
@@ -297,7 +297,7 @@
       } else {
         UMA_HISTOGRAM_ENUMERATION("ReadingList.Download.Status", FAILURE,
                                   STATUS_MAX);
-        reading_list_model_->SetEntryDistilledState(
+        reading_list_model_->SetEntryDistilledStateIfExists(
             url, ReadingListEntry::DISTILLATION_ERROR);
       }
       break;
diff --git a/ios/chrome/browser/reading_list/reading_list_web_state_observer.mm b/ios/chrome/browser/reading_list/reading_list_web_state_observer.mm
index c22c8ba5..009c4d7 100644
--- a/ios/chrome/browser/reading_list/reading_list_web_state_observer.mm
+++ b/ios/chrome/browser/reading_list/reading_list_web_state_observer.mm
@@ -170,7 +170,7 @@
   }
 
   if (load_completion_status == web::PageLoadCompletionStatus::SUCCESS) {
-    reading_list_model_->SetReadStatus(pending_url_, true);
+    reading_list_model_->SetReadStatusIfExists(pending_url_, true);
     UMA_HISTOGRAM_BOOLEAN("ReadingList.OfflineVersionDisplayed", false);
   } else {
     LoadOfflineReadingListEntry();
@@ -281,7 +281,7 @@
                                         item->GetTransitionType(), NO);
     web_state_->OpenURL(params);
   }
-  reading_list_model_->SetReadStatus(entry->URL(), true);
+  reading_list_model_->SetReadStatusIfExists(entry->URL(), true);
   UMA_HISTOGRAM_BOOLEAN("ReadingList.OfflineVersionDisplayed", true);
 }
 
diff --git a/ios/chrome/browser/reading_list/reading_list_web_state_observer_unittest.mm b/ios/chrome/browser/reading_list/reading_list_web_state_observer_unittest.mm
index b64ae24..2c99755 100644
--- a/ios/chrome/browser/reading_list/reading_list_web_state_observer_unittest.mm
+++ b/ios/chrome/browser/reading_list/reading_list_web_state_observer_unittest.mm
@@ -79,8 +79,10 @@
                   /* storage_layer */ nullptr, /* pref_service */ nullptr,
                   base::DefaultClock::GetInstance());
 
-              model->AddEntry(GURL(kTestURL), kTestTitle,
-                              reading_list::ADDED_VIA_CURRENT_APP);
+              model->AddOrReplaceEntry(
+                  GURL(kTestURL), kTestTitle,
+                  reading_list::ADDED_VIA_CURRENT_APP,
+                  /*estimated_read_time=*/base::TimeDelta());
 
               return model;
             }));
@@ -141,7 +143,7 @@
 TEST_F(ReadingListWebStateObserverTest, TestLoadReadingListOnline) {
   GURL url(kTestURL);
   std::string distilled_path = kTestDistilledPath;
-  reading_list_model()->SetEntryDistilledInfo(
+  reading_list_model()->SetEntryDistilledInfoIfExists(
       url, base::FilePath(distilled_path), GURL(kTestDistilledURL), 50,
       base::Time::FromTimeT(100));
   const ReadingListEntry* entry = reading_list_model()->GetEntryByURL(url);
@@ -169,7 +171,7 @@
 TEST_F(ReadingListWebStateObserverTest, TestLoadReadingListDistilledCommitted) {
   GURL url(kTestURL);
   std::string distilled_path = kTestDistilledPath;
-  reading_list_model()->SetEntryDistilledInfo(
+  reading_list_model()->SetEntryDistilledInfoIfExists(
       url, base::FilePath(distilled_path), GURL(kTestDistilledURL), 50,
       base::Time::FromTimeT(100));
   const ReadingListEntry* entry = reading_list_model()->GetEntryByURL(url);
@@ -202,7 +204,7 @@
 TEST_F(ReadingListWebStateObserverTest, TestLoadReadingListDistilledPending) {
   GURL url(kTestURL);
   std::string distilled_path = kTestDistilledPath;
-  reading_list_model()->SetEntryDistilledInfo(
+  reading_list_model()->SetEntryDistilledInfoIfExists(
       url, base::FilePath(distilled_path), GURL(kTestDistilledURL), 50,
       base::Time::FromTimeT(100));
   const ReadingListEntry* entry = reading_list_model()->GetEntryByURL(url);
diff --git a/ios/chrome/browser/share_extension/share_extension_item_receiver.mm b/ios/chrome/browser/share_extension/share_extension_item_receiver.mm
index b3c890a..669c27e 100644
--- a/ios/chrome/browser/share_extension/share_extension_item_receiver.mm
+++ b/ios/chrome/browser/share_extension/share_extension_item_receiver.mm
@@ -286,8 +286,9 @@
   switch (type) {
     case app_group::READING_LIST_ITEM: {
       LogHistogramReceivedItem(READINGLIST_ENTRY);
-      _readingListModel->AddEntry(entryURL, entryTitle,
-                                  reading_list::ADDED_VIA_EXTENSION);
+      _readingListModel->AddOrReplaceEntry(
+          entryURL, entryTitle, reading_list::ADDED_VIA_EXTENSION,
+          /*estimated_read_time=*/base::TimeDelta());
       break;
     }
     case app_group::BOOKMARK_ITEM: {
diff --git a/ios/chrome/browser/ui/browser_view/BUILD.gn b/ios/chrome/browser/ui/browser_view/BUILD.gn
index c8e3d39e..ece301e2 100644
--- a/ios/chrome/browser/ui/browser_view/BUILD.gn
+++ b/ios/chrome/browser/ui/browser_view/BUILD.gn
@@ -49,6 +49,8 @@
     "//ios/chrome/browser/bookmarks",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/browser_state_metrics",
+    "//ios/chrome/browser/commerce/price_notifications",
+    "//ios/chrome/browser/commerce/push_notification",
     "//ios/chrome/browser/crash_report",
     "//ios/chrome/browser/download",
     "//ios/chrome/browser/favicon",
@@ -153,6 +155,7 @@
     "//ios/chrome/browser/ui/popup_menu",
     "//ios/chrome/browser/ui/presenters",
     "//ios/chrome/browser/ui/price_notifications",
+    "//ios/chrome/browser/ui/price_notifications:price_notifications_iph",
     "//ios/chrome/browser/ui/print",
     "//ios/chrome/browser/ui/promos_manager",
     "//ios/chrome/browser/ui/qr_generator",
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
index 94be9997..3af47009 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -20,6 +20,8 @@
 #import "ios/chrome/browser/app_launcher/app_launcher_tab_helper.h"
 #import "ios/chrome/browser/autofill/autofill_tab_helper.h"
 #import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/commerce/price_notifications/price_notifications_tab_helper.h"
+#import "ios/chrome/browser/commerce/push_notification/push_notification_feature.h"
 #import "ios/chrome/browser/download/download_directory_util.h"
 #import "ios/chrome/browser/download/external_app_util.h"
 #import "ios/chrome/browser/download/pass_kit_tab_helper.h"
@@ -120,6 +122,7 @@
 #import "ios/chrome/browser/ui/passwords/password_suggestion_coordinator.h"
 #import "ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.h"
 #import "ios/chrome/browser/ui/presenters/vertical_animation_container.h"
+#import "ios/chrome/browser/ui/price_notifications/price_notifications_iph_coordinator.h"
 #import "ios/chrome/browser/ui/price_notifications/price_notifications_view_coordinator.h"
 #import "ios/chrome/browser/ui/print/print_controller.h"
 #import "ios/chrome/browser/ui/promos_manager/promos_manager_coordinator.h"
@@ -320,6 +323,10 @@
 @property(nonatomic, strong)
     PasswordSuggestionCoordinator* passwordSuggestionCoordinator;
 
+// Coordinator for the price notifications IPH feature.
+@property(nonatomic, strong)
+    PriceNotificationsIPHCoordinator* priceNotificationsIPHCoordinator;
+
 // Coordinator for the price notifications UI presentation.
 @property(nonatomic, strong)
     PriceNotificationsViewCoordinator* priceNotificationsViewCoordiantor;
@@ -984,6 +991,14 @@
   [self.infobarBannerOverlayContainerCoordinator start];
   self.viewController.infobarBannerOverlayContainerViewController =
       self.infobarBannerOverlayContainerCoordinator.viewController;
+
+  if (IsPriceNotificationsEnabled()) {
+    self.priceNotificationsIPHCoordinator =
+        [[PriceNotificationsIPHCoordinator alloc]
+            initWithBaseViewController:self.viewController
+                               browser:self.browser];
+    [self.priceNotificationsIPHCoordinator start];
+  }
 }
 
 // Stops child coordinators.
@@ -1093,6 +1108,9 @@
   [self.passwordSettingsCoordinator stop];
   self.passwordSettingsCoordinator.delegate = nil;
   self.passwordSettingsCoordinator = nil;
+
+  [self.priceNotificationsIPHCoordinator stop];
+  self.priceNotificationsIPHCoordinator = nil;
 }
 
 // Starts mediators owned by this coordinator.
@@ -1957,6 +1975,13 @@
     AnnotationsTabHelper::FromWebState(webState)->SetBaseViewController(
         self.viewController);
   }
+
+  PriceNotificationsTabHelper* priceNotificationsTabHelper =
+      PriceNotificationsTabHelper::FromWebState(webState);
+  if (priceNotificationsTabHelper) {
+    priceNotificationsTabHelper->SetPriceNotificationsIPHPresenter(
+        self.priceNotificationsIPHCoordinator);
+  }
 }
 
 // Uninstalls delegates for `webState`.
@@ -1993,6 +2018,12 @@
   if (AnnotationsTabHelper::FromWebState(webState)) {
     AnnotationsTabHelper::FromWebState(webState)->SetBaseViewController(nil);
   }
+
+  PriceNotificationsTabHelper* priceNotificationsTabHelper =
+      PriceNotificationsTabHelper::FromWebState(webState);
+  if (priceNotificationsTabHelper) {
+    priceNotificationsTabHelper->SetPriceNotificationsIPHPresenter(nil);
+  }
 }
 
 - (void)startObservingRealizationForWebState:(web::WebState*)webState {
@@ -2065,6 +2096,10 @@
   [self.priceNotificationsViewCoordiantor stop];
 }
 
+- (void)presentPriceNotificationsWhileBrowsingIPH {
+  [_bubblePresenter presentPriceNotificationsWhileBrowsingTipBubble];
+}
+
 #pragma mark - PolicyChangeCommands
 
 - (void)showForceSignedOutPrompt {
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index fe7dff7..095854a 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -2262,8 +2262,9 @@
 
   ReadingListModel* readingModel =
       ReadingListModelFactory::GetForBrowserState(self.browserState);
-  readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
-                         reading_list::ADDED_VIA_CURRENT_APP);
+  readingModel->AddOrReplaceEntry(URL, base::SysNSStringToUTF8(title),
+                                  reading_list::ADDED_VIA_CURRENT_APP,
+                                  /*estimated_read_time=*/base::TimeDelta());
 }
 
 #pragma mark - Private SingleNTP feature helper methods
diff --git a/ios/chrome/browser/ui/bubble/BUILD.gn b/ios/chrome/browser/ui/bubble/BUILD.gn
index 1983d13..e44384f 100644
--- a/ios/chrome/browser/ui/bubble/BUILD.gn
+++ b/ios/chrome/browser/ui/bubble/BUILD.gn
@@ -23,6 +23,7 @@
     "//components/feature_engagement/public",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/browser_state",
+    "//ios/chrome/browser/commerce/push_notification",
     "//ios/chrome/browser/feature_engagement",
     "//ios/chrome/browser/flags:system_flags",
     "//ios/chrome/browser/ui/commands",
diff --git a/ios/chrome/browser/ui/bubble/bubble_presenter.h b/ios/chrome/browser/ui/bubble/bubble_presenter.h
index 043f7f5..a65bfc8 100644
--- a/ios/chrome/browser/ui/bubble/bubble_presenter.h
+++ b/ios/chrome/browser/ui/bubble/bubble_presenter.h
@@ -58,6 +58,10 @@
 // Presents a help bubble for What's New, if applicable.
 - (void)presentWhatsNewBottomToolbarBubble;
 
+// Presents a help bubble to inform the user that they can track the price of
+// the item on the current website.
+- (void)presentPriceNotificationsWhileBrowsingTipBubble;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_BUBBLE_BUBBLE_PRESENTER_H_
diff --git a/ios/chrome/browser/ui/bubble/bubble_presenter.mm b/ios/chrome/browser/ui/bubble/bubble_presenter.mm
index d52c4629b..a18c76e 100644
--- a/ios/chrome/browser/ui/bubble/bubble_presenter.mm
+++ b/ios/chrome/browser/ui/bubble/bubble_presenter.mm
@@ -12,6 +12,7 @@
 #import "components/feature_engagement/public/feature_constants.h"
 #import "components/feature_engagement/public/tracker.h"
 #import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/commerce/push_notification/push_notification_feature.h"
 #import "ios/chrome/browser/feature_engagement/tracker_factory.h"
 #import "ios/chrome/browser/flags/system_flags.h"
 #import "ios/chrome/browser/ui/bubble/bubble_presenter_delegate.h"
@@ -69,6 +70,8 @@
     BubbleViewControllerPresenter* defaultPageModeTipBubblePresenter;
 @property(nonatomic, strong)
     BubbleViewControllerPresenter* whatsNewBubblePresenter;
+@property(nonatomic, strong) BubbleViewControllerPresenter*
+    priceNotificationsWhileBrowsingBubbleTipPresenter;
 
 @property(nonatomic, assign) ChromeBrowserState* browserState;
 
@@ -303,7 +306,7 @@
   // the tip, then end early to prevent the potential reassignment of the
   // existing `whatsNewBubblePresenter` to nil.
   BubbleViewControllerPresenter* presenter = [self
-      presentBubbleForFeature:feature_engagement::kIPHWhatsNewFeature
+      presentBubbleForFeature:feature_engagement::kIPHFollowWhileBrowsingFeature
                     direction:arrowDirection
                     alignment:BubbleAlignmentTrailing
                          text:text
@@ -315,6 +318,35 @@
   self.whatsNewBubblePresenter = presenter;
 }
 
+- (void)presentPriceNotificationsWhileBrowsingTipBubble {
+  if (![self canPresentBubble])
+    return;
+
+  BubbleArrowDirection arrowDirection =
+      IsSplitToolbarMode(self.rootViewController) ? BubbleArrowDirectionDown
+                                                  : BubbleArrowDirectionUp;
+  NSString* text = l10n_util::GetNSString(
+      IDS_IOS_PRICE_NOTIFICATIONS_PRICE_TRACK_TOAST_IPH_TEXT);
+  CGPoint toolsMenuAnchor = [self anchorPointToGuide:kToolsMenuGuide
+                                           direction:arrowDirection];
+
+  // If the feature engagement tracker does not consider it valid to display
+  // the tip, then end early to prevent the potential reassignment of the
+  // existing `whatsNewBubblePresenter` to nil.
+  BubbleViewControllerPresenter* presenter =
+      [self presentBubbleForFeature:
+                feature_engagement::kIPHPriceNotificationsWhileBrowsingFeature
+                          direction:arrowDirection
+                          alignment:BubbleAlignmentTrailing
+                               text:text
+              voiceOverAnnouncement:text
+                        anchorPoint:toolsMenuAnchor];
+  if (!presenter)
+    return;
+
+  self.priceNotificationsWhileBrowsingBubbleTipPresenter = presenter;
+}
+
 #pragma mark - Private
 
 - (void)presentBubbles {
@@ -603,6 +635,17 @@
       experimental_flags::ShouldAlwaysShowFollowIPH()) {
     return YES;
   }
+
+  // Always present the price notifications IPH if it's triggered by system
+  // experimental settings.
+  if (feature.name ==
+          feature_engagement::kIPHPriceNotificationsWhileBrowsingFeature.name &&
+      base::FeatureList::IsEnabled(
+          feature_engagement::kIPHPriceNotificationsWhileBrowsingFeature) &&
+      IsPriceNotificationsEnabled()) {
+    return YES;
+  }
+
   return NO;
 }
 
diff --git a/ios/chrome/browser/ui/commands/price_notifications_commands.h b/ios/chrome/browser/ui/commands/price_notifications_commands.h
index de48edad..bf3b6060 100644
--- a/ios/chrome/browser/ui/commands/price_notifications_commands.h
+++ b/ios/chrome/browser/ui/commands/price_notifications_commands.h
@@ -14,6 +14,9 @@
 // Shows the price notifications UI.
 - (void)showPriceNotifications;
 
+// Shows the price notifications IPH.
+- (void)presentPriceNotificationsWhileBrowsingIPH;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_COMMANDS_PRICE_NOTIFICATIONS_COMMANDS_H_
diff --git a/ios/chrome/browser/ui/icons/symbol_names.h b/ios/chrome/browser/ui/icons/symbol_names.h
index 39b89a86..997edef 100644
--- a/ios/chrome/browser/ui/icons/symbol_names.h
+++ b/ios/chrome/browser/ui/icons/symbol_names.h
@@ -89,6 +89,7 @@
 extern NSString* const kEllipsisCircleFillSymbol;
 extern NSString* const kPinSymbol;
 extern NSString* const kPinFillSymbol;
+extern NSString* const kPinSlashSymbol;
 extern NSString* const kSettingsSymbol;
 extern NSString* const kSettingsFilledSymbol;
 extern NSString* const kShareSymbol;
diff --git a/ios/chrome/browser/ui/icons/symbol_names.mm b/ios/chrome/browser/ui/icons/symbol_names.mm
index 38515b90..ab10eecc 100644
--- a/ios/chrome/browser/ui/icons/symbol_names.mm
+++ b/ios/chrome/browser/ui/icons/symbol_names.mm
@@ -82,6 +82,7 @@
 NSString* const kEllipsisCircleFillSymbol = @"ellipsis.circle.fill";
 NSString* const kPinSymbol = @"pin";
 NSString* const kPinFillSymbol = @"pin.fill";
+NSString* const kPinSlashSymbol = @"pin.slash";
 NSString* const kSettingsSymbol = @"gearshape";
 NSString* const kSettingsFilledSymbol = @"gearshape.fill";
 NSString* const kShareSymbol = @"square.and.arrow.up";
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn b/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn
index bc579c0..04b865703 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn
@@ -32,6 +32,7 @@
     "resources:overflow_menu_action_incognito",
     "resources:overflow_menu_action_new_tab",
     "resources:overflow_menu_action_new_window",
+    "resources:overflow_menu_action_pin_tab",
     "resources:overflow_menu_action_price_notifications",
     "resources:overflow_menu_action_read_later",
     "resources:overflow_menu_action_reload",
@@ -44,6 +45,7 @@
     "resources:overflow_menu_action_text_zoom",
     "resources:overflow_menu_action_translate",
     "resources:overflow_menu_action_unfollow",
+    "resources:overflow_menu_action_unpin_tab",
     "resources:overflow_menu_destination_bookmarks",
     "resources:overflow_menu_destination_downloads",
     "resources:overflow_menu_destination_history",
@@ -89,6 +91,7 @@
     "//ios/chrome/browser/ui/popup_menu:metrics_protocols",
     "//ios/chrome/browser/ui/popup_menu/overflow_menu/destination_usage_history",
     "//ios/chrome/browser/ui/reading_list",
+    "//ios/chrome/browser/ui/tab_switcher/pinned_tabs:features",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/ui/whats_new:util",
     "//ios/chrome/browser/url:constants",
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm
index e6a9b26d..aae76e1 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm
@@ -61,6 +61,7 @@
 #import "ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_swift.h"
 #import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h"
 #import "ios/chrome/browser/ui/reading_list/reading_list_utils.h"
+#import "ios/chrome/browser/ui/tab_switcher/pinned_tabs/features.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/browser/ui/whats_new/whats_new_util.h"
 #import "ios/chrome/browser/url/chrome_url_constants.h"
@@ -226,6 +227,9 @@
 @property(nonatomic, strong) OverflowMenuAction* openIncognitoTabAction;
 @property(nonatomic, strong) OverflowMenuAction* openNewWindowAction;
 
+@property(nonatomic, strong) OverflowMenuAction* pinTabAction;
+@property(nonatomic, strong) OverflowMenuAction* unpinTabAction;
+
 @property(nonatomic, strong) OverflowMenuAction* clearBrowsingDataAction;
 @property(nonatomic, strong) OverflowMenuAction* followAction;
 @property(nonatomic, strong) OverflowMenuAction* addBookmarkAction;
@@ -683,6 +687,18 @@
           [weakSelf openNewWindow];
         });
 
+    self.pinTabAction = CreateOverflowMenuAction(
+        IDS_IOS_TOOLS_MENU_PIN_TAB, kPinSymbol,
+        /*systemSymbol=*/YES, /*monochromeSymbol=*/NO, kToolsMenuPinTabId, ^{
+          [weakSelf pinTab];
+        });
+
+    self.unpinTabAction = CreateOverflowMenuAction(
+        IDS_IOS_TOOLS_MENU_UNPIN_TAB, kPinSlashSymbol,
+        /*systemSymbol=*/YES, /*monochromeSymbol=*/NO, kToolsMenuUnpinTabId, ^{
+          [weakSelf unpinTab];
+        });
+
     self.clearBrowsingDataAction = CreateOverflowMenuAction(
         IDS_IOS_TOOLS_MENU_CLEAR_BROWSING_DATA, kTrashSymbol,
         /*systemSymbol=*/YES, /*monochromeSymbol=*/NO,
@@ -809,6 +825,18 @@
           [weakSelf openNewWindow];
         });
 
+    self.pinTabAction = CreateOverflowMenuAction(
+        IDS_IOS_TOOLS_MENU_PIN_TAB, @"overflow_menu_action_pin_tab",
+        kToolsMenuPinTabId, ^{
+          [weakSelf pinTab];
+        });
+
+    self.unpinTabAction = CreateOverflowMenuAction(
+        IDS_IOS_TOOLS_MENU_UNPIN_TAB, @"overflow_menu_action_unpin_tab",
+        kToolsMenuUnpinTabId, ^{
+          [weakSelf unpinTab];
+        });
+
     self.clearBrowsingDataAction =
         CreateOverflowMenuAction(IDS_IOS_TOOLS_MENU_CLEAR_BROWSING_DATA,
                                  @"overflow_menu_action_clear_browsing_data",
@@ -1090,6 +1118,11 @@
     [appActions addObject:self.openNewWindowAction];
   }
 
+  if (IsPinnedTabsEnabled()) {
+    [appActions addObject:([self isTabPinned] ? self.unpinTabAction
+                                              : self.pinTabAction)];
+  }
+
   self.appActionsGroup.actions = appActions;
 
   BOOL pageIsBookmarked =
@@ -1171,7 +1204,11 @@
     self.helpActionsGroup.footer = nil;
   }
 
-  // Enable/disable items based on page state.
+  if (IsPinnedTabsEnabled()) {
+    // Enable/disable items based on page state.
+    self.pinTabAction.enabled = [self isCurrentURLWebURL];
+    self.unpinTabAction.enabled = [self isCurrentURLWebURL];
+  }
 
   // The "Add to Reading List" functionality requires JavaScript execution,
   // which is paused while overlays are displayed over the web content area.
@@ -1343,6 +1380,26 @@
   return action;
 }
 
+- (BOOL)isTabPinned {
+  DCHECK(self.webState);
+  DCHECK(self.webStateList);
+
+  int webStateIndex = self.webStateList->GetIndexOfWebState(self.webState);
+  return self.webStateList->IsWebStatePinnedAt(webStateIndex);
+}
+
+- (void)setTabPinned:(BOOL)pinned {
+  web::WebState* webState = self.webState;
+  WebStateList* webStateList = self.webStateList;
+
+  if (!webState || !webStateList) {
+    return;
+  }
+
+  int webStateIndex = webStateList->GetIndexOfWebState(webState);
+  webStateList->SetWebStatePinnedAt(webStateIndex, pinned);
+}
+
 #pragma mark - CRWWebStateObserver
 
 - (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
@@ -1553,6 +1610,18 @@
                                                   GURL(kChromeUINewTabURL))];
 }
 
+// Dismisses the menu and pins the tab.
+- (void)pinTab {
+  [self setTabPinned:YES];
+  [self.popupMenuCommandsHandler dismissPopupMenuAnimated:YES];
+}
+
+// Dismisses the menu and unpins the tab.
+- (void)unpinTab {
+  [self setTabPinned:NO];
+  [self.popupMenuCommandsHandler dismissPopupMenuAnimated:YES];
+}
+
 // Dismisses the menu and opens the Clear Browsing Data screen.
 - (void)openClearBrowsingData {
   RecordAction(UserMetricsAction("MobileMenuClearBrowsingData"));
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/BUILD.gn b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/BUILD.gn
index a3f01fb..134f2de 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/BUILD.gn
@@ -80,6 +80,14 @@
   ]
 }
 
+imageset("overflow_menu_action_pin_tab") {
+  sources = [
+    "overflow_menu_action_pin_tab.imageset/Contents.json",
+    "overflow_menu_action_pin_tab.imageset/overflow_menu_action_pin_tab@2x.png",
+    "overflow_menu_action_pin_tab.imageset/overflow_menu_action_pin_tab@3x.png",
+  ]
+}
+
 imageset("overflow_menu_action_price_notifications") {
   sources = [
     "overflow_menu_action_price_notifications.imageset/Contents.json",
@@ -176,6 +184,14 @@
   ]
 }
 
+imageset("overflow_menu_action_unpin_tab") {
+  sources = [
+    "overflow_menu_action_unpin_tab.imageset/Contents.json",
+    "overflow_menu_action_unpin_tab.imageset/overflow_menu_action_unpin_tab@2x.png",
+    "overflow_menu_action_unpin_tab.imageset/overflow_menu_action_unpin_tab@3x.png",
+  ]
+}
+
 imageset("overflow_menu_destination_bookmarks") {
   sources = [
     "overflow_menu_destination_bookmarks.imageset/Contents.json",
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_pin_tab.imageset/Contents.json b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_pin_tab.imageset/Contents.json
new file mode 100644
index 0000000..82ca334d
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_pin_tab.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "overflow_menu_action_pin_tab@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "overflow_menu_action_pin_tab@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties": {
+    "template-rendering-intent": "template"
+  }
+}
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_pin_tab.imageset/overflow_menu_action_pin_tab@2x.png b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_pin_tab.imageset/overflow_menu_action_pin_tab@2x.png
new file mode 100644
index 0000000..dfc0990
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_pin_tab.imageset/overflow_menu_action_pin_tab@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_pin_tab.imageset/overflow_menu_action_pin_tab@3x.png b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_pin_tab.imageset/overflow_menu_action_pin_tab@3x.png
new file mode 100644
index 0000000..870efe38
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_pin_tab.imageset/overflow_menu_action_pin_tab@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unpin_tab.imageset/Contents.json b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unpin_tab.imageset/Contents.json
new file mode 100644
index 0000000..73b4494
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unpin_tab.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "overflow_menu_action_unpin_tab@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "overflow_menu_action_unpin_tab@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  },
+  "properties": {
+    "template-rendering-intent": "template"
+  }
+}
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unpin_tab.imageset/overflow_menu_action_unpin_tab@2x.png b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unpin_tab.imageset/overflow_menu_action_unpin_tab@2x.png
new file mode 100644
index 0000000..2947cbb9
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unpin_tab.imageset/overflow_menu_action_unpin_tab@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unpin_tab.imageset/overflow_menu_action_unpin_tab@3x.png b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unpin_tab.imageset/overflow_menu_action_unpin_tab@3x.png
new file mode 100644
index 0000000..6a9b7ee
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unpin_tab.imageset/overflow_menu_action_unpin_tab@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h
index 66da190f..d83b974f 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.h
@@ -26,6 +26,10 @@
 extern NSString* const kToolsMenuNewWindowId;
 // New incognito Tab item accessibility Identifier.
 extern NSString* const kToolsMenuNewIncognitoTabId;
+// Pin Tab item accessibility Identifier.
+extern NSString* const kToolsMenuPinTabId;
+// Unpin Tab item accessibility Identifier.
+extern NSString* const kToolsMenuUnpinTabId;
 // Close all Tabs item accessibility Identifier.
 extern NSString* const kToolsMenuCloseAllTabsId;
 // Close all incognito Tabs item accessibility Identifier.
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm
index 6b07218e..8f047f0b 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_constants.mm
@@ -23,6 +23,8 @@
 NSString* const kToolsMenuNewTabId = @"kToolsMenuNewTabId";
 NSString* const kToolsMenuNewWindowId = @"kToolsMenuNewWindowId";
 NSString* const kToolsMenuNewIncognitoTabId = @"kToolsMenuNewIncognitoTabId";
+NSString* const kToolsMenuPinTabId = @"kToolsMenuPinTabId";
+NSString* const kToolsMenuUnpinTabId = @"kToolsMenuUnpinTabId";
 NSString* const kToolsMenuCloseAllTabsId = @"kToolsMenuCloseAllTabsId";
 NSString* const kToolsMenuCloseAllIncognitoTabsId =
     @"kToolsMenuCloseAllIncognitoTabsId";
diff --git a/ios/chrome/browser/ui/price_notifications/BUILD.gn b/ios/chrome/browser/ui/price_notifications/BUILD.gn
index 7c4d96d2..8887554 100644
--- a/ios/chrome/browser/ui/price_notifications/BUILD.gn
+++ b/ios/chrome/browser/ui/price_notifications/BUILD.gn
@@ -32,6 +32,21 @@
   ]
 }
 
+source_set("price_notifications_iph") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "price_notifications_iph_coordinator.h",
+    "price_notifications_iph_coordinator.mm",
+  ]
+
+  deps = [
+    "//ios/chrome/browser/commerce/price_notifications",
+    "//ios/chrome/browser/main:public",
+    "//ios/chrome/browser/ui/commands",
+    "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
+  ]
+}
+
 source_set("price_notifications_ui") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
diff --git a/ios/chrome/browser/ui/price_notifications/price_notifications_iph_coordinator.h b/ios/chrome/browser/ui/price_notifications/price_notifications_iph_coordinator.h
new file mode 100644
index 0000000..919911a
--- /dev/null
+++ b/ios/chrome/browser/ui/price_notifications/price_notifications_iph_coordinator.h
@@ -0,0 +1,16 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_PRICE_NOTIFICATIONS_PRICE_NOTIFICATIONS_IPH_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_UI_PRICE_NOTIFICATIONS_PRICE_NOTIFICATIONS_IPH_COORDINATOR_H_
+
+#import "ios/chrome/browser/commerce/price_notifications/price_notifications_iph_presenter.h"
+#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
+
+// Coordinator for the Follow IPH feature.
+@interface PriceNotificationsIPHCoordinator
+    : ChromeCoordinator <PriceNotificationsIPHPresenter>
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_PRICE_NOTIFICATIONS_PRICE_NOTIFICATIONS_IPH_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/price_notifications/price_notifications_iph_coordinator.mm b/ios/chrome/browser/ui/price_notifications/price_notifications_iph_coordinator.mm
new file mode 100644
index 0000000..cf43a67e
--- /dev/null
+++ b/ios/chrome/browser/ui/price_notifications/price_notifications_iph_coordinator.mm
@@ -0,0 +1,26 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/price_notifications/price_notifications_iph_coordinator.h"
+
+#import "ios/chrome/browser/main/browser.h"
+#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
+#import "ios/chrome/browser/ui/commands/price_notifications_commands.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation PriceNotificationsIPHCoordinator
+
+#pragma mark - PriceNotificationsIPHPresenter
+
+- (void)presentPriceNotificationsWhileBrowsingIPH {
+  id<PriceNotificationsCommands> priceNotificationCommandsHandler =
+      HandlerForProtocol(self.browser->GetCommandDispatcher(),
+                         PriceNotificationsCommands);
+  [priceNotificationCommandsHandler presentPriceNotificationsWhileBrowsingIPH];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/reading_list/ios_add_to_reading_list_infobar_delegate.mm b/ios/chrome/browser/ui/reading_list/ios_add_to_reading_list_infobar_delegate.mm
index ca00d5f..b40e4e0 100644
--- a/ios/chrome/browser/ui/reading_list/ios_add_to_reading_list_infobar_delegate.mm
+++ b/ios/chrome/browser/ui/reading_list/ios_add_to_reading_list_infobar_delegate.mm
@@ -84,9 +84,9 @@
 }
 
 bool IOSAddToReadingListInfobarDelegate::Accept() {
-  model_->AddEntry(url_, base::UTF16ToUTF8(title_),
-                   reading_list::ADDED_VIA_CURRENT_APP,
-                   base::Minutes(estimated_read_time_));
+  model_->AddOrReplaceEntry(url_, base::UTF16ToUTF8(title_),
+                            reading_list::ADDED_VIA_CURRENT_APP,
+                            base::Minutes(estimated_read_time_));
   ukm::SourceId sourceID = ukm::GetSourceIdForWebStateDocument(web_state_);
   if (sourceID != ukm::kInvalidSourceId) {
     ukm::builders::IOS_PageAddedToReadingList(sourceID)
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_app_interface.mm b/ios/chrome/browser/ui/reading_list/reading_list_app_interface.mm
index 13995fe..9c4fabe 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_app_interface.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_app_interface.mm
@@ -90,8 +90,9 @@
   if (error) {
     return error;
   }
-  for (const GURL& url : model->Keys())
+  for (const GURL& url : model->GetKeys()) {
     model->RemoveEntryByURL(url);
+  }
   return nil;
 }
 
@@ -101,10 +102,12 @@
   if (error) {
     return error;
   }
-  model->AddEntry(net::GURLWithNSURL(url), base::SysNSStringToUTF8(title),
-                  reading_list::ADDED_VIA_CURRENT_APP);
+  model->AddOrReplaceEntry(net::GURLWithNSURL(url),
+                           base::SysNSStringToUTF8(title),
+                           reading_list::ADDED_VIA_CURRENT_APP,
+                           /*estimated_read_time=*/base::TimeDelta());
   if (read) {
-    model->SetReadStatus(net::GURLWithNSURL(url), true);
+    model->SetReadStatusIfExists(net::GURLWithNSURL(url), true);
   }
   return error;
 }
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_mediator.mm b/ios/chrome/browser/ui/reading_list/reading_list_mediator.mm
index 8fd2d23..c689523 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_mediator.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_mediator.mm
@@ -85,7 +85,7 @@
 }
 
 - (void)markEntryRead:(const GURL&)URL {
-  self.model->SetReadStatus(URL, true);
+  self.model->SetReadStatusIfExists(URL, true);
 }
 
 #pragma mark - ReadingListDataSource
@@ -108,7 +108,7 @@
 }
 
 - (void)setReadStatus:(BOOL)read forItem:(id<ReadingListListItem>)item {
-  self.model->SetReadStatus(item.entryURL, read);
+  self.model->SetReadStatusIfExists(item.entryURL, read);
 }
 
 - (const ReadingListEntry*)entryWithURL:(const GURL&)URL {
@@ -125,7 +125,7 @@
   std::vector<const ReadingListEntry*> readEntries;
   std::vector<const ReadingListEntry*> unreadEntries;
 
-  for (const auto& url : self.model->Keys()) {
+  for (const auto& url : self.model->GetKeys()) {
     const ReadingListEntry* entry = self.model->GetEntryByURL(url);
     DCHECK(entry);
     if (entry->IsRead()) {
@@ -147,7 +147,8 @@
         addObject:[self.itemFactory cellItemForReadingListEntry:entry]];
   }
 
-  DCHECK(self.model->Keys().size() == [readArray count] + [unreadArray count]);
+  DCHECK(self.model->GetKeys().size() ==
+         [readArray count] + [unreadArray count]);
 }
 
 - (void)fetchFaviconForItem:(id<ReadingListListItem>)item {
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_mediator_unittest.mm b/ios/chrome/browser/ui/reading_list/reading_list_mediator_unittest.mm
index 049dd64e..826039f 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_mediator_unittest.mm
@@ -60,20 +60,25 @@
 
     no_title_entry_url_ = GURL("http://chromium.org/unread3");
     // The first 3 have the same update time on purpose.
-    model_->AddEntry(GURL("http://chromium.org/unread1"), "unread1",
-                     reading_list::ADDED_VIA_CURRENT_APP);
-    model_->AddEntry(GURL("http://chromium.org/read1"), "read1",
-                     reading_list::ADDED_VIA_CURRENT_APP);
-    model_->SetReadStatus(GURL("http://chromium.org/read1"), true);
-    model_->AddEntry(GURL("http://chromium.org/unread2"), "unread2",
-                     reading_list::ADDED_VIA_CURRENT_APP);
+    model_->AddOrReplaceEntry(GURL("http://chromium.org/unread1"), "unread1",
+                              reading_list::ADDED_VIA_CURRENT_APP,
+                              /*estimated_read_time=*/base::TimeDelta());
+    model_->AddOrReplaceEntry(GURL("http://chromium.org/read1"), "read1",
+                              reading_list::ADDED_VIA_CURRENT_APP,
+                              /*estimated_read_time=*/base::TimeDelta());
+    model_->SetReadStatusIfExists(GURL("http://chromium.org/read1"), true);
+    model_->AddOrReplaceEntry(GURL("http://chromium.org/unread2"), "unread2",
+                              reading_list::ADDED_VIA_CURRENT_APP,
+                              /*estimated_read_time=*/base::TimeDelta());
     clock_.Advance(base::Milliseconds(10));
-    model_->AddEntry(no_title_entry_url_, "",
-                     reading_list::ADDED_VIA_CURRENT_APP);
+    model_->AddOrReplaceEntry(no_title_entry_url_, "",
+                              reading_list::ADDED_VIA_CURRENT_APP,
+                              /*estimated_read_time=*/base::TimeDelta());
     clock_.Advance(base::Milliseconds(10));
-    model_->AddEntry(GURL("http://chromium.org/read2"), "read2",
-                     reading_list::ADDED_VIA_CURRENT_APP);
-    model_->SetReadStatus(GURL("http://chromium.org/read2"), true);
+    model_->AddOrReplaceEntry(GURL("http://chromium.org/read2"), "read2",
+                              reading_list::ADDED_VIA_CURRENT_APP,
+                              /*estimated_read_time=*/base::TimeDelta());
+    model_->SetReadStatusIfExists(GURL("http://chromium.org/read2"), true);
     large_icon_service_.reset(new favicon::LargeIconServiceImpl(
         &mock_favicon_service_, /*image_fetcher=*/nullptr,
         /*desired_size_in_dip_for_server_requests=*/24,
diff --git a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
index 8793ca9..4171d13 100644
--- a/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
+++ b/ios/chrome/browser/ui/recent_tabs/recent_tabs_table_view_controller.mm
@@ -8,6 +8,7 @@
 
 #import "base/check_op.h"
 #import "base/mac/foundation_util.h"
+#import "base/metrics/histogram_functions.h"
 #import "base/metrics/histogram_macros.h"
 #import "base/metrics/user_metrics.h"
 #import "base/metrics/user_metrics_action.h"
@@ -1484,6 +1485,11 @@
   const sessions::SessionTab* toLoad = nullptr;
   if (openTabs->GetForeignTab(distantTab->session_tag, distantTab->tab_id,
                               &toLoad)) {
+    base::TimeDelta time_since_last_use = base::Time::Now() - toLoad->timestamp;
+    base::UmaHistogramCustomTimes("IOS.DistantTab.TimeSinceLastUse",
+                                  time_since_last_use, base::Minutes(1),
+                                  base::Days(24), 50);
+
     base::RecordAction(base::UserMetricsAction(
         "MobileRecentTabManagerTabFromOtherDeviceOpened"));
     if (self.searchTerms.length) {
diff --git a/ios/chrome/browser/ui/settings/privacy/BUILD.gn b/ios/chrome/browser/ui/settings/privacy/BUILD.gn
index 4fb8a9c..c5f7ff6 100644
--- a/ios/chrome/browser/ui/settings/privacy/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/privacy/BUILD.gn
@@ -150,6 +150,7 @@
     "//ios/chrome/browser/ui/table_view:utils",
     "//ios/chrome/browser/ui/table_view/cells",
     "//ios/chrome/test:test_support",
+    "//ios/components/security_interstitials/https_only_mode:feature",
     "//ios/web/public/test",
     "//testing/gtest",
     "//third_party/ocmock:ocmock",
diff --git a/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm
index bc585c5..3aba051 100644
--- a/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm
@@ -35,6 +35,7 @@
 #import "ios/chrome/grit/ios_chromium_strings.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
+#import "ios/components/security_interstitials/https_only_mode/feature.h"
 #import "ios/web/public/test/web_task_environment.h"
 #import "testing/gtest/include/gtest/gtest.h"
 #import "ui/base/l10n/l10n_util.h"
@@ -179,6 +180,10 @@
   CheckController();
 
   int expectedNumberOfSections = 4;
+  if (base::FeatureList::IsEnabled(
+          security_interstitials::features::kHttpsOnlyMode)) {
+    expectedNumberOfSections++;
+  }
   if (base::FeatureList::IsEnabled(kIOS3PIntentsInIncognito)) {
     expectedNumberOfSections++;
   }
@@ -198,6 +203,15 @@
       l10n_util::GetNSString(IDS_IOS_PRIVACY_SAFE_BROWSING_TITLE),
       SafeBrowsingDetailText(), 1, 0);
 
+  // HTTPS-Only Mode section.
+  if (base::FeatureList::IsEnabled(
+          security_interstitials::features::kHttpsOnlyMode)) {
+    currentSection++;
+    EXPECT_EQ(1, NumberOfItemsInSection(currentSection));
+    CheckSwitchCellStateAndTextWithId(
+        NO, IDS_IOS_SETTINGS_HTTPS_ONLY_MODE_TITLE, currentSection, 0);
+  }
+
   // WebServices section.
   currentSection++;
   EXPECT_EQ(1, NumberOfItemsInSection(currentSection));
@@ -256,6 +270,10 @@
   CheckController();
 
   int expectedNumberOfSections = 4;
+  if (base::FeatureList::IsEnabled(
+          security_interstitials::features::kHttpsOnlyMode)) {
+    expectedNumberOfSections++;
+  }
   if (base::FeatureList::IsEnabled(kIOS3PIntentsInIncognito)) {
     expectedNumberOfSections++;
   }
@@ -278,6 +296,10 @@
   CheckController();
 
   int expectedNumberOfSections = 4;
+  if (base::FeatureList::IsEnabled(
+          security_interstitials::features::kHttpsOnlyMode)) {
+    expectedNumberOfSections++;
+  }
   if (base::FeatureList::IsEnabled(kIOS3PIntentsInIncognito)) {
     expectedNumberOfSections++;
   }
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm
index 821c7bc..0670f53b 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_egtest.mm
@@ -451,8 +451,7 @@
 }
 
 // Tests that Clear Browsing Data can be successfully done from tab grid.
-// TODO(crbug.com/1379374): test is flaky on the clear browsing data page.
-- (void)FLAKY_testClearBrowsingData {
+- (void)testClearBrowsingData {
   // Load history
   [self loadTestURLs];
 
diff --git a/ios/chrome/test/app/browsing_data_test_util.h b/ios/chrome/test/app/browsing_data_test_util.h
index 572d0ef..992a60e 100644
--- a/ios/chrome/test/app/browsing_data_test_util.h
+++ b/ios/chrome/test/app/browsing_data_test_util.h
@@ -17,6 +17,10 @@
 // successful or timed out.
 [[nodiscard]] bool ClearBrowsingHistory();
 
+// Clears cookies and site data and returns whether the operation was
+// successful or timed out.
+[[nodiscard]] bool ClearCookiesAndSiteData();
+
 // Clears browsing data and returns whether clearing was successful or timed
 // out.
 // TODO(crbug.com/1016960): The method will time out if it's called from
diff --git a/ios/chrome/test/app/browsing_data_test_util.mm b/ios/chrome/test/app/browsing_data_test_util.mm
index 366b1a0..5626ef1 100644
--- a/ios/chrome/test/app/browsing_data_test_util.mm
+++ b/ios/chrome/test/app/browsing_data_test_util.mm
@@ -62,6 +62,11 @@
                            BrowsingDataRemoveMask::REMOVE_HISTORY);
 }
 
+bool ClearCookiesAndSiteData() {
+  return ClearBrowsingData(/*off_the_record=*/false,
+                           BrowsingDataRemoveMask::REMOVE_SITE_DATA);
+}
+
 bool ClearAllBrowsingData(bool off_the_record) {
   return ClearBrowsingData(off_the_record, BrowsingDataRemoveMask::REMOVE_ALL);
 }
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
index 07d5dfce..a12c4c4 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
@@ -15,7 +15,6 @@
 
 @class ElementSelector;
 @class FakeSystemIdentity;
-@class NamedGuide;
 
 @interface JavaScriptExecutionResult : NSObject
 @property(readonly, nonatomic) BOOL success;
@@ -76,10 +75,6 @@
 // Reloads the page without waiting for the page to load.
 + (void)startReloading;
 
-// Returns the NamedGuide with the given `name`, if one is attached to `view`
-// or one of `view`'s ancestors.  If no guide is found, returns nil.
-+ (NamedGuide*)guideWithName:(NSString*)name view:(UIView*)view;
-
 // Loads `URL` as if it was opened from an external application.
 + (void)openURLFromExternalApp:(NSString*)URL;
 
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
index 853b446d4..5105203c 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -42,7 +42,6 @@
 #import "ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.h"
 #import "ios/chrome/browser/ui/thumb_strip/thumb_strip_feature.h"
 #import "ios/chrome/browser/ui/ui_feature_flags.h"
-#import "ios/chrome/browser/ui/util/named_guide.h"
 #import "ios/chrome/browser/ui/util/rtl_geometry.h"
 #import "ios/chrome/browser/unified_consent/unified_consent_service_factory.h"
 #import "ios/chrome/browser/web/web_navigation_browser_agent.h"
@@ -211,10 +210,6 @@
       ->Reload();
 }
 
-+ (NamedGuide*)guideWithName:(GuideName*)name view:(UIView*)view {
-  return [NamedGuide guideWithName:name view:view];
-}
-
 + (void)openURLFromExternalApp:(NSString*)URL {
   chrome_test_util::OpenChromeFromExternalApp(
       GURL(base::SysNSStringToUTF8(URL)));
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_ui.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_ui.mm
index e4a0df9..2294c13 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_ui.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_ui.mm
@@ -254,6 +254,7 @@
   [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                           HistoryClearBrowsingDataButton()]
       performAction:grey_tap()];
+  [self waitForClearBrowsingDataViewVisible:YES];
   [self selectAllBrowsingDataAndClear];
 
   // Include sufficientlyVisible condition for the case of the clear browsing
@@ -270,6 +271,7 @@
   [self openSettingsMenu];
   [self tapSettingsMenuButton:chrome_test_util::SettingsMenuPrivacyButton()];
   [self tapPrivacyMenuButton:chrome_test_util::ClearBrowsingDataCell()];
+  [self waitForClearBrowsingDataViewVisible:YES];
 
   // Clear all data.
   [self selectAllBrowsingDataAndClear];
@@ -528,4 +530,26 @@
       onElementWithMatcher:ClearBrowsingDataView()] performAction:grey_tap()];
 }
 
+// Waits for the clear browsing data view to become visible if `isVisible` is
+// YES, otherwise waits for it to disappear. If the condition is not met within
+// a timeout, a GREYAssert is induced.
+- (void)waitForClearBrowsingDataViewVisible:(BOOL)isVisible {
+  ConditionBlock condition = ^{
+    NSError* error = nil;
+    id<GREYMatcher> visibleMatcher =
+        isVisible ? grey_sufficientlyVisible() : grey_nil();
+    [[EarlGrey selectElementWithMatcher:ClearBrowsingDataView()]
+        assertWithMatcher:visibleMatcher
+                    error:&error];
+    return error == nil;
+  };
+  NSString* errorMessage = isVisible
+                               ? @"Clear browsing data view was not visible"
+                               : @"Clear browsing data view was visible";
+  bool clearBrowsingDataViewVisibility =
+      base::test::ios::WaitUntilConditionOrTimeout(kWaitForUIElementTimeout,
+                                                   condition);
+  EG_TEST_HELPER_ASSERT_TRUE(clearBrowsingDataViewVisibility, errorMessage);
+}
+
 @end
diff --git a/ios/chrome/test/providers/lens/test_lens.mm b/ios/chrome/test/providers/lens/test_lens.mm
index 8b3b890f..8995ba5f 100644
--- a/ios/chrome/test/providers/lens/test_lens.mm
+++ b/ios/chrome/test/providers/lens/test_lens.mm
@@ -53,22 +53,6 @@
   return absl::nullopt;
 }
 
-web::NavigationManager::WebLoadParams GenerateLensLoadParamsForImage(
-    UIImage* image,
-    LensEntrypoint entry_point,
-    bool is_incognito) {
-  // Lens is not supported for tests.
-  NOTREACHED() << "Lens is not supported.";
-  return web::NavigationManager::WebLoadParams({});
-}
-
-void GenerateLensLoadParamsForImageAsync(UIImage* image,
-                                         LensEntrypoint entry_point,
-                                         bool is_incognito,
-                                         LensWebParamsCallback completion) {
-  NOTREACHED() << "Lens is not supported.";
-}
-
 void GenerateLensLoadParamsAsync(LensQuery* query,
                                  LensWebParamsCallback completion) {
   NOTREACHED() << "Lens is not supported.";
diff --git a/ios/components/security_interstitials/https_only_mode/feature.cc b/ios/components/security_interstitials/https_only_mode/feature.cc
index b2440f7..ab23287 100644
--- a/ios/components/security_interstitials/https_only_mode/feature.cc
+++ b/ios/components/security_interstitials/https_only_mode/feature.cc
@@ -7,9 +7,7 @@
 namespace security_interstitials {
 namespace features {
 
-BASE_FEATURE(kHttpsOnlyMode,
-             "HttpsOnlyMode",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kHttpsOnlyMode, "HttpsOnlyMode", base::FEATURE_ENABLED_BY_DEFAULT);
 
 }  // namespace features
 }  // namespace security_interstitials
diff --git a/ios/public/provider/chrome/browser/lens/lens_api.h b/ios/public/provider/chrome/browser/lens/lens_api.h
index e422891..0a249b7 100644
--- a/ios/public/provider/chrome/browser/lens/lens_api.h
+++ b/ios/public/provider/chrome/browser/lens/lens_api.h
@@ -69,21 +69,6 @@
 absl::optional<LensEntrypoint> GetLensEntryPointFromURL(const GURL& url);
 
 // Generates web load params for a Lens image search for the given
-// `image` and `entry_point`.
-web::NavigationManager::WebLoadParams GenerateLensLoadParamsForImage(
-    UIImage* image,
-    LensEntrypoint entry_point,
-    bool is_incognito);
-
-// Generates web load params for a Lens image search for the given
-// `image` and `entry_point`. `completion` will be run on the main
-// thread.
-void GenerateLensLoadParamsForImageAsync(UIImage* image,
-                                         LensEntrypoint entry_point,
-                                         bool is_incognito,
-                                         LensWebParamsCallback completion);
-
-// Generates web load params for a Lens image search for the given
 // `query`. `completion` will be run on the main thread.
 void GenerateLensLoadParamsAsync(LensQuery* query,
                                  LensWebParamsCallback completion);
diff --git a/ios/public/provider/chrome/browser/signin/BUILD.gn b/ios/public/provider/chrome/browser/signin/BUILD.gn
index 354c031..79cda377 100644
--- a/ios/public/provider/chrome/browser/signin/BUILD.gn
+++ b/ios/public/provider/chrome/browser/signin/BUILD.gn
@@ -122,14 +122,19 @@
 source_set("unit_tests") {
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
-  sources = [ "chrome_identity_service_unittest.mm" ]
+  sources = [
+    "account_capabilities_fetcher_ios_unittest.mm",
+    "chrome_identity_service_unittest.mm",
+  ]
   deps = [
     ":capabilities_types",
+    ":fetcher",
     ":signin",
     "//base",
     "//base/test:test_support",
     "//components/signin/internal/identity_manager",
     "//ios/chrome/browser/signin:fake_system_identity",
+    "//ios/public/provider/chrome/browser/signin:capabilities_types",
     "//testing/gmock",
     "//testing/gtest",
   ]
diff --git a/ios/public/provider/chrome/browser/signin/DEPS b/ios/public/provider/chrome/browser/signin/DEPS
index c26221c..56c0188 100644
--- a/ios/public/provider/chrome/browser/signin/DEPS
+++ b/ios/public/provider/chrome/browser/signin/DEPS
@@ -21,4 +21,7 @@
   "chrome_identity_service_unittest\.mm": [
     "+ios/chrome/browser/signin/fake_system_identity.h",
   ],
+  "account_capabilities_fetcher_ios_unittest\.mm": [
+    "+ios/chrome/browser/signin/fake_system_identity.h",
+  ],
 }
diff --git a/ios/public/provider/chrome/browser/signin/account_capabilities_fetcher_ios_unittest.mm b/ios/public/provider/chrome/browser/signin/account_capabilities_fetcher_ios_unittest.mm
new file mode 100644
index 0000000..6b6a8e6
--- /dev/null
+++ b/ios/public/provider/chrome/browser/signin/account_capabilities_fetcher_ios_unittest.mm
@@ -0,0 +1,105 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/public/provider/chrome/browser/signin/account_capabilities_fetcher_ios.h"
+
+#import "base/callback.h"
+#import "base/run_loop.h"
+#import "base/test/task_environment.h"
+#import "components/signin/internal/identity_manager/account_capabilities_constants.h"
+#import "ios/chrome/browser/signin/fake_system_identity.h"
+#import "ios/public/provider/chrome/browser/signin/capabilities_dict.h"
+#import "testing/gtest/include/gtest/gtest.h"
+#import "testing/gtest_mac.h"
+#import "testing/platform_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// A version of ChromeIdentityService which, when fetching, always return
+// `capabilities`.
+class ChromeIdentityServiceFake : public ios::ChromeIdentityService {
+ public:
+  ChromeIdentityServiceFake(ios::CapabilitiesDict* capabilities)
+      : capabilities_(capabilities) {}
+
+  void FetchCapabilities(
+      id<SystemIdentity> identity,
+      NSArray<NSString*>* capabilities,
+      ios::ChromeIdentityCapabilitiesFetchCompletionBlock completion) override {
+    completion(capabilities_, nullptr);
+  }
+
+ private:
+  ios::CapabilitiesDict* capabilities_;
+};
+
+void CheckHaveEmailAddressDisplayed(
+    signin::Tribool capability_expected,
+    base::RunLoop* run_loop,
+    const CoreAccountId& core_accound_id,
+    const absl::optional<AccountCapabilities>& account_capabilities) {
+  ASSERT_TRUE(account_capabilities.has_value());
+  ASSERT_EQ(account_capabilities->can_have_email_address_displayed(),
+            capability_expected);
+  run_loop->Quit();
+}
+
+class AccountCapabilitiesFetcherIOSTest : public PlatformTest {
+ public:
+  AccountCapabilitiesFetcherIOSTest() {
+    identity_ = [FakeSystemIdentity identityWithEmail:@"foo@bar.com"
+                                               gaiaID:@"foo_bar_id"
+                                                 name:@"Foo"];
+  }
+
+  ~AccountCapabilitiesFetcherIOSTest() override = default;
+
+ protected:
+  // Ensure that callback gets `capability_enabled` on
+  // `kCanHaveEmailAddressDisplayedCapabilityName`.
+  void testCapabilityValueFetchedIsReceived(
+      ios::ChromeIdentityCapabilityResult capability_fetched,
+      signin::Tribool capability_expected) {
+    base::test::SingleThreadTaskEnvironment task_environment;
+    base::RunLoop run_loop;
+    CoreAccountInfo account_info;
+    AccountCapabilitiesFetcher::OnCompleteCallback on_complete_callback =
+        base::BindOnce(&CheckHaveEmailAddressDisplayed, capability_expected,
+                       &run_loop);
+    ios::CapabilitiesDict* capabilities = @{
+      @(kCanHaveEmailAddressDisplayedCapabilityName) :
+          @(static_cast<int>(capability_fetched))
+    };
+    ChromeIdentityServiceFake chrome_identity_service =
+        ChromeIdentityServiceFake(capabilities);
+
+    ios::AccountCapabilitiesFetcherIOS fetcher(
+        account_info, std::move(on_complete_callback), &chrome_identity_service,
+        identity_);
+    fetcher.Start();
+    run_loop.Run();
+  }
+
+  FakeSystemIdentity* identity_ = nil;
+};
+
+// Check that a capability set to True is received as True.
+TEST_F(AccountCapabilitiesFetcherIOSTest, CheckTrueCapability) {
+  testCapabilityValueFetchedIsReceived(
+      ios::ChromeIdentityCapabilityResult::kTrue, signin::Tribool::kTrue);
+}
+
+// Check that a capability set to False is received as False.
+TEST_F(AccountCapabilitiesFetcherIOSTest, CheckFalseCapability) {
+  testCapabilityValueFetchedIsReceived(
+      ios::ChromeIdentityCapabilityResult::kFalse, signin::Tribool::kFalse);
+}
+
+// Check that a capability set to Unknown is received as Unknown.
+TEST_F(AccountCapabilitiesFetcherIOSTest, CheckUnknownCapability) {
+  testCapabilityValueFetchedIsReceived(
+      ios::ChromeIdentityCapabilityResult::kUnknown, signin::Tribool::kUnknown);
+}
diff --git a/ios/web/public/ui/crw_web_view_proxy.h b/ios/web/public/ui/crw_web_view_proxy.h
index aa33aaf..b1a1fbd 100644
--- a/ios/web/public/ui/crw_web_view_proxy.h
+++ b/ios/web/public/ui/crw_web_view_proxy.h
@@ -61,6 +61,12 @@
 // Unregister the registered insets for the given caller.
 - (void)unregisterInsetsForCaller:(id)caller;
 
+// Sets the content view to `nil` and adds a placeholder scroll view if needed.
+// Setting up the placeholder can be costly, so we don't want to proceed with
+// the setup when clearing the content view, unless we intend to set a non-nil
+// content view in the future and wish to preserve state.
+- (void)clearContentViewAndAddPlaceholder:(BOOL)addPlaceholder;
+
 // Wrapper around the addSubview method of the webview.
 - (void)addSubview:(UIView*)view;
 
diff --git a/ios/web/web_state/ui/crw_web_controller.h b/ios/web/web_state/ui/crw_web_controller.h
index 4d6619d..ad70cdc 100644
--- a/ios/web/web_state/ui/crw_web_controller.h
+++ b/ios/web/web_state/ui/crw_web_controller.h
@@ -199,8 +199,9 @@
 // Creates a web view if it's not yet created. Returns the web view.
 - (WKWebView*)ensureWebViewCreated;
 
-// Removes the webView from the view hierarchy.
-- (void)removeWebViewFromViewHierarchy;
+// Removes the webView from the view hierarchy. The `shutdown` parameter
+// indicates if this method was called in a shutdown context.
+- (void)removeWebViewFromViewHierarchyForShutdown:(BOOL)shutdown;
 // Adds the webView back in the view hierarchy.
 - (void)addWebViewToViewHierarchy;
 
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 35724ab..6c78fef 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -566,7 +566,7 @@
 
   // Explicitly reset content to clean up views and avoid dangling KVO
   // observers.
-  [_containerView resetContent];
+  [_containerView resetContentForShutdown:YES];
 
   _webStateImpl = nullptr;
 
@@ -904,8 +904,8 @@
   }
 }
 
-- (void)removeWebViewFromViewHierarchy {
-  [_containerView resetContent];
+- (void)removeWebViewFromViewHierarchyForShutdown:(BOOL)shutdown {
+  [_containerView resetContentForShutdown:shutdown];
 }
 
 - (void)addWebViewToViewHierarchy {
@@ -1760,7 +1760,7 @@
 
   [self setWebView:nil];
   [self.navigationHandler stopLoading];
-  [_containerView resetContent];
+  [_containerView resetContentForShutdown:YES];
 
   // webView:didFailProvisionalNavigation:withError: may never be called after
   // resetting WKWebView, so it is important to clear pending navigations now.
diff --git a/ios/web/web_state/ui/crw_web_controller_container_view.h b/ios/web/web_state/ui/crw_web_controller_container_view.h
index 720e6c4c..576f6c701 100644
--- a/ios/web/web_state/ui/crw_web_controller_container_view.h
+++ b/ios/web/web_state/ui/crw_web_controller_container_view.h
@@ -58,8 +58,9 @@
 // Returns YES if the container view is currently displaying content.
 - (BOOL)isViewAlive;
 
-// Removes all subviews and resets state to default.
-- (void)resetContent;
+// Removes all subviews and resets state to default. The `shutdown` parameter
+// indicates if this method was called in a shutdown context.
+- (void)resetContentForShutdown:(BOOL)shutdown;
 
 // Replaces the currently displayed content with `webViewContentView`.
 - (void)displayWebViewContentView:(CRWWebViewContentView*)webViewContentView;
diff --git a/ios/web/web_state/ui/crw_web_controller_container_view.mm b/ios/web/web_state/ui/crw_web_controller_container_view.mm
index 95677017..1015869 100644
--- a/ios/web/web_state/ui/crw_web_controller_container_view.mm
+++ b/ios/web/web_state/ui/crw_web_controller_container_view.mm
@@ -139,9 +139,9 @@
 
 #pragma mark Content Setters
 
-- (void)resetContent {
+- (void)resetContentForShutdown:(BOOL)shutdown {
   self.webViewContentView = nil;
-  self.contentViewProxy.contentView = nil;
+  [self.contentViewProxy clearContentViewAndAddPlaceholder:!shutdown];
 }
 
 - (void)displayWebViewContentView:(CRWWebViewContentView*)webViewContentView {
diff --git a/ios/web/web_state/ui/crw_web_controller_unittest.mm b/ios/web/web_state/ui/crw_web_controller_unittest.mm
index 327b761..2a1b999 100644
--- a/ios/web/web_state/ui/crw_web_controller_unittest.mm
+++ b/ios/web/web_state/ui/crw_web_controller_unittest.mm
@@ -302,7 +302,7 @@
 
   ASSERT_EQ(web_controller().view, web_view.superview.superview);
 
-  [web_controller() removeWebViewFromViewHierarchy];
+  [web_controller() removeWebViewFromViewHierarchyForShutdown:NO];
   EXPECT_EQ(nil, web_view.superview.superview);
 
   [web_controller() addWebViewToViewHierarchy];
diff --git a/ios/web/web_state/ui/crw_web_view_proxy_impl.mm b/ios/web/web_state/ui/crw_web_view_proxy_impl.mm
index 32a03df..db97fe2 100644
--- a/ios/web/web_state/ui/crw_web_view_proxy_impl.mm
+++ b/ios/web/web_state/ui/crw_web_view_proxy_impl.mm
@@ -166,11 +166,23 @@
   [_registeredInsets removeObjectForKey:callerValue];
 }
 
-- (void)setContentView:(CRWContentView*)contentView {
+// Do not use with a `nil` `contentView`. Instead, use
+// `clearContentViewAndAddPlaceholder` whenever `contentView` needs to be set to
+// `nil`. This allows us to evaluate the need of setting up the placeholder
+// scroll view when clearing the content view.
+- (void)setContentView:(nonnull CRWContentView*)contentView {
+  DCHECK(contentView);
   _contentView = contentView;
   [_contentViewScrollViewProxy setScrollView:contentView.scrollView];
 }
 
+- (void)clearContentViewAndAddPlaceholder:(BOOL)addPlaceholder {
+  _contentView = nil;
+  if (addPlaceholder) {
+    [_contentViewScrollViewProxy setScrollView:nil];
+  }
+}
+
 - (void)addSubview:(UIView*)view {
   return [_contentView addSubview:view];
 }
diff --git a/ios/web/web_state/web_state_impl_realized_web_state.mm b/ios/web/web_state/web_state_impl_realized_web_state.mm
index 3e54976..4f0418b 100644
--- a/ios/web/web_state/web_state_impl_realized_web_state.mm
+++ b/ios/web/web_state/web_state_impl_realized_web_state.mm
@@ -616,7 +616,7 @@
 }
 
 void WebStateImpl::RealizedWebState::DidCoverWebContent() {
-  [web_controller_ removeWebViewFromViewHierarchy];
+  [web_controller_ removeWebViewFromViewHierarchyForShutdown:NO];
   WasHidden();
 }
 
diff --git a/media/mojo/services/media_foundation_service.cc b/media/mojo/services/media_foundation_service.cc
index 85113835..26c2c61 100644
--- a/media/mojo/services/media_foundation_service.cc
+++ b/media/mojo/services/media_foundation_service.cc
@@ -61,13 +61,6 @@
 const char kSwSecureRobustness[] = "SW_SECURE_DECODE";
 const char kHwSecureRobustness[] = "HW_SECURE_ALL";
 
-// We need this char array to query the Windows Media Foundation API
-// to know which codecs have the clear lead fix enabled on the computer.
-// We do not check cbcs-clearlead because clearlead fix itself should be
-// orthogonal to encryption scheme support.
-// https://docs.microsoft.com/en-us/windows/win32/api/mfmediaengine/nf-mfmediaengine-imfextendeddrmtypesupport-istypesupportedex
-const char kClearLeadEncryptionScheme[] = "cenc-clearlead";
-
 // The followings define the supported codecs and encryption schemes that we try
 // to query.
 constexpr VideoCodec kAllVideoCodecs[] = {
@@ -240,18 +233,6 @@
   return IsTypeSupportedInternal(cdm_factory, key_system, is_hw_secure, type);
 }
 
-bool IsClearLeadSupported(ComPtr<IMFContentDecryptionModuleFactory> cdm_factory,
-                          const std::string& key_system,
-                          bool is_hw_secure,
-                          VideoCodec video_codec) {
-  const FeatureMap extra_features = {
-      {kEncryptionSchemeQueryName, kClearLeadEncryptionScheme},
-      {kEncryptionIvQueryName,
-       base::NumberToString(GetIvSize(EncryptionScheme::kCenc))}};
-  return IsTypeSupported(video_codec, /*audio_codec=*/absl::nullopt,
-                         extra_features, cdm_factory, key_system, is_hw_secure);
-}
-
 base::flat_set<EncryptionScheme> GetSupportedEncryptionSchemes(
     ComPtr<IMFContentDecryptionModuleFactory> cdm_factory,
     const std::string& key_system,
@@ -377,20 +358,11 @@
       }
 #endif
 
-      // We check for `is_hw_secure` because clear lead should always be
-      // supported for software security.
-      if (is_hw_secure) {
-        // TODO(b/257115498): We only check for clearlead support in HEVC codec
-        // because the WIN OS only supports clear lead support check for HEVC
-        // and AV1, and AV1 does not have the clear lead fix out for the codec.
-        if (video_codec == VideoCodec::kHEVC) {
-          // When IsClearLeadSupported returns false, this can either happen
-          // because: 1. The OS doesn't support the check of cenc-clearlead yet
-          // or 2. Clear Lead fix for `video_codec` is not available.
-          video_codec_info.supports_clear_lead = IsClearLeadSupported(
-              cdm_factory, key_system, is_hw_secure, video_codec);
-        }
-      } else {
+      // We check for `!is_hw_secure` because clear lead should always be
+      // supported for software security. When clear lead is supported
+      // for hardware security (b/219818166), we will add a query to
+      // set supports_clear_lead.
+      if (!is_hw_secure) {
         video_codec_info.supports_clear_lead = true;
       }
 
diff --git a/net/cert/pki/general_names.cc b/net/cert/pki/general_names.cc
index d2bbd25..4489c3916 100644
--- a/net/cert/pki/general_names.cc
+++ b/net/cert/pki/general_names.cc
@@ -4,6 +4,8 @@
 
 #include "net/cert/pki/general_names.h"
 
+#include <cstring>
+
 #include "net/cert/pki/cert_error_params.h"
 #include "net/cert/pki/cert_errors.h"
 #include "net/cert/pki/string_util.h"
diff --git a/remoting/codec/video_encoder.h b/remoting/codec/video_encoder.h
index 078fa7f..418da7b 100644
--- a/remoting/codec/video_encoder.h
+++ b/remoting/codec/video_encoder.h
@@ -25,8 +25,7 @@
  public:
   virtual ~VideoEncoder() {}
 
-  // Request that the encoder provide lossless encoding, or color, if possible.
-  virtual void SetLosslessEncode(bool want_lossless) {}
+  // Request that the encoder provide lossless color, if possible.
   virtual void SetLosslessColor(bool want_lossless) {}
 
   // Encode an image stored in |frame|. If |frame.updated_region()| is empty
diff --git a/remoting/codec/video_encoder_vpx.cc b/remoting/codec/video_encoder_vpx.cc
index 59e760a..8bcc394 100644
--- a/remoting/codec/video_encoder_vpx.cc
+++ b/remoting/codec/video_encoder_vpx.cc
@@ -34,8 +34,7 @@
 const int kVp9I420ProfileNumber = 0;
 const int kVp9I444ProfileNumber = 1;
 
-// Magic encoder constants for adaptive quantization strategy.
-const int kVp9AqModeNone = 0;
+// Magic encoder constant for adaptive quantization strategy.
 const int kVp9AqModeCyclicRefresh = 3;
 
 void SetCommonCodecParameters(vpx_codec_enc_cfg_t* config,
@@ -84,29 +83,21 @@
 
 void SetVp9CodecParameters(vpx_codec_enc_cfg_t* config,
                            const webrtc::DesktopSize& size,
-                           bool lossless_color,
-                           bool lossless_encode) {
+                           bool lossless_color) {
   SetCommonCodecParameters(config, size);
 
   // Configure VP9 for I420 or I444 source frames.
   config->g_profile =
       lossless_color ? kVp9I444ProfileNumber : kVp9I420ProfileNumber;
 
-  if (lossless_encode) {
-    // Disable quantization entirely, putting the encoder in "lossless" mode.
-    config->rc_min_quantizer = 0;
-    config->rc_max_quantizer = 0;
-    config->rc_end_usage = VPX_VBR;
-  } else {
-    // TODO(wez): Set quantization range to 4-40, once the libvpx encoder is
-    // updated not to output any bits if nothing needs topping-off.
-    config->rc_min_quantizer = 20;
-    config->rc_max_quantizer = 30;
-    config->rc_end_usage = VPX_CBR;
-    // In the absence of a good bandwidth estimator set the target bitrate to a
-    // conservative default.
-    config->rc_target_bitrate = 500;
-  }
+  // TODO(wez): Set quantization range to 4-40, once the libvpx encoder is
+  // updated not to output any bits if nothing needs topping-off.
+  config->rc_min_quantizer = 20;
+  config->rc_max_quantizer = 30;
+  config->rc_end_usage = VPX_CBR;
+  // In the absence of a good bandwidth estimator set the target bitrate to a
+  // conservative default.
+  config->rc_target_bitrate = 500;
 }
 
 void SetVp8CodecOptions(vpx_codec_ctx_t* codec) {
@@ -121,12 +112,9 @@
   DCHECK_EQ(VPX_CODEC_OK, ret) << "Failed to set noise sensitivity";
 }
 
-void SetVp9CodecOptions(vpx_codec_ctx_t* codec, bool lossless_encode) {
-  // Request the lowest-CPU usage that VP9 supports, which depends on whether
-  // we are encoding lossy or lossless.
+void SetVp9CodecOptions(vpx_codec_ctx_t* codec) {
   // Note that this is configured via the same parameter as for VP8.
-  int cpu_used = lossless_encode ? 5 : 6;
-  vpx_codec_err_t ret = vpx_codec_control(codec, VP8E_SET_CPUUSED, cpu_used);
+  vpx_codec_err_t ret = vpx_codec_control(codec, VP8E_SET_CPUUSED, 6);
   DCHECK_EQ(VPX_CODEC_OK, ret) << "Failed to set CPUUSED";
 
   // Use the lowest level of noise sensitivity so as to spend less time
@@ -139,9 +127,8 @@
       codec, VP9E_SET_TUNE_CONTENT, VP9E_CONTENT_SCREEN);
   DCHECK_EQ(VPX_CODEC_OK, ret) << "Failed to set screen content mode";
 
-  // Set cyclic refresh (aka "top-off") only for lossy encoding.
-  int aq_mode = lossless_encode ? kVp9AqModeNone : kVp9AqModeCyclicRefresh;
-  ret = vpx_codec_control(codec, VP9E_SET_AQ_MODE, aq_mode);
+  // Set cyclic refresh (aka "top-off") for lossy encoding.
+  ret = vpx_codec_control(codec, VP9E_SET_AQ_MODE, kVp9AqModeCyclicRefresh);
   DCHECK_EQ(VPX_CODEC_OK, ret) << "Failed to set aq mode";
 }
 
@@ -243,15 +230,6 @@
   clock_ = tick_clock;
 }
 
-void VideoEncoderVpx::SetLosslessEncode(bool want_lossless) {
-  if (use_vp9_ && (want_lossless != lossless_encode_)) {
-    lossless_encode_ = want_lossless;
-    if (codec_)
-      Configure(webrtc::DesktopSize(codec_->config.enc->g_w,
-                                    codec_->config.enc->g_h));
-  }
-}
-
 void VideoEncoderVpx::SetLosslessColor(bool want_lossless) {
   if (use_vp9_ && (want_lossless != lossless_color_)) {
     lossless_color_ = want_lossless;
@@ -305,7 +283,7 @@
       << "Details: " << vpx_codec_error(codec_.get()) << "\n"
       << vpx_codec_error_detail(codec_.get());
 
-  if (use_vp9_ && !lossless_encode_) {
+  if (use_vp9_) {
     ret = vpx_codec_control(codec_.get(), VP9E_GET_ACTIVEMAP, &act_map);
     DCHECK_EQ(ret, VPX_CODEC_OK)
         << "Failed to fetch active map: "
@@ -352,7 +330,6 @@
 
 void VideoEncoderVpx::Configure(const webrtc::DesktopSize& size) {
   DCHECK(use_vp9_ || !lossless_color_);
-  DCHECK(use_vp9_ || !lossless_encode_);
 
   // Tear down |image_| if it no longer matches the size and color settings.
   // PrepareImage() will then create a new buffer of the required dimensions if
@@ -390,7 +367,7 @@
 
   // Customize the default configuration to our needs.
   if (use_vp9_) {
-    SetVp9CodecParameters(&config, size, lossless_color_, lossless_encode_);
+    SetVp9CodecParameters(&config, size, lossless_color_);
   } else {
     SetVp8CodecParameters(&config, size);
   }
@@ -407,7 +384,7 @@
 
   // Apply further customizations to the codec now it's initialized.
   if (use_vp9_) {
-    SetVp9CodecOptions(codec_.get(), lossless_encode_);
+    SetVp9CodecOptions(codec_.get());
   } else {
     SetVp8CodecOptions(codec_.get());
   }
diff --git a/remoting/codec/video_encoder_vpx.h b/remoting/codec/video_encoder_vpx.h
index 2ae99a9d..37160a8 100644
--- a/remoting/codec/video_encoder_vpx.h
+++ b/remoting/codec/video_encoder_vpx.h
@@ -38,7 +38,6 @@
   void SetTickClockForTests(const base::TickClock* tick_clock);
 
   // VideoEncoder interface.
-  void SetLosslessEncode(bool want_lossless) override;
   void SetLosslessColor(bool want_lossless) override;
   std::unique_ptr<VideoPacket> Encode(
       const webrtc::DesktopFrame& frame) override;
@@ -47,7 +46,7 @@
   explicit VideoEncoderVpx(bool use_vp9);
 
   // (Re)Configures this instance to encode frames of the specified |size|,
-  // with the configured lossless color & encoding modes.
+  // with the configured lossless color mode.
   void Configure(const webrtc::DesktopSize& size);
 
   // Prepares |image_| for encoding. Writes updated rectangles into
@@ -66,9 +65,7 @@
   // True if the encoder is for VP9, false for VP8.
   const bool use_vp9_;
 
-  // Options controlling VP9 encode quantization and color space.
-  // These are always off (false) for VP8.
-  bool lossless_encode_ = false;
+  // Option controlling VP9 color space, this is always off (false) for VP8.
   bool lossless_color_ = false;
 
   // Holds the initialized & configured codec.
diff --git a/remoting/codec/video_encoder_vpx_unittest.cc b/remoting/codec/video_encoder_vpx_unittest.cc
index df69cca..59769d5 100644
--- a/remoting/codec/video_encoder_vpx_unittest.cc
+++ b/remoting/codec/video_encoder_vpx_unittest.cc
@@ -22,8 +22,8 @@
 const uint32_t kGreenColor = 0x00ff00;
 
 // Creates a frame stippled between blue and red pixels, which is useful for
-// lossy/lossless encode and color tests. By default all pixels in the frame
-// are included in the updated_region().
+// lossy/lossless color tests. By default all pixels in the frame are included
+// in the updated_region().
 static std::unique_ptr<webrtc::DesktopFrame> CreateTestFrame(
     const webrtc::DesktopSize& frame_size) {
   std::unique_ptr<webrtc::DesktopFrame> frame(
@@ -48,36 +48,10 @@
 
 TEST(VideoEncoderVpxTest, Vp9) {
   std::unique_ptr<VideoEncoderVpx> encoder(VideoEncoderVpx::CreateForVP9());
-  // VP9 encoder defaults to lossless encode and lossy (I420) color.
+  // VP9 encoder defaults to lossy (I420) color.
   TestVideoEncoder(encoder.get(), false);
 }
 
-// Test that the VP9 encoder can switch between lossy & lossless encode.
-TEST(VideoEncoderVpxTest, Vp9LossyEncodeSwitching) {
-  std::unique_ptr<VideoEncoderVpx> encoder(VideoEncoderVpx::CreateForVP9());
-
-  webrtc::DesktopSize frame_size(100, 100);
-  std::unique_ptr<webrtc::DesktopFrame> frame(CreateTestFrame(frame_size));
-
-  // Lossy encode the first frame.
-  encoder->SetLosslessEncode(false);
-  std::unique_ptr<VideoPacket> lossy_packet = encoder->Encode(*frame);
-
-  // Lossless encode the second frame.
-  encoder->SetLosslessEncode(true);
-  std::unique_ptr<VideoPacket> lossless_packet = encoder->Encode(*frame);
-  // Lossless encode is so good that the frames are smaller than the lossy
-  // encodes. This comparison needs to be revisited.
-  // http://crbug.com/439166
-  // EXPECT_GT(lossless_packet->data().size(), lossy_packet->data().size());
-
-  // Lossy encode one more frame.
-  encoder->SetLosslessEncode(false);
-  lossy_packet = encoder->Encode(*frame);
-  // Same bug as above.
-  // EXPECT_LT(lossy_packet->data().size(), lossless_packet->data().size());
-}
-
 // Test that the VP9 encoder can switch between lossy & lossless color.
 TEST(VideoEncoderVpxTest, Vp9LossyColorSwitching) {
   std::unique_ptr<VideoEncoderVpx> encoder(VideoEncoderVpx::CreateForVP9());
@@ -106,7 +80,6 @@
   std::unique_ptr<webrtc::DesktopFrame> frame(CreateTestFrame(frame_size));
 
   // Encode a frame, to give the encoder a chance to crash if misconfigured.
-  encoder->SetLosslessEncode(true);
   encoder->SetLosslessColor(true);
   std::unique_ptr<VideoPacket> packet = encoder->Encode(*frame);
   EXPECT_TRUE(packet);
@@ -169,15 +142,8 @@
   TestVideoEncoderEmptyFrames(encoder.get(), 0);
 }
 
-TEST(VideoEncoderVpxTest, Vp9LosslessUnchangedFrame) {
-  std::unique_ptr<VideoEncoderVpx> encoder(VideoEncoderVpx::CreateForVP9());
-  encoder->SetLosslessEncode(true);
-  TestVideoEncoderEmptyFrames(encoder.get(), 0);
-}
-
 TEST(VideoEncoderVpxTest, Vp9LossyUnchangedFrame) {
   std::unique_ptr<VideoEncoderVpx> encoder(VideoEncoderVpx::CreateForVP9());
-  encoder->SetLosslessEncode(false);
   // Expect that VP9+CR should generate no more than 10 top-off frames
   // per cycle, and take no more than 2 cycles to top-off.
   TestVideoEncoderEmptyFrames(encoder.get(), 20);
diff --git a/remoting/codec/webrtc_video_encoder.h b/remoting/codec/webrtc_video_encoder.h
index 873bb8f..d73ce9c 100644
--- a/remoting/codec/webrtc_video_encoder.h
+++ b/remoting/codec/webrtc_video_encoder.h
@@ -14,6 +14,7 @@
 #include "base/time/time.h"
 #include "third_party/webrtc/api/video/encoded_image.h"
 #include "third_party/webrtc/api/video/video_codec_type.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
 
 namespace webrtc {
@@ -69,6 +70,9 @@
     base::TimeDelta send_pending_delay{base::TimeDelta::Max()};
     base::TimeDelta rtt_estimate{base::TimeDelta::Max()};
     int bandwidth_estimate_kbps = -1;
+
+    // The screen that this frame was captured from.
+    webrtc::ScreenId screen_id = webrtc::kInvalidScreenId;
   };
 
   struct EncodedFrame {
diff --git a/remoting/codec/webrtc_video_encoder_av1.cc b/remoting/codec/webrtc_video_encoder_av1.cc
index 31041e9..80c25d4 100644
--- a/remoting/codec/webrtc_video_encoder_av1.cc
+++ b/remoting/codec/webrtc_video_encoder_av1.cc
@@ -45,10 +45,6 @@
 }
 WebrtcVideoEncoderAV1::~WebrtcVideoEncoderAV1() = default;
 
-void WebrtcVideoEncoderAV1::SetLosslessEncode(bool want_lossless) {
-  NOTIMPLEMENTED();
-}
-
 void WebrtcVideoEncoderAV1::SetLosslessColor(bool want_lossless) {
   if (want_lossless != lossless_color_) {
     lossless_color_ = want_lossless;
diff --git a/remoting/codec/webrtc_video_encoder_av1.h b/remoting/codec/webrtc_video_encoder_av1.h
index 517ba18c..f654426 100644
--- a/remoting/codec/webrtc_video_encoder_av1.h
+++ b/remoting/codec/webrtc_video_encoder_av1.h
@@ -31,7 +31,6 @@
   ~WebrtcVideoEncoderAV1() override;
 
   // WebrtcVideoEncoder interface.
-  void SetLosslessEncode(bool want_lossless) override;
   void SetLosslessColor(bool want_lossless) override;
   void Encode(std::unique_ptr<webrtc::DesktopFrame> frame,
               const FrameParams& params,
diff --git a/remoting/codec/webrtc_video_encoder_vpx.cc b/remoting/codec/webrtc_video_encoder_vpx.cc
index ca58938..143c028a90 100644
--- a/remoting/codec/webrtc_video_encoder_vpx.cc
+++ b/remoting/codec/webrtc_video_encoder_vpx.cc
@@ -37,8 +37,7 @@
 constexpr int kVp9I420ProfileNumber = 0;
 constexpr int kVp9I444ProfileNumber = 1;
 
-// Magic encoder constants for adaptive quantization strategy.
-constexpr int kVp9AqModeNone = 0;
+// Magic encoder constant for adaptive quantization strategy.
 constexpr int kVp9AqModeCyclicRefresh = 3;
 
 constexpr int kDefaultTargetBitrateKbps = 1000;
@@ -111,25 +110,17 @@
 
 void SetVp9CodecParameters(vpx_codec_enc_cfg_t* config,
                            const webrtc::DesktopSize& size,
-                           bool lossless_color,
-                           bool lossless_encode) {
+                           bool lossless_color) {
   SetCommonCodecParameters(config, size);
 
   // Configure VP9 for I420 or I444 source frames.
   config->g_profile =
       lossless_color ? kVp9I444ProfileNumber : kVp9I420ProfileNumber;
 
-  if (lossless_encode) {
-    // Disable quantization entirely, putting the encoder in "lossless" mode.
-    config->rc_min_quantizer = 0;
-    config->rc_max_quantizer = 0;
-    config->rc_end_usage = VPX_VBR;
-  } else {
-    config->rc_end_usage = VPX_CBR;
-    // In the absence of a good bandwidth estimator set the target bitrate to a
-    // conservative default.
-    config->rc_target_bitrate = 500;
-  }
+  config->rc_end_usage = VPX_CBR;
+  // In the absence of a good bandwidth estimator set the target bitrate to a
+  // conservative default.
+  config->rc_target_bitrate = 500;
 }
 
 void SetVp8CodecOptions(vpx_codec_ctx_t* codec) {
@@ -144,9 +135,7 @@
   DCHECK_EQ(VPX_CODEC_OK, ret) << "Failed to set noise sensitivity";
 }
 
-void SetVp9CodecOptions(vpx_codec_ctx_t* codec,
-                        bool lossless_encode,
-                        int encoder_speed) {
+void SetVp9CodecOptions(vpx_codec_ctx_t* codec, int encoder_speed) {
   // Note that this knob uses the same parameter name as VP8.
   vpx_codec_err_t ret =
       vpx_codec_control(codec, VP8E_SET_CPUUSED, encoder_speed);
@@ -170,9 +159,8 @@
   ret = vpx_codec_control(codec, VP9E_SET_TUNE_CONTENT, VP9E_CONTENT_SCREEN);
   DCHECK_EQ(VPX_CODEC_OK, ret) << "Failed to set screen content mode";
 
-  // Set cyclic refresh (aka "top-off") only for lossy encoding.
-  int aq_mode = lossless_encode ? kVp9AqModeNone : kVp9AqModeCyclicRefresh;
-  ret = vpx_codec_control(codec, VP9E_SET_AQ_MODE, aq_mode);
+  // Set cyclic refresh (aka "top-off") for lossy encoding.
+  ret = vpx_codec_control(codec, VP9E_SET_AQ_MODE, kVp9AqModeCyclicRefresh);
   DCHECK_EQ(VPX_CODEC_OK, ret) << "Failed to set aq mode";
 }
 
@@ -197,20 +185,6 @@
   clock_ = tick_clock;
 }
 
-void WebrtcVideoEncoderVpx::SetLosslessEncode(bool want_lossless) {
-  if (!use_vp9_)
-    return;
-
-  if (want_lossless != lossless_encode_) {
-    lossless_encode_ = want_lossless;
-    SetEncoderSpeed(lossless_encode_ ? kVp9LosslessEncodeSpeed
-                                     : kVp9DefaultEncoderSpeed);
-    if (codec_)
-      Configure(webrtc::DesktopSize(codec_->config.enc->g_w,
-                                    codec_->config.enc->g_h));
-  }
-}
-
 void WebrtcVideoEncoderVpx::SetLosslessColor(bool want_lossless) {
   if (!use_vp9_)
     return;
@@ -301,7 +275,7 @@
 
   if (use_active_map_) {
     // VP8 doesn't return an active map so we assume it hasn't changed.
-    if (use_vp9_ && !lossless_encode_) {
+    if (use_vp9_) {
       ret = vpx_codec_control(codec_.get(), VP9E_GET_ACTIVEMAP, &act_map);
       DCHECK_EQ(ret, VPX_CODEC_OK)
           << "Failed to fetch active map: " << vpx_codec_err_to_string(ret)
@@ -373,13 +347,10 @@
 
 void WebrtcVideoEncoderVpx::Configure(const webrtc::DesktopSize& size) {
   DCHECK(use_vp9_ || !lossless_color_);
-  DCHECK(use_vp9_ || !lossless_encode_);
 
   if (use_vp9_) {
     VLOG(0) << "Configuring VP9 encoder with lossless-color="
-            << (lossless_color_ ? "true" : "false")
-            << ", lossless-encode=" << (lossless_encode_ ? "true" : "false")
-            << ".";
+            << (lossless_color_ ? "true" : "false") << ".";
   }
 
   // Tear down |image_| if it doesn't match the new frame size.
@@ -406,7 +377,7 @@
 
   // Customize the default configuration to our needs.
   if (use_vp9_) {
-    SetVp9CodecParameters(&config_, size, lossless_color_, lossless_encode_);
+    SetVp9CodecParameters(&config_, size, lossless_color_);
   } else {
     SetVp8CodecParameters(&config_, size);
   }
@@ -425,7 +396,7 @@
 
   // Apply further customizations to the codec now it's initialized.
   if (use_vp9_) {
-    SetVp9CodecOptions(codec_.get(), lossless_encode_, vp9_encoder_speed_);
+    SetVp9CodecOptions(codec_.get(), vp9_encoder_speed_);
   } else {
     SetVp8CodecOptions(codec_.get());
   }
diff --git a/remoting/codec/webrtc_video_encoder_vpx.h b/remoting/codec/webrtc_video_encoder_vpx.h
index a446a50..9c98cc0 100644
--- a/remoting/codec/webrtc_video_encoder_vpx.h
+++ b/remoting/codec/webrtc_video_encoder_vpx.h
@@ -43,7 +43,6 @@
   void SetTickClockForTests(const base::TickClock* tick_clock);
 
   // WebrtcVideoEncoder interface.
-  void SetLosslessEncode(bool want_lossless) override;
   void SetLosslessColor(bool want_lossless) override;
   void SetEncoderSpeed(int encoder_speed) override;
   void Encode(std::unique_ptr<webrtc::DesktopFrame> frame,
@@ -54,7 +53,7 @@
   explicit WebrtcVideoEncoderVpx(bool use_vp9);
 
   // (Re)Configures this instance to encode frames of the specified |size|,
-  // with the configured lossless color & encoding modes.
+  // with the configured lossless color mode.
   void Configure(const webrtc::DesktopSize& size);
 
   // Updates codec configuration.
@@ -79,9 +78,7 @@
   // True if the encoder is for VP9, false for VP8.
   const bool use_vp9_;
 
-  // Options controlling VP9 encode quantization, color space, and speed.
-  // These are not used when configuring VP8.
-  bool lossless_encode_ = false;
+  // Controls VP9 color space and encode speed. Not used when configuring VP8.
   bool lossless_color_ = false;
   int vp9_encoder_speed_ = -1;
 
diff --git a/remoting/host/client_session.cc b/remoting/host/client_session.cc
index 05dd3b66..f7b199a 100644
--- a/remoting/host/client_session.cc
+++ b/remoting/host/client_session.cc
@@ -165,22 +165,6 @@
       video_stream->Pause(pause_video_);
     }
   }
-  if (video_control.has_lossless_encode()) {
-    VLOG(1) << "Received VideoControl (lossless_encode="
-            << video_control.lossless_encode() << ")";
-    lossless_video_encode_ = video_control.lossless_encode();
-    for (const auto& [_, video_stream] : video_streams_) {
-      video_stream->SetLosslessEncode(lossless_video_encode_);
-    }
-  }
-  if (video_control.has_lossless_color()) {
-    VLOG(1) << "Received VideoControl (lossless_color="
-            << video_control.lossless_color() << ")";
-    lossless_video_color_ = video_control.lossless_color();
-    for (const auto& [_, video_stream] : video_streams_) {
-      video_stream->SetLosslessColor(lossless_video_color_);
-    }
-  }
 }
 
 void ClientSession::ControlAudio(const protocol::AudioControl& audio_control) {
@@ -533,10 +517,6 @@
 
   video_stream->SetObserver(this);
 
-  // Apply video-control parameters to the new stream.
-  video_stream->SetLosslessEncode(lossless_video_encode_);
-  video_stream->SetLosslessColor(lossless_video_color_);
-
   // Pause capturing if necessary.
   video_stream->Pause(pause_video_);
 
diff --git a/remoting/host/client_session.h b/remoting/host/client_session.h
index 162c6c8..29b7976 100644
--- a/remoting/host/client_session.h
+++ b/remoting/host/client_session.h
@@ -371,10 +371,8 @@
   // Set to true after all data channels have been connected.
   bool channels_connected_ = false;
 
-  // Used to store video channel pause & lossless parameters.
+  // Used to store video channel pause parameter.
   bool pause_video_ = false;
-  bool lossless_video_encode_ = false;
-  bool lossless_video_color_ = false;
 
   // VideoLayout is sent only after the control channel is connected. Until
   // then it's stored in |pending_video_layout_message_|.
diff --git a/remoting/proto/control.proto b/remoting/proto/control.proto
index 993cf8b3..e7c819e 100644
--- a/remoting/proto/control.proto
+++ b/remoting/proto/control.proto
@@ -39,9 +39,9 @@
   // Enables the video channel if true, pauses if false.
   optional bool enable = 1;
 
-  // Controls whether lossless encode and color translation are requested.
-  optional bool lossless_encode = 2;
-  optional bool lossless_color = 3;
+  // Lossless encode and color settings are no longer set via this message.
+  reserved 2, 3;
+  reserved "lossless_encode", "lossless_color";
 }
 
 message AudioControl {
diff --git a/remoting/protocol/BUILD.gn b/remoting/protocol/BUILD.gn
index f1c2e6f5..002d745 100644
--- a/remoting/protocol/BUILD.gn
+++ b/remoting/protocol/BUILD.gn
@@ -278,6 +278,8 @@
       "ice_connection_to_client.h",
       "video_frame_pump.cc",
       "video_frame_pump.h",
+      "video_stream_event_router.cc",
+      "video_stream_event_router.h",
       "webrtc_audio_source_adapter.cc",
       "webrtc_audio_source_adapter.h",
       "webrtc_audio_stream.cc",
diff --git a/remoting/protocol/connection_unittest.cc b/remoting/protocol/connection_unittest.cc
index 2b642f8..bac040b 100644
--- a/remoting/protocol/connection_unittest.cc
+++ b/remoting/protocol/connection_unittest.cc
@@ -557,7 +557,7 @@
 
   std::unique_ptr<VideoStream> video_stream =
       host_connection_->StartVideoStream(
-          "stream", std::make_unique<TestScreenCapturer>());
+          "screen_stream", std::make_unique<TestScreenCapturer>());
 
   // Receive 5 frames.
   for (int i = 0; i < 5; ++i) {
@@ -571,7 +571,7 @@
 #else
 #define MAYBE_VideoWithSlowSignaling VideoWithSlowSignaling
 #endif
-// Verifies that the VideoStream doesn't loose any video frames while the
+// Verifies that the VideoStream doesn't lose any video frames while the
 // connection is being established.
 TEST_P(ConnectionTest, MAYBE_VideoWithSlowSignaling) {
   // Add signaling delay to slow down connection handshake.
@@ -582,7 +582,7 @@
 
   std::unique_ptr<VideoStream> video_stream =
       host_connection_->StartVideoStream(
-          "stream", base::WrapUnique(new TestScreenCapturer()));
+          "screen_stream", base::WrapUnique(new TestScreenCapturer()));
 
   WaitNextVideoFrame();
 }
@@ -614,8 +614,8 @@
 
 // TODO(crbug.com/1146302): Test is flaky.
 TEST_P(ConnectionTest, DISABLED_VideoStats) {
-  // Currently this test only works for WebRTC because for ICE connections stats
-  // are reported by SoftwareVideoRenderer which is not used in this test.
+  // Currently this test only works for WebRTC because ICE connections stats are
+  // reported by SoftwareVideoRenderer which is not used in this test.
   // TODO(sergeyu): Fix this.
   if (!is_using_webrtc())
     return;
@@ -632,7 +632,7 @@
 
   std::unique_ptr<VideoStream> video_stream =
       host_connection_->StartVideoStream(
-          "stream", std::make_unique<TestScreenCapturer>());
+          "screen_stream", std::make_unique<TestScreenCapturer>());
   video_stream->SetEventTimestampsSource(input_event_timestamps_source);
 
   WaitNextVideoFrame();
@@ -695,7 +695,7 @@
   auto capturer = std::make_unique<TestScreenCapturer>();
   capturer->FailNthFrame(0);
   auto video_stream =
-      host_connection_->StartVideoStream("stream", std::move(capturer));
+      host_connection_->StartVideoStream("screen_stream", std::move(capturer));
 
   WaitNextVideoFrame();
 }
@@ -708,7 +708,7 @@
   auto capturer = std::make_unique<TestScreenCapturer>();
   capturer->FailNthFrame(1);
   auto video_stream =
-      host_connection_->StartVideoStream("stream", std::move(capturer));
+      host_connection_->StartVideoStream("screen_stream", std::move(capturer));
 
   WaitNextVideoFrame();
   WaitNextVideoFrame();
diff --git a/remoting/protocol/fake_connection_to_client.cc b/remoting/protocol/fake_connection_to_client.cc
index d14208b..4336c51 100644
--- a/remoting/protocol/fake_connection_to_client.cc
+++ b/remoting/protocol/fake_connection_to_client.cc
@@ -23,10 +23,6 @@
 
 void FakeVideoStream::Pause(bool pause) {}
 
-void FakeVideoStream::SetLosslessEncode(bool want_lossless) {}
-
-void FakeVideoStream::SetLosslessColor(bool want_lossless) {}
-
 void FakeVideoStream::SetObserver(Observer* observer) {
   observer_ = observer;
 }
diff --git a/remoting/protocol/fake_connection_to_client.h b/remoting/protocol/fake_connection_to_client.h
index b61a1b4..6056d6b 100644
--- a/remoting/protocol/fake_connection_to_client.h
+++ b/remoting/protocol/fake_connection_to_client.h
@@ -33,8 +33,6 @@
   void SetEventTimestampsSource(scoped_refptr<InputEventTimestampsSource>
                                     event_timestamps_source) override;
   void Pause(bool pause) override;
-  void SetLosslessEncode(bool want_lossless) override;
-  void SetLosslessColor(bool want_lossless) override;
   void SetObserver(Observer* observer) override;
   void SelectSource(webrtc::ScreenId id) override;
   void SetComposeEnabled(bool enabled) override;
diff --git a/remoting/protocol/video_frame_pump.cc b/remoting/protocol/video_frame_pump.cc
index ba25804..382839d 100644
--- a/remoting/protocol/video_frame_pump.cc
+++ b/remoting/protocol/video_frame_pump.cc
@@ -79,24 +79,6 @@
   capture_scheduler_.Pause(pause);
 }
 
-void VideoFramePump::SetLosslessEncode(bool want_lossless) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
-  encode_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&VideoEncoder::SetLosslessEncode,
-                     base::Unretained(encoder_.get()), want_lossless));
-}
-
-void VideoFramePump::SetLosslessColor(bool want_lossless) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
-  encode_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&VideoEncoder::SetLosslessColor,
-                     base::Unretained(encoder_.get()), want_lossless));
-}
-
 void VideoFramePump::SetObserver(Observer* observer) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   observer_ = observer;
diff --git a/remoting/protocol/video_frame_pump.h b/remoting/protocol/video_frame_pump.h
index 7deb779..56c4bfc 100644
--- a/remoting/protocol/video_frame_pump.h
+++ b/remoting/protocol/video_frame_pump.h
@@ -85,8 +85,6 @@
   void SetEventTimestampsSource(scoped_refptr<InputEventTimestampsSource>
                                     event_timestamps_source) override;
   void Pause(bool pause) override;
-  void SetLosslessEncode(bool want_lossless) override;
-  void SetLosslessColor(bool want_lossless) override;
   void SetObserver(Observer* observer) override;
   void SelectSource(webrtc::ScreenId id) override;
   void SetComposeEnabled(bool enabled) override;
diff --git a/remoting/protocol/video_frame_pump_unittest.cc b/remoting/protocol/video_frame_pump_unittest.cc
index 62aa4dd..31e88fab 100644
--- a/remoting/protocol/video_frame_pump_unittest.cc
+++ b/remoting/protocol/video_frame_pump_unittest.cc
@@ -58,8 +58,6 @@
   MockVideoEncoder() = default;
   ~MockVideoEncoder() override = default;
 
-  MOCK_METHOD1(SetLosslessEncode, void(bool));
-  MOCK_METHOD1(SetLosslessColor, void(bool));
   MOCK_METHOD1(EncodePtr, VideoPacket*(const webrtc::DesktopFrame&));
 
   std::unique_ptr<VideoPacket> Encode(
diff --git a/remoting/protocol/video_stream.h b/remoting/protocol/video_stream.h
index 9222d52..778b0642 100644
--- a/remoting/protocol/video_stream.h
+++ b/remoting/protocol/video_stream.h
@@ -38,11 +38,6 @@
   // only affects capture scheduling and does not stop/start the capturer.
   virtual void Pause(bool pause) = 0;
 
-  // Sets whether the video encoder should be requested to encode losslessly,
-  // or to use a lossless color space (typically requiring higher bandwidth).
-  virtual void SetLosslessEncode(bool want_lossless) = 0;
-  virtual void SetLosslessColor(bool want_lossless) = 0;
-
   // Control mouse cursor compositing in the video stream.
   virtual void SetComposeEnabled(bool enabled) = 0;
   virtual void SetMouseCursor(
diff --git a/remoting/protocol/video_stream_event_router.cc b/remoting/protocol/video_stream_event_router.cc
new file mode 100644
index 0000000..3ce2ea02
--- /dev/null
+++ b/remoting/protocol/video_stream_event_router.cc
@@ -0,0 +1,97 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/protocol/video_stream_event_router.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+
+namespace remoting::protocol {
+
+namespace {
+constexpr char kSingleStreamName[] = "screen_stream";
+}  // namespace
+
+VideoStreamEventRouter::VideoStreamEventRouter() = default;
+VideoStreamEventRouter::~VideoStreamEventRouter() = default;
+
+void VideoStreamEventRouter::OnTargetFramerateChanged(
+    webrtc::ScreenId screen_id,
+    int framerate) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  auto observer = GetObserver(screen_id);
+  if (observer) {
+    observer->OnTargetFramerateChanged(framerate);
+  } else {
+    LOG(WARNING) << "No registered VideoChannelStateObserver for " << screen_id;
+  }
+}
+
+void VideoStreamEventRouter::OnEncodedFrameSent(
+    webrtc::ScreenId screen_id,
+    webrtc::EncodedImageCallback::Result result,
+    const WebrtcVideoEncoder::EncodedFrame& frame) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  auto observer = GetObserver(screen_id);
+  if (observer) {
+    observer->OnEncodedFrameSent(result, frame);
+  } else {
+    LOG(WARNING) << "No registered VideoChannelStateObserver for " << screen_id;
+  }
+}
+
+void VideoStreamEventRouter::SetVideoChannelStateObserver(
+    const std::string& stream_name,
+    base::WeakPtr<VideoChannelStateObserver> video_channel_state_observer) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  if (stream_name == kSingleStreamName) {
+    single_stream_state_observer_ = video_channel_state_observer;
+    // If switching modes, clear out all registered multi-stream observers.
+    multi_stream_state_observers_.clear();
+    return;
+  }
+
+  // Clear out the single stream observer in case the mode changed.
+  single_stream_state_observer_ = nullptr;
+
+  auto screen_id_index = stream_name.find_last_of('_');
+  if (screen_id_index == std::string::npos) {
+    LOG(ERROR) << "Unexpected stream name format: " << stream_name;
+    return;
+  }
+
+  int64_t screen_id;
+  if (!base::StringToInt64(stream_name.substr(screen_id_index + 1),
+                           &screen_id)) {
+    LOG(ERROR) << "Failed to extract screen id from: " << stream_name;
+    return;
+  }
+
+  multi_stream_state_observers_.insert(
+      {screen_id, video_channel_state_observer});
+}
+
+base::WeakPtr<VideoChannelStateObserver> VideoStreamEventRouter::GetObserver(
+    webrtc::ScreenId screen_id) {
+  if (single_stream_state_observer_) {
+    return single_stream_state_observer_;
+  }
+
+  if (multi_stream_state_observers_.contains(screen_id)) {
+    auto observer = multi_stream_state_observers_.at(screen_id);
+    if (!observer) {
+      LOG(WARNING) << "Removing invalid observer for screen_id: " << screen_id;
+      multi_stream_state_observers_.erase(screen_id);
+    }
+
+    return observer;
+  }
+
+  return nullptr;
+}
+
+}  // namespace remoting::protocol
\ No newline at end of file
diff --git a/remoting/protocol/video_stream_event_router.h b/remoting/protocol/video_stream_event_router.h
new file mode 100644
index 0000000..3735a8c
--- /dev/null
+++ b/remoting/protocol/video_stream_event_router.h
@@ -0,0 +1,55 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_PROTOCOL_VIDEO_STREAM_EVENT_ROUTER_H_
+#define REMOTING_PROTOCOL_VIDEO_STREAM_EVENT_ROUTER_H_
+
+#include "base/containers/flat_map.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "remoting/protocol/video_channel_state_observer.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
+
+namespace remoting::protocol {
+
+class VideoStreamEventRouter {
+ public:
+  VideoStreamEventRouter();
+
+  VideoStreamEventRouter(const VideoStreamEventRouter&) = delete;
+  VideoStreamEventRouter& operator=(const VideoStreamEventRouter&) = delete;
+
+  ~VideoStreamEventRouter();
+
+  // Mirrors the VideoChannelStateObserver interface with an additional
+  // |screen_id| param used to route the value to the appropriate video stream.
+  void OnTargetFramerateChanged(webrtc::ScreenId screen_id, int framerate);
+  void OnEncodedFrameSent(webrtc::ScreenId screen_id,
+                          webrtc::EncodedImageCallback::Result result,
+                          const WebrtcVideoEncoder::EncodedFrame& frame);
+
+  void SetVideoChannelStateObserver(
+      const std::string& stream_name,
+      base::WeakPtr<VideoChannelStateObserver> video_channel_state_observer);
+
+  base::WeakPtr<VideoStreamEventRouter> GetWeakPtr() {
+    return weak_factory_.GetWeakPtr();
+  }
+
+ private:
+  base::WeakPtr<VideoChannelStateObserver> GetObserver(
+      webrtc::ScreenId screen_id);
+
+  base::WeakPtr<VideoChannelStateObserver> single_stream_state_observer_;
+  base::flat_map<webrtc::ScreenId, base::WeakPtr<VideoChannelStateObserver>>
+      multi_stream_state_observers_;
+
+  THREAD_CHECKER(thread_checker_);
+
+  base::WeakPtrFactory<VideoStreamEventRouter> weak_factory_{this};
+};
+
+}  // namespace remoting::protocol
+
+#endif  // REMOTING_PROTOCOL_VIDEO_STREAM_EVENT_ROUTER_H_
\ No newline at end of file
diff --git a/remoting/protocol/webrtc_video_encoder_factory.cc b/remoting/protocol/webrtc_video_encoder_factory.cc
index de4c78b..56b51b3 100644
--- a/remoting/protocol/webrtc_video_encoder_factory.cc
+++ b/remoting/protocol/webrtc_video_encoder_factory.cc
@@ -39,7 +39,7 @@
       base::ThreadPool::CreateSingleThreadTaskRunner(
           {base::TaskPriority::HIGHEST},
           base::SingleThreadTaskRunnerThreadMode::DEDICATED),
-      video_channel_state_observer_);
+      event_router_.GetWeakPtr());
 }
 
 std::vector<webrtc::SdpVideoFormat>
@@ -47,12 +47,6 @@
   return supported_formats_;
 }
 
-void WebrtcVideoEncoderFactory::SetVideoChannelStateObserver(
-    base::WeakPtr<VideoChannelStateObserver> video_channel_state_observer) {
-  DCHECK(main_task_runner_->BelongsToCurrentThread());
-  video_channel_state_observer_ = video_channel_state_observer;
-}
-
 void WebrtcVideoEncoderFactory::ApplySessionOptions(
     const SessionOptions& options) {
   DCHECK(main_task_runner_->BelongsToCurrentThread());
diff --git a/remoting/protocol/webrtc_video_encoder_factory.h b/remoting/protocol/webrtc_video_encoder_factory.h
index d00ab8c..5bc43da 100644
--- a/remoting/protocol/webrtc_video_encoder_factory.h
+++ b/remoting/protocol/webrtc_video_encoder_factory.h
@@ -9,9 +9,9 @@
 #include <vector>
 
 #include "base/memory/scoped_refptr.h"
-#include "base/memory/weak_ptr.h"
 #include "base/task/single_thread_task_runner.h"
 #include "remoting/base/session_options.h"
+#include "remoting/protocol/video_stream_event_router.h"
 #include "third_party/webrtc/api/video_codecs/av1_profile.h"
 #include "third_party/webrtc/api/video_codecs/sdp_video_format.h"
 #include "third_party/webrtc/api/video_codecs/video_encoder_factory.h"
@@ -20,8 +20,6 @@
 
 namespace remoting::protocol {
 
-class VideoChannelStateObserver;
-
 // This is the encoder factory that is passed to WebRTC when the peer connection
 // is created. This factory creates the video encoder, which is an instance of
 // WebrtcVideoEncoderWrapper which wraps a video codec from remoting/codec.
@@ -35,11 +33,10 @@
       const webrtc::SdpVideoFormat& format) override;
   std::vector<webrtc::SdpVideoFormat> GetSupportedFormats() const override;
 
-  void SetVideoChannelStateObserver(
-      base::WeakPtr<VideoChannelStateObserver> video_channel_state_observer);
-
   void ApplySessionOptions(const SessionOptions& options);
 
+  VideoStreamEventRouter& video_stream_event_router() { return event_router_; }
+
  private:
   scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
 
@@ -59,7 +56,9 @@
 
   SessionOptions session_options_;
 
-  base::WeakPtr<VideoChannelStateObserver> video_channel_state_observer_;
+  // Enables events to be routed from WebRTC created video encoders to the video
+  // stream representing the display being captured and encoded.
+  VideoStreamEventRouter event_router_;
 };
 
 }  // namespace remoting::protocol
diff --git a/remoting/protocol/webrtc_video_encoder_wrapper.cc b/remoting/protocol/webrtc_video_encoder_wrapper.cc
index 472ddb48..102c3bc 100644
--- a/remoting/protocol/webrtc_video_encoder_wrapper.cc
+++ b/remoting/protocol/webrtc_video_encoder_wrapper.cc
@@ -23,7 +23,7 @@
 #include "remoting/base/session_options.h"
 #include "remoting/codec/webrtc_video_encoder_av1.h"
 #include "remoting/codec/webrtc_video_encoder_vpx.h"
-#include "remoting/protocol/video_channel_state_observer.h"
+#include "remoting/protocol/video_stream_event_router.h"
 #include "remoting/protocol/webrtc_video_frame_adapter.h"
 #include "third_party/webrtc/api/video_codecs/av1_profile.h"
 #include "third_party/webrtc/api/video_codecs/sdp_video_format.h"
@@ -117,10 +117,10 @@
     const SessionOptions& session_options,
     scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
     scoped_refptr<base::SingleThreadTaskRunner> encode_task_runner,
-    base::WeakPtr<VideoChannelStateObserver> video_channel_state_observer)
+    base::WeakPtr<VideoStreamEventRouter> video_stream_event_router)
     : main_task_runner_(main_task_runner),
       encode_task_runner_(encode_task_runner),
-      video_channel_state_observer_(video_channel_state_observer) {
+      video_stream_event_router_(video_stream_event_router) {
   codec_type_ = webrtc::PayloadStringToCodecType(format.name);
   switch (codec_type_) {
     case webrtc::kVideoCodecVP8:
@@ -205,15 +205,6 @@
     DCHECK_EQ(1, codec_settings->VP9().numberOfSpatialLayers);
   }
 
-  // If the client has requested a specific framerate for this encoder then
-  // update the target framerate on the RtpSender for this video streams.
-  if (target_frame_rate_ != kTargetFrameRate) {
-    main_task_runner_->PostTask(
-        FROM_HERE,
-        base::BindOnce(&VideoChannelStateObserver::OnTargetFramerateChanged,
-                       video_channel_state_observer_, target_frame_rate_));
-  }
-
   return WEBRTC_VIDEO_CODEC_OK;
 }
 
@@ -297,6 +288,20 @@
     return WEBRTC_VIDEO_CODEC_ERROR;
   }
 
+  if (!screen_id_.has_value()) {
+    screen_id_ = frame_stats_->screen_id;
+
+    // Now that we know which screen id this encoder is associated with, we can
+    // let that video stream know if a non-default framerate has been requested.
+    if (target_frame_rate_ != kTargetFrameRate) {
+      main_task_runner_->PostTask(
+          FROM_HERE,
+          base::BindOnce(&VideoStreamEventRouter::OnTargetFramerateChanged,
+                         video_stream_event_router_, *screen_id_,
+                         target_frame_rate_));
+    }
+  }
+
   frame_stats_->encode_started_time = now;
 
   auto desktop_frame = video_frame_adapter->TakeDesktopFrame();
@@ -538,9 +543,9 @@
   // base::OnTaskRunnerDeleter posts the frame-deleter task to run after this
   // task has executed.
   main_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&VideoChannelStateObserver::OnEncodedFrameSent,
-                                video_channel_state_observer_, send_result,
-                                std::ref(*frame)));
+      FROM_HERE, base::BindOnce(&VideoStreamEventRouter::OnEncodedFrameSent,
+                                video_stream_event_router_, *screen_id_,
+                                send_result, std::ref(*frame)));
 }
 
 void WebrtcVideoEncoderWrapper::NotifyFrameDropped() {
diff --git a/remoting/protocol/webrtc_video_encoder_wrapper.h b/remoting/protocol/webrtc_video_encoder_wrapper.h
index d229eae..4144970 100644
--- a/remoting/protocol/webrtc_video_encoder_wrapper.h
+++ b/remoting/protocol/webrtc_video_encoder_wrapper.h
@@ -16,18 +16,20 @@
 #include "remoting/base/running_samples.h"
 #include "remoting/base/session_options.h"
 #include "remoting/codec/webrtc_video_encoder.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/webrtc/api/video/video_codec_type.h"
 #include "third_party/webrtc/api/video_codecs/sdp_video_format.h"
 #include "third_party/webrtc/api/video_codecs/video_encoder.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
 
 namespace remoting::protocol {
 
-class VideoChannelStateObserver;
+class VideoStreamEventRouter;
 
-// WebrtcVideoEncoderWrapper is a wrapper around the remoting codecs, which
-// implements the webrtc::VideoEncoder interface. This class is instantiated
-// by WebRTC via the webrtc::VideoEncoderFactory, and all methods (including
-// the ctor) are called on WebRTC's foreground worker thread.
+// WebrtcVideoEncoderWrapper is a wrapper around the remoting codecs which
+// implement the webrtc::VideoEncoder interface. This class is instantiated by
+// WebRTC via the webrtc::VideoEncoderFactory, and all methods (including the
+// c'tor) are called on WebRTC's foreground worker thread.
 class WebrtcVideoEncoderWrapper : public webrtc::VideoEncoder {
  public:
   // Called by the VideoEncoderFactory. |video_channel_state_observer| is
@@ -37,7 +39,7 @@
       const SessionOptions& session_options,
       scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
       scoped_refptr<base::SingleThreadTaskRunner> encode_task_runner,
-      base::WeakPtr<VideoChannelStateObserver> video_channel_state_observer);
+      base::WeakPtr<VideoStreamEventRouter> video_stream_event_router);
   ~WebrtcVideoEncoderWrapper() override;
 
   void SetEncoderForTest(std::unique_ptr<WebrtcVideoEncoder> encoder);
@@ -167,7 +169,11 @@
   int target_frame_rate_ = kTargetFrameRate;
   base::TimeDelta target_frame_interval_;
 
-  base::WeakPtr<VideoChannelStateObserver> video_channel_state_observer_;
+  // Represents the screen which is being encoded by this instance. Initialized
+  // after the first captured frame has been received.
+  absl::optional<webrtc::ScreenId> screen_id_;
+
+  base::WeakPtr<VideoStreamEventRouter> video_stream_event_router_;
 
   // This class lives on WebRTC's encoding thread. All methods (including ctor
   // and dtor) are expected to be called on the same thread.
diff --git a/remoting/protocol/webrtc_video_encoder_wrapper_unittest.cc b/remoting/protocol/webrtc_video_encoder_wrapper_unittest.cc
index 80f2a95..390078fa 100644
--- a/remoting/protocol/webrtc_video_encoder_wrapper_unittest.cc
+++ b/remoting/protocol/webrtc_video_encoder_wrapper_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/test/task_environment.h"
 #include "remoting/base/session_options.h"
 #include "remoting/protocol/video_channel_state_observer.h"
+#include "remoting/protocol/video_stream_event_router.h"
 #include "remoting/protocol/webrtc_video_frame_adapter.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -20,6 +21,7 @@
 using testing::NiceMock;
 using testing::Pointee;
 using testing::Return;
+using testing::StrictMock;
 using webrtc::BasicDesktopFrame;
 using webrtc::CodecSpecificInfo;
 using webrtc::DesktopRect;
@@ -39,9 +41,11 @@
 
 namespace {
 
-const int kInputFrameWidth = 800;
-const int kInputFrameHeight = 600;
-const int kBitrateBps = 8000000;
+constexpr int kInputFrameWidth = 800;
+constexpr int kInputFrameHeight = 600;
+constexpr int kBitrateBps = 8000000;
+constexpr int kTestScreenId = 16;
+
 const VideoEncoder::Capabilities kVideoEncoderCapabilities(
     /*loss_notification*/ false);
 const VideoEncoder::Settings kVideoEncoderSettings(kVideoEncoderCapabilities,
@@ -84,6 +88,7 @@
   DesktopSize size(kInputFrameWidth, kInputFrameHeight);
   auto frame = std::make_unique<BasicDesktopFrame>(size);
   auto stats = std::make_unique<WebrtcVideoEncoder::FrameStats>();
+  stats->screen_id = kTestScreenId;
   frame->mutable_updated_region()->SetRect(webrtc::DesktopRect::MakeSize(size));
   return WebrtcVideoFrameAdapter::CreateVideoFrame(std::move(frame),
                                                    std::move(stats));
@@ -93,6 +98,7 @@
   DesktopSize size(kInputFrameWidth, kInputFrameHeight);
   auto frame = std::make_unique<BasicDesktopFrame>(size);
   auto stats = std::make_unique<WebrtcVideoEncoder::FrameStats>();
+  stats->screen_id = kTestScreenId;
   return WebrtcVideoFrameAdapter::CreateVideoFrame(std::move(frame),
                                                    std::move(stats));
 }
@@ -176,13 +182,17 @@
           std::move(done).Run(WebrtcVideoEncoder::EncodeResult::SUCCEEDED,
                               std::move(encoded_frame));
         });
+
+    video_stream_event_router_.SetVideoChannelStateObserver(
+        "screen_stream", observer_.GetWeakPtr());
   }
 
   std::unique_ptr<WebrtcVideoEncoderWrapper> InitEncoder(SdpVideoFormat sdp,
                                                          VideoCodec codec) {
     auto encoder = std::make_unique<WebrtcVideoEncoderWrapper>(
         sdp, SessionOptions(), task_environment_.GetMainThreadTaskRunner(),
-        task_environment_.GetMainThreadTaskRunner(), observer_.GetWeakPtr());
+        task_environment_.GetMainThreadTaskRunner(),
+        video_stream_event_router_.GetWeakPtr());
     encoder->InitEncode(&codec, kVideoEncoderSettings);
     encoder->RegisterEncodeCompleteCallback(&callback_);
     encoder->SetRates(DefaultRateControlParameters());
@@ -201,6 +211,7 @@
 
   base::RunLoop run_loop_;
 
+  VideoStreamEventRouter video_stream_event_router_;
   NiceMock<MockVideoChannelStateObserver> observer_;
   MockEncodedImageCallback callback_;
   std::unique_ptr<NiceMock<MockVideoEncoder>> mock_video_encoder_;
@@ -232,30 +243,71 @@
 
 TEST_F(WebrtcVideoEncoderWrapperTest, NotifiesOnFramerateChanged) {
   EXPECT_CALL(observer_, OnTargetFramerateChanged(42));
+  EXPECT_CALL(callback_, OnEncodedImage(_, Field(&CodecSpecificInfo::codecType,
+                                                 kVideoCodecVP9)))
+      .WillOnce(Return(kResultOk));
 
   auto sdp_format = GetVp9Format();
   sdp_format.parameters.emplace("max-fr", "42");
   auto encoder = InitEncoder(std::move(sdp_format), GetVp9Codec());
+  std::vector<VideoFrameType> frame_types{VideoFrameType::kVideoFrameKey};
+  encoder->Encode(MakeVideoFrame(), &frame_types);
+
+  PostQuitAndRun();
+}
+
+TEST_F(WebrtcVideoEncoderWrapperTest,
+       NotifiesOnFramerateChangedInMultiStreamMode) {
+  EXPECT_CALL(observer_, OnTargetFramerateChanged(42));
+  EXPECT_CALL(callback_, OnEncodedImage(_, Field(&CodecSpecificInfo::codecType,
+                                                 kVideoCodecVP9)))
+      .WillOnce(Return(kResultOk));
+
+  // Register a multi-stream observer for |kTestScreenId|.
+  video_stream_event_router_.SetVideoChannelStateObserver(
+      "screen_stream_16", observer_.GetWeakPtr());
+
+  // Also set up a strict mock observer for another screen id to ensure the
+  // proper observer is called.
+  StrictMock<MockVideoChannelStateObserver> observer;
+  video_stream_event_router_.SetVideoChannelStateObserver(
+      "screen_stream_17", observer.GetWeakPtr());
+
+  auto sdp_format = GetVp9Format();
+  sdp_format.parameters.emplace("max-fr", "42");
+  auto encoder = InitEncoder(std::move(sdp_format), GetVp9Codec());
+  std::vector<VideoFrameType> frame_types{VideoFrameType::kVideoFrameKey};
+  encoder->Encode(MakeVideoFrame(), &frame_types);
 
   PostQuitAndRun();
 }
 
 TEST_F(WebrtcVideoEncoderWrapperTest, FramerateClampedToLowerBound) {
   EXPECT_CALL(observer_, OnTargetFramerateChanged(1));
+  EXPECT_CALL(callback_, OnEncodedImage(_, Field(&CodecSpecificInfo::codecType,
+                                                 kVideoCodecVP9)))
+      .WillOnce(Return(kResultOk));
 
   auto sdp_format = GetVp9Format();
   sdp_format.parameters.emplace("max-fr", "0");
   auto encoder = InitEncoder(std::move(sdp_format), GetVp9Codec());
+  std::vector<VideoFrameType> frame_types{VideoFrameType::kVideoFrameKey};
+  encoder->Encode(MakeVideoFrame(), &frame_types);
 
   PostQuitAndRun();
 }
 
 TEST_F(WebrtcVideoEncoderWrapperTest, FramerateClampedToUpperBound) {
   EXPECT_CALL(observer_, OnTargetFramerateChanged(1000));
+  EXPECT_CALL(callback_, OnEncodedImage(_, Field(&CodecSpecificInfo::codecType,
+                                                 kVideoCodecVP9)))
+      .WillOnce(Return(kResultOk));
 
   auto sdp_format = GetVp9Format();
   sdp_format.parameters.emplace("max-fr", "1001");
   auto encoder = InitEncoder(std::move(sdp_format), GetVp9Codec());
+  std::vector<VideoFrameType> frame_types{VideoFrameType::kVideoFrameKey};
+  encoder->Encode(MakeVideoFrame(), &frame_types);
 
   PostQuitAndRun();
 }
@@ -276,6 +328,33 @@
   PostQuitAndRun();
 }
 
+TEST_F(WebrtcVideoEncoderWrapperTest,
+       NotifiesFrameEncodedAndReturnedInMultiStreamMode) {
+  EXPECT_CALL(callback_, OnEncodedImage(_, Field(&CodecSpecificInfo::codecType,
+                                                 kVideoCodecVP9)))
+      .WillOnce(Return(kResultOk));
+  EXPECT_CALL(observer_,
+              OnEncodedFrameSent(Field(&EncodedImageCallback::Result::error,
+                                       EncodedImageCallback::Result::OK),
+                                 _));
+
+  // Register a multi-stream observer for |kTestScreenId|.
+  video_stream_event_router_.SetVideoChannelStateObserver(
+      "screen_stream_16", observer_.GetWeakPtr());
+
+  // Also set up a strict mock observer for another screen id to ensure the
+  // proper observer is called.
+  StrictMock<MockVideoChannelStateObserver> observer;
+  video_stream_event_router_.SetVideoChannelStateObserver(
+      "screen_stream_17", observer.GetWeakPtr());
+
+  auto encoder = InitEncoder(GetVp9Format(), GetVp9Codec());
+  std::vector<VideoFrameType> frame_types{VideoFrameType::kVideoFrameKey};
+  encoder->Encode(MakeVideoFrame(), &frame_types);
+
+  PostQuitAndRun();
+}
+
 TEST_F(WebrtcVideoEncoderWrapperTest, FrameDroppedIfAsyncEncoderBusy) {
   EXPECT_CALL(callback_, OnEncodedImage(_, Field(&CodecSpecificInfo::codecType,
                                                  kVideoCodecVP9)))
@@ -438,4 +517,26 @@
   PostQuitAndRun();
 }
 
+TEST_F(WebrtcVideoEncoderWrapperTest,
+       NoRegisteredObserverIsHandledInMultiStreamMode) {
+  EXPECT_CALL(callback_, OnEncodedImage(_, Field(&CodecSpecificInfo::codecType,
+                                                 kVideoCodecVP9)))
+      .WillOnce(Return(kResultOk));
+
+  // Register a multi-stream observer for |kTestScreenId|.
+  auto observer = std::make_unique<StrictMock<MockVideoChannelStateObserver>>();
+  video_stream_event_router_.SetVideoChannelStateObserver(
+      "screen_stream_16", observer->GetWeakPtr());
+
+  observer.reset();
+
+  auto sdp_format = GetVp9Format();
+  sdp_format.parameters.emplace("max-fr", "42");
+  auto encoder = InitEncoder(std::move(sdp_format), GetVp9Codec());
+  std::vector<VideoFrameType> frame_types{VideoFrameType::kVideoFrameKey};
+  encoder->Encode(MakeVideoFrame(), &frame_types);
+
+  PostQuitAndRun();
+}
+
 }  // namespace remoting::protocol
diff --git a/remoting/protocol/webrtc_video_stream.cc b/remoting/protocol/webrtc_video_stream.cc
index c9a377ea..5f2b952 100644
--- a/remoting/protocol/webrtc_video_stream.cc
+++ b/remoting/protocol/webrtc_video_stream.cc
@@ -57,7 +57,6 @@
   base::TimeDelta capture_delay;
 
   uint32_t capturer_id = 0;
-  webrtc::ScreenId screen_id = webrtc::kInvalidScreenId;
 };
 
 class WebrtcVideoStream::Core : public webrtc::DesktopCapturer::Callback {
@@ -289,8 +288,8 @@
 
   webrtc_transport->OnVideoTransceiverCreated(transceiver_);
 
-  video_encoder_factory->SetVideoChannelStateObserver(
-      weak_factory_.GetWeakPtr());
+  video_encoder_factory->video_stream_event_router()
+      .SetVideoChannelStateObserver(stream_name_, weak_factory_.GetWeakPtr());
 
   core_ = std::make_unique<Core>(std::move(desktop_capturer),
                                  weak_factory_.GetWeakPtr());
@@ -332,14 +331,6 @@
                                 base::Unretained(core_.get()), pause));
 }
 
-void WebrtcVideoStream::SetLosslessEncode(bool want_lossless_encode) {
-  NOTIMPLEMENTED();
-}
-
-void WebrtcVideoStream::SetLosslessColor(bool want_lossless_color) {
-  NOTIMPLEMENTED();
-}
-
 void WebrtcVideoStream::SetObserver(Observer* observer) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   observer_ = observer;
diff --git a/remoting/protocol/webrtc_video_stream.h b/remoting/protocol/webrtc_video_stream.h
index be2493c..8c0593d3 100644
--- a/remoting/protocol/webrtc_video_stream.h
+++ b/remoting/protocol/webrtc_video_stream.h
@@ -61,8 +61,6 @@
   void SetEventTimestampsSource(scoped_refptr<InputEventTimestampsSource>
                                     event_timestamps_source) override;
   void Pause(bool pause) override;
-  void SetLosslessEncode(bool want_lossless) override;
-  void SetLosslessColor(bool want_lossless) override;
   void SetObserver(Observer* observer) override;
   void SelectSource(webrtc::ScreenId id) override;
   void SetComposeEnabled(bool enabled) override;
diff --git a/remoting/test/codec_perftest.cc b/remoting/test/codec_perftest.cc
index 286b23c..2c73ce5c 100644
--- a/remoting/test/codec_perftest.cc
+++ b/remoting/test/codec_perftest.cc
@@ -17,11 +17,10 @@
 constexpr auto kIntervalBetweenFrames = base::Seconds(1) / 30;
 
 struct CodecParams {
-  CodecParams(bool use_vp9, bool lossless, bool lossless_color)
-      : use_vp9(use_vp9), lossless(lossless), lossless_color(lossless_color) {}
+  CodecParams(bool use_vp9, bool lossless_color)
+      : use_vp9(use_vp9), lossless_color(lossless_color) {}
 
   bool use_vp9;
-  bool lossless;
   bool lossless_color;
 };
 
@@ -31,7 +30,6 @@
   void SetUp() override {
     if (GetParam().use_vp9) {
       encoder_ = VideoEncoderVpx::CreateForVP9();
-      encoder_->SetLosslessEncode(GetParam().lossless);
       encoder_->SetLosslessColor(GetParam().lossless_color);
     } else {
       encoder_ = VideoEncoderVpx::CreateForVP8();
@@ -50,16 +48,13 @@
 
 INSTANTIATE_TEST_SUITE_P(VP8,
                          CodecPerfTest,
-                         ::testing::Values(CodecParams(false, false, false)));
+                         ::testing::Values(CodecParams(false, false)));
 INSTANTIATE_TEST_SUITE_P(VP9,
                          CodecPerfTest,
-                         ::testing::Values(CodecParams(true, false, false)));
-INSTANTIATE_TEST_SUITE_P(VP9Lossless,
-                         CodecPerfTest,
-                         ::testing::Values(CodecParams(true, true, false)));
+                         ::testing::Values(CodecParams(true, false)));
 INSTANTIATE_TEST_SUITE_P(VP9LosslessColor,
                          CodecPerfTest,
-                         ::testing::Values(CodecParams(true, false, true)));
+                         ::testing::Values(CodecParams(true, true)));
 
 TEST_P(CodecPerfTest, EncodeLatency) {
   const int kTotalFrames = 300;
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 945f7ce9..4da74c4 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -9351,8 +9351,7 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android31.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_12.sandbox_linux_unittests.filter"
+          "--avd-config=../../tools/android/avd/proto/generic_android31.textpb"
         ],
         "merge": {
           "args": [
diff --git a/testing/buildbot/chromium.cft.json b/testing/buildbot/chromium.cft.json
index a6d9da22..2f3fabf5 100644
--- a/testing/buildbot/chromium.cft.json
+++ b/testing/buildbot/chromium.cft.json
@@ -243,6 +243,9 @@
         "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
       },
       {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/linux.linux-rel-cft.browser_tests.filter"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -2527,6 +2530,9 @@
         "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
       },
       {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/mac.mac-rel-cft.browser_tests.filter"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -4436,6 +4442,9 @@
         "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
       },
       {
+        "args": [
+          "--test-launcher-filter-file=../../testing/buildbot/filters/win.win-rel-cft.browser_tests.filter"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index a02429f..588f155a 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5774,9 +5774,9 @@
       {
         "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_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -5788,8 +5788,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
@@ -5945,9 +5945,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -5959,8 +5959,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
@@ -6097,9 +6097,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -6111,8 +6111,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.fuchsia.fyi.json b/testing/buildbot/chromium.fuchsia.fyi.json
index 2de2fc1..3108d206 100644
--- a/testing/buildbot/chromium.fuchsia.fyi.json
+++ b/testing/buildbot/chromium.fuchsia.fyi.json
@@ -2154,7 +2154,7 @@
       },
       {
         "args": [
-          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.services_unittests.filter;../../testing/buildbot/filters/fuchsia.lsan.service_unittests.filter"
+          "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.services_unittests.filter;../../testing/buildbot/filters/fuchsia.lsan.services_unittests.filter"
         ],
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 5178994..aeaddb1 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -85280,9 +85280,9 @@
       {
         "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_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -85294,8 +85294,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -85421,9 +85421,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -85435,8 +85435,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -85548,9 +85548,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -85562,8 +85562,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -86896,9 +86896,9 @@
       {
         "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_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -86909,8 +86909,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
@@ -87067,9 +87067,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -87080,8 +87080,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
@@ -87219,9 +87219,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -87232,8 +87232,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
@@ -88757,9 +88757,9 @@
       {
         "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_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -88770,8 +88770,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
@@ -88928,9 +88928,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -88941,8 +88941,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
@@ -89080,9 +89080,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -89093,8 +89093,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
@@ -89866,9 +89866,9 @@
       {
         "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_v110.0.5477.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -89879,8 +89879,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index eed2ba6..2eee0f1 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -18653,12 +18653,12 @@
       {
         "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_v110.0.5477.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18670,8 +18670,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
@@ -18844,12 +18844,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5477.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18861,8 +18861,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
@@ -19011,12 +19011,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5477.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 110.0.5477.0",
+        "description": "Run with ash-chrome version 110.0.5478.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -19028,8 +19028,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v110.0.5477.0",
-              "revision": "version:110.0.5477.0"
+              "location": "lacros_version_skew_tests_v110.0.5478.0",
+              "revision": "version:110.0.5478.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index cf6aba9..6128510bb 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -59,12 +59,15 @@
     "//testing/buildbot/filters/fuchsia.browser_tests.filter",
     "//testing/buildbot/filters/linux-chromeos.browser_tests.require_lacros.filter",
     "//testing/buildbot/filters/linux-lacros.browser_tests.filter",
+    "//testing/buildbot/filters/linux.linux-rel-cft.browser_tests.filter",
+    "//testing/buildbot/filters/mac.mac-rel-cft.browser_tests.filter",
+    "//testing/buildbot/filters/mac.mac-rel.browser_tests.filter",
     "//testing/buildbot/filters/mac.mac11-arm64-rel.browser_tests.filter",
     "//testing/buildbot/filters/mac.mac12-arm64-rel.browser_tests.filter",
-    "//testing/buildbot/filters/mac.mac-rel.browser_tests.filter",
     "//testing/buildbot/filters/ozone-linux.wayland_browser_tests.filter",
     "//testing/buildbot/filters/pixel_tests.filter",
     "//testing/buildbot/filters/webrtc_functional.browser_tests.filter",
+    "//testing/buildbot/filters/win.win-rel-cft.browser_tests.filter",
     "//testing/buildbot/filters/win_backuprefptr_fyi.browser_tests.filter",
   ]
 }
@@ -327,10 +330,7 @@
 }
 
 source_set("sandbox_linux_unittests_filters") {
-  data = [
-    "//testing/buildbot/filters/android.emulator.sandbox_linux_unittests.filter",
-    "//testing/buildbot/filters/android.emulator_12.sandbox_linux_unittests.filter",
-  ]
+  data = [ "//testing/buildbot/filters/android.emulator.sandbox_linux_unittests.filter" ]
 }
 
 source_set("unit_tests_filters") {
diff --git a/testing/buildbot/filters/android.emulator_12.sandbox_linux_unittests.filter b/testing/buildbot/filters/android.emulator_12.sandbox_linux_unittests.filter
deleted file mode 100644
index c0f36c5..0000000
--- a/testing/buildbot/filters/android.emulator_12.sandbox_linux_unittests.filter
+++ /dev/null
@@ -1,39 +0,0 @@
-# crbug.com/1272865
--BrokerProcessIntegrationTest.BadPaths
--BrokerProcessIntegrationTest.BrokerDied
--BrokerProcessIntegrationTest.CreateFile
--BrokerProcessIntegrationTest.MkdirFileNoPermissions
--BrokerProcessIntegrationTest.MkdirFileROPermissions
--BrokerProcessIntegrationTest.MkdirFileRWPermissions
--BrokerProcessIntegrationTest.MkdirNoCommand
--BrokerProcessIntegrationTest.MkdirNonExistentRWPermissions
--BrokerProcessIntegrationTest.MkdirNonexistentNoPermissions
--BrokerProcessIntegrationTest.MkdirNonexistentROPermissions
--BrokerProcessIntegrationTest.OpenCpuinfoNonRecursive
--BrokerProcessIntegrationTest.OpenCpuinfoRecursive
--BrokerProcessIntegrationTest.ReadlinkNoCommand
--BrokerProcessIntegrationTest.ReadlinkNoPermissions
--BrokerProcessIntegrationTest.ReadlinkNonexistentNoPermissions
--BrokerProcessIntegrationTest.RenameNoCommand
--BrokerProcessIntegrationTest.RenameNoPermNew
--BrokerProcessIntegrationTest.RenameNoPermOld
--BrokerProcessIntegrationTest.RenameReadPermNew
--BrokerProcessIntegrationTest.RenameReadPermOld
--BrokerProcessIntegrationTest.RmdirFileNoPermissions
--BrokerProcessIntegrationTest.RmdirFileROPermissions
--BrokerProcessIntegrationTest.RmdirFileRWPermissions
--BrokerProcessIntegrationTest.RmdirNoCommand
--BrokerProcessIntegrationTest.RmdirNonExistentRWPermissions
--BrokerProcessIntegrationTest.RmdirNonexistentNoPermissions
--BrokerProcessIntegrationTest.RmdirNonexistentROPermissions
--BrokerProcessIntegrationTest.TestOpenFilePermsEPERM
--BrokerProcessIntegrationTest.UnlinkFileNoPermissions
--BrokerProcessIntegrationTest.UnlinkFileROPermissions
--BrokerProcessIntegrationTest.UnlinkFileRWPermissions
--BrokerProcessIntegrationTest.UnlinkNoCommand
--BrokerProcessIntegrationTest.UnlinkNonexistentNoPermissions
--BrokerProcessIntegrationTest.UnlinkNonexistentROPermissions
--BrokerProcessIntegrationTest.UnlinkNonExistentRWPermissions
-
-# crbug.com/1368296
--BrokerProcessIntegrationTest.TestOpenFilePermsENOENT
diff --git a/testing/buildbot/filters/linux.linux-rel-cft.browser_tests.filter b/testing/buildbot/filters/linux.linux-rel-cft.browser_tests.filter
new file mode 100644
index 0000000..2d996d4
--- /dev/null
+++ b/testing/buildbot/filters/linux.linux-rel-cft.browser_tests.filter
@@ -0,0 +1,61 @@
+# Filter out failing infobar tests from Chrome for Testing trybots
+
+# Chrome for Testing (CfT) adds a global info bar.
+# As a side effect, this fails all tests that explicitly assert the
+# infobar count, as they always differ by one.
+
+# Since those tests are already covered by non-cft trybots, we just disable
+# them for cft trybots.
+
+# https://ci.chromium.org/ui/p/chromium/builders/try/linux-rel/1222923
+-BackForwardCachePageLoadMetricsObserverBrowserTest.CumulativeLayoutShiftAfterBackForwardCacheRestore
+-BackForwardCachePageLoadMetricsObserverBrowserTest.LayoutShiftNormalization_AfterBackForwardCacheRestore
+-CollectedCookiesViewsTest.ChangeAndCloseDialog
+-CollectedCookiesViewsTest.ChangeAndNavigateAway
+-CollectedCookiesViewsTest.CloseDialog
+-DebuggerApiTest.InfoBar
+-DebuggerApiTest.InfoBarIsNotRemovedIfAttachAgainBeforeFiveSeconds
+-DebuggerApiTest.InfoBarIsRemovedAfterFiveSeconds
+-DevToolsProtocolTest.AutomationOverrideAddsOneInfoBarOnly
+-DevToolsProtocolTest.AutomationOverrideShowsAndRemovesInfoBar
+-ExtensionInstallUIBrowserTest.TestThemeInstallUndoResetsToPreviousTheme
+-GlobalConfirmInfoBarTest.CreateAndCloseInfobar
+-GlobalConfirmInfoBarTest.UserInteraction
+-GlobalConfirmInfoBarTest.VerifyInfobarNonDefaultProperties
+-InfoBarsTest.TestInfoBarsCloseOnNewTheme
+-KnownInterceptionDisclosureInfobarTest.CooldownResetsOnBrowserRestartDesktop
+-KnownInterceptionDisclosureInfobarTest.OnlyShowDisclosureOncePerSession
+-MultipleTabSharingUIViewsBrowserTest.CloseTabs
+-MultipleTabSharingUIViewsBrowserTest.StopSharing
+-MultipleTabSharingUIViewsBrowserTest.VerifyUi
+-TabSharingUIViewsPreferCurrentTabBrowserTest.VerifyUiWhenCapturingAnotherTab
+-WebAppIntegration.ToggleWindowControlsOverlay
+-WebAppIntegration.WindowControlsOverlayStatePreservesBetweenLaunches
+-WebRtcDesktopCaptureBrowserTest.CloseAndReopenNonSharedTab
+-WebRtcDesktopCaptureBrowserTest.SwitchSharedTabBackAndForth
+-All/PageSpecificSiteDataDialogBrowserTest.AllowMenuItem/1
+-All/PageSpecificSiteDataDialogBrowserTest.BlockMenuItem/1
+-All/PageSpecificSiteDataDialogBrowserTest.ClearOnExitMenuItem/1
+-All/PageSpecificSiteDataDialogBrowserTest.CloseDialog/0
+-All/PageSpecificSiteDataDialogBrowserTest.CloseDialog/1
+-All/PageSpecificSiteDataDialogBrowserTest.DeleteMenuItem/1
+-All/TabSharingUIViewsBrowserTest.ChangeCapturedTabFavicon/0
+-All/TabSharingUIViewsBrowserTest.ChangeCapturedTabFavicon/1
+-All/TabSharingUIViewsBrowserTest.ChangeCapturingTabFavicon/0
+-All/TabSharingUIViewsBrowserTest.ChangeCapturingTabFavicon/1
+-All/TabSharingUIViewsBrowserTest.ChangeOtherTabFavicon/0
+-All/TabSharingUIViewsBrowserTest.ChangeOtherTabFavicon/1
+-All/TabSharingUIViewsBrowserTest.CloseTab/0
+-All/TabSharingUIViewsBrowserTest.CloseTab/1
+-All/TabSharingUIViewsBrowserTest.CloseTabInIncognitoBrowser/0
+-All/TabSharingUIViewsBrowserTest.CloseTabInIncognitoBrowser/1
+-All/TabSharingUIViewsBrowserTest.InfobarLabelUpdatedOnNavigation/0
+-All/TabSharingUIViewsBrowserTest.InfobarLabelUpdatedOnNavigation/1
+-All/TabSharingUIViewsBrowserTest.KillSharedTab/0
+-All/TabSharingUIViewsBrowserTest.KillSharedTab/1
+-All/TabSharingUIViewsBrowserTest.StartSharing/0
+-All/TabSharingUIViewsBrowserTest.StartSharing/1
+-All/TabSharingUIViewsBrowserTest.StopSharing/0
+-All/TabSharingUIViewsBrowserTest.StopSharing/1
+-All/TabSharingUIViewsBrowserTest.SwitchSharedTab/0
+-All/TabSharingUIViewsBrowserTest.SwitchSharedTab/1
diff --git a/testing/buildbot/filters/mac.mac-rel-cft.browser_tests.filter b/testing/buildbot/filters/mac.mac-rel-cft.browser_tests.filter
new file mode 100644
index 0000000..2d996d4
--- /dev/null
+++ b/testing/buildbot/filters/mac.mac-rel-cft.browser_tests.filter
@@ -0,0 +1,61 @@
+# Filter out failing infobar tests from Chrome for Testing trybots
+
+# Chrome for Testing (CfT) adds a global info bar.
+# As a side effect, this fails all tests that explicitly assert the
+# infobar count, as they always differ by one.
+
+# Since those tests are already covered by non-cft trybots, we just disable
+# them for cft trybots.
+
+# https://ci.chromium.org/ui/p/chromium/builders/try/linux-rel/1222923
+-BackForwardCachePageLoadMetricsObserverBrowserTest.CumulativeLayoutShiftAfterBackForwardCacheRestore
+-BackForwardCachePageLoadMetricsObserverBrowserTest.LayoutShiftNormalization_AfterBackForwardCacheRestore
+-CollectedCookiesViewsTest.ChangeAndCloseDialog
+-CollectedCookiesViewsTest.ChangeAndNavigateAway
+-CollectedCookiesViewsTest.CloseDialog
+-DebuggerApiTest.InfoBar
+-DebuggerApiTest.InfoBarIsNotRemovedIfAttachAgainBeforeFiveSeconds
+-DebuggerApiTest.InfoBarIsRemovedAfterFiveSeconds
+-DevToolsProtocolTest.AutomationOverrideAddsOneInfoBarOnly
+-DevToolsProtocolTest.AutomationOverrideShowsAndRemovesInfoBar
+-ExtensionInstallUIBrowserTest.TestThemeInstallUndoResetsToPreviousTheme
+-GlobalConfirmInfoBarTest.CreateAndCloseInfobar
+-GlobalConfirmInfoBarTest.UserInteraction
+-GlobalConfirmInfoBarTest.VerifyInfobarNonDefaultProperties
+-InfoBarsTest.TestInfoBarsCloseOnNewTheme
+-KnownInterceptionDisclosureInfobarTest.CooldownResetsOnBrowserRestartDesktop
+-KnownInterceptionDisclosureInfobarTest.OnlyShowDisclosureOncePerSession
+-MultipleTabSharingUIViewsBrowserTest.CloseTabs
+-MultipleTabSharingUIViewsBrowserTest.StopSharing
+-MultipleTabSharingUIViewsBrowserTest.VerifyUi
+-TabSharingUIViewsPreferCurrentTabBrowserTest.VerifyUiWhenCapturingAnotherTab
+-WebAppIntegration.ToggleWindowControlsOverlay
+-WebAppIntegration.WindowControlsOverlayStatePreservesBetweenLaunches
+-WebRtcDesktopCaptureBrowserTest.CloseAndReopenNonSharedTab
+-WebRtcDesktopCaptureBrowserTest.SwitchSharedTabBackAndForth
+-All/PageSpecificSiteDataDialogBrowserTest.AllowMenuItem/1
+-All/PageSpecificSiteDataDialogBrowserTest.BlockMenuItem/1
+-All/PageSpecificSiteDataDialogBrowserTest.ClearOnExitMenuItem/1
+-All/PageSpecificSiteDataDialogBrowserTest.CloseDialog/0
+-All/PageSpecificSiteDataDialogBrowserTest.CloseDialog/1
+-All/PageSpecificSiteDataDialogBrowserTest.DeleteMenuItem/1
+-All/TabSharingUIViewsBrowserTest.ChangeCapturedTabFavicon/0
+-All/TabSharingUIViewsBrowserTest.ChangeCapturedTabFavicon/1
+-All/TabSharingUIViewsBrowserTest.ChangeCapturingTabFavicon/0
+-All/TabSharingUIViewsBrowserTest.ChangeCapturingTabFavicon/1
+-All/TabSharingUIViewsBrowserTest.ChangeOtherTabFavicon/0
+-All/TabSharingUIViewsBrowserTest.ChangeOtherTabFavicon/1
+-All/TabSharingUIViewsBrowserTest.CloseTab/0
+-All/TabSharingUIViewsBrowserTest.CloseTab/1
+-All/TabSharingUIViewsBrowserTest.CloseTabInIncognitoBrowser/0
+-All/TabSharingUIViewsBrowserTest.CloseTabInIncognitoBrowser/1
+-All/TabSharingUIViewsBrowserTest.InfobarLabelUpdatedOnNavigation/0
+-All/TabSharingUIViewsBrowserTest.InfobarLabelUpdatedOnNavigation/1
+-All/TabSharingUIViewsBrowserTest.KillSharedTab/0
+-All/TabSharingUIViewsBrowserTest.KillSharedTab/1
+-All/TabSharingUIViewsBrowserTest.StartSharing/0
+-All/TabSharingUIViewsBrowserTest.StartSharing/1
+-All/TabSharingUIViewsBrowserTest.StopSharing/0
+-All/TabSharingUIViewsBrowserTest.StopSharing/1
+-All/TabSharingUIViewsBrowserTest.SwitchSharedTab/0
+-All/TabSharingUIViewsBrowserTest.SwitchSharedTab/1
diff --git a/testing/buildbot/filters/win.win-rel-cft.browser_tests.filter b/testing/buildbot/filters/win.win-rel-cft.browser_tests.filter
new file mode 100644
index 0000000..fde040dd4
--- /dev/null
+++ b/testing/buildbot/filters/win.win-rel-cft.browser_tests.filter
@@ -0,0 +1,65 @@
+# Filter out failing infobar tests from Chrome for Testing trybots
+
+# Chrome for Testing (CfT) adds a global info bar.
+# As a side effect, this fails all tests that explicitly assert the
+# infobar count, as they always differ by one.
+
+# Since those tests are already covered by non-cft trybots, we just disable
+# them for cft trybots.
+
+# https://ci.chromium.org/ui/p/chromium/builders/try/linux-rel/1222923
+-BackForwardCachePageLoadMetricsObserverBrowserTest.CumulativeLayoutShiftAfterBackForwardCacheRestore
+-BackForwardCachePageLoadMetricsObserverBrowserTest.LayoutShiftNormalization_AfterBackForwardCacheRestore
+-CollectedCookiesViewsTest.ChangeAndCloseDialog
+-CollectedCookiesViewsTest.ChangeAndNavigateAway
+-CollectedCookiesViewsTest.CloseDialog
+-DebuggerApiTest.InfoBar
+-DebuggerApiTest.InfoBarIsNotRemovedIfAttachAgainBeforeFiveSeconds
+-DebuggerApiTest.InfoBarIsRemovedAfterFiveSeconds
+-DevToolsProtocolTest.AutomationOverrideAddsOneInfoBarOnly
+-DevToolsProtocolTest.AutomationOverrideShowsAndRemovesInfoBar
+-ExtensionInstallUIBrowserTest.TestThemeInstallUndoResetsToPreviousTheme
+-GlobalConfirmInfoBarTest.CreateAndCloseInfobar
+-GlobalConfirmInfoBarTest.UserInteraction
+-GlobalConfirmInfoBarTest.VerifyInfobarNonDefaultProperties
+-InfoBarsTest.TestInfoBarsCloseOnNewTheme
+-KnownInterceptionDisclosureInfobarTest.CooldownResetsOnBrowserRestartDesktop
+-KnownInterceptionDisclosureInfobarTest.OnlyShowDisclosureOncePerSession
+-MultipleTabSharingUIViewsBrowserTest.CloseTabs
+-MultipleTabSharingUIViewsBrowserTest.StopSharing
+-MultipleTabSharingUIViewsBrowserTest.VerifyUi
+-TabSharingUIViewsPreferCurrentTabBrowserTest.VerifyUiWhenCapturingAnotherTab
+-WebAppIntegration.ToggleWindowControlsOverlay
+-WebAppIntegration.WindowControlsOverlayStatePreservesBetweenLaunches
+-WebRtcDesktopCaptureBrowserTest.CloseAndReopenNonSharedTab
+-WebRtcDesktopCaptureBrowserTest.SwitchSharedTabBackAndForth
+-All/PageSpecificSiteDataDialogBrowserTest.AllowMenuItem/1
+-All/PageSpecificSiteDataDialogBrowserTest.BlockMenuItem/1
+-All/PageSpecificSiteDataDialogBrowserTest.ClearOnExitMenuItem/1
+-All/PageSpecificSiteDataDialogBrowserTest.CloseDialog/0
+-All/PageSpecificSiteDataDialogBrowserTest.CloseDialog/1
+-All/PageSpecificSiteDataDialogBrowserTest.DeleteMenuItem/1
+-All/TabSharingUIViewsBrowserTest.ChangeCapturedTabFavicon/0
+-All/TabSharingUIViewsBrowserTest.ChangeCapturedTabFavicon/1
+-All/TabSharingUIViewsBrowserTest.ChangeCapturingTabFavicon/0
+-All/TabSharingUIViewsBrowserTest.ChangeCapturingTabFavicon/1
+-All/TabSharingUIViewsBrowserTest.ChangeOtherTabFavicon/0
+-All/TabSharingUIViewsBrowserTest.ChangeOtherTabFavicon/1
+-All/TabSharingUIViewsBrowserTest.CloseTab/0
+-All/TabSharingUIViewsBrowserTest.CloseTab/1
+-All/TabSharingUIViewsBrowserTest.CloseTabInIncognitoBrowser/0
+-All/TabSharingUIViewsBrowserTest.CloseTabInIncognitoBrowser/1
+-All/TabSharingUIViewsBrowserTest.InfobarLabelUpdatedOnNavigation/0
+-All/TabSharingUIViewsBrowserTest.InfobarLabelUpdatedOnNavigation/1
+-All/TabSharingUIViewsBrowserTest.KillSharedTab/0
+-All/TabSharingUIViewsBrowserTest.KillSharedTab/1
+-All/TabSharingUIViewsBrowserTest.StartSharing/0
+-All/TabSharingUIViewsBrowserTest.StartSharing/1
+-All/TabSharingUIViewsBrowserTest.StopSharing/0
+-All/TabSharingUIViewsBrowserTest.StopSharing/1
+-All/TabSharingUIViewsBrowserTest.SwitchSharedTab/0
+-All/TabSharingUIViewsBrowserTest.SwitchSharedTab/1
+
+# https://ci.chromium.org/ui/p/chromium/builders/try/win-rel/25038/test-results
+-FindInPageControllerTest.FindMovesWhenObscuring
+-InteractionTestUtilBrowserTest.CompareScreenshot_WebPage
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index ffe90a3..f982529 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1082,6 +1082,11 @@
           'shards': 20,
         },
       },
+      'linux-rel-cft': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/linux.linux-rel-cft.browser_tests.filter',
+        ],
+      },
       'mac-code-coverage': {
         'args': [
           '--coverage-continuous-mode=1',
@@ -1092,6 +1097,11 @@
           'shards': 12,
         },
       },
+      'mac-rel-cft': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/mac.mac-rel-cft.browser_tests.filter',
+        ],
+      },
       'mac11-arm64-rel-tests': {
         'args': [
           # crbug.com/1262402
@@ -1123,6 +1133,11 @@
           '--test-launcher-filter-file=../../testing/buildbot/filters/win_backuprefptr_fyi.browser_tests.filter',
         ],
       },
+      'win-rel-cft': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/win.win-rel-cft.browser_tests.filter',
+        ],
+      },
       'win10-rel-no-external-ip': {
         # crbug.com/868082
         'args': [
@@ -3120,11 +3135,6 @@
   },
   'sandbox_linux_unittests': {
     'modifications': {
-      'android-12-x64-rel': {
-        'args': [
-          '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_12.sandbox_linux_unittests.filter',
-        ]
-      },
       'android-nougat-x86-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator.sandbox_linux_unittests.filter',
@@ -3241,7 +3251,7 @@
       },
       'fuchsia-fyi-x64-asan': {
         'args': [
-          '--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.lsan.service_unittests.filter',
+          '--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.lsan.services_unittests.filter',
         ],
       },
     }
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index a1556c14..6be43c10 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -22,16 +22,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5477.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v110.0.5478.0/test_ash_chrome',
     ],
-    'description': 'Run with ash-chrome version 110.0.5477.0',
+    'description': 'Run with ash-chrome version 110.0.5478.0',
     'identifier': 'Lacros version skew testing ash canary',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v110.0.5477.0',
-          'revision': 'version:110.0.5477.0',
+          'location': 'lacros_version_skew_tests_v110.0.5478.0',
+          'revision': 'version:110.0.5478.0',
         },
       ],
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index d3a85aec..efb322c 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -4126,26 +4126,6 @@
             ]
         }
     ],
-    "DisruptiveNotificationPermissionRevocation": [
-        {
-            "platforms": [
-                "android",
-                "chromeos",
-                "chromeos_lacros",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled_20221114",
-                    "enable_features": [
-                        "DisruptiveNotificationPermissionRevocation"
-                    ]
-                }
-            ]
-        }
-    ],
     "DnsHttpsSvcbTimeout": [
         {
             "platforms": [
@@ -4539,8 +4519,29 @@
             ],
             "experiments": [
                 {
-                    "name": "Control_V1",
-                    "disable_features": [
+                    "name": "ExcludeEntropyL_V1",
+                    "params": {
+                        "min_bpp": "0.01"
+                    },
+                    "enable_features": [
+                        "ExcludeLowEntropyImagesFromLCP"
+                    ]
+                },
+                {
+                    "name": "ExcludeEntropyM_V1",
+                    "params": {
+                        "min_bpp": "0.05"
+                    },
+                    "enable_features": [
+                        "ExcludeLowEntropyImagesFromLCP"
+                    ]
+                },
+                {
+                    "name": "ExcludeEntropyH_V1",
+                    "params": {
+                        "min_bpp": "0.2"
+                    },
+                    "enable_features": [
                         "ExcludeLowEntropyImagesFromLCP"
                     ]
                 }
diff --git a/third_party/OWNERS b/third_party/OWNERS
index ed1eaf03..a590f08 100644
--- a/third_party/OWNERS
+++ b/third_party/OWNERS
@@ -11,8 +11,8 @@
 # you're making changes and there isn't a more obvious person, the owner is
 # probably you!
 
-# Eng reviewer
-file://ENG_REVIEW_OWNERS
+# ATL reviewer
+file://ATL_OWNERS
 
 # Automatic round-robin assignment of reviewer for third-party licenses.
 # No one receives email to this list, just use it as a reviewer.
diff --git a/third_party/androidx/build.gradle.template b/third_party/androidx/build.gradle.template
index 98f549d..8cebb4d 100644
--- a/third_party/androidx/build.gradle.template
+++ b/third_party/androidx/build.gradle.template
@@ -94,10 +94,10 @@
     androidTestCompile 'androidx.test:monitor:1.4.0-rc01'
     androidTestCompile 'androidx.test:rules:1.2.0'
     androidTestCompile 'androidx.test:runner:1.2.0'
-    androidTestCompile 'androidx.test.espresso:espresso-contrib:3.2.0'
-    androidTestCompile 'androidx.test.espresso:espresso-core:3.2.0'
-    androidTestCompile 'androidx.test.espresso:espresso-intents:3.2.0'
-    androidTestCompile 'androidx.test.espresso:espresso-web:3.2.0'
+    androidTestCompile 'androidx.test.espresso:espresso-contrib:3.5.0'
+    androidTestCompile 'androidx.test.espresso:espresso-core:3.5.0'
+    androidTestCompile 'androidx.test.espresso:espresso-intents:3.5.0'
+    androidTestCompile 'androidx.test.espresso:espresso-web:3.5.0'
     androidTestCompile 'androidx.test.ext:junit:1.1.1'
     androidTestCompile 'androidx.test.services:storage:1.4.1'
     androidTestCompile 'androidx.test.uiautomator:uiautomator:2.2.0'
diff --git a/third_party/blink/public/common/attribution_reporting/DEPS b/third_party/blink/public/common/attribution_reporting/DEPS
deleted file mode 100644
index 88b90fbe..0000000
--- a/third_party/blink/public/common/attribution_reporting/DEPS
+++ /dev/null
@@ -1,4 +0,0 @@
-include_rules = [
-  "+components/aggregation_service",
-  "+components/attribution_reporting",
-]
diff --git a/third_party/blink/public/common/attribution_reporting/OWNERS b/third_party/blink/public/common/attribution_reporting/OWNERS
deleted file mode 100644
index f050755d..0000000
--- a/third_party/blink/public/common/attribution_reporting/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-file://content/browser/attribution_reporting/OWNERS
-
-per-file *mojom_traits*.*=set noparent
-per-file *mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/third_party/blink/public/common/attribution_reporting/mojom_traits.h b/third_party/blink/public/common/attribution_reporting/mojom_traits.h
deleted file mode 100644
index a1f5821d..0000000
--- a/third_party/blink/public/common/attribution_reporting/mojom_traits.h
+++ /dev/null
@@ -1,451 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_ATTRIBUTION_REPORTING_MOJOM_TRAITS_H_
-#define THIRD_PARTY_BLINK_PUBLIC_COMMON_ATTRIBUTION_REPORTING_MOJOM_TRAITS_H_
-
-#include <stdint.h>
-
-#include <utility>
-
-#include "base/time/time.h"
-#include "components/aggregation_service/aggregation_service.mojom.h"
-#include "components/attribution_reporting/aggregatable_trigger_data.h"
-#include "components/attribution_reporting/aggregatable_values.h"
-#include "components/attribution_reporting/aggregation_keys.h"
-#include "components/attribution_reporting/event_trigger_data.h"
-#include "components/attribution_reporting/filters.h"
-#include "components/attribution_reporting/source_registration.h"
-#include "components/attribution_reporting/suitable_origin.h"
-#include "components/attribution_reporting/trigger_registration.h"
-#include "third_party/abseil-cpp/absl/numeric/int128.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/common/common_export.h"
-#include "third_party/blink/public/mojom/conversions/attribution_data_host.mojom-shared.h"
-#include "url/mojom/origin_mojom_traits.h"
-#include "url/origin.h"
-
-namespace mojo {
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::AttributionDebugKeyDataView, uint64_t> {
-  static uint64_t value(uint64_t debug_key) { return debug_key; }
-
-  static bool Read(blink::mojom::AttributionDebugKeyDataView data,
-                   uint64_t* out) {
-    *out = data.value();
-    return true;
-  }
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::AttributionSuitableOriginDataView,
-                 attribution_reporting::SuitableOrigin> {
-  static const url::Origin& origin(
-      const attribution_reporting::SuitableOrigin& origin) {
-    return *origin;
-  }
-
-  static bool Read(blink::mojom::AttributionSuitableOriginDataView data,
-                   attribution_reporting::SuitableOrigin* out) {
-    url::Origin origin;
-    if (!data.ReadOrigin(&origin))
-      return false;
-
-    auto suitable_origin =
-        attribution_reporting::SuitableOrigin::Create(std::move(origin));
-    if (!suitable_origin)
-      return false;
-
-    *out = std::move(*suitable_origin);
-    return true;
-  }
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::AttributionTriggerDedupKeyDataView, uint64_t> {
-  static uint64_t value(uint64_t debug_key) { return debug_key; }
-
-  static bool Read(blink::mojom::AttributionTriggerDedupKeyDataView data,
-                   uint64_t* out) {
-    *out = data.value();
-    return true;
-  }
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::AttributionFilterDataDataView,
-                 attribution_reporting::FilterData> {
-  static const attribution_reporting::FilterValues& filter_values(
-      const attribution_reporting::FilterData& filter_data) {
-    return filter_data.filter_values();
-  }
-
-  // TODO(apaseltiner): Define this in a separate .cc file.
-  static bool Read(blink::mojom::AttributionFilterDataDataView data,
-                   attribution_reporting::FilterData* out) {
-    attribution_reporting::FilterValues filter_values;
-    if (!data.ReadFilterValues(&filter_values))
-      return false;
-
-    absl::optional<attribution_reporting::FilterData> filter_data =
-        attribution_reporting::FilterData::Create(std::move(filter_values));
-    if (!filter_data.has_value())
-      return false;
-
-    *out = std::move(*filter_data);
-    return true;
-  }
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::AttributionAggregationKeysDataView,
-                 attribution_reporting::AggregationKeys> {
-  static const attribution_reporting::AggregationKeys::Keys& keys(
-      const attribution_reporting::AggregationKeys& aggregation_keys) {
-    return aggregation_keys.keys();
-  }
-
-  // TODO(apaseltiner): Define this in a separate .cc file.
-  static bool Read(blink::mojom::AttributionAggregationKeysDataView data,
-                   attribution_reporting::AggregationKeys* out) {
-    attribution_reporting::AggregationKeys::Keys keys;
-    if (!data.ReadKeys(&keys))
-      return false;
-
-    absl::optional<attribution_reporting::AggregationKeys> aggregation_keys =
-        attribution_reporting::AggregationKeys::FromKeys(std::move(keys));
-    if (!aggregation_keys.has_value())
-      return false;
-
-    *out = std::move(*aggregation_keys);
-    return true;
-  }
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::AttributionSourceDataDataView,
-                 attribution_reporting::SourceRegistration> {
-  static const attribution_reporting::SuitableOrigin& destination(
-      const attribution_reporting::SourceRegistration& source) {
-    return source.destination;
-  }
-
-  static uint64_t source_event_id(
-      const attribution_reporting::SourceRegistration& source) {
-    return source.source_event_id;
-  }
-
-  static absl::optional<base::TimeDelta> expiry(
-      const attribution_reporting::SourceRegistration& source) {
-    return source.expiry;
-  }
-
-  static absl::optional<base::TimeDelta> event_report_window(
-      const attribution_reporting::SourceRegistration& source) {
-    return source.event_report_window;
-  }
-
-  static absl::optional<base::TimeDelta> aggregatable_report_window(
-      const attribution_reporting::SourceRegistration& source) {
-    return source.aggregatable_report_window;
-  }
-
-  static int64_t priority(
-      const attribution_reporting::SourceRegistration& source) {
-    return source.priority;
-  }
-
-  static absl::optional<uint64_t> debug_key(
-      const attribution_reporting::SourceRegistration& source) {
-    return source.debug_key;
-  }
-
-  static const attribution_reporting::FilterData& filter_data(
-      const attribution_reporting::SourceRegistration& source) {
-    return source.filter_data;
-  }
-
-  static const attribution_reporting::AggregationKeys& aggregation_keys(
-      const attribution_reporting::SourceRegistration& source) {
-    return source.aggregation_keys;
-  }
-
-  static bool debug_reporting(
-      const attribution_reporting::SourceRegistration& source) {
-    return source.debug_reporting;
-  }
-
-  // TODO(apaseltiner): Define this in a separate .cc file.
-  static bool Read(blink::mojom::AttributionSourceDataDataView data,
-                   attribution_reporting::SourceRegistration* out) {
-    if (!data.ReadDestination(&out->destination))
-      return false;
-
-    if (!data.ReadExpiry(&out->expiry))
-      return false;
-
-    if (!data.ReadEventReportWindow(&out->event_report_window))
-      return false;
-
-    if (!data.ReadAggregatableReportWindow(&out->aggregatable_report_window))
-      return false;
-
-    if (!data.ReadDebugKey(&out->debug_key))
-      return false;
-
-    if (!data.ReadFilterData(&out->filter_data))
-      return false;
-
-    if (!data.ReadAggregationKeys(&out->aggregation_keys))
-      return false;
-
-    out->source_event_id = data.source_event_id();
-    out->priority = data.priority();
-    out->debug_reporting = data.debug_reporting();
-    return true;
-  }
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::AttributionFiltersDataView,
-                 attribution_reporting::Filters> {
-  static const attribution_reporting::FilterValues& filter_values(
-      const attribution_reporting::Filters& filters) {
-    return filters.filter_values();
-  }
-
-  static bool Read(blink::mojom::AttributionFiltersDataView data,
-                   attribution_reporting::Filters* out) {
-    attribution_reporting::FilterValues filter_values;
-    if (!data.ReadFilterValues(&filter_values))
-      return false;
-
-    absl::optional<attribution_reporting::Filters> filters =
-        attribution_reporting::Filters::Create(std::move(filter_values));
-    if (!filters.has_value())
-      return false;
-
-    *out = std::move(*filters);
-    return true;
-  }
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::EventTriggerDataDataView,
-                 attribution_reporting::EventTriggerData> {
-  static uint64_t data(const attribution_reporting::EventTriggerData& data) {
-    return data.data;
-  }
-
-  static int64_t priority(const attribution_reporting::EventTriggerData& data) {
-    return data.priority;
-  }
-
-  static absl::optional<uint64_t> dedup_key(
-      const attribution_reporting::EventTriggerData& data) {
-    return data.dedup_key;
-  }
-
-  static const attribution_reporting::Filters& filters(
-      const attribution_reporting::EventTriggerData& data) {
-    return data.filters;
-  }
-
-  static const attribution_reporting::Filters& not_filters(
-      const attribution_reporting::EventTriggerData& data) {
-    return data.not_filters;
-  }
-
-  static bool Read(blink::mojom::EventTriggerDataDataView data,
-                   attribution_reporting::EventTriggerData* out) {
-    if (!data.ReadDedupKey(&out->dedup_key))
-      return false;
-
-    if (!data.ReadFilters(&out->filters))
-      return false;
-
-    if (!data.ReadNotFilters(&out->not_filters))
-      return false;
-
-    out->data = data.data();
-    out->priority = data.priority();
-    return true;
-  }
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::AttributionAggregatableTriggerDataDataView,
-                 attribution_reporting::AggregatableTriggerData> {
-  static absl::uint128 key_piece(
-      const attribution_reporting::AggregatableTriggerData& data) {
-    return data.key_piece();
-  }
-
-  static const attribution_reporting::AggregatableTriggerData::Keys&
-  source_keys(const attribution_reporting::AggregatableTriggerData& data) {
-    return data.source_keys();
-  }
-
-  static const attribution_reporting::Filters& filters(
-      const attribution_reporting::AggregatableTriggerData& data) {
-    return data.filters();
-  }
-
-  static const attribution_reporting::Filters& not_filters(
-      const attribution_reporting::AggregatableTriggerData& data) {
-    return data.not_filters();
-  }
-
-  static bool Read(
-      blink::mojom::AttributionAggregatableTriggerDataDataView data,
-      attribution_reporting::AggregatableTriggerData* out) {
-    absl::uint128 key_piece;
-    if (!data.ReadKeyPiece(&key_piece))
-      return false;
-
-    attribution_reporting::AggregatableTriggerData::Keys source_keys;
-    if (!data.ReadSourceKeys(&source_keys))
-      return false;
-
-    attribution_reporting::Filters filters;
-    if (!data.ReadFilters(&filters))
-      return false;
-
-    attribution_reporting::Filters not_filters;
-    if (!data.ReadNotFilters(&not_filters))
-      return false;
-
-    auto aggregatable_trigger_data =
-        attribution_reporting::AggregatableTriggerData::Create(
-            key_piece, std::move(source_keys), std::move(filters),
-            std::move(not_filters));
-    if (!aggregatable_trigger_data)
-      return false;
-
-    *out = std::move(*aggregatable_trigger_data);
-    return true;
-  }
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::AttributionTriggerDataDataView,
-                 attribution_reporting::TriggerRegistration> {
-  static const std::vector<attribution_reporting::EventTriggerData>&
-  event_triggers(const attribution_reporting::TriggerRegistration& trigger) {
-    return trigger.event_triggers.vec();
-  }
-
-  static const attribution_reporting::Filters& filters(
-      const attribution_reporting::TriggerRegistration& trigger) {
-    return trigger.filters;
-  }
-
-  static const attribution_reporting::Filters& not_filters(
-      const attribution_reporting::TriggerRegistration& trigger) {
-    return trigger.not_filters;
-  }
-
-  static const std::vector<attribution_reporting::AggregatableTriggerData>&
-  aggregatable_trigger_data(
-      const attribution_reporting::TriggerRegistration& trigger) {
-    return trigger.aggregatable_trigger_data.vec();
-  }
-
-  static const attribution_reporting::AggregatableValues::Values&
-  aggregatable_values(
-      const attribution_reporting::TriggerRegistration& trigger) {
-    return trigger.aggregatable_values.values();
-  }
-
-  static absl::optional<uint64_t> debug_key(
-      const attribution_reporting::TriggerRegistration& trigger) {
-    return trigger.debug_key;
-  }
-
-  static absl::optional<uint64_t> aggregatable_dedup_key(
-      const attribution_reporting::TriggerRegistration& trigger) {
-    return trigger.aggregatable_dedup_key;
-  }
-
-  static bool debug_reporting(
-      const attribution_reporting::TriggerRegistration& trigger) {
-    return trigger.debug_reporting;
-  }
-
-  static aggregation_service::mojom::AggregationCoordinator
-  aggregation_coordinator(
-      const attribution_reporting::TriggerRegistration& trigger) {
-    return trigger.aggregation_coordinator;
-  }
-
-  static bool Read(blink::mojom::AttributionTriggerDataDataView data,
-                   attribution_reporting::TriggerRegistration* out) {
-    std::vector<attribution_reporting::EventTriggerData> event_triggers;
-    if (!data.ReadEventTriggers(&event_triggers))
-      return false;
-
-    auto event_triggers_list =
-        attribution_reporting::EventTriggerDataList::Create(
-            std::move(event_triggers));
-    if (!event_triggers_list)
-      return false;
-
-    out->event_triggers = std::move(*event_triggers_list);
-
-    if (!data.ReadFilters(&out->filters))
-      return false;
-
-    if (!data.ReadNotFilters(&out->not_filters))
-      return false;
-
-    std::vector<attribution_reporting::AggregatableTriggerData>
-        aggregatable_trigger_data;
-    if (!data.ReadAggregatableTriggerData(&aggregatable_trigger_data))
-      return false;
-
-    auto aggregatable_trigger_data_list =
-        attribution_reporting::AggregatableTriggerDataList::Create(
-            std::move(aggregatable_trigger_data));
-    if (!aggregatable_trigger_data_list)
-      return false;
-
-    out->aggregatable_trigger_data = std::move(*aggregatable_trigger_data_list);
-
-    attribution_reporting::AggregatableValues::Values values;
-    if (!data.ReadAggregatableValues(&values))
-      return false;
-
-    auto aggregatable_values =
-        attribution_reporting::AggregatableValues::Create(std::move(values));
-    if (!aggregatable_values)
-      return false;
-
-    out->aggregatable_values = std::move(*aggregatable_values);
-
-    if (!data.ReadDebugKey(&out->debug_key))
-      return false;
-
-    if (!data.ReadAggregatableDedupKey(&out->aggregatable_dedup_key))
-      return false;
-
-    out->debug_reporting = data.debug_reporting();
-    out->aggregation_coordinator = data.aggregation_coordinator();
-    return true;
-  }
-};
-
-}  // namespace mojo
-
-#endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_ATTRIBUTION_REPORTING_MOJOM_TRAITS_H_
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index 5eed991..28b10b3 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -253,7 +253,7 @@
     ":script_type_mojo_bindings",
     ":web_feature_mojo_bindings",
     "//cc/mojom",
-    "//components/aggregation_service:mojom",
+    "//components/attribution_reporting:registration_mojom",
     "//components/payments/mojom",
     "//components/schema_org/common:mojom",
     "//components/services/filesystem/public/mojom",
@@ -532,64 +532,6 @@
     {
       types = [
         {
-          mojom = "blink.mojom.AttributionDebugKey"
-          cpp = "uint64_t"
-        },
-        {
-          mojom = "blink.mojom.AttributionTriggerDedupKey"
-          cpp = "uint64_t"
-        },
-        {
-          mojom = "blink.mojom.AttributionSuitableOrigin"
-          cpp = "::attribution_reporting::SuitableOrigin"
-
-          # Avoid expensive copies by forcing Mojo methods to take the type by
-          # value, not const ref
-          move_only = true
-        },
-        {
-          mojom = "blink.mojom.AttributionFilterData"
-          cpp = "::attribution_reporting::FilterData"
-        },
-        {
-          mojom = "blink.mojom.AttributionAggregationKeys"
-          cpp = "::attribution_reporting::AggregationKeys"
-        },
-        {
-          mojom = "blink.mojom.AttributionSourceData"
-          cpp = "::attribution_reporting::SourceRegistration"
-
-          # Avoid expensive copies by forcing Mojo methods to take the type by
-          # value, not const ref
-          move_only = true
-        },
-        {
-          mojom = "blink.mojom.AttributionFilters"
-          cpp = "::attribution_reporting::Filters"
-        },
-        {
-          mojom = "blink.mojom.EventTriggerData"
-          cpp = "::attribution_reporting::EventTriggerData"
-        },
-        {
-          mojom = "blink.mojom.AttributionAggregatableTriggerData"
-          cpp = "::attribution_reporting::AggregatableTriggerData"
-        },
-        {
-          mojom = "blink.mojom.AttributionTriggerData"
-          cpp = "::attribution_reporting::TriggerRegistration"
-
-          # Avoid expensive copies by forcing Mojo methods to take the type by
-          # value, not const ref
-          move_only = true
-        },
-      ]
-      traits_headers = [ "//third_party/blink/public/common/attribution_reporting/mojom_traits.h" ]
-      traits_public_deps = [ "//components/attribution_reporting" ]
-    },
-    {
-      types = [
-        {
           mojom = "blink.mojom.Opaque"
           cpp = "::blink::FencedFrame::Opaque"
         },
diff --git a/third_party/blink/public/mojom/conversions/attribution_data_host.mojom b/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
index 90b0a8cf..b215b2a 100644
--- a/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
+++ b/third_party/blink/public/mojom/conversions/attribution_data_host.mojom
@@ -4,160 +4,7 @@
 
 module blink.mojom;
 
-import "components/aggregation_service/aggregation_service.mojom";
-import "mojo/public/mojom/base/int128.mojom";
-import "mojo/public/mojom/base/time.mojom";
-import "url/mojom/origin.mojom";
-
-struct AttributionDebugKey {
-  uint64 value;
-};
-
-// Encapsulates a potentially trustworthy origin. Equivalent to
-// attribution_reporting::SuitableOrigin.
-struct AttributionSuitableOrigin {
-  url.mojom.Origin origin;
-};
-
-// Filter data for selectively matching attribution sources and triggers.
-// See https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#optional-attribution-filters
-// for details.
-struct AttributionFilterData {
-  // Map of filter name to a possibly empty set of values. Must not contain a
-  // `source_type` key.
-  map<string, array<string>> filter_values;
-};
-
-// Filters for selectively matching attribution sources and triggers.
-// See https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#optional-attribution-filters
-// for details.
-struct AttributionFilters {
-  // Map of filter name to a possibly empty set of values. May contain a
-  // `source_type` key.
-  map<string, array<string>> filter_values;
-};
-
-// See https://github.com/WICG/attribution-reporting-api/blob/main/AGGREGATE.md#attribution-source-registration
-// for details.
-struct AttributionAggregationKeys {
-  map<string, mojo_base.mojom.Uint128> keys;
-};
-
-// Struct containing the trigger-side aggregatable data.
-struct AttributionAggregatableTriggerData {
-  mojo_base.mojom.Uint128 key_piece;
-  array<string> source_keys;
-  AttributionFilters filters;
-  AttributionFilters not_filters;
-};
-
-struct AttributionSourceData {
-  // Target site where this source will be triggered.
-  //
-  // For sources associated with a navigation, the destination site must be
-  // same-site with the final committed url of the navigation. If they are not
-  // same-site, this source will be ignored by the browser.
-  AttributionSuitableOrigin destination;
-
-  // Data that will be sent in attribution reports to identify this source.
-  uint64 source_event_id = 0;
-
-  // Specifies how long this source is eligible for attribution.
-  mojo_base.mojom.TimeDelta? expiry;
-
-  // Optionally specifies how long after source registration an event-level report
-  // can be generated with this source.
-  mojo_base.mojom.TimeDelta? event_report_window;
-
-  // Optionally specifies how long after source registration an aggregatable
-  // report can be generated with this source.
-  mojo_base.mojom.TimeDelta? aggregatable_report_window;
-
-  // Priority for this source.
-  int64 priority = 0;
-
-  // A key that is propagated through the Attribution Reporting API for
-  // debugging purposes.
-  AttributionDebugKey? debug_key;
-
-  AttributionFilterData filter_data;
-
-  AttributionAggregationKeys aggregation_keys;
-
-  // Specifies whether to enable verbose debug reporting.
-  bool debug_reporting = false;
-};
-
-// Deduplication key set by a reporting origin which prevents duplicate triggers
-// from generating multiple attribution reports for a given source.
-struct AttributionTriggerDedupKey {
-  // Arbitrary value for deduplication set by the reporting origin.
-  uint64 value;
-};
-
-// Mojo representation of the trigger configuration provided by a reporting
-// origin. This data is provided arbitrarily by certain subresources on a
-// page which invoke Attribution Reporting.
-struct EventTriggerData {
-  // Value which identifies this trigger in attribution reports, determined by
-  // reporting origin.
-  uint64 data = 0;
-
-  // Priority of this trigger relative to other attributed triggers for a
-  // source. Reports created with high priority triggers will be reported over
-  // lower priority ones.
-  int64 priority = 0;
-
-  // Key which allows deduplication against existing attributions for the same
-  // source.
-  AttributionTriggerDedupKey? dedup_key;
-
-  // If non-empty, this trigger will be ignored unless the attributed source's
-  // filter data matches.
-  AttributionFilters filters;
-
-  // If non-empty, this trigger will be ignored unless the attributed source's
-  // filter data does *NOT* match.
-  AttributionFilters not_filters;
-};
-
-// Represents a request from a reporting origin to trigger attribution on a
-// given site. See:
-// https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#triggering-attribution
-struct AttributionTriggerData {
-  // List of all event trigger data objects declared by the event trigger
-  // header. This data is arbitrarily set by the reporting origin.
-  array<EventTriggerData> event_triggers;
-
-  // If non-empty, this trigger will be ignored unless the attributed source's
-  // filter data matches.
-  AttributionFilters filters;
-
-  // If non-empty, this trigger will be ignored unless the attributed source's
-  // filter data does *NOT* match.
-  AttributionFilters not_filters;
-
-  // List of all aggregatable trigger data objects declared by the trigger
-  // header.
-  array<AttributionAggregatableTriggerData> aggregatable_trigger_data;
-
-  // A map of aggregation key identifier and the corresponding value.
-  map<string, uint32> aggregatable_values;
-
-  // A key that is propagated through the Attribution Reporting API for
-  // debugging purposes.
-  AttributionDebugKey? debug_key;
-
-  // Key which allows deduplication against existing aggregatable reports for
-  // the same source.
-  AttributionTriggerDedupKey? aggregatable_dedup_key;
-
-  // Specifies whether to enable verbose debug reporting.
-  bool debug_reporting = false;
-
-  // Specifies the deployment option for the aggregation service.
-  aggregation_service.mojom.AggregationCoordinator aggregation_coordinator;
-};
+import "components/attribution_reporting/registration.mojom";
 
 // Indicates which registrations an AttributionDataHost permits.
 enum AttributionRegistrationType {
@@ -172,11 +19,11 @@
 interface AttributionDataHost {
   // Called when data from the renderer is available for a given attributionsrc
   // request.
-  SourceDataAvailable(AttributionSuitableOrigin reporting_origin,
-                      AttributionSourceData data);
+  SourceDataAvailable(attribution_reporting.mojom.SuitableOrigin reporting_origin,
+                      attribution_reporting.mojom.SourceRegistration data);
 
   // Called when trigger data from the renderer is available for a given
   // attributionsrc request.
-  TriggerDataAvailable(AttributionSuitableOrigin reporting_origin,
-                       AttributionTriggerData data);
+  TriggerDataAvailable(attribution_reporting.mojom.SuitableOrigin reporting_origin,
+                       attribution_reporting.mojom.TriggerRegistration data);
 };
diff --git a/third_party/blink/public/mojom/conversions/attribution_reporting.mojom b/third_party/blink/public/mojom/conversions/attribution_reporting.mojom
index 8f9080e..3543f64 100644
--- a/third_party/blink/public/mojom/conversions/attribution_reporting.mojom
+++ b/third_party/blink/public/mojom/conversions/attribution_reporting.mojom
@@ -11,10 +11,3 @@
   kWindowOpen = 1,
   kContextMenu = 2,
 };
-
-// Indicates whether OS-level attribution is enabled.
-// See https://github.com/WICG/attribution-reporting-api/blob/main/app_to_web.md.
-enum AttributionOsSupport {
-  kDisabled,
-  kEnabled,
-};
diff --git a/third_party/blink/public/platform/DEPS b/third_party/blink/public/platform/DEPS
index df1c7750..b1e9c980 100644
--- a/third_party/blink/public/platform/DEPS
+++ b/third_party/blink/public/platform/DEPS
@@ -26,6 +26,7 @@
     "+base/trace_event",
     "+build/build_config.h",
     "+cc",
+    "+components/attribution_reporting",
     "+components/viz/common",
     "+media/base/audio_capturer_source.h",
     "+media/base/audio_latency.h",
diff --git a/third_party/blink/public/platform/platform.h b/third_party/blink/public/platform/platform.h
index eb0652f1..a2a4996 100644
--- a/third_party/blink/public/platform/platform.h
+++ b/third_party/blink/public/platform/platform.h
@@ -44,6 +44,7 @@
 #include "build/build_config.h"
 #include "cc/tiles/raster_dark_mode_filter.h"
 #include "cc/trees/raster_context_provider_wrapper.h"
+#include "components/attribution_reporting/os_support.mojom-shared.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "media/base/audio_capturer_source.h"
 #include "media/base/audio_latency.h"
@@ -51,7 +52,6 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/security/protocol_handler_security_level.h"
 #include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom-shared.h"
 #include "third_party/blink/public/platform/audio/web_audio_device_source_type.h"
 #include "third_party/blink/public/platform/cross_variant_mojo_util.h"
 #include "third_party/blink/public/platform/url_loader_throttle_provider.h"
@@ -793,9 +793,9 @@
   // Returns whether OS-level support is enabled for Attribution Reporting API.
   // See
   // https://github.com/WICG/attribution-reporting-api/blob/main/app_to_web.md.
-  virtual blink::mojom::AttributionOsSupport
+  virtual attribution_reporting::mojom::OsSupport
   GetOsSupportForAttributionReporting() {
-    return blink::mojom::AttributionOsSupport::kDisabled;
+    return attribution_reporting::mojom::OsSupport::kDisabled;
   }
 
  private:
diff --git a/third_party/blink/renderer/core/css/css_value_keywords.json5 b/third_party/blink/renderer/core/css/css_value_keywords.json5
index 0219f15..7428dda 100644
--- a/third_party/blink/renderer/core/css/css_value_keywords.json5
+++ b/third_party/blink/renderer/core/css/css_value_keywords.json5
@@ -1054,6 +1054,7 @@
     "minimal-ui",
     "browser",
     "window-controls-overlay",
+    "tabbed",
 
     // position
     "sticky",
diff --git a/third_party/blink/renderer/core/css/media_query_evaluator.cc b/third_party/blink/renderer/core/css/media_query_evaluator.cc
index ac70358f..21246db 100644
--- a/third_party/blink/renderer/core/css/media_query_evaluator.cc
+++ b/third_party/blink/renderer/core/css/media_query_evaluator.cc
@@ -376,6 +376,8 @@
       return mode == blink::mojom::DisplayMode::kWindowControlsOverlay;
     case CSSValueID::kBorderless:
       return mode == blink::mojom::DisplayMode::kBorderless;
+    case CSSValueID::kTabbed:
+      return mode == blink::mojom::DisplayMode::kTabbed;
     default:
       NOTREACHED();
       return false;
diff --git a/third_party/blink/renderer/core/css/media_query_evaluator_test.cc b/third_party/blink/renderer/core/css/media_query_evaluator_test.cc
index 0c63d91..ac072b9 100644
--- a/third_party/blink/renderer/core/css/media_query_evaluator_test.cc
+++ b/third_party/blink/renderer/core/css/media_query_evaluator_test.cc
@@ -97,6 +97,7 @@
     {"(display-mode: @browser)", false},
     {"(display-mode: 'browser')", false},
     {"(display-mode: @junk browser)", false},
+    {"(display-mode: tabbed)", false},
     {"(max-device-aspect-ratio: 4294967295/1)", true},
     {"(min-device-aspect-ratio: 1/4294967296)", true},
     {"(max-device-aspect-ratio: 0.5)", false},
diff --git a/third_party/blink/renderer/core/css/media_query_exp.cc b/third_party/blink/renderer/core/css/media_query_exp.cc
index a55c5887..8d0751a 100644
--- a/third_party/blink/renderer/core/css/media_query_exp.cc
+++ b/third_party/blink/renderer/core/css/media_query_exp.cc
@@ -59,7 +59,7 @@
            ident == CSSValueID::kStandalone ||
            ident == CSSValueID::kMinimalUi ||
            ident == CSSValueID::kWindowControlsOverlay ||
-           ident == CSSValueID::kBrowser;
+           ident == CSSValueID::kBrowser || ident == CSSValueID::kTabbed;
   }
 
   if (media_feature == media_feature_names::kOrientationMediaFeature)
diff --git a/third_party/blink/renderer/core/css/parser/font_variant_alternates_parser.cc b/third_party/blink/renderer/core/css/parser/font_variant_alternates_parser.cc
index 7c55358..69bd35c 100644
--- a/third_party/blink/renderer/core/css/parser/font_variant_alternates_parser.cc
+++ b/third_party/blink/renderer/core/css/parser/font_variant_alternates_parser.cc
@@ -67,8 +67,11 @@
   CSSParserTokenRange inner = css_parsing_utils::ConsumeFunction(range_copy);
   CSSValueList* aliases =
       ConsumeCommaSeparatedList(ConsumeCustomIdent, inner, context);
-  if (!inner.AtEnd())
+  // At least one argument is required:
+  // https://drafts.csswg.org/css-fonts-4/#font-variant-alternates-prop
+  if (!aliases || !inner.AtEnd()) {
     return false;
+  }
   if (aliases->length() > 1 && !multiple_idents_allowed) {
     return false;
   }
diff --git a/third_party/blink/renderer/core/exported/web_media_player_impl_unittest.cc b/third_party/blink/renderer/core/exported/web_media_player_impl_unittest.cc
index 7d4b03c2..c2984e27 100644
--- a/third_party/blink/renderer/core/exported/web_media_player_impl_unittest.cc
+++ b/third_party/blink/renderer/core/exported/web_media_player_impl_unittest.cc
@@ -2157,6 +2157,48 @@
   EXPECT_CALL(*surface_layer_bridge_ptr_, ClearObserver());
 }
 
+TEST_F(WebMediaPlayerImplTest, DisplayTypeChange) {
+  InitializeWebMediaPlayerImpl();
+
+  scoped_refptr<cc::Layer> layer = cc::Layer::Create();
+
+  EXPECT_CALL(*surface_layer_bridge_ptr_, CreateSurfaceLayer());
+  EXPECT_CALL(*surface_layer_bridge_ptr_, GetSurfaceId())
+      .WillRepeatedly(ReturnRef(surface_id_));
+  EXPECT_CALL(*compositor_, EnableSubmission(_, _, _));
+  EXPECT_CALL(*surface_layer_bridge_ptr_, SetContentsOpaque(false));
+  EXPECT_CALL(*surface_layer_bridge_ptr_, GetCcLayer())
+      .WillRepeatedly(Return(layer.get()));
+
+  media::PipelineMetadata metadata;
+  metadata.has_video = true;
+  OnMetadata(metadata);
+
+  // When entering PIP mode the CC layer is set to null so we are not
+  // compositing the video in the original window.
+  EXPECT_CALL(client_, IsInAutoPIP()).WillOnce(Return(false));
+  EXPECT_CALL(client_, SetCcLayer(nullptr));
+  wmpi_->OnDisplayTypeChanged(DisplayType::kPictureInPicture);
+
+  // When switching back to the inline mode the CC layer is set back to the
+  // bridge CC layer.
+  EXPECT_CALL(client_, SetCcLayer(testing::NotNull()));
+  wmpi_->OnDisplayTypeChanged(DisplayType::kInline);
+
+  // When in persistent state (e.g. auto-pip), video is not playing in the
+  // regular Picture-in-Picture mode. Don't set the CC layer to null.
+  EXPECT_CALL(client_, IsInAutoPIP()).WillOnce(Return(true));
+  EXPECT_CALL(client_, SetCcLayer(_)).Times(0);
+  wmpi_->OnDisplayTypeChanged(DisplayType::kPictureInPicture);
+
+  // When switching back to fullscreen mode the CC layer is set back to the
+  // bridge CC layer.
+  EXPECT_CALL(client_, SetCcLayer(testing::NotNull()));
+  wmpi_->OnDisplayTypeChanged(DisplayType::kFullscreen);
+
+  EXPECT_CALL(*surface_layer_bridge_ptr_, ClearObserver());
+}
+
 TEST_F(WebMediaPlayerImplTest, RegisterFrameSinkHierarchy) {
   InitializeWebMediaPlayerImpl();
   media::PipelineMetadata metadata;
diff --git a/third_party/blink/renderer/core/frame/attribution_src_loader.cc b/third_party/blink/renderer/core/frame/attribution_src_loader.cc
index 1a022a3a..c2a896c63 100644
--- a/third_party/blink/renderer/core/frame/attribution_src_loader.cc
+++ b/third_party/blink/renderer/core/frame/attribution_src_loader.cc
@@ -13,6 +13,8 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
 #include "base/types/expected.h"
+#include "components/attribution_reporting/os_registration.h"
+#include "components/attribution_reporting/os_support.mojom-shared.h"
 #include "components/attribution_reporting/source_registration.h"
 #include "components/attribution_reporting/source_registration_error.mojom-shared.h"
 #include "components/attribution_reporting/suitable_origin.h"
@@ -26,7 +28,6 @@
 #include "third_party/blink/public/common/navigation/impression.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/conversions/attribution_data_host.mojom-blink.h"
-#include "third_party/blink/public/mojom/conversions/attribution_reporting.mojom-blink.h"
 #include "third_party/blink/public/mojom/conversions/conversions.mojom-blink.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom-blink.h"
@@ -428,19 +429,13 @@
 }
 
 AtomicString AttributionSrcLoader::GetSupportHeader() const {
-  static constexpr const char kAttributionSupportWeb[] = "web";
-  static constexpr const char kAttributionSupportWebAndOs[] = "web, os";
-
-  if (HasOsSupport()) {
-    return kAttributionSupportWebAndOs;
-  } else {
-    return kAttributionSupportWeb;
-  }
+  return AtomicString(String::FromUTF8(attribution_reporting::GetSupportHeader(
+      Platform::Current()->GetOsSupportForAttributionReporting())));
 }
 
 bool AttributionSrcLoader::HasOsSupport() const {
   return Platform::Current()->GetOsSupportForAttributionReporting() ==
-         mojom::blink::AttributionOsSupport::kEnabled;
+         attribution_reporting::mojom::OsSupport::kEnabled;
 }
 
 bool AttributionSrcLoader::MaybeRegisterAttributionHeaders(
diff --git a/third_party/blink/renderer/modules/xr/xr_system.cc b/third_party/blink/renderer/modules/xr/xr_system.cc
index 53914025..4d55dab 100644
--- a/third_party/blink/renderer/modules/xr/xr_system.cc
+++ b/third_party/blink/renderer/modules/xr/xr_system.cc
@@ -40,6 +40,7 @@
 #include "third_party/blink/renderer/modules/xr/xr_frame_provider.h"
 #include "third_party/blink/renderer/modules/xr/xr_session.h"
 #include "third_party/blink/renderer/modules/xr/xr_session_viewport_scaler.h"
+#include "third_party/blink/renderer/modules/xr/xr_utils.h"
 #include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -168,96 +169,6 @@
   return result;
 }
 
-// Converts the given string to an XRSessionFeature. If the string is
-// unrecognized, returns nullopt. Based on the spec:
-// https://immersive-web.github.io/webxr/#feature-name
-absl::optional<device::mojom::XRSessionFeature> StringToXRSessionFeature(
-    const ExecutionContext* context,
-    const String& feature_string) {
-  if (feature_string == "viewer") {
-    return device::mojom::XRSessionFeature::REF_SPACE_VIEWER;
-  } else if (feature_string == "local") {
-    return device::mojom::XRSessionFeature::REF_SPACE_LOCAL;
-  } else if (feature_string == "local-floor") {
-    return device::mojom::XRSessionFeature::REF_SPACE_LOCAL_FLOOR;
-  } else if (feature_string == "bounded-floor") {
-    return device::mojom::XRSessionFeature::REF_SPACE_BOUNDED_FLOOR;
-  } else if (feature_string == "unbounded") {
-    return device::mojom::XRSessionFeature::REF_SPACE_UNBOUNDED;
-  } else if (RuntimeEnabledFeatures::WebXRHitTestEnabled(context) &&
-             feature_string == "hit-test") {
-    return device::mojom::XRSessionFeature::HIT_TEST;
-  } else if (RuntimeEnabledFeatures::WebXRAnchorsEnabled(context) &&
-             feature_string == "anchors") {
-    return device::mojom::XRSessionFeature::ANCHORS;
-  } else if (feature_string == "dom-overlay") {
-    return device::mojom::XRSessionFeature::DOM_OVERLAY;
-  } else if (RuntimeEnabledFeatures::WebXRLightEstimationEnabled(context) &&
-             feature_string == "light-estimation") {
-    return device::mojom::XRSessionFeature::LIGHT_ESTIMATION;
-  } else if (RuntimeEnabledFeatures::WebXRCameraAccessEnabled(context) &&
-             feature_string == "camera-access") {
-    return device::mojom::XRSessionFeature::CAMERA_ACCESS;
-  } else if (RuntimeEnabledFeatures::WebXRPlaneDetectionEnabled(context) &&
-             feature_string == "plane-detection") {
-    return device::mojom::XRSessionFeature::PLANE_DETECTION;
-  } else if (RuntimeEnabledFeatures::WebXRDepthEnabled(context) &&
-             feature_string == "depth-sensing") {
-    return device::mojom::XRSessionFeature::DEPTH;
-  } else if (RuntimeEnabledFeatures::WebXRImageTrackingEnabled(context) &&
-             feature_string == "image-tracking") {
-    return device::mojom::XRSessionFeature::IMAGE_TRACKING;
-  } else if (RuntimeEnabledFeatures::WebXRHandInputEnabled(context) &&
-             feature_string == "hand-tracking") {
-    return device::mojom::XRSessionFeature::HAND_INPUT;
-  } else if (feature_string == "secondary-views") {
-    return device::mojom::XRSessionFeature::SECONDARY_VIEWS;
-  } else if (RuntimeEnabledFeatures::WebXRLayersEnabled(context) &&
-             feature_string == "layers") {
-    return device::mojom::XRSessionFeature::LAYERS;
-  }
-
-  return absl::nullopt;
-}
-
-// Inverse of |StringToXRSessionFeature()|, used for logging to console.
-String XRSessionFeatureToString(device::mojom::XRSessionFeature feature) {
-  switch (feature) {
-    case device::mojom::XRSessionFeature::REF_SPACE_VIEWER:
-      return "viewer";
-    case device::mojom::XRSessionFeature::REF_SPACE_LOCAL:
-      return "local";
-    case device::mojom::XRSessionFeature::REF_SPACE_LOCAL_FLOOR:
-      return "local-floor";
-    case device::mojom::XRSessionFeature::REF_SPACE_BOUNDED_FLOOR:
-      return "bounded-floor";
-    case device::mojom::XRSessionFeature::REF_SPACE_UNBOUNDED:
-      return "unbounded";
-    case device::mojom::XRSessionFeature::DOM_OVERLAY:
-      return "dom-overlay";
-    case device::mojom::XRSessionFeature::HIT_TEST:
-      return "hit-test";
-    case device::mojom::XRSessionFeature::LIGHT_ESTIMATION:
-      return "light-estimation";
-    case device::mojom::XRSessionFeature::ANCHORS:
-      return "anchors";
-    case device::mojom::XRSessionFeature::CAMERA_ACCESS:
-      return "camera-access";
-    case device::mojom::XRSessionFeature::PLANE_DETECTION:
-      return "plane-detection";
-    case device::mojom::XRSessionFeature::DEPTH:
-      return "depth-sensing";
-    case device::mojom::XRSessionFeature::IMAGE_TRACKING:
-      return "image-tracking";
-    case device::mojom::XRSessionFeature::HAND_INPUT:
-      return "hand-tracking";
-    case device::mojom::XRSessionFeature::SECONDARY_VIEWS:
-      return "secondary-views";
-    case device::mojom::XRSessionFeature::LAYERS:
-      return "layers";
-  }
-}
-
 bool IsFeatureValidForMode(device::mojom::XRSessionFeature feature,
                            device::mojom::blink::XRSessionMode mode,
                            XRSessionInit* session_init,
diff --git a/third_party/blink/renderer/modules/xr/xr_utils.cc b/third_party/blink/renderer/modules/xr/xr_utils.cc
index 5d9441ee..3b61d55 100644
--- a/third_party/blink/renderer/modules/xr/xr_utils.cc
+++ b/third_party/blink/renderer/modules/xr/xr_utils.cc
@@ -178,4 +178,92 @@
   }
 }
 
+absl::optional<device::mojom::XRSessionFeature> StringToXRSessionFeature(
+    const ExecutionContext* context,
+    const String& feature_string) {
+  if (feature_string == "viewer") {
+    return device::mojom::XRSessionFeature::REF_SPACE_VIEWER;
+  } else if (feature_string == "local") {
+    return device::mojom::XRSessionFeature::REF_SPACE_LOCAL;
+  } else if (feature_string == "local-floor") {
+    return device::mojom::XRSessionFeature::REF_SPACE_LOCAL_FLOOR;
+  } else if (feature_string == "bounded-floor") {
+    return device::mojom::XRSessionFeature::REF_SPACE_BOUNDED_FLOOR;
+  } else if (feature_string == "unbounded") {
+    return device::mojom::XRSessionFeature::REF_SPACE_UNBOUNDED;
+  } else if (RuntimeEnabledFeatures::WebXRHitTestEnabled(context) &&
+             feature_string == "hit-test") {
+    return device::mojom::XRSessionFeature::HIT_TEST;
+  } else if (RuntimeEnabledFeatures::WebXRAnchorsEnabled(context) &&
+             feature_string == "anchors") {
+    return device::mojom::XRSessionFeature::ANCHORS;
+  } else if (feature_string == "dom-overlay") {
+    return device::mojom::XRSessionFeature::DOM_OVERLAY;
+  } else if (RuntimeEnabledFeatures::WebXRLightEstimationEnabled(context) &&
+             feature_string == "light-estimation") {
+    return device::mojom::XRSessionFeature::LIGHT_ESTIMATION;
+  } else if (RuntimeEnabledFeatures::WebXRCameraAccessEnabled(context) &&
+             feature_string == "camera-access") {
+    return device::mojom::XRSessionFeature::CAMERA_ACCESS;
+  } else if (RuntimeEnabledFeatures::WebXRPlaneDetectionEnabled(context) &&
+             feature_string == "plane-detection") {
+    return device::mojom::XRSessionFeature::PLANE_DETECTION;
+  } else if (RuntimeEnabledFeatures::WebXRDepthEnabled(context) &&
+             feature_string == "depth-sensing") {
+    return device::mojom::XRSessionFeature::DEPTH;
+  } else if (RuntimeEnabledFeatures::WebXRImageTrackingEnabled(context) &&
+             feature_string == "image-tracking") {
+    return device::mojom::XRSessionFeature::IMAGE_TRACKING;
+  } else if (RuntimeEnabledFeatures::WebXRHandInputEnabled(context) &&
+             feature_string == "hand-tracking") {
+    return device::mojom::XRSessionFeature::HAND_INPUT;
+  } else if (feature_string == "secondary-views") {
+    return device::mojom::XRSessionFeature::SECONDARY_VIEWS;
+  } else if (RuntimeEnabledFeatures::WebXRLayersEnabled(context) &&
+             feature_string == "layers") {
+    return device::mojom::XRSessionFeature::LAYERS;
+  }
+
+  return absl::nullopt;
+}
+
+String XRSessionFeatureToString(device::mojom::XRSessionFeature feature) {
+  switch (feature) {
+    case device::mojom::XRSessionFeature::REF_SPACE_VIEWER:
+      return "viewer";
+    case device::mojom::XRSessionFeature::REF_SPACE_LOCAL:
+      return "local";
+    case device::mojom::XRSessionFeature::REF_SPACE_LOCAL_FLOOR:
+      return "local-floor";
+    case device::mojom::XRSessionFeature::REF_SPACE_BOUNDED_FLOOR:
+      return "bounded-floor";
+    case device::mojom::XRSessionFeature::REF_SPACE_UNBOUNDED:
+      return "unbounded";
+    case device::mojom::XRSessionFeature::DOM_OVERLAY:
+      return "dom-overlay";
+    case device::mojom::XRSessionFeature::HIT_TEST:
+      return "hit-test";
+    case device::mojom::XRSessionFeature::LIGHT_ESTIMATION:
+      return "light-estimation";
+    case device::mojom::XRSessionFeature::ANCHORS:
+      return "anchors";
+    case device::mojom::XRSessionFeature::CAMERA_ACCESS:
+      return "camera-access";
+    case device::mojom::XRSessionFeature::PLANE_DETECTION:
+      return "plane-detection";
+    case device::mojom::XRSessionFeature::DEPTH:
+      return "depth-sensing";
+    case device::mojom::XRSessionFeature::IMAGE_TRACKING:
+      return "image-tracking";
+    case device::mojom::XRSessionFeature::HAND_INPUT:
+      return "hand-tracking";
+    case device::mojom::XRSessionFeature::SECONDARY_VIEWS:
+      return "secondary-views";
+    case device::mojom::XRSessionFeature::LAYERS:
+      return "layers";
+  }
+
+  return "";
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_utils.h b/third_party/blink/renderer/modules/xr/xr_utils.h
index b9b20ed..a8ed873 100644
--- a/third_party/blink/renderer/modules/xr/xr_utils.h
+++ b/third_party/blink/renderer/modules/xr/xr_utils.h
@@ -20,6 +20,7 @@
 namespace blink {
 
 class DOMPointReadOnly;
+class ExecutionContext;
 class WebGLRenderingContextBase;
 
 DOMFloat32Array* transformationMatrixToDOMFloat32Array(const gfx::Transform&);
@@ -51,6 +52,16 @@
     const String& hand_joint_string);
 String MojomHandJointToString(device::mojom::blink::XRHandJoint hand_joint);
 
+// Converts the given string to an XRSessionFeature. If the string is
+// unrecognized, returns nullopt. Based on the spec:
+// https://immersive-web.github.io/webxr/#feature-name
+absl::optional<device::mojom::XRSessionFeature> StringToXRSessionFeature(
+    const ExecutionContext* context,
+    const String& feature_string);
+
+// Inverse of |StringToXRSessionFeature()|, used for logging to console and for
+// |XRSession::enabledFeatures|.
+String XRSessionFeatureToString(device::mojom::XRSessionFeature feature);
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_UTILS_H_
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
index 24ae715..2c45d01 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc
@@ -491,19 +491,16 @@
 
   void WillDraw() override { WillDrawInternal(true); }
 
-  void RasterRecord(sk_sp<cc::PaintRecord> last_recording,
-                    bool preserve_recording) override {
+  void RasterRecord(sk_sp<cc::PaintRecord> last_recording) override {
     if (!use_oop_rasterization_) {
-      CanvasResourceProvider::RasterRecord(std::move(last_recording),
-                                           preserve_recording);
+      CanvasResourceProvider::RasterRecord(std::move(last_recording));
       return;
     }
     WillDrawInternal(true);
     const bool needs_clear = !is_cleared_;
     is_cleared_ = true;
     RasterRecordOOP(last_recording, needs_clear,
-                    resource()->GetOrCreateGpuMailbox(kUnverifiedSyncToken),
-                    preserve_recording);
+                    resource()->GetOrCreateGpuMailbox(kUnverifiedSyncToken));
   }
 
   bool ShouldReplaceTargetBuffer(
@@ -834,17 +831,15 @@
         GetSkImageInfo().refColorSpace(), &props);
   }
 
-  void RasterRecord(sk_sp<cc::PaintRecord> last_recording,
-                    bool preserve_recording) override {
+  void RasterRecord(sk_sp<cc::PaintRecord> last_recording) override {
     TRACE_EVENT0("blink", "CanvasResourceProviderSwapChain::RasterRecord");
     if (!use_oop_rasterization_) {
-      CanvasResourceProvider::RasterRecord(std::move(last_recording),
-                                           preserve_recording);
+      CanvasResourceProvider::RasterRecord(std::move(last_recording));
       return;
     }
     WillDraw();
     RasterRecordOOP(last_recording, initial_needs_clear_,
-                    resource_->GetBackBufferMailbox(), preserve_recording);
+                    resource_->GetBackBufferMailbox());
     initial_needs_clear_ = false;
   }
 
@@ -1405,7 +1400,7 @@
     return nullptr;
   clear_frame_ = false;
   sk_sp<cc::PaintRecord> last_recording = recorder_.finishRecordingAsPicture();
-  RasterRecord(last_recording, preserve_recording);
+  RasterRecord(last_recording);
   total_pinned_image_bytes_ = 0;
   cc::PaintCanvas* canvas = recorder_.beginRecording(Size());
   if (restore_clip_stack_callback_)
@@ -1415,8 +1410,8 @@
   return last_recording;
 }
 
-void CanvasResourceProvider::RasterRecord(sk_sp<cc::PaintRecord> last_recording,
-                                          bool) {
+void CanvasResourceProvider::RasterRecord(
+    sk_sp<cc::PaintRecord> last_recording) {
   EnsureSkiaCanvas();
   skia_canvas_->drawPicture(std::move(last_recording));
   GetSkSurface()->flushAndSubmit();
@@ -1425,8 +1420,7 @@
 void CanvasResourceProvider::RasterRecordOOP(
     sk_sp<cc::PaintRecord> last_recording,
     bool needs_clear,
-    gpu::Mailbox mailbox,
-    bool preserve_recording) {
+    gpu::Mailbox mailbox) {
   if (IsGpuContextLost())
     return;
   gpu::raster::RasterInterface* ri = RasterInterface();
@@ -1461,8 +1455,7 @@
 
   ri->RasterCHROMIUM(list.get(), GetOrCreateCanvasImageProvider(), size,
                      full_raster_rect, playback_rect, post_translate,
-                     post_scale, false /* requires_clear */, &max_op_size_hint,
-                     preserve_recording);
+                     post_scale, false /* requires_clear */, &max_op_size_hint);
 
   ri->EndRasterCHROMIUM();
 }
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
index da47aad..e898397f 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
@@ -319,11 +319,10 @@
   // decodes/uploads in the cache is invalidated only when the canvas contents
   // change.
   cc::PaintImage MakeImageSnapshot();
-  virtual void RasterRecord(sk_sp<cc::PaintRecord>, bool preserve_recording);
+  virtual void RasterRecord(sk_sp<cc::PaintRecord>);
   void RasterRecordOOP(sk_sp<cc::PaintRecord> last_recording,
                        bool needs_clear,
-                       gpu::Mailbox mailbox,
-                       bool preserve_recording);
+                       gpu::Mailbox mailbox);
 
   CanvasImageProvider* GetOrCreateCanvasImageProvider();
   void TearDownSkSurface();
diff --git a/third_party/blink/renderer/platform/media/video_frame_compositor.cc b/third_party/blink/renderer/platform/media/video_frame_compositor.cc
index fdbf31d..582b4c4 100644
--- a/third_party/blink/renderer/platform/media/video_frame_compositor.cc
+++ b/third_party/blink/renderer/platform/media/video_frame_compositor.cc
@@ -395,7 +395,9 @@
 
 void VideoFrameCompositor::SetForceSubmit(bool force_submit) {
   DCHECK(task_runner_->BelongsToCurrentThread());
-  submitter_->SetForceSubmit(force_submit);
+  // The `submitter_` can be null in tests.
+  if (submitter_)
+    submitter_->SetForceSubmit(force_submit);
 }
 
 base::TimeDelta VideoFrameCompositor::GetLastIntervalWithoutLock()
diff --git a/third_party/blink/renderer/platform/media/web_media_player_impl.cc b/third_party/blink/renderer/platform/media/web_media_player_impl.cc
index a50a427..8ee5b92 100644
--- a/third_party/blink/renderer/platform/media/web_media_player_impl.cc
+++ b/third_party/blink/renderer/platform/media/web_media_player_impl.cc
@@ -799,33 +799,47 @@
 }
 
 void WebMediaPlayerImpl::OnDisplayTypeChanged(DisplayType display_type) {
+  DVLOG(2) << __func__ << ": display_type=" << static_cast<int>(display_type);
+
   if (surface_layer_for_video_enabled_) {
     vfc_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(&VideoFrameCompositor::SetForceSubmit,
                        base::Unretained(compositor_.get()),
                        display_type == DisplayType::kPictureInPicture));
-  }
 
-  if (!watch_time_reporter_)
-    return;
-
-  switch (display_type) {
-    case DisplayType::kInline:
-      watch_time_reporter_->OnDisplayTypeInline();
-      break;
-    case DisplayType::kFullscreen:
-      watch_time_reporter_->OnDisplayTypeFullscreen();
-      break;
-    case DisplayType::kPictureInPicture:
-      watch_time_reporter_->OnDisplayTypePictureInPicture();
+    if (display_type == DisplayType::kPictureInPicture) {
+      // In picture in picture mode, since the video is compositing in the PIP
+      // windows, stop composting it in the original window. One exception is
+      // for persistent video, where can happen in auto-pip mode, where the
+      // video is not playing in the regular Picture-in-Picture mode.
+      if (!client_->IsInAutoPIP()) {
+        client_->SetCcLayer(nullptr);
+      }
 
       // Resumes playback if it was paused when hidden.
       if (paused_when_hidden_) {
         paused_when_hidden_ = false;
         client_->ResumePlayback();
       }
-      break;
+    } else {
+      // Resume compositing in the original window if not already doing so.
+      client_->SetCcLayer(bridge_->GetCcLayer());
+    }
+  }
+
+  if (watch_time_reporter_) {
+    switch (display_type) {
+      case DisplayType::kInline:
+        watch_time_reporter_->OnDisplayTypeInline();
+        break;
+      case DisplayType::kFullscreen:
+        watch_time_reporter_->OnDisplayTypeFullscreen();
+        break;
+      case DisplayType::kPictureInPicture:
+        watch_time_reporter_->OnDisplayTypePictureInPicture();
+        break;
+    }
   }
 
   SetPersistentState(display_type == DisplayType::kPictureInPicture);
@@ -2680,6 +2694,7 @@
 }
 
 void WebMediaPlayerImpl::SetPersistentState(bool value) {
+  DVLOG(2) << __func__ << ": value=" << value;
   overlay_info_.is_persistent_video = value;
   MaybeSendOverlayInfoToDecoder();
 }
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index d0a4f0b..9489e7b70 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2455,7 +2455,8 @@
     {
       name: "SecurePaymentConfirmationOptOut",
       origin_trial_feature_name: "SecurePaymentConfirmationOptOut",
-      origin_trial_allows_third_party: true
+      origin_trial_allows_third_party: true,
+      status: "stable",
     },
     {
       // When a Web application calls getDisplayMedia() and asks for video,
diff --git a/third_party/blink/web_tests/SmokeTests/Mac.txt b/third_party/blink/web_tests/SmokeTests/Mac.txt
index 1d9f550..196db8c 100644
--- a/third_party/blink/web_tests/SmokeTests/Mac.txt
+++ b/third_party/blink/web_tests/SmokeTests/Mac.txt
@@ -1,8 +1,6 @@
 accessibility/content-changed-notification-causes-crash.html
 accessibility/scroll-window-sends-notification.html
 animations/animation-paused-hardware.html
-animations/missing-values-first-keyframe.html
-animations/missing-values-last-keyframe.html
 compositing/geometry/clipping-foreground.html
 compositing/geometry/video-fixed-scrolling.html
 compositing/geometry/video-opacity-overlay.html
diff --git a/third_party/blink/web_tests/SmokeTests/skia-vulkan-swiftshader b/third_party/blink/web_tests/SmokeTests/skia-vulkan-swiftshader
index 1156e671e..5e0dac9 100644
--- a/third_party/blink/web_tests/SmokeTests/skia-vulkan-swiftshader
+++ b/third_party/blink/web_tests/SmokeTests/skia-vulkan-swiftshader
@@ -404,7 +404,6 @@
 animations/keyframes-iteration-count-non-integer.html
 animations/keyframes-missing-arguments.html
 animations/missing-from-to.html
-animations/missing-values-last-keyframe.html
 animations/prefixed/duplicated-keyframes-name-unprefixed-03.html
 animations/prefixed/keyframes-cssom-unprefixed-01.html
 animations/prefixed/keyframes-unprefixed-01.html
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 6ac99a94..5549411 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -204,10 +204,6 @@
 # --- Skia roll test suppressions
 # --- END Skia roll test suppresions
 
-# Disable tests for DevTools frontend CL (crrev.com/c/4044821)
-crbug.com/1383735 http/tests/devtools/elements/styles-1/add-new-rule-inline-style-csp.js [ Failure Pass ]
-crbug.com/1383735 http/tests/devtools/modify-cross-domain-rule.js [ Failure Pass ]
-
 # Sheriff on 2020-09-03
 crbug.com/1124352 media/picture-in-picture/clear-after-request.html [ Crash Pass ]
 crbug.com/1124352 media/picture-in-picture/controls/picture-in-picture-button.html [ Crash Pass ]
@@ -4489,14 +4485,10 @@
 # rebaselined. TODO(jonross): triage these into any existing bugs or file more
 # specific bugs.
 crbug.com/1043675 [ Linux ] animations/animation-paused-hardware.html [ Failure ]
-crbug.com/1043675 [ Linux ] animations/missing-values-first-keyframe.html [ Failure ]
-crbug.com/1043675 [ Linux ] animations/missing-values-last-keyframe.html [ Failure ]
 crbug.com/1043675 [ Linux ] external/wpt/css/filter-effects/backdrop-filter-basic-opacity-2.html [ Failure ]
 crbug.com/1043675 [ Linux ] http/tests/media/video-frame-size-change.html [ Failure ]
 crbug.com/1043675 [ Linux ] svg/custom/svg-root-with-opacity.html [ Failure ]
 crbug.com/1043675 [ Win ] animations/animation-paused-hardware.html [ Failure ]
-crbug.com/1043675 [ Win ] animations/missing-values-first-keyframe.html [ Failure ]
-crbug.com/1043675 [ Win ] animations/missing-values-last-keyframe.html [ Failure ]
 crbug.com/1043675 [ Win ] external/wpt/css/filter-effects/backdrop-filter-basic-opacity-2.html [ Failure ]
 crbug.com/1043675 [ Win ] svg/custom/svg-root-with-opacity.html [ Failure ]
 
@@ -5080,8 +5072,6 @@
 
 # For SkiaRenderer on MacOS
 crbug.com/1208173 [ Mac ] animations/animation-paused-hardware.html [ Failure ]
-crbug.com/1208173 [ Mac ] animations/missing-values-first-keyframe.html [ Failure Timeout ]
-crbug.com/1208173 [ Mac ] animations/missing-values-last-keyframe.html [ Failure ]
 crbug.com/1208173 [ Mac ] css3/filters/backdrop-filter-boundary.html [ Failure ]
 crbug.com/1208173 [ Mac ] external/wpt/css/css-paint-api/background-image-alpha.https.html [ Failure ]
 crbug.com/1208173 [ Mac ] external/wpt/css/filter-effects/backdrop-filter-basic-opacity-2.html [ Failure ]
@@ -6214,7 +6204,6 @@
 
 # Sheriff 2022-08-06
 crbug.com/1350337 [ Linux ] external/wpt/web-locks/query-ordering.tentative.https.html [ Failure Pass ]
-crbug.com/1350341 [ Linux ] virtual/threaded-no-composited-antialiasing/animations/direction-and-fill/fill-mode-missing-from-to-keyframes.html [ Failure Pass ]
 
 # These seem to consistently fail with disabling code cache.
 crbug.com/1351903 http/tests/devtools/network/font-face.js [ Failure Pass ]
@@ -6414,7 +6403,6 @@
 crbug.com/1385881 [ Win ] external/wpt/fetch/metadata/generated/audioworklet.https.sub.html [ Timeout ]
 crbug.com/1385870 [ Win ] external/wpt/screen-capture/getdisplaymedia.https.html [ Skip Timeout ]
 crbug.com/1385870 [ Win ] external/wpt/screen-capture/getdisplaymedia-capture-controller.https.window.html [ Skip Timeout ]
-crbug.com/1385858 [ Win ] virtual/web-bluetooth-new-permissions-backend/wpt_internal/bluetooth/requestDevice/filter-does-not-match.https.html [ Skip Timeout ]
 crbug.com/1385929 [ Win ] editing/selection/mouse/mouse-selection-vertical-lr.html [ Timeout ]
 crbug.com/1385929 [ Win ] editing/selection/mouse/mouse-selection-horizontal.html [ Timeout ]
 crbug.com/1392480 [ Win ] wpt_internal/hid/hidDevice_reports.https.window.html [ Timeout ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index a832dc90..86583ca 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -975,6 +975,8 @@
       "external/wpt/webmessaging/broadcastchannel/cross-partition.https.tentative.html",
       "external/wpt/IndexedDB",
       "external/wpt/webstorage/localstorage-about-blank-3P-iframe-opens-3P-window.partitioned.tentative.html",
+      "external/wpt/secure-contexts/shared-worker-insecure-first.https.html",
+      "external/wpt/secure-contexts/shared-worker-secure-first.https.html",
       "fast/filesystem",
       "external/wpt/html/browsers/windows/post-message",
       "http/tests/inspector-protocol/storage/dom-storage-set-items-by-storage-key.js",
diff --git a/third_party/blink/web_tests/animations/direction-and-fill/fill-mode-missing-from-to-keyframes.html b/third_party/blink/web_tests/animations/direction-and-fill/fill-mode-missing-from-to-keyframes.html
index 1bbeb1da..ce03d4f 100644
--- a/third_party/blink/web_tests/animations/direction-and-fill/fill-mode-missing-from-to-keyframes.html
+++ b/third_party/blink/web_tests/animations/direction-and-fill/fill-mode-missing-from-to-keyframes.html
@@ -4,14 +4,15 @@
 <style type="text/css" media="screen">
 .box {
   position: relative;
+  background:  blue;
   left: 100px;
   top: 10px;
   height: 30px;
   width: 200px;
-  animation-delay: 0.1s;
   animation-duration: 0.1s;
-  animation-timing-function: linear;
+  animation-play-state: paused;
 }
+
 @keyframes anim1 {
   from { left: 200px; }
   50% { left: 250px; }
@@ -29,133 +30,112 @@
   50% { left: 250px; }
 }
 
-#a {
-  background-color: blue;
-  animation-fill-mode: none;
-  animation-name: anim1;
-}
-#b {
-  background-color: red;
-  animation-fill-mode: backwards;
-  animation-name: anim1;
-}
-#c {
-  background-color: green;
-  animation-fill-mode: forwards;
-  animation-name: anim1;
-}
-#d {
-  background-color: yellow;
-  animation-fill-mode: both;
-  animation-name: anim1;
-}
-#e {
-  background-color: #999;
-  animation-fill-mode: both;
-  animation-iteration-count: 2;
-  animation-direction: alternate;
-  animation-name: anim1;
+.fill-none {
+  animation-fill-mode:  none;
 }
 
-#f {
-  background-color: blue;
-  animation-fill-mode: none;
-  animation-name: anim2;
+.fill-forwards {
+  animation-fill-mode:  forwards;
 }
-#g {
-  background-color: red;
-  animation-fill-mode: backwards;
-  animation-name: anim2;
+
+.fill-backwards {
+  animation-fill-mode:  backwards;
 }
-#h {
-  background-color: green;
-  animation-fill-mode: forwards;
-  animation-name: anim2;
+
+.fill-both {
+  animation-fill-mode:  both;
 }
-#i {
-  background-color: yellow;
-  animation-fill-mode: both;
-  animation-name: anim2;
+
+.from-to {
+  animation-name:  anim1;
 }
-#j {
-  background-color: #999;
-  animation-fill-mode: both;
-  animation-iteration-count: 2;
-  animation-direction: alternate;
-  animation-name: anim2;
+
+.missing-from {
+  animation-name:  anim2;
+}
+
+.missing-to {
+  animation-name:  anim3;
+}
+
+.missing-from-to {
+  animation-name:  anim4;
 }
 </style>
+<body>
+  <!-- Dynamically inject content here. -->
+</body>
 <script src="../../resources/testharness.js"></script>
 <script src="../../resources/testharnessreport.js"></script>
 <script type="text/javascript" charset="utf-8">
-const numAnims = 10;
-var animsFinished = 0;
-const allowance = 5;
-const expectedValues = [
-  {id: "a", start: 100, end: 100},
-  {id: "b", start: 200, end: 100},
-  {id: "c", start: 100, end: 300},
-  {id: "d", start: 200, end: 300},
-  {id: "e", start: 200, end: 200}
-];
 
-async_test(t => {
-  function endTest() {
-    for (var i=0; i < expectedValues.length; i++) {
-      var el = document.getElementById(expectedValues[i].id);
-      var expectedValue = expectedValues[i].end;
-      var realValue = parseFloat(window.getComputedStyle(el).left);
-      var diff = Math.abs(expectedValue - realValue);
-      assert_less_than(diff, allowance, "end of animation - id: " + expectedValues[i].id + " expected: " + expectedValue + " actual: " + realValue);
-    }
-    t.done();
+  function createDiv(test, classList) {
+    const el = document.createElement('div');
+    document.body.appendChild(el);
+    el.classList.add('box');
+    classList.forEach(className => {
+      el.classList.add(className);
+    });
+    test.add_cleanup(() => {
+      el.remove();
+    });
+    return el;
   }
 
-  window.addEventListener("load", t.step_func(() => {
-    for (var i=0; i < expectedValues.length; i++) {
-      var el = document.getElementById(expectedValues[i].id);
-      var expectedValue = expectedValues[i].start;
-      var realValue = parseFloat(window.getComputedStyle(el).left);
-      var diff = Math.abs(expectedValue - realValue);
-      assert_less_than(diff, allowance, "start of animation - id: " + expectedValues[i].id + " expected: " + expectedValue + " actual: " + realValue);
-    }
-    document.addEventListener("animationend", t.step_func(() => {
-      if (++animsFinished == numAnims) {
-          t.step_timeout(endTest, 0); // this set timeout should be ok in the test environment
-                                      // since we're just giving style a chance to resolve
-      }
-    }));
-  }));
-}, "This test performs an animation of the left property with four different fill modes. It animates over 0.1 second with a 0.1 second delay. It takes snapshots at document load and the end of the animation");
-</script>
-<div id="a" class="box">
-  None - from/to present
-</div>
-<div id="b" class="box">
-  Backwards - from/to present
-</div>
-<div id="c" class="box">
-  Forwards - from/to present
-</div>
-<div id="d" class="box">
-  Both - from/to present
-</div>
-<div id="e" class="box">
-  Both iterating - from/to present
-</div>
+  // Validates the computed style when the animations is in the before and
+  // after phases.
+  function assert_before_and_after_styles(element, expected_before,
+                                          expected_after) {
+    const anim = element.getAnimations()[0];
+    anim.currentTime = -1;
+    assert_equals(getComputedStyle(element).left, expected_before,
+                  `${element.classList}: unexpected value in the before phase`);
+    anim.currentTime = 100;
+    assert_equals(getComputedStyle(element).left, expected_after,
+                  `${element.classList}: unexpected value in the after phase`);
+  }
 
-<div id="f" class="box">
-  None - from missing
-</div>
-<div id="g" class="box">
-  Backwards - from missing
-</div>
-<div id="h" class="box">
-  Forwards - from missing
-</div>
-<div id="i" class="box">
-  Both - from missing
-</div>
-<div id="j" class="box">
-  Both iterating - from missing
-</div>
+  test(t => {
+    const fillNone = createDiv(t, ['fill-none', 'from-to']);
+    const fillForwards = createDiv(t, ['fill-forwards', 'from-to']);
+    const fillBackwards = createDiv(t, ['fill-backwards', 'from-to']);
+    const fillBoth = createDiv(t, ['fill-both', 'from-to']);
+    assert_before_and_after_styles(fillNone, '100px', '100px');
+    assert_before_and_after_styles(fillForwards, '100px', '300px');
+    assert_before_and_after_styles(fillBackwards, '200px', '100px');
+    assert_before_and_after_styles(fillBoth, '200px', '300px');
+  }, 'Values in before and after phase when both "from" and "to" are present');
+
+  test(t => {
+    const fillNone = createDiv(t, ['fill-none', 'missing-from']);
+    const fillForwards = createDiv(t, ['fill-forwards', 'missing-from']);
+    const fillBackwards = createDiv(t, ['fill-backwards', 'missing-from']);
+    const fillBoth = createDiv(t, ['fill-both', 'missing-from']);
+    assert_before_and_after_styles(fillNone, '100px', '100px');
+    assert_before_and_after_styles(fillForwards, '100px', '300px');
+    assert_before_and_after_styles(fillBackwards, '100px', '100px');
+    assert_before_and_after_styles(fillBoth, '100px', '300px');
+  }, 'Values in before and after phase when missing "from" keyframe');
+
+  test(t => {
+    const fillNone = createDiv(t, ['fill-none', 'missing-to']);
+    const fillForwards = createDiv(t, ['fill-forwards', 'missing-to']);
+    const fillBackwards = createDiv(t, ['fill-backwards', 'missing-to']);
+    const fillBoth = createDiv(t, ['fill-both', 'missing-to']);
+    assert_before_and_after_styles(fillNone, '100px', '100px');
+    assert_before_and_after_styles(fillForwards, '100px', '100px');
+    assert_before_and_after_styles(fillBackwards, '200px', '100px');
+    assert_before_and_after_styles(fillBoth, '200px', '100px');
+  }, 'Values in before and after phase when missing "to" keyframe');
+
+  test(t => {
+    const fillNone = createDiv(t, ['fill-none', 'missing-from-to']);
+    const fillForwards = createDiv(t, ['fill-forwards', 'missing-from-to']);
+    const fillBackwards = createDiv(t, ['fill-backwards', 'missing-from-to']);
+    const fillBoth = createDiv(t, ['fill-both', 'missing-from-to']);
+    assert_before_and_after_styles(fillNone, '100px', '100px');
+    assert_before_and_after_styles(fillForwards, '100px', '100px');
+    assert_before_and_after_styles(fillBackwards, '100px', '100px');
+    assert_before_and_after_styles(fillBoth, '100px', '100px');
+  }, 'Values in before and after phase when missing "from" and "to" keyframes');
+</script>
diff --git a/third_party/blink/web_tests/animations/missing-values-first-keyframe-expected.html b/third_party/blink/web_tests/animations/missing-values-first-keyframe-expected.html
deleted file mode 100644
index 1170b86..0000000
--- a/third_party/blink/web_tests/animations/missing-values-first-keyframe-expected.html
+++ /dev/null
@@ -1,56 +0,0 @@
-
-<!DOCTYPE html>
-<html>
-<head>
-  <style type="text/css" media="screen">
-    body {
-      margin: 0;
-    }
-
-    .box {
-      position: relative;
-      width: 100px;
-      height: 100px;
-      left: 0;
-      background-color: green;
-    }
-    
-    .indicator {
-      position: absolute;
-      width: 100px;
-      height: 100px;
-      left: 100px;
-      background-color: red;
-    }
-    #indicator1 {
-      top: 0;
-    }
-    #indicator2 {
-      top: 100px;
-    }
-    
-    #box1 {
-      left: 100px;
-    }
-    
-    #box2 {
-      transform: translateX(100px);
-    }
-    
-  </style>
-</head>
-<body>
-  <!-- In the pixel result, you should see two vertically adjacent green squares. There should be no red.
-  Test is only reliable when run in DRT. -->
-  <div class="indicator" id="indicator1"></div>
-  <div class="indicator" id="indicator2"></div>
-
-  <div class="box" id="box1"></div>
-  <div class="box" id="box2"></div>
-
-  <div id="result">
-    PASS - "left" property for "box1" element at 0.5s saw something close to: 100<br>
-    PASS - "transform.4" property for "box2" element at 0.5s saw something close to: 100
-  </div>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/animations/missing-values-first-keyframe.html b/third_party/blink/web_tests/animations/missing-values-first-keyframe.html
deleted file mode 100644
index 06afdd9..0000000
--- a/third_party/blink/web_tests/animations/missing-values-first-keyframe.html
+++ /dev/null
@@ -1,100 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <style type="text/css" media="screen">
-    body {
-      margin: 0;
-    }
-
-    .box {
-      position: relative;
-      width: 100px;
-      height: 100px;
-      left: 0;
-      background-color: green;
-    }
-    
-    .indicator {
-      position: absolute;
-      width: 100px;
-      height: 100px;
-      left: 100px;
-      background-color: red;
-    }
-    #indicator1 {
-      top: 0;
-    }
-    #indicator2 {
-      top: 100px;
-    }
-    
-    #box1 {
-      left: 200px;
-      animation: move-left 2s linear;
-    }
-    
-    #box2 {
-      transform: translateX(200px);
-      animation: move-transform 2s linear;
-    }
-    
-    @keyframes move-left {
-      0% {
-        opacity: 1;
-      }
-      25% {
-        opacity: 1;
-      }
-      50% {
-        left: 0;
-        opacity: 1;
-      }
-      100% {
-        left: 0;
-        opacity: 0;
-      }
-    }
-
-    @keyframes move-transform {
-      0% {
-        opacity: 1;
-      }
-      25% {
-        opacity: 1;
-      }
-      50% {
-        transform: translateX(0);
-        opacity: 1;
-      }
-      100% {
-        transform: translateX(0);
-        opacity: 0;
-      }
-    }
-  </style>
-  <script src="resources/animation-test-helpers.js" type="text/javascript"></script>
-  <script type="text/javascript">
-    
-    const expectedValues = [
-      // [time, element-id, property, expected-value, tolerance]
-      [0.5, "box1", "left", 100, 15],
-      [0.5, "box2", "transform.4", 100, 15],
-    ];
-    
-    var doPixelTest = true;
-    var disablePauseAPI = false;
-    runAnimationTest(expectedValues, null, undefined, disablePauseAPI, doPixelTest);
-  </script>
-</head>
-<body>
-  <!-- In the pixel result, you should see two vertically adjacent green squares. There should be no red.
-  Test is only reliable when run in DRT. -->
-  <div class="indicator" id="indicator1"></div>
-  <div class="indicator" id="indicator2"></div>
-
-  <div class="box" id="box1"></div>
-  <div class="box" id="box2"></div>
-
-  <div id="result"></div>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/animations/missing-values-last-keyframe-expected.html b/third_party/blink/web_tests/animations/missing-values-last-keyframe-expected.html
deleted file mode 100644
index 10325f5..0000000
--- a/third_party/blink/web_tests/animations/missing-values-last-keyframe-expected.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <style type="text/css" media="screen">
-    body {
-      margin: 0;
-    }
-
-    .box {
-      position: relative;
-      width: 100px;
-      height: 100px;
-      left: 0;
-      background-color: green;
-    }
-    
-    .indicator {
-      position: absolute;
-      width: 100px;
-      height: 100px;
-      left: 100px;
-      background-color: red;
-    }
-    #indicator1 {
-      top: 0;
-    }
-    #indicator2 {
-      top: 100px;
-    }
-    
-    #box1 {
-      left: 100px;
-    }
-    
-    #box2 {
-      transform: translateX(100px);
-    }
-    
-  </style>
-</head>
-<body>
-  <!-- In the pixel result, you should see two vertically adjacent green squares. There should be no red.
-  Test is only reliable when run in DRT. -->
-  <div class="indicator" id="indicator1"></div>
-  <div class="indicator" id="indicator2"></div>
-
-  <div class="box" id="box1"></div>
-  <div class="box" id="box2"></div>
-
-  <div id="result">
-    PASS - "left" property for "box1" element at 7.5s saw something close to: 100<br>
-    PASS - "transform.4" property for "box2" element at 7.5s saw something close to: 100
-  </div>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/animations/missing-values-last-keyframe.html b/third_party/blink/web_tests/animations/missing-values-last-keyframe.html
deleted file mode 100644
index 24a0a52..0000000
--- a/third_party/blink/web_tests/animations/missing-values-last-keyframe.html
+++ /dev/null
@@ -1,100 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <style type="text/css" media="screen">
-    body {
-      margin: 0;
-    }
-
-    .box {
-      position: relative;
-      width: 100px;
-      height: 100px;
-      left: 0;
-      background-color: green;
-    }
-
-    .indicator {
-      position: absolute;
-      width: 100px;
-      height: 100px;
-      left: 100px;
-      background-color: red;
-    }
-    #indicator1 {
-      top: 0;
-    }
-    #indicator2 {
-      top: 100px;
-    }
-
-    #box1 {
-      left: 200px;
-      animation: move-left 10s linear;
-    }
-
-    #box2 {
-      transform: translateX(200px);
-      animation: move-transform 10s linear;
-    }
-
-    @keyframes move-left {
-      0% {
-        left: 0;
-        opacity: 0;
-      }
-      50% {
-        left: 0;
-        opacity: 1;
-      }
-      75% {
-        opacity: 1;
-      }
-      100% {
-        opacity: 1;
-      }
-    }
-
-    @keyframes move-transform {
-      0% {
-        transform: translateX(0);
-        opacity: 0;
-      }
-      50% {
-        transform: translateX(0);
-        opacity: 1;
-      }
-      75% {
-        opacity: 1;
-      }
-      100% {
-        opacity: 1;
-      }
-    }
-  </style>
-  <script src="resources/animation-test-helpers.js" type="text/javascript"></script>
-  <script type="text/javascript">
-
-    const expectedValues = [
-      // [time, element-id, property, expected-value, tolerance]
-      [7.5, "box1", "left", 100, 0],
-      [7.5, "box2", "transform.4", 100, 0],
-    ];
-
-    var doPixelTest = true;
-    var disablePauseAPI = false;
-    runAnimationTest(expectedValues, null, undefined, disablePauseAPI, doPixelTest);
-  </script>
-</head>
-<body>
-  <!-- In the pixel result, you should see two vertically adjacent green squares. There should be no red.
-  Test is only reliable when run in DRT. -->
-  <div class="indicator" id="indicator1"></div>
-  <div class="indicator" id="indicator2"></div>
-
-  <div class="box" id="box1"></div>
-  <div class="box" id="box2"></div>
-
-  <div id="result"></div>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/missing-values-first-keyframe.html b/third_party/blink/web_tests/external/wpt/css/css-animations/missing-values-first-keyframe.html
new file mode 100644
index 0000000..311e2625
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/missing-values-first-keyframe.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<title>Missing properties in first keyframe</title>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<link rel="help" href="https://www.w3.org/TR/web-animations-1/#the-effect-value-of-a-keyframe-animation-effect">
+<meta name="assert"
+      content="CSS animation correctly interpolates from neutral keyframe">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/web-animations/testcommon.js"></script>
+<style type="text/css" media="screen">
+  body {
+    margin: 0;
+  }
+
+  .box {
+    position: relative;
+    width: 100px;
+    height: 100px;
+    left: 0;
+    background-color: green;
+  }
+
+  #box1 {
+    left: 200px;
+    animation: move-left 2s paused linear;
+  }
+
+  #box2 {
+    transform: translateX(200px);
+    animation: move-transform 2s paused linear;
+  }
+
+  @keyframes move-left {
+    0% {
+      opacity: 1;
+    }
+    25% {
+      opacity: 1;
+    }
+    50% {
+      left: 0;
+      opacity: 1;
+    }
+    100% {
+      left: 0;
+      opacity: 0;
+    }
+  }
+
+  @keyframes move-transform {
+    0% {
+      opacity: 1;
+    }
+    25% {
+      opacity: 1;
+    }
+    50% {
+      transform: translateX(0);
+      opacity: 1;
+    }
+    100% {
+      transform: translateX(0);
+      opacity: 0;
+    }
+  }
+</style>
+<body>
+  <div class="box" id="box1"></div>
+  <div class="box" id="box2"></div>
+</body>
+<script>
+  promise_test(async t => {
+    document.getAnimations().forEach(anim => {
+      anim.currentTime = 500;
+    });
+    assert_equals(getComputedStyle(box1).left, "100px");
+    assert_matrix_equals(
+        getComputedStyle(box2).transform,
+        'matrix(1, 0, 0, 1, 100, 0)');
+  }, 'Missing property values in the first keyframe are correctly ' +
+     'interpolated from a neutral keyframe value');
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/missing-values-last-keyframe.html b/third_party/blink/web_tests/external/wpt/css/css-animations/missing-values-last-keyframe.html
new file mode 100644
index 0000000..04c4eba
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/missing-values-last-keyframe.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<title>Missing properties in last keyframe</title>
+<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1">
+<link rel="help" href="https://www.w3.org/TR/web-animations-1/#the-effect-value-of-a-keyframe-animation-effect">
+<meta name="assert"
+      content="CSS animation correctly interpolates from neutral keyframe">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/web-animations/testcommon.js"></script>
+<style type="text/css" media="screen">
+  body {
+    margin: 0;
+  }
+
+  .box {
+    position: relative;
+    width: 100px;
+    height: 100px;
+    left: 0;
+    background-color: green;
+  }
+
+  #box1 {
+    left: 200px;
+    animation: move-left 10s linear;
+  }
+
+  #box2 {
+    transform: translateX(200px);
+    animation: move-transform 10s linear;
+  }
+
+  @keyframes move-left {
+    0% {
+      left: 0;
+      opacity: 0;
+    }
+    50% {
+      left: 0;
+      opacity: 1;
+    }
+    75% {
+      opacity: 1;
+    }
+    100% {
+      opacity: 1;
+    }
+  }
+
+  @keyframes move-transform {
+    0% {
+      transform: translateX(0);
+      opacity: 0;
+    }
+    50% {
+      transform: translateX(0);
+      opacity: 1;
+    }
+    75% {
+      opacity: 1;
+    }
+    100% {
+      opacity: 1;
+    }
+  }
+</style>
+<body>
+  <div class="box" id="box1"></div>
+  <div class="box" id="box2"></div>
+</body>
+<script>
+  promise_test(async t => {
+    document.getAnimations().forEach(anim => {
+      anim.currentTime = 7500;
+    });
+    assert_equals(getComputedStyle(box1).left, "100px");
+    assert_matrix_equals(
+        getComputedStyle(box2).transform,
+        'matrix(1, 0, 0, 1, 100, 0)');
+  }, 'Missing property values in the last keyframe are correctly ' +
+     'interpolated from a neutral keyframe value');
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-variant-alternates-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-variant-alternates-invalid.html
index baf35ac..8cd17d08 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-variant-alternates-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-variant-alternates-invalid.html
@@ -34,6 +34,7 @@
         "font-variant-alternates",
         "historical-forms(argument)"
       );
+      test_invalid_value("font-variant-alternates", "annotation()");
       test_invalid_value("font-variant-alternates", "annotation");
       test_invalid_value("font-variant-alternates", "swash");
       test_invalid_value("font-variant-alternates", "ornaments stylistic");
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-1/add-new-rule-inline-style-csp-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/styles-1/add-new-rule-inline-style-csp-expected.txt
index c1e946e0..3587ab0 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-1/add-new-rule-inline-style-csp-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-1/add-new-rule-inline-style-csp-expected.txt
@@ -11,7 +11,6 @@
 Running: testAddProperty
 === Added rule modified ===
 ['width':'100%'] @[1:4-1:16] 
-['width':'100%'] @[undefined-undefined] 
 === Selector changed ===
 body {
     width: 100%;
@@ -21,5 +20,4 @@
 Running: testModifyInlineStyle
 === Inline style modified ===
 ['font-size':'14px'] @[1:4-1:20] 
-['font-size':'14px'] @[undefined-undefined] 
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/modify-cross-domain-rule-expected.txt b/third_party/blink/web_tests/http/tests/devtools/modify-cross-domain-rule-expected.txt
index 00dee19..9ffa3c5 100644
--- a/third_party/blink/web_tests/http/tests/devtools/modify-cross-domain-rule-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/modify-cross-domain-rule-expected.txt
@@ -7,8 +7,6 @@
 === Rule modified ===
 ['color':'green'] @[1:2-1:15] 
 ['width':'100%'] @[2:2-2:14] 
-['color':'green'] @[undefined-undefined] 
-['width':'100%'] @[undefined-undefined] 
 === Selector changed ===
 body {
   color: green;
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/storage/indexed-db-storage-key-track-untrack.js b/third_party/blink/web_tests/http/tests/inspector-protocol/storage/indexed-db-storage-key-track-untrack.js
index f21b2c3..570e440 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/storage/indexed-db-storage-key-track-untrack.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/storage/indexed-db-storage-key-track-untrack.js
@@ -15,7 +15,7 @@
   setTimeout(() => {
     testRunner.log(protocolMessages);
     testRunner.die('Timeout', errorForLog);
-  }, 9000);
+  }, 5000);
 
   const frameId = (await dp.Page.getResourceTree()).result.frameTree.frame.id;
   errorForLog = new Error();
diff --git a/third_party/blink/web_tests/virtual/third-party-storage-partitioning/external/wpt/secure-contexts/shared-worker-insecure-first.https-expected.txt b/third_party/blink/web_tests/virtual/third-party-storage-partitioning/external/wpt/secure-contexts/shared-worker-insecure-first.https-expected.txt
new file mode 100644
index 0000000..20c67ae5
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/third-party-storage-partitioning/external/wpt/secure-contexts/shared-worker-insecure-first.https-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL Shared worker in subframe assert_true: SharedWorker connection should generate an error. expected true got false
+FAIL Nested worker in shared worker in subframe assert_true: SharedWorker connection should generate an error. expected true got false
+PASS Shared worker in popup
+FAIL Nested worker from shared worker in popup assert_false: expected false got "Nested workers not supported."
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/third-party-storage-partitioning/external/wpt/secure-contexts/shared-worker-secure-first.https-expected.txt b/third_party/blink/web_tests/virtual/third-party-storage-partitioning/external/wpt/secure-contexts/shared-worker-secure-first.https-expected.txt
new file mode 100644
index 0000000..77a376026f
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/third-party-storage-partitioning/external/wpt/secure-contexts/shared-worker-secure-first.https-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+PASS Shared worker in subframe
+FAIL Nested worker in shared worker in subframe assert_true: SharedWorker is a secure context expected true got "Nested workers not supported."
+FAIL Shared worker in popup assert_true: SharedWorker connection should error out. expected true got false
+FAIL Nested worker from shared worker in popup assert_true: SharedWorker connection should error out. expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/wpt_internal/serial/serial_detachedFrame.https.window.js b/third_party/blink/web_tests/wpt_internal/serial/serial_detachedFrame.https.window.js
index 666d5ce..2aef63e 100644
--- a/third_party/blink/web_tests/wpt_internal/serial/serial_detachedFrame.https.window.js
+++ b/third_party/blink/web_tests/wpt_internal/serial/serial_detachedFrame.https.window.js
@@ -25,6 +25,7 @@
 
   try {
     await detachedSerial.getPorts();
+    assert_unreached();
   } catch (e) {
     // Cannot use promise_rejects_dom() because |e| is thrown from a different
     // global.
@@ -37,6 +38,7 @@
 
   try {
     await detachedSerial.requestPort();
+    assert_unreached();
   } catch (e) {
     // Cannot use promise_rejects_dom() because |e| is thrown from a different
     // global.
diff --git a/third_party/closure_compiler/externs/autofill_private.js b/third_party/closure_compiler/externs/autofill_private.js
index a5cc6cd..6ed1fd72 100644
--- a/third_party/closure_compiler/externs/autofill_private.js
+++ b/third_party/closure_compiler/externs/autofill_private.js
@@ -117,6 +117,16 @@
 
 /**
  * @typedef {{
+ *   guid: (string|undefined),
+ *   value: (string|undefined),
+ *   nickname: (string|undefined),
+ *   metadata: (!chrome.autofillPrivate.AutofillMetadata|undefined)
+ * }}
+ */
+chrome.autofillPrivate.IbanEntry;
+
+/**
+ * @typedef {{
  *   phoneNumbers: !Array<string>,
  *   indexOfNewNumber: number,
  *   countryCode: string
@@ -164,6 +174,13 @@
 chrome.autofillPrivate.saveCreditCard = function(card) {};
 
 /**
+ * Saves the given IBAN. If `iban` has an empty string as its ID, it will be
+ * assigned a new one and added as a new entry.
+ * @param {!chrome.autofillPrivate.IbanEntry} iban The IBAN entry to save.
+ */
+chrome.autofillPrivate.saveIban = function(iban) {};
+
+/**
  * Removes the entry (address or credit card) with the given ID.
  * @param {string} guid ID of the entry to remove.
  */
@@ -188,6 +205,13 @@
 chrome.autofillPrivate.getCreditCardList = function(callback) {};
 
 /**
+ * Gets the list of IBANs.
+ * @param {function(!Array<!chrome.autofillPrivate.IbanEntry>): void} callback
+ *     Callback which will be called with the list of IBANs.
+ */
+chrome.autofillPrivate.getIbanList = function(callback) {};
+
+/**
  * Clears the data associated with a wallet card which was saved locally so that
  * the saved copy is masked (e.g., "Card ending in 1234").
  * @param {string} guid GUID of the credit card to mask.
diff --git a/third_party/rust/README.md b/third_party/rust/README.md
index bfec577f..1a94792 100644
--- a/third_party/rust/README.md
+++ b/third_party/rust/README.md
@@ -47,7 +47,7 @@
 
 At this time adding new 3rd party crates requires a review by:
 
-- `//build/rust/OWNERS`- i.e. a Chrome Eng Review is not needed while broader
+- `//build/rust/OWNERS`- i.e. a Chrome ATL review is not needed while broader
   Rust usage is not allowed / while Rust usage remains an experiment.
 - security@chromium.org (or chrome-security@google.com, Google-only)
     - Earlier examples of audits/documents/emails that are good role models
diff --git a/third_party/wuffs/BUILD.gn b/third_party/wuffs/BUILD.gn
index 466352cc..6e7468ac 100644
--- a/third_party/wuffs/BUILD.gn
+++ b/third_party/wuffs/BUILD.gn
@@ -25,7 +25,7 @@
 #
 # In summary, non-trivial changes to how Chromium uses third_party/wuffs (i.e.
 # this BUILD.gn file), both adding new build targets or changing existing build
-# targets' configuration or visibility, require consulting ENG_REVIEW_OWNERS,
+# targets' configuration or visibility, require consulting ATL_OWNERS,
 # as per the "adding_to_third_party.md" link above.
 
 import("//third_party/wuffs/config.gni")
diff --git a/third_party/wuffs/OWNERS b/third_party/wuffs/OWNERS
index 36cd955..1b10b28 100644
--- a/third_party/wuffs/OWNERS
+++ b/third_party/wuffs/OWNERS
@@ -2,4 +2,4 @@
 scroggo@google.com
 
 # See the comment at the top of the BUILD.gn file for the rationale.
-per-file BUILD.gn=file://ENG_REVIEW_OWNERS
+per-file BUILD.gn=file://ATL_OWNERS
diff --git a/tools/android/modularization/gn/dep_operations.py b/tools/android/modularization/gn/dep_operations.py
index 92638a9da..4dc0f1c 100755
--- a/tools/android/modularization/gn/dep_operations.py
+++ b/tools/android/modularization/gn/dep_operations.py
@@ -17,7 +17,7 @@
 import json_gn_editor
 import utils
 
-_TOOLS_ANDROID_PATH = pathlib.Path(__file__).parents[2].resolve()
+_TOOLS_ANDROID_PATH = pathlib.Path(__file__).resolve().parents[2]
 if str(_TOOLS_ANDROID_PATH) not in sys.path:
     sys.path.append(str(_TOOLS_ANDROID_PATH))
 from python_utils import git_metadata_utils, subprocess_utils
diff --git a/tools/android/modularization/owners/getowners.py b/tools/android/modularization/owners/getowners.py
index c69bd576..36ebc6a7 100755
--- a/tools/android/modularization/owners/getowners.py
+++ b/tools/android/modularization/owners/getowners.py
@@ -4,21 +4,19 @@
 # found in the LICENSE file.
 r'''Get chromium OWNERS information for android directories.
 
-   tools/android/modularization/owners/getowners.py -- \
+   tools/android/modularization/owners/getowners.py \
    --git-dir ~/chromium/src \
    -o ~/owners.json
 '''
 
 import argparse
-import collections
-import dataclasses
 import datetime
 import functools
 import multiprocessing
 import os
 import re
 import time
-from typing import Dict, List, Optional, Tuple
+from typing import Dict, Tuple
 
 import owners_data
 import owners_dir_metadata
diff --git a/tools/android/modularization/owners/owners_dir_metadata.py b/tools/android/modularization/owners/owners_dir_metadata.py
index 04e7cedd..8f48e1b2 100644
--- a/tools/android/modularization/owners/owners_dir_metadata.py
+++ b/tools/android/modularization/owners/owners_dir_metadata.py
@@ -4,19 +4,16 @@
 # found in the LICENSE file.
 
 import json
-import os
 import pathlib
 import sys
 from typing import Dict
 
 import owners_data
 
-_SRC_PATH = os.path.abspath(
-    os.path.join(os.path.dirname(__file__), '..', '..', '..', '..'))
-
-sys.path.append(
-    os.path.join(_SRC_PATH, 'tools', 'android', 'dependency_analysis'))
-import subprocess_utils
+_TOOLS_ANDROID_PATH = pathlib.Path(__file__).resolve().parents[2]
+if str(_TOOLS_ANDROID_PATH) not in sys.path:
+  sys.path.append(str(_TOOLS_ANDROID_PATH))
+from python_utils import subprocess_utils
 
 
 def read_raw_dir_metadata(chromium_root: str, dirmd_path: str) -> Dict:
@@ -39,8 +36,7 @@
 synthetic_dir_metadatas: Dict[pathlib.Path, owners_data.DirMetadata] = {}
 
 
-def _build_dir_metadata_recursive(all_dir_metadata: Dict,
-                                  path: pathlib.Path
+def _build_dir_metadata_recursive(all_dir_metadata: Dict, path: pathlib.Path
                                   ) -> owners_data.DirMetadata:
   # Use memoized value
   if path in synthetic_dir_metadatas:
diff --git a/tools/clang/rewrite_raw_ptr_fields/tests/run_all_tests.py b/tools/clang/rewrite_raw_ptr_fields/tests/run_all_tests.py
index da3c1e0..5207e49 100755
--- a/tools/clang/rewrite_raw_ptr_fields/tests/run_all_tests.py
+++ b/tools/clang/rewrite_raw_ptr_fields/tests/run_all_tests.py
@@ -38,7 +38,7 @@
 
 
 def main():
-  if not os.path.exists("ENG_REVIEW_OWNERS"):
+  if not os.path.exists("ATL_OWNERS"):
     sys.stderr.write(
         "Please run run_all_tests.py from the root dir of Chromium")
     return -1
diff --git a/tools/clang/scripts/build.py b/tools/clang/scripts/build.py
index 518b4558..90955aee 100755
--- a/tools/clang/scripts/build.py
+++ b/tools/clang/scripts/build.py
@@ -185,11 +185,8 @@
   ], universal_newlines=True).rstrip()
 
 
-def AddCMakeToPath(args):
+def AddCMakeToPath():
   """Download CMake and add it to PATH."""
-  if args.use_system_cmake:
-    return
-
   if sys.platform == 'win32':
     zip_name = 'cmake-3.23.0-windows-x86_64.zip'
     dir_name = ['cmake-3.23.0-windows-x86_64', 'bin']
@@ -626,8 +623,8 @@
   WriteStampFile('', STAMP_FILE)
   WriteStampFile('', FORCE_HEAD_REVISION_FILE)
 
-  AddCMakeToPath(args)
-
+  if not args.use_system_cmake:
+    AddCMakeToPath()
 
   if args.skip_build:
     return 0
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 36d46b5..040a599 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -35505,6 +35505,8 @@
   <int value="1740" label="AUTOTESTPRIVATE_REFRESHREMOTECOMMANDS"/>
   <int value="1741" label="FILESYSTEMPROVIDERINTERNAL_RESPONDTOMOUNTREQUEST"/>
   <int value="1742" label="OS_DIAGNOSTICS_RUNEMMCLIFETIMEROUTINE"/>
+  <int value="1743" label="AUTOFILLPRIVATE_SAVEIBAN"/>
+  <int value="1744" label="AUTOFILLPRIVATE_GETIBANLIST"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -36462,6 +36464,46 @@
   <int value="2" label="User Enabled Supervision"/>
 </enum>
 
+<enum name="FamilyLinkUserParentAccessWidgetError">
+  <summary>
+    Error that occurs in the parent access widget. ChromeOS only.
+  </summary>
+  <int value="0" label="OAuth2 Error">
+    Error that occurs when OAuth2 token request fails.
+  </int>
+  <int value="1" label="Delegate Not Available">
+    Error that occurs when Delegate is not available in ParentAccessUIHandler.
+    Likely caused by trying to create WebUI without a dialog.
+  </int>
+  <int value="2" label="Decoding Error">
+    Error that occurs when parent access result cannot be decoded from base64.
+  </int>
+  <int value="3" label="Parsing Error">
+    Error that occurs when decoded parent access result cannot be parsed into a
+    proto.
+  </int>
+  <int value="4" label="Unknown Callback">
+    Error that occurs when an unknown type of callback is provided for
+    ParentAccessHandler::OnParentAccessCallback.
+  </int>
+</enum>
+
+<enum name="FamilyLinkUserParentAccessWidgetShowDialogError">
+  <summary>
+    Error that prevents the parent access widget dialog from showing. ChromeOS
+    only.
+  </summary>
+  <int value="0" label="Unknown Error">Unknown error in ParentAccess UI.</int>
+  <int value="1" label="Dialog Already Visible">
+    Error that occurs when ParentAccess UI invoked by non-child user. Indicates
+    a programming error.
+  </int>
+  <int value="2" label="Not a Child User">
+    Error that occurs when ParentAccess UI invoked while instance already
+    visible. Indicates a programming error.
+  </int>
+</enum>
+
 <enum name="FamilyLinkWebFilterType">
   <int value="0" label="Allow All Sites"/>
   <int value="1" label="Try To Block Mature Sites"/>
@@ -58087,6 +58129,7 @@
   <int value="-1490298774" label="enable-captive-portal-bypass-proxy-option"/>
   <int value="-1490048536"
       label="PageVisibilityPageContentAnnotations:disabled"/>
+  <int value="-1488867232" label="PageInfoAboutThisSiteNonEn:disabled"/>
   <int value="-1488744539" label="QuickUnlockFingerprint:enabled"/>
   <int value="-1488329159" label="ScreenAI:enabled"/>
   <int value="-1487243228" label="NewUsbBackend:disabled"/>
@@ -59174,6 +59217,7 @@
   <int value="-883694393" label="SyncPseudoUSSSupervisedUsers:disabled"/>
   <int value="-883608641" label="enable-cros-action-recorder"/>
   <int value="-882434910" label="EnableAggregatedMlSearchRanking:enabled"/>
+  <int value="-881477103" label="PageInfoAboutThisSiteNonEn:enabled"/>
   <int value="-881447505" label="ash-disable-shelf-model-synchronization"/>
   <int value="-881054479" label="WebAssemblyStreaming:disabled"/>
   <int value="-880201293" label="OmniboxAssistantVoiceSearch:disabled"/>
@@ -81157,6 +81201,20 @@
     The URL is not eligible to be prefetched, because in the default network
     context it is configured to use a proxy server.
   </int>
+  <int value="39" label="Prefetch Not Eligible: Browser Context Off The Record">
+    The URL is not eligible to be prefetched because in the browser is in
+    Incognito or Guest mode.
+  </int>
+  <int value="40" label="Prefetch Heldback">
+    The URL is eligible but heldback because it belongs to the Holdback group.
+  </int>
+  <int value="41" label="Prefetch Allowed">
+    The URL is eligible and not in the Holdback group (allowed to prefetch).
+  </int>
+  <int value="42" label="Prefetch Response Used">
+    The URL is prefetched successfully and the repsonse is used for the next
+    navigation.
+  </int>
 </enum>
 
 <enum name="PrefetchRedirect">
@@ -100284,6 +100342,7 @@
   <int value="1026059105" label="CROS Cr50 0.3.4"/>
   <int value="1039624416" label="CROS Cr50 0.3.10"/>
   <int value="1048372479" label="CROS Cr50 0.6.4"/>
+  <int value="1053975981" label="CROS Cr50 0.5.141"/>
   <int value="1065916186" label="CROS Cr50 0.0.19"/>
   <int value="1069046756" label="CROS Cr50 0.5.51"/>
   <int value="1080347583" label="CROS Cr50 0.6.2"/>
@@ -100298,6 +100357,7 @@
   <int value="1318849875" label="CROS Cr50 0.6.100"/>
   <int value="1353576267" label="IFX 9635 fw 3.18 build 0009"/>
   <int value="1374260250" label="CROS Cr50 0.6.7"/>
+  <int value="1389941253" label="CROS Cr50 0.5.140"/>
   <int value="1394118225" label="CROS Cr50 0.6.6"/>
   <int value="1395572841" label="CROS Cr50 0.4.15 Flags 0x10(pre-pvt)"/>
   <int value="1419460966" label="CROS Cr50 0.6.71"/>
@@ -100306,6 +100366,7 @@
   <int value="1440786499" label="CROS Cr50 0.6.72"/>
   <int value="1451134301" label="IFX 9635 147.18 build 000f"/>
   <int value="1451860784" label="CROS Cr50 0.6.110"/>
+  <int value="1457492447" label="CROS Cr50 0.6.141"/>
   <int value="1478323543" label="CROS Cr50 0.5.120"/>
   <int value="1490841853" label="IFX 9645 rev 49 fw 133.32 build 0050"/>
   <int value="1514324233" label="CROS Cr50 0.5.1"/>
@@ -100323,6 +100384,7 @@
   <int value="1787609211" label="CROS Cr50 0.3.26 aka 0.5.0"/>
   <int value="1799811131" label="CROS Cr50 0.3.9"/>
   <int value="1800672648" label="CROS Cr50 0.5.30"/>
+  <int value="1805048980" label="CROS Cr50 0.6.140"/>
   <int value="1820069194" label="CROS Cr50 0.6.10 aka 0.6.20"/>
   <int value="1828625721" label="IFX 9655 rev 32 fw 4.31 build 02c2"/>
   <int value="1841636222" label="CROS Cr50 0.5.7"/>
@@ -100336,6 +100398,7 @@
   <int value="1903933708" label="CROS Cr50 0.4.3 Flags 0x10(pre-pvt)"/>
   <int value="1935803215" label="CROS Cr50 0.3.25"/>
   <int value="1941011596" label="IFX 9655 rev 35 fw 4.34 build 03f2"/>
+  <int value="1951490249" label="CROS Cr50 0.5.130"/>
   <int value="1953654937" label="IFX 9645 rev 45 fw 133.33 build 00e3"/>
   <int value="1994299966" label="CROS Cr50 0.4.27"/>
   <int value="2024714959" label="IFX 9655 rev 32 fw 4.34 build 03f2"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index 0d82e49..dcc63339 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -4472,6 +4472,19 @@
   </summary>
 </histogram>
 
+<histogram name="Android.WebView.RequestedWithHeader.PageSchemeIsCryptographic"
+    enum="BooleanSecure" expires_after="2023-07-01">
+  <owner>pbirk@chromium.org</owner>
+  <owner>src/android_webview/OWNERS</owner>
+  <summary>
+    Records if the WebViewXRequestedWithDeprecation Origin Trial was enabled on
+    a resource request with a scheme that uses cryptography, i.e. HTTPS or WSS.
+    This histogram is recorded on every request sent by WebView if the
+    WebViewXRequestedWithDeprecation trial is enabled and the scheme is either
+    HTTP/HTTPS or WS/WSS.
+  </summary>
+</histogram>
+
 <histogram
     name="Android.WebView.RequestedWithHeader.SetRequestedWithHeaderMode"
     enum="WebViewRequestedWithHeaderMode" expires_after="2023-01-01">
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index ff2e753..7f72314 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -2485,6 +2485,17 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.Float.FloatWindowMoveToAnotherDeskCounts" units="windows"
+    expires_after="2023-12-11">
+  <owner>nupurjain@google.com</owner>
+  <owner>shidi@chromium.org</owner>
+  <owner>sammiequon@chromium.org</owner>
+  <summary>
+    The counts of how many times floated windows moved to another desk per
+    session, which is from the time a user logs in to the time they log off.
+  </summary>
+</histogram>
+
 <histogram name="Ash.Float.MultitaskMenuEntryType.{TabletOrClamshell}"
     enum="MultitaskMenuEntryType" expires_after="2023-11-11">
   <owner>nupurjain@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/families/histograms.xml b/tools/metrics/histograms/metadata/families/histograms.xml
index a9ca816..edd63d3 100644
--- a/tools/metrics/histograms/metadata/families/histograms.xml
+++ b/tools/metrics/histograms/metadata/families/histograms.xml
@@ -162,6 +162,35 @@
   </token>
 </histogram>
 
+<histogram name="ChromeOS.FamilyLinkUser.ParentAccessWidgetError.{FlowType}"
+    enum="FamilyLinkUserParentAccessWidgetError" expires_after="2023-03-26">
+  <owner>agawronska@chromium.org</owner>
+  <owner>cros-families-eng@google.com</owner>
+  <summary>
+    Records an error that occurs in the parent access widget. ChromeOS Only.
+  </summary>
+  <token key="FlowType">
+    <variant name="All"/>
+    <variant name="WebApprovals"/>
+  </token>
+</histogram>
+
+<histogram
+    name="ChromeOS.FamilyLinkUser.ParentAccessWidgetShowDialogError.{FlowType}"
+    enum="FamilyLinkUserParentAccessWidgetShowDialogError"
+    expires_after="2023-03-26">
+  <owner>agawronska@chromium.org</owner>
+  <owner>cros-families-eng@google.com</owner>
+  <summary>
+    Records an error where the parent access widget dialog cannot be shown.
+    Reported by the browser when the dialog fails to show. ChromeOS only.
+  </summary>
+  <token key="FlowType">
+    <variant name="All"/>
+    <variant name="WebApprovals"/>
+  </token>
+</histogram>
+
 <histogram name="ChromeOS.FamilyUser.ChildUserTypeMismatchError"
     enum="ChildUserTypeMismatchError" expires_after="2023-12-12">
   <owner>agawronska@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index 8305e95..dae5509 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -557,6 +557,18 @@
   </token>
 </histogram>
 
+<histogram name="IOS.DistantTab.TimeSinceLastUse" units="ms"
+    expires_after="2023-11-01">
+  <owner>ewannpv@chromium.org</owner>
+  <owner>gambard@chromium.org</owner>
+  <owner>bling-team@google.com</owner>
+  <summary>
+    The delay in milliseconds between the Tab is opened from this device and the
+    last time the same Tab was modified on the other device. The 'last modified'
+    time is not updated when the Tab is opened from this device.
+  </summary>
+</histogram>
+
 <histogram name="IOS.EnterTabSwitcherSnapshotResult"
     enum="EnterTabSwitcherSnapshotResult" expires_after="2021-08-01">
   <owner>edchin@chromium.org</owner>
@@ -2152,7 +2164,7 @@
 </histogram>
 
 <histogram name="ManualFallback.PresentedOptions.AllPasswords"
-    units="Credentials" expires_after="2022-12-11">
+    units="Credentials" expires_after="2023-05-01">
   <owner>tmartino@chromium.org</owner>
   <owner>djean@chromium.org</owner>
   <summary>
@@ -2163,7 +2175,7 @@
 </histogram>
 
 <histogram name="ManualFallback.PresentedOptions.CreditCards" units="Cards"
-    expires_after="2022-12-11">
+    expires_after="2023-05-01">
   <owner>tmartino@chromium.org</owner>
   <owner>djean@chromium.org</owner>
   <summary>
@@ -2173,7 +2185,7 @@
 </histogram>
 
 <histogram name="ManualFallback.PresentedOptions.Passwords" units="Credentials"
-    expires_after="2022-12-11">
+    expires_after="2023-05-01">
   <owner>tmartino@chromium.org</owner>
   <owner>djean@chromium.org</owner>
   <summary>
@@ -2184,7 +2196,7 @@
 </histogram>
 
 <histogram name="ManualFallback.PresentedOptions.Profiles" units="Profiles"
-    expires_after="2022-12-11">
+    expires_after="2023-05-01">
   <owner>tmartino@chromium.org</owner>
   <owner>djean@chromium.org</owner>
   <summary>
@@ -2194,7 +2206,7 @@
 </histogram>
 
 <histogram name="ManualFallback.VisibleSuggestions.OpenCreditCards"
-    units="Suggestions" expires_after="2022-12-11">
+    units="Suggestions" expires_after="2023-05-01">
   <owner>tmartino@chromium.org</owner>
   <owner>djean@chromium.org</owner>
   <summary>
@@ -2204,7 +2216,7 @@
 </histogram>
 
 <histogram name="ManualFallback.VisibleSuggestions.OpenPasswords"
-    units="Suggestions" expires_after="2022-12-11">
+    units="Suggestions" expires_after="2023-05-01">
   <owner>tmartino@chromium.org</owner>
   <owner>djean@chromium.org</owner>
   <summary>
@@ -2214,7 +2226,7 @@
 </histogram>
 
 <histogram name="ManualFallback.VisibleSuggestions.OpenProfiles"
-    units="Suggestions" expires_after="2022-12-11">
+    units="Suggestions" expires_after="2023-05-01">
   <owner>tmartino@chromium.org</owner>
   <owner>djean@chromium.org</owner>
   <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index a2a75af..0d90eae 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,24 +5,24 @@
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "f6875c40f8daf10ab6c14f7934a075c90a9bfff3",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/2648c757a2555e5e1c5946e46202fc385d68625b/trace_processor_shell.exe"
+            "hash": "ecbfa1398bd09e5a57bc5218aa9c7aec691cce25",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/ae7f044c460c89dac35657f618549998d665d7eb/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "6373f26144aad58f230d11d6a91efda5a09c9873",
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "4f0adda7bdef9039b2aeb0592cf1e250dd7c0778",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/2648c757a2555e5e1c5946e46202fc385d68625b/trace_processor_shell"
+            "hash": "60685262d29ae642b3846ca2900284b2b44be891",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/ae7f044c460c89dac35657f618549998d665d7eb/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "5f47ee79e59d00bf3889d30ca52315522c158040",
             "full_remote_path": "perfetto-luci-artifacts/v31.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "1117786c6b16e8c7c51e27f5f67c9458defe1381",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/2648c757a2555e5e1c5946e46202fc385d68625b/trace_processor_shell"
+            "hash": "47d0feffdbcd8c0dfe7650dd0874b4c7a291153f",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/ae7f044c460c89dac35657f618549998d665d7eb/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/rust/build_rust.py b/tools/rust/build_rust.py
index f702c68..0a424cf 100755
--- a/tools/rust/build_rust.py
+++ b/tools/rust/build_rust.py
@@ -49,7 +49,7 @@
     os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'clang',
                  'scripts'))
 
-from build import RunCommand
+from build import (AddCMakeToPath, RunCommand)
 from update import (CLANG_REVISION, CLANG_SUB_REVISION, LLVM_BUILD_DIR,
                     GetDefaultHostOs, RmTree, UpdatePackage)
 import build
@@ -137,8 +137,10 @@
 def RunXPy(sub, args, gcc_toolchain_path, verbose):
     ''' Run x.py, Rust's build script'''
     RUSTENV = collections.defaultdict(str, os.environ)
-    # Cargo normally stores files in $HOME. Override this.
-    RUSTENV['CARGO_HOME'] = CARGO_HOME_DIR
+
+    ##### For C/C++ compilation steps #####
+    # The Rust toolchain does include some C/C++ code, and these env vars
+    # control the compilation and library making for that code.
 
     clang_path = os.path.join(LLVM_BUILD_DIR, 'bin')
     if sys.platform == 'win32':
@@ -174,34 +176,33 @@
         RUSTENV['AR'] = os.path.join(clang_path, 'llvm-ar')
         RUSTENV['CC'] = os.path.join(clang_path, 'clang')
         RUSTENV['CXX'] = os.path.join(clang_path, 'clang++')
-        RUSTENV['LD'] = os.path.join(clang_path, 'clang')
 
-    # We use these flags to avoid linking with the system libstdc++.
-    gcc_toolchain_flag = (f'--gcc-toolchain={gcc_toolchain_path}'
-                          if gcc_toolchain_path else '')
-    # These affect how C/C++ files are compiled, but not Rust libs/exes.
-    RUSTENV['CFLAGS'] += f' {gcc_toolchain_flag}'
-    RUSTENV['CXXFLAGS'] += f' {gcc_toolchain_flag}'
-    RUSTENV['LDFLAGS'] += f' {gcc_toolchain_flag}'
+    if gcc_toolchain_path:
+        # We use these flags to avoid linking with the system libstdc++.
+        gcc_toolchain_flag = (f'--gcc-toolchain={gcc_toolchain_path}')
+        RUSTENV['CFLAGS'] += f' {gcc_toolchain_flag}'
+        RUSTENV['CXXFLAGS'] += f' {gcc_toolchain_flag}'
+        RUSTENV['LDFLAGS'] += f' {gcc_toolchain_flag}'
 
-    # These affect how Rust crates are built. A `-Clink-arg=<foo>` arg passes
-    # foo to the clang invocation used to link.
+    ##### For Rust compilation steps #####
+    # These env vars set arguments passed to the rust compiler when building
+    # Rust target.
     #
-    # TODO(https://crbug.com/1281664): remove --no-gc-sections argument.
-    # Workaround for a bug causing std::env::args() to return an empty list,
-    # making Rust binaries unable to take command line arguments. Fix is landed
-    # upstream in LLVM but hasn't rolled into Chromium. Also see:
-    # * https://github.com/rust-lang/rust/issues/92181
-    # * https://reviews.llvm.org/D116528
-    RUSTENV['RUSTFLAGS_BOOTSTRAP'] = (f'-Clinker={clang_path} '
-                                      f'-Clink-arg=-fuse-ld=lld '
-                                      f'-Clink-arg=-Wl,--no-gc-sections')
+    # A `-Clink-arg=<foo>` arg passes `foo`` to the linker invovation.
+
+    RUSTENV['RUSTFLAGS_BOOTSTRAP'] = ''
     if gcc_toolchain_flag:
         RUSTENV['RUSTFLAGS_BOOTSTRAP'] += f' -Clink-arg={gcc_toolchain_flag} '
     if gcc_toolchain_path:
         RUSTENV['RUSTFLAGS_BOOTSTRAP'] += (
             f' -L native={gcc_toolchain_path}/lib64')
     RUSTENV['RUSTFLAGS_NOT_BOOTSTRAP'] = RUSTENV['RUSTFLAGS_BOOTSTRAP']
+
+    ##### Do the build now #####
+
+    # Cargo normally stores files in $HOME. Override this.
+    RUSTENV['CARGO_HOME'] = CARGO_HOME_DIR
+
     os.chdir(RUST_SRC_DIR)
     cmd = [sys.executable, 'x.py', sub]
     if verbose and verbose > 0:
@@ -300,6 +301,8 @@
     # Set up config.toml in Rust source tree to configure build.
     Configure(llvm_libs_root)
 
+    AddCMakeToPath()
+
     if args.run_xpy:
         if rest[0] == '--':
             rest = rest[1:]
diff --git a/tools/rust/update_rust.py b/tools/rust/update_rust.py
index 2614536..ef999443 100755
--- a/tools/rust/update_rust.py
+++ b/tools/rust/update_rust.py
@@ -38,6 +38,10 @@
 CRUBIT_REVISION = 'f5cbdf4b54b0e6b9f63a4464a2c901c82e0f0209'
 CRUBIT_SUB_REVISION = 1
 
+# TODO(crbug.com/1401042): Set this back to None once Clang rolls block on Rust
+# building. Until Clang rolls block on Rust, they frequently roll without a
+# Rust compiler, which causes developer machines/bots to 404 in gclient sync.
+#
 # If not None, use a Rust package built with an older LLVM version than
 # specified in tools/clang/scripts/update.py. This is a fallback for when an
 # LLVM update breaks the Rust build.
@@ -45,7 +49,7 @@
 # This should almost always be None. When a breakage happens the fallback should
 # be temporary. Once fixed, the applicable revision(s) above should be updated
 # and FALLBACK_CLANG_VERSION should be reset to None.
-FALLBACK_CLANG_VERSION = None
+FALLBACK_CLANG_VERSION = 'llvmorg-16-init-13328-g110fe4f4-1'
 
 # Hash of src/stage0.json, which itself contains the stage0 toolchain hashes.
 # We trust the Rust build system checks, but to ensure it is not tampered with
diff --git a/tools/typescript/definitions/autofill_private.d.ts b/tools/typescript/definitions/autofill_private.d.ts
index a3cd9dd3..8d39143 100644
--- a/tools/typescript/definitions/autofill_private.d.ts
+++ b/tools/typescript/definitions/autofill_private.d.ts
@@ -85,6 +85,13 @@
         metadata?: AutofillMetadata;
       }
 
+      export interface IbanEntry {
+        guid?: string;
+        value?: string;
+        nickname?: string;
+        metadata?: AutofillMetadata;
+      }
+
       export interface ValidatePhoneParams {
         phoneNumbers: string[];
         indexOfNewNumber: number;
@@ -97,10 +104,12 @@
           countryCode: string): Promise<AddressComponents>;
       export function getAddressList(): Promise<AddressEntry[]>;
       export function saveCreditCard(card: CreditCardEntry): void;
+      export function saveIban(iban: IbanEntry): void;
       export function removeEntry(guid: string): void;
       export function validatePhoneNumbers(
           params: ValidatePhoneParams): Promise<string[]>;
       export function getCreditCardList(): Promise<CreditCardEntry[]>;
+      export function getIbanList(): Promise<IbanEntry[]>;
       export function maskCreditCard(guid: string): void;
       export function migrateCreditCards(): void;
       export function logServerCardLinkClicked(): void;
@@ -110,7 +119,8 @@
       export function removeVirtualCard(cardId: string): void;
 
       export const onPersonalDataChanged: ChromeEvent<
-          (addresses: AddressEntry[], creditCards: CreditCardEntry[]) => void>;
+          (addresses: AddressEntry[], creditCards: CreditCardEntry[],
+          ibans: IbanEntry[]) => void>;
     }
   }
 }
diff --git a/ui/accessibility/platform/inspect/ax_target_win.cc b/ui/accessibility/platform/inspect/ax_target_win.cc
index 07ed9d49e..a5d1052e 100644
--- a/ui/accessibility/platform/inspect/ax_target_win.cc
+++ b/ui/accessibility/platform/inspect/ax_target_win.cc
@@ -9,39 +9,42 @@
 namespace ui {
 
 AXTargetWin::AXTargetWin() = default;
-AXTargetWin::AXTargetWin(std::nullptr_t) : value_(absl::monostate()) {}
+AXTargetWin::AXTargetWin(std::nullptr_t) : value_(nullptr) {}
 AXTargetWin::AXTargetWin(const AXTargetWin&) = default;
 AXTargetWin::AXTargetWin(AXTargetWin&&) = default;
 
 AXTargetWin::~AXTargetWin() = default;
 
 std::string AXTargetWin::ToString() const {
-  if (absl::holds_alternative<IAccessibleComPtr>(value_))
+  if (!value_)
+    return "NULL";
+
+  if (Is<IAccessibleComPtr>())
     return "IAccessible";
 
-  if (absl::holds_alternative<IA2ComPtr>(value_))
+  if (Is<IA2ComPtr>())
     return "IAccessible2Interface";
 
-  if (absl::holds_alternative<IA2HypertextComPtr>(value_))
+  if (Is<IA2HypertextComPtr>())
     return "IAccessible2HyperlinkInferface";
 
-  if (absl::holds_alternative<IA2TableComPtr>(value_))
+  if (Is<IA2TableComPtr>())
     return "IAccessible2TableInterface";
 
-  if (absl::holds_alternative<IA2TableCellComPtr>(value_))
+  if (Is<IA2TableCellComPtr>())
     return "IAccessible2TableCellInterface";
 
-  if (absl::holds_alternative<IA2TextComPtr>(value_))
+  if (Is<IA2TextComPtr>())
     return "IAccessible2TextInterface";
 
-  if (absl::holds_alternative<IA2ValueComPtr>(value_))
+  if (Is<IA2ValueComPtr>())
     return "IAccessible2ValueInterface";
 
-  if (absl::holds_alternative<std::string>(value_))
-    return "\"" + absl::get<std::string>(value_) + "\"";
+  if (Is<std::string>())
+    return "\"" + As<std::string>() + "\"";
 
-  if (absl::holds_alternative<int>(value_))
-    return base::NumberToString(absl::get<int>(value_));
+  if (Is<int>())
+    return base::NumberToString(As<int>());
 
   return "Unsupported";
 }
diff --git a/ui/accessibility/platform/inspect/ax_target_win.h b/ui/accessibility/platform/inspect/ax_target_win.h
index 0114b3b..c2adb41 100644
--- a/ui/accessibility/platform/inspect/ax_target_win.h
+++ b/ui/accessibility/platform/inspect/ax_target_win.h
@@ -31,42 +31,45 @@
   AXTargetWin(AXTargetWin&&);
 
   template <typename Type>
-  constexpr AXTargetWin(Type&& v) : value_(std::forward<Type>(v)) {}
+  constexpr AXTargetWin(Type&& v)
+      : value_(std::make_shared<VariantType>(std::move(v))) {}
 
   ~AXTargetWin();
 
   template <typename Type>
   bool Is() const {
-    return absl::holds_alternative<Type>(value_);
+    return value_ && absl::holds_alternative<Type>(*value_);
   }
 
   template <typename Type>
   const Type& As() const {
-    return absl::get<Type>(value_);
+    return absl::get<Type>(*value_);
   }
 
   std::string ToString() const;
 
   AXTargetWin& operator=(const AXTargetWin&) = default;
   AXTargetWin& operator=(AXTargetWin&&) = default;
-  constexpr bool operator!() const { return value_.index() == 0; }
+  constexpr bool operator!() const { return value_ == nullptr; }
 
   friend bool operator!=(const AXTargetWin& lhs, const AXTargetWin& rhs) {
     return !(lhs.value_ == rhs.value_);
   }
 
  private:
-  absl::variant<absl::monostate,
-                std::string,
-                int,
-                IAccessibleComPtr,
-                IA2ComPtr,
-                IA2HypertextComPtr,
-                IA2TableComPtr,
-                IA2TableCellComPtr,
-                IA2TextComPtr,
-                IA2ValueComPtr>
-      value_;
+  using VariantType = absl::variant<std::string,
+                                    int,
+                                    IAccessibleComPtr,
+                                    IA2ComPtr,
+                                    IA2HypertextComPtr,
+                                    IA2TableComPtr,
+                                    IA2TableCellComPtr,
+                                    IA2TextComPtr,
+                                    IA2ValueComPtr>;
+
+  // Keep the value const to prevent accidental change of the value shared
+  // between multiple instances of AXTargetWin.
+  std::shared_ptr<const VariantType> value_;  // nocheck
 };
 
 }  // namespace ui
diff --git a/ui/base/cocoa/base_view.mm b/ui/base/cocoa/base_view.mm
index 69e510c..f22f1ea 100644
--- a/ui/base/cocoa/base_view.mm
+++ b/ui/base/cocoa/base_view.mm
@@ -145,6 +145,14 @@
 }
 
 - (void)mouseExited:(NSEvent*)theEvent {
+  // Suppress spurious events that are out of the bounds of this view.
+  // For unknown reasons this happens shortly after mouseMoved on the toolbar if
+  // the overlay window is above the NSToolbarFullScreenWindow.
+  NSRect frameInWindow = [self convertRect:[self bounds] toView:nil];
+  if (NSPointInRect([theEvent locationInWindow], frameInWindow)) {
+    return;
+  }
+
   // The tracking area will send an exit event even during a drag, which isn't
   // how the event flow for drags should work. This stores the exit event, and
   // sends it when the drag completes instead.
diff --git a/ui/chromeos/events/BUILD.gn b/ui/chromeos/events/BUILD.gn
index 3aec02d..4ae81f5 100644
--- a/ui/chromeos/events/BUILD.gn
+++ b/ui/chromeos/events/BUILD.gn
@@ -13,6 +13,8 @@
   sources = [
     "event_rewriter_chromeos.cc",
     "event_rewriter_chromeos.h",
+    "keyboard_capability.cc",
+    "keyboard_capability.h",
     "keyboard_layout_util.cc",
     "keyboard_layout_util.h",
     "modifier_key.h",
@@ -23,6 +25,7 @@
     "//ash/constants",
     "//base",
     "//device/udev_linux",
+    "//ui/base",
     "//ui/base:features",
     "//ui/base/ime/ash",
     "//ui/events",
diff --git a/ui/chromeos/events/OWNERS b/ui/chromeos/events/OWNERS
new file mode 100644
index 0000000..1bef084
--- /dev/null
+++ b/ui/chromeos/events/OWNERS
@@ -0,0 +1 @@
+zentaro@chromium.org
diff --git a/ui/chromeos/events/keyboard_capability.cc b/ui/chromeos/events/keyboard_capability.cc
new file mode 100644
index 0000000..91c412dc
--- /dev/null
+++ b/ui/chromeos/events/keyboard_capability.cc
@@ -0,0 +1,38 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/chromeos/events/keyboard_capability.h"
+
+#include "base/containers/contains.h"
+#include "base/containers/flat_set.h"
+#include "base/no_destructor.h"
+
+namespace ui {
+
+// static
+bool KeyboardCapability::IsSixPackKey(const KeyboardCode& key_code) {
+  return base::Contains(kSixPackKeyToSystemKeyMap, key_code);
+}
+
+bool KeyboardCapability::IsTopRowKey(const KeyboardCode& key_code) const {
+  // A set that includes all top row keys from different keyboards.
+  // TODO(longbowei): Now only include top row keys from layout2, add more top
+  // row keys from other keyboards in the future.
+  static const base::NoDestructor<base::flat_set<KeyboardCode>>
+      top_row_action_keys({
+          VKEY_BROWSER_BACK,
+          VKEY_BROWSER_REFRESH,
+          VKEY_ZOOM,
+          VKEY_MEDIA_LAUNCH_APP1,
+          VKEY_BRIGHTNESS_DOWN,
+          VKEY_BRIGHTNESS_UP,
+          VKEY_MEDIA_PLAY_PAUSE,
+          VKEY_VOLUME_MUTE,
+          VKEY_VOLUME_DOWN,
+          VKEY_VOLUME_UP,
+      });
+  return base::Contains(*top_row_action_keys, key_code);
+}
+
+}  // namespace ui
diff --git a/ui/chromeos/events/keyboard_capability.h b/ui/chromeos/events/keyboard_capability.h
new file mode 100644
index 0000000..bc8c9ada
--- /dev/null
+++ b/ui/chromeos/events/keyboard_capability.h
@@ -0,0 +1,59 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_CHROMEOS_EVENTS_KEYBOARD_CAPABILITY_H_
+#define UI_CHROMEOS_EVENTS_KEYBOARD_CAPABILITY_H_
+
+#include "base/containers/fixed_flat_map.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+
+namespace ui {
+
+// A map between top row keys to function keys.
+inline constexpr auto kLayout2TopRowKeyToFKeyMap =
+    base::MakeFixedFlatMap<KeyboardCode, KeyboardCode>({
+        {KeyboardCode::VKEY_BROWSER_BACK, KeyboardCode::VKEY_F1},
+        {KeyboardCode::VKEY_BROWSER_FORWARD, KeyboardCode::VKEY_F2},
+        {KeyboardCode::VKEY_BROWSER_REFRESH, KeyboardCode::VKEY_F3},
+        {KeyboardCode::VKEY_ZOOM, KeyboardCode::VKEY_F4},
+        {KeyboardCode::VKEY_MEDIA_LAUNCH_APP1, KeyboardCode::VKEY_F5},
+        {KeyboardCode::VKEY_BRIGHTNESS_DOWN, KeyboardCode::VKEY_F6},
+        {KeyboardCode::VKEY_BRIGHTNESS_UP, KeyboardCode::VKEY_F7},
+        {KeyboardCode::VKEY_VOLUME_MUTE, KeyboardCode::VKEY_F8},
+        {KeyboardCode::VKEY_VOLUME_DOWN, KeyboardCode::VKEY_F9},
+        {KeyboardCode::VKEY_VOLUME_UP, KeyboardCode::VKEY_F10},
+    });
+
+// A map between six pack keys to system keys.
+inline constexpr auto kSixPackKeyToSystemKeyMap =
+    base::MakeFixedFlatMap<KeyboardCode, KeyboardCode>({
+        {KeyboardCode::VKEY_DELETE, KeyboardCode::VKEY_BACK},
+        {KeyboardCode::VKEY_HOME, KeyboardCode::VKEY_LEFT},
+        {KeyboardCode::VKEY_UP, KeyboardCode::VKEY_PRIOR},
+        {KeyboardCode::VKEY_END, KeyboardCode::VKEY_RIGHT},
+        {KeyboardCode::VKEY_NEXT, KeyboardCode::VKEY_DOWN},
+        {KeyboardCode::VKEY_INSERT, KeyboardCode::VKEY_BACK},
+    });
+
+// A keyboard util API to provide various keyboard capability information, such
+// as top row key layout, existence of certain keys, what is top right key, etc.
+class KeyboardCapability {
+ public:
+  KeyboardCapability() = default;
+  KeyboardCapability(const KeyboardCapability&) = delete;
+  KeyboardCapability& operator=(const KeyboardCapability&) = delete;
+  ~KeyboardCapability() = default;
+
+  // Check if a key code is one of the six pack keys.
+  static bool IsSixPackKey(const KeyboardCode& key_code);
+
+  // Check if a key code is one of the top row keys.
+  // TODO(zhangwenyu): Support all 4 legacy layouts and custom vivaldi layouts.
+  bool IsTopRowKey(const ui::KeyboardCode& key_code) const;
+};
+
+}  // namespace ui
+
+#endif  // UI_CHROMEOS_EVENTS_KEYBOARD_CAPABILITY_H_
diff --git a/ui/gfx/color_conversions.cc b/ui/gfx/color_conversions.cc
index 449c4b99..52d07612 100644
--- a/ui/gfx/color_conversions.cc
+++ b/ui/gfx/color_conversions.cc
@@ -4,6 +4,8 @@
 
 #include "ui/gfx/color_conversions.h"
 
+#include <cmath>
+
 #include "skia/ext/skcolorspace_primaries.h"
 #include "skia/ext/skcolorspace_trfn.h"
 #include "third_party/skia/include/core/SkColorSpace.h"
@@ -248,7 +250,7 @@
     if (t <= delta_limit)
       return (841.0f / 108.0f) * t + (16.0f / 116.0f);
     else
-      return pow(t, 1.0f / 3.0f);
+      return std::pow(t, 1.0f / 3.0f);
   };
 
   x = LabTransferFunction(x / kD50_x);
@@ -567,4 +569,4 @@
 
   return result;
 }
-}  // namespace gfx
\ No newline at end of file
+}  // namespace gfx
diff --git a/ui/ozone/platform/flatland/BUILD.gn b/ui/ozone/platform/flatland/BUILD.gn
index 27ce4f9..7f71b1f 100644
--- a/ui/ozone/platform/flatland/BUILD.gn
+++ b/ui/ozone/platform/flatland/BUILD.gn
@@ -102,6 +102,7 @@
     "//base",
     "//base/test:test_support",
     "//testing/gtest",
+    "//third_party/fuchsia-sdk/sdk/pkg/scenic_cpp_testing",
     "//ui/events:test_support",
     "//ui/ozone:platform",
     "//ui/ozone:test_support",
diff --git a/ui/ozone/platform/flatland/flatland_surface_unittest.cc b/ui/ozone/platform/flatland/flatland_surface_unittest.cc
index 67c58ef..bc07fab5 100644
--- a/ui/ozone/platform/flatland/flatland_surface_unittest.cc
+++ b/ui/ozone/platform/flatland/flatland_surface_unittest.cc
@@ -5,32 +5,115 @@
 #include "ui/ozone/platform/flatland/flatland_surface.h"
 
 #include <fuchsia/ui/composition/cpp/fidl.h>
+#include <lib/ui/scenic/cpp/testing/fake_flatland.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
 #include <vector>
 
 #include "base/callback_helpers.h"
+#include "base/fuchsia/koid.h"
 #include "base/fuchsia/scoped_service_publisher.h"
 #include "base/fuchsia/test_component_context_for_process.h"
 #include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/gpu_fence_handle.h"
+#include "ui/gfx/overlay_plane_data.h"
 #include "ui/ozone/platform/flatland/flatland_surface_factory.h"
 #include "ui/ozone/platform/flatland/flatland_sysmem_native_pixmap.h"
-#include "ui/ozone/platform/flatland/tests/fake_flatland.h"
 #include "ui/ozone/public/overlay_plane.h"
 
+using ::scenic::FakeGraph;
+using ::scenic::FakeImage;
+using ::scenic::FakeTransform;
+using ::scenic::FakeTransformPtr;
+using ::scenic::FakeView;
 using ::testing::_;
+using ::testing::AllOf;
+using ::testing::Field;
+using ::testing::IsEmpty;
+using ::testing::Matcher;
+using ::testing::Optional;
+using ::testing::Pointee;
+using ::testing::Property;
 using ::testing::SaveArg;
+using ::testing::VariantWith;
 
 namespace ui {
 
 namespace {
 
-const int kTestLogicalsize = 100;
-const float kTestDevicePixelRatio = 1.5;
-const int kTestPhysicalSize = 150;
+Matcher<FakeGraph> IsSurfaceGraph(
+    const fuchsia::ui::composition::ParentViewportWatcherPtr&
+        parent_viewport_watcher,
+    const fuchsia::ui::views::ViewportCreationToken& viewport_creation_token,
+    const fuchsia::math::VecF& scale,
+    std::vector<Matcher<FakeTransformPtr>> child_transform_matchers) {
+  auto view_token_koid = base::GetRelatedKoid(viewport_creation_token.value);
+  auto watcher_koid = base::GetRelatedKoid(parent_viewport_watcher.channel());
+
+  return AllOf(
+      Field("root_transform", &FakeGraph::root_transform,
+            Pointee(AllOf(Field("translation", &FakeTransform::translation,
+                                FakeTransform::kDefaultTranslation),
+                          Field("scale", &FakeTransform::scale, scale),
+                          Field("opacity", &FakeTransform::opacity,
+                                FakeTransform::kDefaultOpacity),
+                          Field("children", &FakeTransform::children,
+                                ElementsAreArray(child_transform_matchers))))),
+      Field("view", &FakeGraph::view,
+            Optional(AllOf(
+                Field("view_token", &FakeView::view_token, view_token_koid),
+                Field("parent_viewport_watcher",
+                      &FakeView::parent_viewport_watcher, watcher_koid)))));
+}
+
+Matcher<fuchsia::ui::composition::ImageProperties> IsImageProperties(
+    const fuchsia::math::SizeU& size) {
+  return AllOf(
+      Property("has_size", &fuchsia::ui::composition::ImageProperties::has_size,
+               true),
+      Property("size", &fuchsia::ui::composition::ImageProperties::size, size));
+}
+
+Matcher<FakeTransformPtr> IsImageTransform(
+    const fuchsia::math::SizeU& size,
+    fuchsia::ui::composition::BlendMode blend_mode,
+    const fuchsia::math::Vec& translation = FakeTransform::kDefaultTranslation,
+    const fuchsia::math::SizeU& destination_size =
+        FakeImage::kDefaultDestinationSize,
+    float image_opacity = FakeImage::kDefaultOpacity) {
+  return Pointee(AllOf(
+      Field("translation", &FakeTransform::translation, translation),
+      Field("scale", &FakeTransform::scale, FakeTransform::kDefaultScale),
+      Field("opacity", &FakeTransform::opacity, FakeTransform::kDefaultOpacity),
+      Field("children", &FakeTransform::children, IsEmpty()),
+      Field("content", &FakeTransform::content,
+            Pointee(VariantWith<FakeImage>(AllOf(
+                Field("image_properties", &FakeImage::image_properties,
+                      IsImageProperties(size)),
+                Field("destination_size", &FakeImage::destination_size,
+                      destination_size),
+                Field("blend_mode", &FakeImage::blend_mode, blend_mode),
+                Field("opacity", &FakeImage::opacity, image_opacity)))))));
+}
+
+scoped_refptr<FlatlandSysmemNativePixmap> CreateFlatlandSysmemNativePixmap(
+    uint32_t image_size) {
+  gfx::NativePixmapHandle handle;
+  zx::eventpair service_handle;
+  zx::eventpair::create(0, &service_handle, &handle.buffer_collection_handle);
+  auto collection = base::MakeRefCounted<FlatlandSysmemBufferCollection>();
+  collection->InitializeForTesting(std::move(service_handle),
+                                   gfx::BufferUsage::SCANOUT);
+  return base::MakeRefCounted<FlatlandSysmemNativePixmap>(
+      collection, std::move(handle), gfx::Size(image_size, image_size));
+}
 
 }  // namespace
 
@@ -62,11 +145,10 @@
     return flatland_surface_.get();
   }
 
-  void SetLayoutInfo() {
+  void SetLayoutInfo(uint32_t width, uint32_t height, float dpr) {
     fuchsia::ui::composition::LayoutInfo layout_info;
-    layout_info.set_logical_size({kTestLogicalsize, kTestLogicalsize});
-    layout_info.set_device_pixel_ratio(
-        {kTestDevicePixelRatio, kTestDevicePixelRatio});
+    layout_info.set_logical_size({width, height});
+    layout_info.set_device_pixel_ratio({dpr, dpr});
     flatland_surface_->OnGetLayout(std::move(layout_info));
   }
 
@@ -74,9 +156,14 @@
     return flatland_surface_->pending_present_closures_.size();
   }
 
+  const fuchsia::ui::composition::ParentViewportWatcherPtr&
+  parent_viewport_watcher() {
+    return flatland_surface_->parent_viewport_watcher_;
+  }
+
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
-  FakeFlatland fake_flatland_;
+  scenic::FakeFlatland fake_flatland_;
 
  private:
   base::TestComponentContextForProcess test_context_;
@@ -105,47 +192,61 @@
 }
 
 TEST_F(FlatlandSurfaceTest, PresentPrimaryPlane) {
-  fake_flatland_.SetPresentHandler(base::DoNothing());
+  size_t presents_called = 0u;
+  fake_flatland_.SetPresentHandler(
+      [&presents_called](auto) { ++presents_called; });
 
   FlatlandSurface* surface = CreateFlatlandSurface();
-  SetLayoutInfo();
+  auto platform_handle = surface->CreateView();
+  fuchsia::ui::views::ViewportCreationToken viewport_creation_token;
+  viewport_creation_token.value = zx::channel(platform_handle.TakeHandle());
 
-  gfx::NativePixmapHandle handle;
-  zx::eventpair service_handle;
-  zx::eventpair::create(0, &service_handle, &handle.buffer_collection_handle);
-  auto collection = base::MakeRefCounted<FlatlandSysmemBufferCollection>();
-  collection->InitializeForTesting(std::move(service_handle),
-                                   gfx::BufferUsage::SCANOUT);
-  auto primary_plane_pixmap = base::MakeRefCounted<FlatlandSysmemNativePixmap>(
-      collection, std::move(handle),
-      gfx::Size(kTestPhysicalSize, kTestPhysicalSize));
+  const int kTestLogicalSize = 100;
+  const float kTestDevicePixelRatio = 1.5;
+  SetLayoutInfo(kTestLogicalSize, kTestLogicalSize, kTestDevicePixelRatio);
+  const float expected_scale = 1 / kTestDevicePixelRatio;
+  const uint32_t expected_image_size = kTestLogicalSize * kTestDevicePixelRatio;
+
+  auto primary_plane_pixmap =
+      CreateFlatlandSysmemNativePixmap(expected_image_size);
   surface->Present(
       primary_plane_pixmap, std::vector<ui::OverlayPlane>(),
       std::vector<gfx::GpuFenceHandle>(), std::vector<gfx::GpuFenceHandle>(),
       base::BindOnce([](gfx::SwapCompletionResult result) {}),
       base::BindOnce([](const gfx::PresentationFeedback& feedback) {}));
 
-  // There should be a present call in FakeFlatland.
+  // There should be a present call in FakeFlatland after flushing the tasks.
+  EXPECT_EQ(0u, presents_called);
   task_environment_.RunUntilIdle();
+  EXPECT_EQ(1u, presents_called);
 
-  // TODO(crbug.com/1307545): Extend with checks on Fake Flatland and Allocator
-  // once they move into fuchsia SDK.
+  EXPECT_THAT(
+      fake_flatland_.graph(),
+      IsSurfaceGraph(
+          parent_viewport_watcher(), viewport_creation_token,
+          {expected_scale, expected_scale},
+          {IsImageTransform({expected_image_size, expected_image_size},
+                            fuchsia::ui::composition::BlendMode::SRC_OVER)}));
 }
 
 TEST_F(FlatlandSurfaceTest, PresentBeforeLayoutInfo) {
-  fake_flatland_.SetPresentHandler(base::DoNothing());
+  size_t presents_called = 0u;
+  fake_flatland_.SetPresentHandler(
+      [&presents_called](auto) { ++presents_called; });
 
   FlatlandSurface* surface = CreateFlatlandSurface();
 
-  gfx::NativePixmapHandle handle;
-  zx::eventpair service_handle;
-  zx::eventpair::create(0, &service_handle, &handle.buffer_collection_handle);
-  auto collection = base::MakeRefCounted<FlatlandSysmemBufferCollection>();
-  collection->InitializeForTesting(std::move(service_handle),
-                                   gfx::BufferUsage::SCANOUT);
-  auto primary_plane_pixmap = base::MakeRefCounted<FlatlandSysmemNativePixmap>(
-      collection, std::move(handle),
-      gfx::Size(kTestPhysicalSize, kTestPhysicalSize));
+  auto platform_handle = surface->CreateView();
+  fuchsia::ui::views::ViewportCreationToken viewport_creation_token;
+  viewport_creation_token.value = zx::channel(platform_handle.TakeHandle());
+
+  const int kTestLogicalSize = 80;
+  const float kTestDevicePixelRatio = 2;
+  const float expected_scale = 1 / kTestDevicePixelRatio;
+  const uint32_t expected_image_size = kTestLogicalSize * kTestDevicePixelRatio;
+
+  auto primary_plane_pixmap =
+      CreateFlatlandSysmemNativePixmap(expected_image_size);
   surface->Present(
       primary_plane_pixmap, std::vector<ui::OverlayPlane>(),
       std::vector<gfx::GpuFenceHandle>(), std::vector<gfx::GpuFenceHandle>(),
@@ -155,8 +256,82 @@
   // There should be a one pending present.
   EXPECT_EQ(1u, NumberOfPendingClosures());
 
-  SetLayoutInfo();
+  SetLayoutInfo(kTestLogicalSize, kTestLogicalSize, kTestDevicePixelRatio);
   EXPECT_EQ(0u, NumberOfPendingClosures());
+
+  EXPECT_EQ(0u, presents_called);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(1u, presents_called);
+  EXPECT_THAT(
+      fake_flatland_.graph(),
+      IsSurfaceGraph(
+          parent_viewport_watcher(), viewport_creation_token,
+          {expected_scale, expected_scale},
+          {IsImageTransform({expected_image_size, expected_image_size},
+                            fuchsia::ui::composition::BlendMode::SRC_OVER)}));
+}
+
+TEST_F(FlatlandSurfaceTest, PresentOverlayPlane) {
+  size_t presents_called = 0u;
+  fake_flatland_.SetPresentHandler(
+      [&presents_called](auto) { ++presents_called; });
+
+  FlatlandSurface* surface = CreateFlatlandSurface();
+  auto platform_handle = surface->CreateView();
+  fuchsia::ui::views::ViewportCreationToken viewport_creation_token;
+  viewport_creation_token.value = zx::channel(platform_handle.TakeHandle());
+
+  const int kTestLogicalSize = 100;
+  const float kTestDevicePixelRatio = 1.5;
+  SetLayoutInfo(kTestLogicalSize, kTestLogicalSize, kTestDevicePixelRatio);
+  const float expected_scale = 1 / kTestDevicePixelRatio;
+  const uint32_t expected_image_size = kTestLogicalSize * kTestDevicePixelRatio;
+
+  auto primary_plane_pixmap =
+      CreateFlatlandSysmemNativePixmap(expected_image_size);
+
+  const float kOverlayOpacity = .7f;
+  const int32_t kOverlayX = 10;
+  const int32_t kOverlayY = 20;
+  const uint32_t kOverlayWidth = expected_image_size - 30;
+  const uint32_t kOverlayHeight = expected_image_size - 40;
+  const gfx::RectF kOverlayBounds(kOverlayX, kOverlayY, kOverlayWidth,
+                                  kOverlayHeight);
+  gfx::OverlayPlaneData overlay_data(
+      /*z_order=*/1, gfx::OVERLAY_TRANSFORM_NONE, kOverlayBounds,
+      /*crop_rect=*/gfx::RectF(),
+      /*enable_blend=*/true,
+      /*damage_rect=*/gfx::Rect(), kOverlayOpacity,
+      gfx::OverlayPriorityHint::kNone,
+      /*rounded_corners=*/gfx::RRectF(), gfx::ColorSpace(),
+      /*hdr_metadata=*/absl::nullopt);
+  ui::OverlayPlane overlay_plane(
+      CreateFlatlandSysmemNativePixmap(expected_image_size), nullptr,
+      overlay_data);
+  std::vector<ui::OverlayPlane> overlays;
+  overlays.push_back(std::move(overlay_plane));
+
+  surface->Present(
+      primary_plane_pixmap, std::move(overlays),
+      std::vector<gfx::GpuFenceHandle>(), std::vector<gfx::GpuFenceHandle>(),
+      base::BindOnce([](gfx::SwapCompletionResult result) {}),
+      base::BindOnce([](const gfx::PresentationFeedback& feedback) {}));
+
+  EXPECT_EQ(0u, presents_called);
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ(1u, presents_called);
+  EXPECT_THAT(
+      fake_flatland_.graph(),
+      IsSurfaceGraph(
+          parent_viewport_watcher(), viewport_creation_token,
+          {expected_scale, expected_scale},
+          {IsImageTransform({expected_image_size, expected_image_size},
+                            fuchsia::ui::composition::BlendMode::SRC_OVER),
+           IsImageTransform({expected_image_size, expected_image_size},
+                            fuchsia::ui::composition::BlendMode::SRC_OVER,
+                            {kOverlayX, kOverlayY},
+                            {kOverlayWidth, kOverlayHeight},
+                            kOverlayOpacity)}));
 }
 
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_keyboard.cc b/ui/ozone/platform/wayland/host/wayland_keyboard.cc
index 3cbe29d3..a8ddeb6e 100644
--- a/ui/ozone/platform/wayland/host/wayland_keyboard.cc
+++ b/ui/ozone/platform/wayland/host/wayland_keyboard.cc
@@ -37,6 +37,34 @@
 
 namespace ui {
 
+namespace {
+
+bool IsModifierKey(int key) {
+  auto dom_code = KeycodeConverter::EvdevCodeToDomCode(key);
+  switch (dom_code) {
+    // Based on ui::NonPrintableCodeEntry map.
+    case DomCode::ALT_LEFT:
+    case DomCode::ALT_RIGHT:
+    case DomCode::SHIFT_LEFT:
+    case DomCode::SHIFT_RIGHT:
+    case DomCode::CONTROL_LEFT:
+    case DomCode::CONTROL_RIGHT:
+    case DomCode::FN:
+    case DomCode::FN_LOCK:
+    case DomCode::HYPER:
+    case DomCode::META_LEFT:
+    case DomCode::META_RIGHT:
+    case DomCode::CAPS_LOCK:
+    case DomCode::NUM_LOCK:
+    case DomCode::SCROLL_LOCK:
+    case DomCode::SUPER:
+      return true;
+    default:
+      return false;
+  }
+}
+}  // namespace
+
 class WaylandKeyboard::ZCRExtendedKeyboard {
  public:
   // Takes the ownership of |extended_keyboard|.
@@ -277,9 +305,9 @@
                                                serial);
   }
 
-  if (kind == KeyEventKind::kKey) {
+  if (kind == KeyEventKind::kKey && !IsModifierKey(key)) {
     auto_repeat_handler_.UpdateKeyRepeat(key, 0 /*scan_code*/, down,
-                                         false /*suppress_auto_repeat*/,
+                                         /*suppress_auto_repeat=*/false,
                                          device_id());
   }
 
diff --git a/ui/ozone/platform/wayland/host/wayland_keyboard_unittest.cc b/ui/ozone/platform/wayland/host/wayland_keyboard_unittest.cc
index 7d079e7..be522a6 100644
--- a/ui/ozone/platform/wayland/host/wayland_keyboard_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_keyboard_unittest.cc
@@ -338,4 +338,102 @@
   });
 }
 
+// This test verifies the following scenario:
+//
+// 1/ press and hold a modifier key (in this case SHIFT, ALT, CTRL);
+// 2/ ensure that no auto-repeat gets triggered;
+// 3/ with the modifier key still pressed, press another
+//    key (in this case 'a');
+// 4/ ensure that they auto-repeat logic gets started and
+//    the modifier key is properly handled as part of the
+//    event construction.
+TEST_F(WaylandKeyboardTest, NoEventAutoRepeatForModifiers) {
+  constexpr int32_t rate = 5;    // num key events per second.
+  constexpr int32_t delay = 60;  // in milliseconds.
+
+  SendEnter(rate, delay);
+
+  const struct {
+    int evdev_key;
+    KeyboardCode key_code;
+    int mods_depressed;
+    int modifier;
+  } kIncomeModifiersAndExpectedResults[] = {
+      {42 /*shift*/, VKEY_SHIFT, 1, EF_SHIFT_DOWN},
+      {29 /*ctrl*/, VKEY_CONTROL, 4, EF_CONTROL_DOWN},
+      {56 /*alt*/, VKEY_MENU, 8, EF_ALT_DOWN},
+  };
+
+  for (auto values : kIncomeModifiersAndExpectedResults) {
+    // Press a modifier key and wait 1s, to ensure no repeated events come.
+    base::RunLoop run_loop;
+    std::vector<std::unique_ptr<Event>> events;
+    EXPECT_CALL(delegate_, DispatchEvent(_))
+        .WillRepeatedly(AppendEvent(&events));
+
+    PostToServerAndWait([&values](wl::TestWaylandServerThread* server) {
+      auto* const keyboard = server->seat()->keyboard()->resource();
+
+      wl_keyboard_send_key(keyboard, server->GetNextSerial(),
+                           server->GetNextTime(), values.evdev_key,
+                           WL_KEYBOARD_KEY_STATE_PRESSED);
+      wl_keyboard_send_modifiers(keyboard, server->GetNextSerial(),
+                                 values.mods_depressed /* mods_depressed*/,
+                                 0 /* mods_latched */, 0 /* mods_locked */,
+                                 0 /* group */);
+    });
+
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+        FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(1000));
+    run_loop.Run();
+    Mock::VerifyAndClearExpectations(&delegate_);
+
+    // Ensure that only the modifier key down event is processed, ie no auto
+    // repeat is triggered.
+    ASSERT_EQ(events.size(), 1u);
+    EXPECT_EQ(events[0]->AsKeyEvent()->key_code(), values.key_code);
+
+    // With the modifier key still held, press another key ('a' in this case).
+    std::vector<std::unique_ptr<Event>> events2;
+    base::RunLoop run_loop2;
+    EXPECT_CALL(delegate_, DispatchEvent(_))
+        .WillRepeatedly(
+            AppendEventAndQuitLoop(&events2, 5u, run_loop2.QuitClosure()));
+    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
+      auto* const keyboard = server->seat()->keyboard()->resource();
+
+      wl_keyboard_send_key(keyboard, server->GetNextSerial(),
+                           server->GetNextTime(), 30 /* a */,
+                           WL_KEYBOARD_KEY_STATE_PRESSED);
+    });
+    run_loop2.Run();
+
+    // Ensure that 4 consecutive auto-repeat key press events are dispatched,
+    // with the proper modifier key.
+    auto check_repeat_event = [&values](const Event& event) {
+      EXPECT_EQ(ET_KEY_PRESSED, event.type());
+      EXPECT_TRUE(event.flags() & (EF_IS_REPEAT | values.modifier));
+      EXPECT_EQ(KeyboardCode::VKEY_A, event.AsKeyEvent()->key_code());
+    };
+    for (size_t i = 1; i < events2.size(); i++) {
+      const auto& repeat_event = *events2[i];
+      check_repeat_event(repeat_event);
+    }
+
+    // Release the keys.
+    Mock::VerifyAndClearExpectations(&delegate_);
+
+    PostToServerAndWait([&values](wl::TestWaylandServerThread* server) {
+      auto* const keyboard = server->seat()->keyboard()->resource();
+
+      wl_keyboard_send_key(keyboard, server->GetNextSerial(),
+                           server->GetNextTime(), values.evdev_key,
+                           WL_KEYBOARD_KEY_STATE_RELEASED);
+      wl_keyboard_send_key(keyboard, server->GetNextSerial(),
+                           server->GetNextTime(), 30 /* a */,
+                           WL_KEYBOARD_KEY_STATE_RELEASED);
+    });
+  }
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index 6f2f59b..cfe40d27 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -3180,15 +3180,7 @@
   });
 }
 
-// TODO(crbug.com/1393155): Flaky on Linux TSAN.
-// TODO(crbug.com/1396725): Failing on Linux MSAN.
-#if BUILDFLAG(IS_LINUX) && \
-    (defined(THREAD_SANITIZER) || defined(MEMORY_SANITIZER))
-#define MAYBE_ReattachesBackgroundOnShow DISABLED_ReattachesBackgroundOnShow
-#else
-#define MAYBE_ReattachesBackgroundOnShow ReattachesBackgroundOnShow
-#endif
-TEST_P(WaylandWindowTest, MAYBE_ReattachesBackgroundOnShow) {
+TEST_P(WaylandWindowTest, ReattachesBackgroundOnShow) {
   EXPECT_TRUE(connection_->buffer_manager_host());
 
   auto interface_ptr = connection_->buffer_manager_host()->BindInterface();
@@ -3231,6 +3223,10 @@
   overlays.push_back(std::move(background));
   buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 1u, gl::FrameData(),
                                       std::move(overlays));
+
+  // Let mojo messages from gpu to host go through.
+  base::RunLoop().RunUntilIdle();
+
   PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
     auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
     mock_surface->SendFrameCallback();
@@ -3270,6 +3266,9 @@
   buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 2u, gl::FrameData(),
                                       std::move(overlays));
 
+  // Let mojo messages from gpu to host go through.
+  base::RunLoop().RunUntilIdle();
+
   PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
     auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
     // WaylandWindow should automatically reattach the background.
diff --git a/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc b/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
index bf69fb1..4fdffd0ec 100644
--- a/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
+++ b/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
@@ -2431,16 +2431,8 @@
 
 // Tests that destroying a channel doesn't result in resetting surface state
 // and buffers can be attached after the channel has been reinitialized.
-// TODO(crbug.com/1396725): Failing on Linux.
-#if BUILDFLAG(IS_LINUX)
-#define MAYBE_CanSubmitBufferAfterChannelDestroyedAndInitialized \
-  DISABLED_CanSubmitBufferAfterChannelDestroyedAndInitialized
-#else
-#define MAYBE_CanSubmitBufferAfterChannelDestroyedAndInitialized \
-  CanSubmitBufferAfterChannelDestroyedAndInitialized
-#endif
 TEST_P(WaylandBufferManagerTest,
-       MAYBE_CanSubmitBufferAfterChannelDestroyedAndInitialized) {
+       CanSubmitBufferAfterChannelDestroyedAndInitialized) {
   constexpr uint32_t kBufferId1 = 1;
 
   const gfx::AcceleratedWidget widget = window_->GetWidget();
@@ -2493,6 +2485,9 @@
   manager_host_->OnChannelDestroyed();
   manager_host_ = connection_->buffer_manager_host();
 
+  // Let mojo messages from host to gpu go through.
+  base::RunLoop().RunUntilIdle();
+
   // The surface must has the buffer detached and all the buffers are destroyed.
   // Release the fence as there is no further need to hold that as the client
   // no longer expects that. Moreover, its next attach may result in a DCHECK,
@@ -2603,6 +2598,9 @@
   manager_host_->OnChannelDestroyed();
   manager_host_ = connection_->buffer_manager_host();
 
+  // Let mojo messages from host to gpu go through.
+  base::RunLoop().RunUntilIdle();
+
   // The root surface should still have the buffer attached....
   PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
     EXPECT_TRUE(server->GetObject<wl::MockSurface>(id)->attached_buffer());
diff --git a/ui/views/cocoa/native_widget_mac_ns_window_host.h b/ui/views/cocoa/native_widget_mac_ns_window_host.h
index f6d8e88..1c30148 100644
--- a/ui/views/cocoa/native_widget_mac_ns_window_host.h
+++ b/ui/views/cocoa/native_widget_mac_ns_window_host.h
@@ -330,6 +330,7 @@
                                 bool full_keyboard_access_enabled) override;
   void OnWindowStateRestorationDataChanged(
       const std::vector<uint8_t>& data) override;
+  void OnWindowParentChanged(uint64_t new_parent_id) override;
   void DoDialogButtonAction(ui::DialogButton button) override;
   bool GetDialogButtonInfo(ui::DialogButton type,
                            bool* button_exists,
diff --git a/ui/views/cocoa/native_widget_mac_ns_window_host.mm b/ui/views/cocoa/native_widget_mac_ns_window_host.mm
index 020c952e..5b71ee9 100644
--- a/ui/views/cocoa/native_widget_mac_ns_window_host.mm
+++ b/ui/views/cocoa/native_widget_mac_ns_window_host.mm
@@ -94,6 +94,7 @@
                                 bool full_keyboard_access_enabled) override {}
   void OnWindowStateRestorationDataChanged(
       const std::vector<uint8_t>& data) override {}
+  void OnWindowParentChanged(uint64_t new_parent_id) override {}
   void DoDialogButtonAction(ui::DialogButton button) override {}
   void OnFocusWindowToolbar() override {}
   void SetRemoteAccessibilityTokens(
@@ -656,17 +657,7 @@
 }
 
 gfx::Rect NativeWidgetMacNSWindowHost::GetContentBoundsInScreen() const {
-  NSView* contentView =
-      (NSView*)GetNativeWindowProperty(kImmersiveContentNSView);
-  if (!contentView)
-    return content_bounds_in_screen_;
-
-  // In immersive fullscreen, the content view is hosted in another NSWindow.
-  NSRect boundsInWindow = [contentView convertRect:contentView.bounds
-                                            toView:nil];
-  NSRect boundsInScreen =
-      [contentView.window convertRectToScreen:boundsInWindow];
-  return gfx::ScreenRectFromNSRect(boundsInScreen);
+  return content_bounds_in_screen_;
 }
 
 gfx::Rect NativeWidgetMacNSWindowHost::GetRestoredBounds() const {
@@ -1241,6 +1232,32 @@
   native_widget_mac_->GetWidget()->OnNativeWidgetWorkspaceChanged();
 }
 
+void NativeWidgetMacNSWindowHost::OnWindowParentChanged(
+    uint64_t new_parent_id) {
+  NativeWidgetMacNSWindowHost* new_parent = GetFromId(new_parent_id);
+
+  if (new_parent == parent_) {
+    return;
+  }
+
+  if (parent_) {
+    auto found = base::ranges::find(parent_->children_, this);
+    CHECK(found != parent_->children_.end());
+    parent_->children_.erase(found);
+    parent_ = nullptr;
+  }
+
+  parent_ = new_parent;
+  if (parent_) {
+    parent_->children_.push_back(this);
+  }
+
+  if (Widget* widget = native_widget_mac_->GetWidget()) {
+    widget->OnNativeWidgetParentChanged(
+        parent_ ? parent_->native_widget_mac()->GetNativeView() : nullptr);
+  }
+}
+
 void NativeWidgetMacNSWindowHost::DoDialogButtonAction(
     ui::DialogButton button) {
   views::DialogDelegate* dialog =
diff --git a/ui/views/widget/native_widget_mac.mm b/ui/views/widget/native_widget_mac.mm
index 009cf159..f9d6283 100644
--- a/ui/views/widget/native_widget_mac.mm
+++ b/ui/views/widget/native_widget_mac.mm
@@ -233,15 +233,7 @@
   // platform-specific corner cases.
   if (params.parent &&
       (!params.z_order || params.z_order == ui::ZOrderLevel::kNormal)) {
-    // In immersive fullscreen, bubbles will be shown under the toolbar by
-    // default. Fix it by using a higher z-order level.
-    // TODO(mek): Figure out how to make this work with remote remote_cocoa
-    // windows.
-    if (remote_cocoa::IsNSToolbarFullScreenWindow(
-            params.parent.GetNativeNSView().window)) {
-      params.z_order = ui::ZOrderLevel::kFloatingWindow;
-    } else if (auto* parent_widget =
-                   Widget::GetWidgetForNativeView(params.parent)) {
+    if (auto* parent_widget = Widget::GetWidgetForNativeView(params.parent)) {
       // If our parent is z-ordered above us, then float a bit higher.
       params.z_order =
           std::max(params.z_order.value_or(ui::ZOrderLevel::kNormal),
diff --git a/ui/webui/resources/js/metrics_reporter/BUILD.gn b/ui/webui/resources/js/metrics_reporter/BUILD.gn
index 487a50b..6fcb00b 100644
--- a/ui/webui/resources/js/metrics_reporter/BUILD.gn
+++ b/ui/webui/resources/js/metrics_reporter/BUILD.gn
@@ -10,7 +10,6 @@
   sources = [ "metrics_reporter.mojom" ]
   webui_module_path = "chrome://resources/js/metrics_reporter"
   public_deps = [ "//mojo/public/mojom/base" ]
-  use_typescript_sources = true
 }
 
 # Output folder used to hold ts_library() output.
@@ -21,24 +20,24 @@
   root_dir = target_gen_dir
   out_dir = preprocess_folder
   composite = true
+  tsconfig_base = "tsconfig_base.json"
   in_files = [
     "metrics_reporter.ts",
     "browser_proxy.ts",
-    "metrics_reporter.mojom-webui.ts",
+    "metrics_reporter.mojom-webui.js",
   ]
   definitions = [ "//tools/typescript/definitions/chrome_timeticks.d.ts" ]
   deps = [
     "//ui/webui/resources:library",
     "//ui/webui/resources/mojo:library",
   ]
-  extra_deps = [
-    ":copy_src",
-    ":mojo_bindings_ts__generator",
-  ]
+  extra_deps = [ ":copy_src_and_mojom" ]
 }
 
-copy("copy_src") {
+copy("copy_src_and_mojom") {
+  deps = [ ":mojo_bindings_js__generator" ]
   sources = [
+    "$root_gen_dir/mojom-webui/ui/webui/resources/js/metrics_reporter/metrics_reporter.mojom-webui.js",
     "browser_proxy.ts",
     "metrics_reporter.ts",
   ]
diff --git a/ui/webui/resources/js/metrics_reporter/tsconfig_base.json b/ui/webui/resources/js/metrics_reporter/tsconfig_base.json
new file mode 100644
index 0000000..5502828
--- /dev/null
+++ b/ui/webui/resources/js/metrics_reporter/tsconfig_base.json
@@ -0,0 +1,6 @@
+{
+  "extends": "../../../../../tools/typescript/tsconfig_base.json",
+  "compilerOptions": {
+    "allowJs": true
+  }
+}