diff --git a/DEPS b/DEPS
index e065460..5c487ac 100644
--- a/DEPS
+++ b/DEPS
@@ -253,19 +253,19 @@
   # 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': 'a83b8c6a78bd9ce39b621fa557e52e770269864f',
+  'skia_revision': 'c6e373d47d5172544ed45dba717d1963bca743b5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '8ff9ed3e60e742ff412cf8dd5f16f6ed343fe809',
+  'v8_revision': 'dbe4449b3e52cf1205dcf1ff05b721b0a180b638',
   # 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': 'c874943b952e4aa24ec1c37a5542e3bcb8c20fad',
+  'angle_revision': '1e1505b53cf065466f21424b5279ace31570ff4c',
   # 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': '732515a031faeecfee06c821d9039ebabd6bb71e',
+  'swiftshader_revision': '7021c484697491248e8336f1be45aa6d068a8b80',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -296,7 +296,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling NaCl
   # and whatever else without interference from each other.
-  'nacl_revision': 'eb062b0b356274c74fe3f567d0165ad97a428ae1',
+  'nacl_revision': 'ab384dc06c9b5bafd0f6ba74083cc67b5fdc16ed',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -368,7 +368,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': '420f4c9fae6773f41982c0fa1883804db995d3ed',
+  'dawn_revision': '2335a974eff76ab3b396652f2a0f051f115ec08c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1131,7 +1131,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'c1ab734908f09df3d4c6672acd66c0cfcd584114',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '6b28c1ddef177b44521ab5e8c438942c397a56d8',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1271,7 +1271,7 @@
     Var('chromium_git') + '/chromium/deps/hunspell_dictionaries.git' + '@' + '18e09b9197a3b1d771c077c530d1a4ebad04c167',
 
   'src/third_party/icu':
-    Var('chromium_git') + '/chromium/deps/icu.git' + '@' + '901474b1806d6b5a068606006dbb4c08e9974353',
+    Var('chromium_git') + '/chromium/deps/icu.git' + '@' + '1fa4e3959ec6637182b7318ac1d382799454806d',
 
   'src/third_party/icu4j': {
       'packages': [
@@ -1735,7 +1735,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '96e54a1b92e78f651941f64c703b98a34df37a1f',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'caf2063a9e8afbdec9af767395bbe74e69237ae3',
+    Var('webrtc_git') + '/src.git' + '@' + '48cda468aa2343731e252a821f926b05411a1603',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1805,7 +1805,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@b0e85070320af039efe9a4dd3be4f41375159ad2',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@dfd275583c7e04bb78054d2f10af210669d2a362',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_contents.cc b/android_webview/browser/aw_contents.cc
index 56e69c9..12d9709 100644
--- a/android_webview/browser/aw_contents.cc
+++ b/android_webview/browser/aw_contents.cc
@@ -418,6 +418,18 @@
   return render_process->GetJavaObject();
 }
 
+ScopedJavaLocalRef<jstring> AwContents::GetVariationsHeader(JNIEnv* env) {
+  const bool is_signed_in = false;
+  auto headers =
+      variations::VariationsIdsProvider::GetInstance()->GetClientDataHeaders(
+          is_signed_in);
+  if (!headers)
+    return ConvertUTF8ToJavaString(env, "");
+  return ConvertUTF8ToJavaString(
+      env,
+      headers->headers_map.at(variations::mojom::GoogleWebVisibility::ANY));
+}
+
 void AwContents::Destroy(JNIEnv* env) {
   java_ref_.reset();
   delete this;
diff --git a/android_webview/browser/aw_contents.h b/android_webview/browser/aw_contents.h
index d10b619e..9674bb99 100644
--- a/android_webview/browser/aw_contents.h
+++ b/android_webview/browser/aw_contents.h
@@ -111,6 +111,7 @@
   base::android::ScopedJavaLocalRef<jobject> GetRenderProcess(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& obj);
+  base::android::ScopedJavaLocalRef<jstring> GetVariationsHeader(JNIEnv* env);
 
   void Destroy(JNIEnv* env);
   void DocumentHasImages(JNIEnv* env,
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/SharedWebViewChromium.java b/android_webview/glue/java/src/com/android/webview/chromium/SharedWebViewChromium.java
index 9f0a2a0e..cd31a2af 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/SharedWebViewChromium.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/SharedWebViewChromium.java
@@ -174,6 +174,14 @@
         return mContentsClientAdapter.getWebViewRendererClientAdapter();
     }
 
+    public String getVariationsHeader() {
+        mAwInit.startYourEngines(true);
+        if (checkNeedsPost()) {
+            return mRunQueue.runOnUiThreadBlocking(() -> getVariationsHeader());
+        }
+        return mAwContents.getVariationsHeader();
+    }
+
     protected boolean checkNeedsPost() {
         boolean needsPost = !mRunQueue.chromiumHasStarted() || !ThreadUtils.runningOnUiThread();
         if (!needsPost && mAwContents == null) {
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index c165420..3020ed3 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -3512,6 +3512,13 @@
         return AwContentsJni.get().getRenderProcess(mNativeAwContents, AwContents.this);
     }
 
+    public String getVariationsHeader() {
+        if (isDestroyed(NO_WARN)) {
+            return "";
+        }
+        return AwContentsJni.get().getVariationsHeader(mNativeAwContents);
+    }
+
     @VisibleForTesting
     public AwDisplayCutoutController getDisplayCutoutController() {
         return mDisplayCutoutController;
@@ -4425,5 +4432,6 @@
         WebMessageListenerInfo[] getJsObjectsInfo(
                 long nativeAwContents, AwContents caller, Class clazz);
         void onConfigurationChanged(long nativeAwContents);
+        String getVariationsHeader(long nativeAwContents);
     }
 }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/VariationsHeadersTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/VariationsHeadersTest.java
index d176ebb..9a94eaa 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/VariationsHeadersTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/VariationsHeadersTest.java
@@ -77,6 +77,14 @@
         Assert.assertFalse(mTestServer.getLastRequest(PATH).headerValue(HEADER_NAME).isEmpty());
     }
 
+    @MediumTest
+    @Test
+    public void testMatchesApiValue() throws Throwable {
+        mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), mUrl);
+        String serverHeaderValue = mTestServer.getLastRequest(PATH).headerValue(HEADER_NAME);
+        Assert.assertEquals(serverHeaderValue, mAwContents.getVariationsHeader());
+    }
+
     @CommandLineFlags.Add({"disable-features=WebViewSendVariationsHeaders"})
     @MediumTest
     @Test
diff --git a/android_webview/nonembedded/java/src/org/chromium/android_webview/services/CrashReceiverService.java b/android_webview/nonembedded/java/src/org/chromium/android_webview/services/CrashReceiverService.java
index 39c7329..79eb69cf 100644
--- a/android_webview/nonembedded/java/src/org/chromium/android_webview/services/CrashReceiverService.java
+++ b/android_webview/nonembedded/java/src/org/chromium/android_webview/services/CrashReceiverService.java
@@ -25,6 +25,8 @@
 import java.util.List;
 import java.util.Map;
 
+import javax.annotation.concurrent.GuardedBy;
+
 /**
  * Service that is responsible for receiving crash dumps from an application, for upload.
  */
@@ -32,6 +34,8 @@
     private static final String TAG = "CrashReceiverService";
 
     private final Object mCopyingLock = new Object();
+
+    @GuardedBy("mCopyingLock")
     private boolean mIsCopying;
 
     private final ICrashReceiverService.Stub mBinder = new ICrashReceiverService.Stub() {
diff --git a/android_webview/support_library/boundary_interfaces/BUILD.gn b/android_webview/support_library/boundary_interfaces/BUILD.gn
index 3938d4e..348cbf57 100644
--- a/android_webview/support_library/boundary_interfaces/BUILD.gn
+++ b/android_webview/support_library/boundary_interfaces/BUILD.gn
@@ -13,7 +13,6 @@
     "src/org/chromium/support_lib_boundary/ProxyControllerBoundaryInterface.java",
     "src/org/chromium/support_lib_boundary/SafeBrowsingResponseBoundaryInterface.java",
     "src/org/chromium/support_lib_boundary/ScriptHandlerBoundaryInterface.java",
-    "src/org/chromium/support_lib_boundary/ScriptReferenceBoundaryInterface.java",
     "src/org/chromium/support_lib_boundary/ServiceWorkerClientBoundaryInterface.java",
     "src/org/chromium/support_lib_boundary/ServiceWorkerControllerBoundaryInterface.java",
     "src/org/chromium/support_lib_boundary/ServiceWorkerWebSettingsBoundaryInterface.java",
diff --git a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/ScriptHandlerBoundaryInterface.java b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/ScriptHandlerBoundaryInterface.java
index 4dcaa75..0be98f8 100644
--- a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/ScriptHandlerBoundaryInterface.java
+++ b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/ScriptHandlerBoundaryInterface.java
@@ -7,4 +7,6 @@
 /**
  * Boundary interface for AwContents.addDocumentStartJavascript().
  */
-public interface ScriptHandlerBoundaryInterface extends ScriptReferenceBoundaryInterface {}
+public interface ScriptHandlerBoundaryInterface {
+    void remove();
+}
diff --git a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/ScriptReferenceBoundaryInterface.java b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/ScriptReferenceBoundaryInterface.java
deleted file mode 100644
index aba822a..0000000
--- a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/ScriptReferenceBoundaryInterface.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.support_lib_boundary;
-
-/**
- * Boundary interface for AwContents.addDocumentStartJavascript().
- *
- * TODO(ctzsm): Delete this interface once we've updated the APKs on
- * the AndroidX bots and move the remove method to ScriptHandlerBoundaryInterface.
- */
-public interface ScriptReferenceBoundaryInterface {
-    void remove();
-}
diff --git a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebViewProviderBoundaryInterface.java b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebViewProviderBoundaryInterface.java
index d524b31..c59afcf 100644
--- a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebViewProviderBoundaryInterface.java
+++ b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebViewProviderBoundaryInterface.java
@@ -28,4 +28,5 @@
     /* WebViewRendererClient */ InvocationHandler getWebViewRendererClient();
     void setWebViewRendererClient(
             /* WebViewRendererClient */ InvocationHandler webViewRendererClient);
+    String getVariationsHeader();
 }
diff --git a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/util/Features.java b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/util/Features.java
index 238e343..53fd95c1 100644
--- a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/util/Features.java
+++ b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/util/Features.java
@@ -184,7 +184,7 @@
     public static final String SET_SUPPORT_LIBRARY_VERSION = "SET_SUPPORT_LIBRARY_VERSION";
 
     // WebViewCompat.addDocumentStartJavascript
-    public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
+    public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT:1";
 
     // WebSettingsCompat.setWebAuthnSupport
     // WebSettingsCompat.getWebAuthnSupport
@@ -195,4 +195,7 @@
     // ServiceWorkerWebSettingsCompat.setRequestedWithHeaderMode
     // ServiceWorkerWebSettingsCompat.getRequestedWithHeaderMode
     public static final String REQUESTED_WITH_HEADER_CONTROL = "REQUESTED_WITH_HEADER_CONTROL";
+
+    // WebViewCompat.getVariationsHeader
+    public static final String GET_VARIATIONS_HEADER = "GET_VARIATIONS_HEADER";
 }
diff --git a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromium.java b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromium.java
index 0b0dd257..a52884070 100644
--- a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromium.java
+++ b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromium.java
@@ -135,4 +135,10 @@
                         ? new SupportLibWebViewRendererClientAdapter(webViewRendererClient)
                         : null);
     }
+
+    @Override
+    public String getVariationsHeader() {
+        recordApiCall(ApiCall.GET_VARIATIONS_HEADER);
+        return mSharedWebViewChromium.getVariationsHeader();
+    }
 }
diff --git a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java
index fb1a6e8..c26be720 100644
--- a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java
+++ b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java
@@ -84,7 +84,8 @@
                     Features.SET_SUPPORT_LIBRARY_VERSION + Features.DEV_SUFFIX,
                     Features.DOCUMENT_START_SCRIPT,
                     Features.PROXY_OVERRIDE_REVERSE_BYPASS,
-                    Features.REQUESTED_WITH_HEADER_CONTROL + Features.DEV_SUFFIX
+                    Features.REQUESTED_WITH_HEADER_CONTROL + Features.DEV_SUFFIX,
+                    Features.GET_VARIATIONS_HEADER + Features.DEV_SUFFIX
             };
 
     // These values are persisted to logs. Entries should not be renumbered and
@@ -148,7 +149,8 @@
             ApiCall.WEB_SETTINGS_SET_REQUESTED_WITH_HEADER_MODE,
             ApiCall.WEB_SETTINGS_GET_REQUESTED_WITH_HEADER_MODE,
             ApiCall.SERVICE_WORKER_SETTINGS_SET_REQUESTED_WITH_HEADER_MODE,
-            ApiCall.SERVICE_WORKER_SETTINGS_GET_REQUESTED_WITH_HEADER_MODE})
+            ApiCall.SERVICE_WORKER_SETTINGS_GET_REQUESTED_WITH_HEADER_MODE,
+            ApiCall.GET_VARIATIONS_HEADER})
     public @interface ApiCall {
         int ADD_WEB_MESSAGE_LISTENER = 0;
         int CLEAR_PROXY_OVERRIDE = 1;
@@ -210,8 +212,9 @@
         int WEB_SETTINGS_GET_REQUESTED_WITH_HEADER_MODE = 57;
         int SERVICE_WORKER_SETTINGS_SET_REQUESTED_WITH_HEADER_MODE = 58;
         int SERVICE_WORKER_SETTINGS_GET_REQUESTED_WITH_HEADER_MODE = 59;
+        int GET_VARIATIONS_HEADER = 60;
         // Remember to update AndroidXWebkitApiCall in enums.xml when adding new values here
-        int COUNT = 60;
+        int COUNT = 61;
     }
     // clang-format on
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 74d22535..6cfa4e1b 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1985,6 +1985,7 @@
     "wm/wm_event.h",
     "wm/wm_highlight_item_border.cc",
     "wm/wm_highlight_item_border.h",
+    "wm/wm_metrics.h",
     "wm/wm_shadow_controller_delegate.cc",
     "wm/wm_shadow_controller_delegate.h",
     "wm/work_area_insets.cc",
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index fb8aa89..a3eb71c 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -79,6 +79,7 @@
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_event.h"
+#include "ash/wm/wm_metrics.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
@@ -901,7 +902,11 @@
                           : WM_EVENT_CYCLE_SNAP_SECONDARY);
   aura::Window* active_window = window_util::GetActiveWindow();
   DCHECK(active_window);
-  WindowState::Get(active_window)->OnWMEvent(&event);
+
+  auto* window_state = WindowState::Get(active_window);
+  window_state->set_snap_action_source(
+      WindowSnapActionSource::kKeyboardShortcutToSnap);
+  window_state->OnWMEvent(&event);
 }
 
 void HandleWindowMinimize() {
diff --git a/ash/ash_prefs.cc b/ash/ash_prefs.cc
index b53bed43..780fb04 100644
--- a/ash/ash_prefs.cc
+++ b/ash/ash_prefs.cc
@@ -44,6 +44,7 @@
 #include "ash/system/unified/hps_notify_controller.h"
 #include "ash/system/unified/top_shortcuts_view.h"
 #include "ash/system/unified/unified_system_tray_controller.h"
+#include "ash/system/usb_peripheral/usb_peripheral_notification_controller.h"
 #include "ash/touch/touch_devices_controller.h"
 #include "ash/wallpaper/wallpaper_controller_impl.h"
 #include "ash/wm/desks/desks_restore_util.h"
@@ -100,6 +101,7 @@
   tray::VPNListView::RegisterProfilePrefs(registry);
   UnifiedSystemTrayController::RegisterProfilePrefs(registry);
   MediaTray::RegisterProfilePrefs(registry);
+  UsbPeripheralNotificationController::RegisterProfilePrefs(registry);
   WallpaperControllerImpl::RegisterProfilePrefs(registry);
   WindowCycleController::RegisterProfilePrefs(registry);
 
diff --git a/ash/components/cryptohome/userdataauth_util.cc b/ash/components/cryptohome/userdataauth_util.cc
index b5623aa..ea4932c 100644
--- a/ash/components/cryptohome/userdataauth_util.cc
+++ b/ash/components/cryptohome/userdataauth_util.cc
@@ -65,6 +65,10 @@
 template COMPONENT_EXPORT(ASH_COMPONENTS_CRYPTOHOME) CryptohomeErrorCode
     ReplyToCryptohomeError(const absl::optional<AddCredentialsReply>&);
 template COMPONENT_EXPORT(ASH_COMPONENTS_CRYPTOHOME) CryptohomeErrorCode
+    ReplyToCryptohomeError(const absl::optional<AuthenticateAuthFactorReply>&);
+template COMPONENT_EXPORT(ASH_COMPONENTS_CRYPTOHOME) CryptohomeErrorCode
+    ReplyToCryptohomeError(const absl::optional<AddAuthFactorReply>&);
+template COMPONENT_EXPORT(ASH_COMPONENTS_CRYPTOHOME) CryptohomeErrorCode
     ReplyToCryptohomeError(const absl::optional<UnmountReply>&);
 
 std::vector<cryptohome::KeyDefinition> GetKeyDataReplyToKeyDefinitions(
diff --git a/ash/constants/ash_pref_names.cc b/ash/constants/ash_pref_names.cc
index 166abae..c1544f8 100644
--- a/ash/constants/ash_pref_names.cc
+++ b/ash/constants/ash_pref_names.cc
@@ -780,6 +780,11 @@
 // Ignored unless powerd is configured to honor charging-related prefs.
 const char kUsbPowerShareEnabled[] = "ash.power.usb_power_share_enabled";
 
+// A bool pref to block the USB-C cable limiting device speed notification if it
+// has already been clicked by the user.
+const char kUsbPeripheralCableSpeedNotificationShown[] =
+    "ash.usb_peripheral_cable_speed_notification_shown";
+
 // An integer pref that specifies how many times the Suggested Content privacy
 // info has been shown in Launcher. This value will increment by one every time
 // when Launcher changes state from Peeking to Half or FullscreenSearch up to a
diff --git a/ash/constants/ash_pref_names.h b/ash/constants/ash_pref_names.h
index d7027cb4..f9dd68d 100644
--- a/ash/constants/ash_pref_names.h
+++ b/ash/constants/ash_pref_names.h
@@ -355,6 +355,9 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kUsbPowerShareEnabled[];
 
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kUsbPeripheralCableSpeedNotificationShown[];
+
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kSuggestedContentInfoShownInLauncher[];
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kSuggestedContentInfoDismissedInLauncher[];
diff --git a/ash/drag_drop/tab_drag_drop_delegate.cc b/ash/drag_drop/tab_drag_drop_delegate.cc
index 4d650ba..ef6f5bec 100644
--- a/ash/drag_drop/tab_drag_drop_delegate.cc
+++ b/ash/drag_drop/tab_drag_drop_delegate.cc
@@ -18,6 +18,7 @@
 #include "ash/wm/splitview/split_view_drag_indicators.h"
 #include "ash/wm/splitview/split_view_utils.h"
 #include "ash/wm/tablet_mode/tablet_mode_browser_window_drag_session_windows_hider.h"
+#include "ash/wm/wm_metrics.h"
 #include "base/pickle.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chromeos/crosapi/cpp/lacros_startup_state.h"
@@ -224,6 +225,8 @@
           snap_position) {
     overview_session->MergeWindowIntoOverviewForWebUITabStrip(new_window);
   } else {
+    WindowState::Get(new_window)
+        ->set_snap_action_source(WindowSnapActionSource::kDragTabToSnap);
     split_view_controller->SnapWindow(new_window, snap_position,
                                       /*activate_window=*/true);
   }
@@ -243,6 +246,8 @@
   // |source_window_| is itself a child window of the browser since it
   // hosts web content (specifically, the tab strip WebUI). Snap its
   // toplevel window which is the browser window.
+  WindowState::Get(new_window)
+      ->set_snap_action_source(WindowSnapActionSource::kDragTabToSnap);
   split_view_controller->SnapWindow(source_window_, opposite_position);
 }
 
diff --git a/ash/frame/snap_controller_impl.cc b/ash/frame/snap_controller_impl.cc
index b21005b..79080ad 100644
--- a/ash/frame/snap_controller_impl.cc
+++ b/ash/frame/snap_controller_impl.cc
@@ -64,6 +64,9 @@
     return;
 
   WindowState* window_state = WindowState::Get(window);
+  window_state->set_snap_action_source(
+      WindowSnapActionSource::kUseCaptionButtonToSnap);
+
   const WMEvent snap_event(snap == chromeos::SnapDirection::kPrimary
                                ? WM_EVENT_SNAP_PRIMARY
                                : WM_EVENT_SNAP_SECONDARY);
diff --git a/ash/quick_pair/message_stream/message_stream_lookup_impl.cc b/ash/quick_pair/message_stream/message_stream_lookup_impl.cc
index c4c1d16b..9007ce1 100644
--- a/ash/quick_pair/message_stream/message_stream_lookup_impl.cc
+++ b/ash/quick_pair/message_stream/message_stream_lookup_impl.cc
@@ -225,8 +225,8 @@
     base::TimeTicks connect_to_service_start_time,
     const CreateMessageStreamAttemptType& type,
     scoped_refptr<device::BluetoothSocket> socket) {
-  QP_LOG(VERBOSE) << __func__ << ": device = " << device_address
-                  << " Type = " << CreateMessageStreamAttemptTypeToString(type);
+  QP_LOG(INFO) << __func__ << ": device = " << device_address
+               << " Type = " << CreateMessageStreamAttemptTypeToString(type);
   RecordMessageStreamConnectToServiceResult(/*success=*/true);
   RecordMessageStreamConnectToServiceTime(base::TimeTicks::Now() -
                                           connect_to_service_start_time);
@@ -246,8 +246,8 @@
   // Because we need to attempt to create MessageStreams at many different
   // iterations due to the variability of Bluetooth APIs, we can expect to
   // see errors here frequently, along with errors followed by a success.
-  QP_LOG(VERBOSE) << __func__ << ": Error = [ " << error_message << "]. Type = "
-                  << CreateMessageStreamAttemptTypeToString(type);
+  QP_LOG(INFO) << __func__ << ": Error = [ " << error_message
+               << "]. Type = " << CreateMessageStreamAttemptTypeToString(type);
   RecordMessageStreamConnectToServiceResult(/*success=*/false);
   RecordMessageStreamConnectToServiceError(error_message);
 }
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
index de5b8b9..765f633 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
@@ -215,7 +215,7 @@
       // will not be able to find the device this way, and we will have to
       // connect via address and add ourselves as a pairing delegate.
 
-      QP_LOG(VERBOSE) << "Key-based pairing changed. Address: "
+      QP_LOG(VERBOSE) << "Sending pair request to device. Address: "
                       << device_address << ". Found device: "
                       << ((bt_device != nullptr) ? "Yes" : "No") << ".";
 
@@ -229,7 +229,8 @@
                       PAIRING_DELEGATE_PRIORITY_HIGH);
 
         adapter_->ConnectDevice(
-            device_address, /*address_type=*/absl::nullopt,
+            device_address,
+            device::BluetoothDevice::AddressType::ADDR_TYPE_PUBLIC,
             base::BindOnce(&FastPairPairerImpl::OnConnectDevice,
                            weak_ptr_factory_.GetWeakPtr()),
             base::BindOnce(&FastPairPairerImpl::OnConnectError,
diff --git a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
index da42ca5..f747040 100644
--- a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
+++ b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
@@ -65,6 +65,10 @@
     return;
   }
 
+  // If we get to this point in the constructor, it means that the user is
+  // logged in to enable this scenario, so we can being our observations. If we
+  // get any log in events, we know to ignore them, since we already
+  // instantiated our retroactive pairing detector.
   retroactive_pairing_detector_instatiated_ = true;
 
   device::BluetoothAdapterFactory::Get()->GetAdapter(
diff --git a/ash/shelf/drag_window_from_shelf_controller.cc b/ash/shelf/drag_window_from_shelf_controller.cc
index 27f5f8c..ca01761 100644
--- a/ash/shelf/drag_window_from_shelf_controller.cc
+++ b/ash/shelf/drag_window_from_shelf_controller.cc
@@ -422,6 +422,8 @@
       SplitViewController::Get(Shell::GetPrimaryRootWindow());
   if (split_view_controller->InSplitViewMode() ||
       snap_position != SplitViewController::NONE) {
+    WindowState::Get(window_)->set_snap_action_source(
+        WindowSnapActionSource::kDragUpFromShelfToSnap);
     split_view_controller->OnWindowDragEnded(
         window_, snap_position, gfx::ToRoundedPoint(location_in_screen));
   }
diff --git a/ash/system/toast/toast_manager_unittest.cc b/ash/system/toast/toast_manager_unittest.cc
index d26bb68..994c7a6d 100644
--- a/ash/system/toast/toast_manager_unittest.cc
+++ b/ash/system/toast/toast_manager_unittest.cc
@@ -514,16 +514,8 @@
   EXPECT_EQ(3, GetToastSerial());
 }
 
-// TODO(crbug.com/1298361): Flakes on CrOS.
-#if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE_ReplaceContentsOfCurrentToastBeforePriorReplacementFinishes \
-  DISABLED_ReplaceContentsOfCurrentToastBeforePriorReplacementFinishes
-#else
-#define MAYBE_ReplaceContentsOfCurrentToastBeforePriorReplacementFinishes \
-  ReplaceContentsOfCurrentToastBeforePriorReplacementFinishes
-#endif
 TEST_F(ToastManagerImplTest,
-       MAYBE_ReplaceContentsOfCurrentToastBeforePriorReplacementFinishes) {
+       ReplaceContentsOfCurrentToastBeforePriorReplacementFinishes) {
   // By default, the animation duration is zero in tests. Set the animation
   // duration to non-zero so that toasts don't immediately close.
   ui::ScopedAnimationDurationScaleMode animation_duration(
@@ -555,7 +547,7 @@
 
   // Cancel the shown toast and wait for the animation to finish.
   CancelToast(id1);
-  task_environment()->FastForwardBy(base::Seconds(1));
+  task_environment()->FastForwardBy(base::Seconds(2));
 
   // Confirm that the toast now showing corresponds with id2.
   EXPECT_EQ(u"TEXT2", GetCurrentText());
diff --git a/ash/system/usb_peripheral/usb_peripheral_notification_controller.cc b/ash/system/usb_peripheral/usb_peripheral_notification_controller.cc
index e34b239..149e789 100644
--- a/ash/system/usb_peripheral/usb_peripheral_notification_controller.cc
+++ b/ash/system/usb_peripheral/usb_peripheral_notification_controller.cc
@@ -4,12 +4,15 @@
 
 #include "ash/system/usb_peripheral/usb_peripheral_notification_controller.h"
 
+#include "ash/constants/ash_pref_names.h"
 #include "ash/public/cpp/new_window_delegate.h"
 #include "ash/public/cpp/notification_utils.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/cros_system_api/dbus/typecd/dbus-constants.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -37,6 +40,18 @@
 const char kNotificationDeviceLandingPageUrl[] =
     "https://support.google.com/chromebook?p=cable_notification_2";
 
+bool GetCableSpeedNotificationShownPref() {
+  PrefService* prefs =
+      Shell::Get()->session_controller()->GetActivePrefService();
+  return prefs->GetBoolean(prefs::kUsbPeripheralCableSpeedNotificationShown);
+}
+
+void SetCableSpeedNotificationShownPref(bool pref) {
+  PrefService* prefs =
+      Shell::Get()->session_controller()->GetActivePrefService();
+  prefs->SetBoolean(prefs::kUsbPeripheralCableSpeedNotificationShown, pref);
+}
+
 bool ShouldDisplayNotification() {
   return Shell::Get()->session_controller()->GetSessionState() ==
              session_manager::SessionState::ACTIVE &&
@@ -46,6 +61,9 @@
 void OnCableNotificationClicked(const std::string& notification_id,
                                 const std::string& landing_page,
                                 absl::optional<int> button_index) {
+  if (notification_id == kUsbPeripheralSpeedLimitingCableNotificationId)
+    SetCableSpeedNotificationShownPref(true);
+
   if (button_index) {
     NewWindowDelegate::GetInstance()->OpenUrl(
         GURL(landing_page), NewWindowDelegate::OpenUrlFrom::kUserInteraction);
@@ -68,6 +86,13 @@
     ash::PeripheralNotificationManager::Get()->RemoveObserver(this);
 }
 
+// static
+void UsbPeripheralNotificationController::RegisterProfilePrefs(
+    PrefRegistrySimple* registry) {
+  registry->RegisterBooleanPref(
+      prefs::kUsbPeripheralCableSpeedNotificationShown, false);
+}
+
 void UsbPeripheralNotificationController::
     OnPeripheralNotificationManagerInitialized() {
   DCHECK(ash::PeripheralNotificationManager::IsInitialized());
@@ -207,7 +232,7 @@
 
 // Notify the user that the cable limits USB device performance.
 void UsbPeripheralNotificationController::OnSpeedLimitingCableWarning() {
-  if (!ShouldDisplayNotification())
+  if (!ShouldDisplayNotification() || GetCableSpeedNotificationShownPref())
     return;
 
   message_center::RichNotificationData optional;
diff --git a/ash/system/usb_peripheral/usb_peripheral_notification_controller.h b/ash/system/usb_peripheral/usb_peripheral_notification_controller.h
index a466014..ad48d76 100644
--- a/ash/system/usb_peripheral/usb_peripheral_notification_controller.h
+++ b/ash/system/usb_peripheral/usb_peripheral_notification_controller.h
@@ -8,6 +8,8 @@
 #include "ash/ash_export.h"
 #include "ash/components/peripheral_notification/peripheral_notification_manager.h"
 
+class PrefRegistrySimple;
+
 namespace message_center {
 class MessageCenter;
 }  // namespace message_center
@@ -25,6 +27,8 @@
       const UsbPeripheralNotificationController&) = delete;
   ~UsbPeripheralNotificationController() override;
 
+  static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
   // Called after parent class is initialized.
   void OnPeripheralNotificationManagerInitialized();
 
diff --git a/ash/system/usb_peripheral/usb_peripheral_notification_controller_unittest.cc b/ash/system/usb_peripheral/usb_peripheral_notification_controller_unittest.cc
index a7f1c53d..3f76eaf6 100644
--- a/ash/system/usb_peripheral/usb_peripheral_notification_controller_unittest.cc
+++ b/ash/system/usb_peripheral/usb_peripheral_notification_controller_unittest.cc
@@ -4,8 +4,10 @@
 
 #include "ash/system/usb_peripheral/usb_peripheral_notification_controller.h"
 
+#include "ash/constants/ash_features.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
+#include "base/test/scoped_feature_list.h"
 #include "ui/message_center/message_center.h"
 
 using message_center::MessageCenter;
@@ -29,7 +31,9 @@
 
 class UsbPeripheralNotificationControllerTest : public AshTestBase {
  public:
-  UsbPeripheralNotificationControllerTest() {}
+  UsbPeripheralNotificationControllerTest() {
+    feature_list_.InitAndEnableFeature(features::kUsbNotificationController);
+  }
   UsbPeripheralNotificationControllerTest(
       const UsbPeripheralNotificationControllerTest&) = delete;
   UsbPeripheralNotificationControllerTest& operator=(
@@ -39,6 +43,9 @@
   UsbPeripheralNotificationController* controller() {
     return Shell::Get()->usb_peripheral_notification_controller();
   }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
 };
 
 TEST_F(UsbPeripheralNotificationControllerTest, InvalidDpCableNotification) {
@@ -118,4 +125,24 @@
   EXPECT_EQ(MessageCenter::Get()->NotificationCount(), 1u);
 }
 
+TEST_F(UsbPeripheralNotificationControllerTest,
+       SpeedLimitingCableNotificationWithClick) {
+  EXPECT_EQ(MessageCenter::Get()->NotificationCount(), 0u);
+  controller()->OnSpeedLimitingCableWarning();
+  EXPECT_EQ(MessageCenter::Get()->NotificationCount(), 1u);
+
+  message_center::Notification* notification =
+      MessageCenter::Get()->FindVisibleNotificationById(
+          kUsbPeripheralSpeedLimitingCableNotificationId);
+  ASSERT_TRUE(notification);
+
+  // Click the notification to close it.
+  notification->delegate()->Click(absl::nullopt, absl::nullopt);
+
+  // Resend the notification, but expect it not to show after being clicked.
+  EXPECT_EQ(MessageCenter::Get()->NotificationCount(), 0u);
+  controller()->OnSpeedLimitingCableWarning();
+  EXPECT_EQ(MessageCenter::Get()->NotificationCount(), 0u);
+}
+
 }  // namespace ash
diff --git a/ash/wm/base_state.cc b/ash/wm/base_state.cc
index d479262..78ed57f 100644
--- a/ash/wm/base_state.cc
+++ b/ash/wm/base_state.cc
@@ -142,6 +142,8 @@
   // then snap |window| to the side that corresponds to |desired_snap_state|.
   if (window_state->CanSnap() &&
       window_state->GetStateType() != desired_snap_state) {
+    window_state->RecordAndResetWindowSnapActionSource();
+
     if (Shell::Get()->overview_controller()->InOverviewSession()) {
       // |window| must already be in split view, and so we do not need to check
       // |SplitViewController::CanSnapWindow|, although in general it is more
diff --git a/ash/wm/client_controlled_state.cc b/ash/wm/client_controlled_state.cc
index e18c9dc..3797b36 100644
--- a/ash/wm/client_controlled_state.cc
+++ b/ash/wm/client_controlled_state.cc
@@ -328,6 +328,13 @@
                            next_state_type == WindowStateType::kPrimarySnapped
                                ? WM_EVENT_SNAP_PRIMARY
                                : WM_EVENT_SNAP_SECONDARY);
+
+      if (event_type == WM_EVENT_RESTORE) {
+        window_state->set_snap_action_source(
+            WindowSnapActionSource::kSnapByWindowStateRestore);
+      }
+      window_state->RecordAndResetWindowSnapActionSource();
+
       // Get the desired window bounds for the snap state.
       gfx::Rect bounds =
           GetSnappedWindowBoundsInParent(window, next_state_type);
diff --git a/ash/wm/default_state.cc b/ash/wm/default_state.cc
index 1d1ecb7..b69a075 100644
--- a/ash/wm/default_state.cc
+++ b/ash/wm/default_state.cc
@@ -365,6 +365,15 @@
     return;
   }
 
+  if (next_state_type == WindowStateType::kPrimarySnapped ||
+      next_state_type == WindowStateType::kSecondarySnapped) {
+    if (type == WM_EVENT_RESTORE) {
+      window_state->set_snap_action_source(
+          WindowSnapActionSource::kSnapByWindowStateRestore);
+    }
+    window_state->RecordAndResetWindowSnapActionSource();
+  }
+
   EnterToNextState(window_state, next_state_type);
 }
 
diff --git a/ash/wm/overview/overview_window_drag_controller.cc b/ash/wm/overview/overview_window_drag_controller.cc
index b643be9..c2b3bec4 100644
--- a/ash/wm/overview/overview_window_drag_controller.cc
+++ b/ash/wm/overview/overview_window_drag_controller.cc
@@ -635,13 +635,15 @@
   const gfx::Point rounded_screen_point =
       gfx::ToRoundedPoint(location_in_screen);
   if (should_allow_split_view_) {
-    // Update the split view divider bar stuatus if necessary. The divider bar
+    // Update the split view divider bar status if necessary. The divider bar
     // should be placed above the dragged window after drag ends. Note here the
     // passed parameters |snap_position_| and |location_in_screen| won't be used
     // in this function for this case, but they are passed in as placeholders.
+    aura::Window* window = item_->GetWindow();
+    WindowState::Get(window)->set_snap_action_source(
+        WindowSnapActionSource::kDragOrSelectOverviewWindowToSnap);
     SplitViewController::Get(Shell::GetPrimaryRootWindow())
-        ->OnWindowDragEnded(item_->GetWindow(), snap_position_,
-                            rounded_screen_point);
+        ->OnWindowDragEnded(window, snap_position_, rounded_screen_point);
 
     // Update window grid bounds and |snap_position_| in case the screen
     // orientation was changed.
@@ -812,6 +814,9 @@
   DCHECK(!SplitViewController::Get(Shell::GetPrimaryRootWindow())
               ->IsDividerAnimating());
   aura::Window* window = item_->GetWindow();
+  WindowState::Get(window)->set_snap_action_source(
+      WindowSnapActionSource::kDragOrSelectOverviewWindowToSnap);
+
   split_view_controller->SnapWindow(window, snap_position,
                                     /*activate_window=*/true);
   item_ = nullptr;
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index b3cf199..426a662 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -288,6 +288,9 @@
   void EndTabDragging(aura::Window* window, bool is_being_destroyed) {
     dragged_window_->RemoveObserver(this);
     dragged_window_ = nullptr;
+
+    WindowState::Get(window)->set_snap_action_source(
+        WindowSnapActionSource::kDragTabToSnap);
     split_view_controller_->EndWindowDragImpl(window, is_being_destroyed,
                                               desired_snap_position_,
                                               last_location_in_screen_);
@@ -564,6 +567,8 @@
 
     // Snap the window on the non-default side of the screen if split view mode
     // is active.
+    WindowState::Get(window)->set_snap_action_source(
+        WindowSnapActionSource::kAutoSnapBySplitview);
     split_view_controller_->SnapWindow(
         window, (split_view_controller_->default_snap_position() == LEFT)
                     ? RIGHT
@@ -1404,6 +1409,9 @@
       OverviewStartAction::kOverviewButtonLongPress,
       OverviewEnterExitType::kImmediateEnter);
 
+  WindowState::Get(target_window)
+      ->set_snap_action_source(
+          WindowSnapActionSource::kLongPressOverviewButtonToSnap);
   SnapWindow(target_window, SplitViewController::LEFT,
              /*activate_window=*/true);
   base::RecordAction(
@@ -1714,6 +1722,9 @@
       // is notified.
       overview_item->RestoreWindow(/*reset_transform=*/false);
       overview_session->RemoveItem(overview_item.get());
+
+      WindowState::Get(window)->set_snap_action_source(
+          WindowSnapActionSource::kAutoSnapBySplitview);
       SnapWindow(window, (default_snap_position_ == LEFT) ? RIGHT : LEFT);
       // If ending overview causes a window to snap, also do not do exiting
       // overview animation.
diff --git a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
index 4611cbf..e026714 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
@@ -286,6 +286,9 @@
         ShouldDropWindowIntoOverview(snap_position, location_in_screen),
         snap_position != SplitViewController::NONE);
   }
+
+  WindowState::Get(dragged_window_)
+      ->set_snap_action_source(WindowSnapActionSource::kDragDownFromTopToSnap);
   split_view_controller_->OnWindowDragEnded(
       dragged_window_, snap_position, gfx::ToRoundedPoint(location_in_screen));
   split_view_drag_indicators_->SetWindowDraggingState(
diff --git a/ash/wm/tablet_mode/tablet_mode_window_state.cc b/ash/wm/tablet_mode/tablet_mode_window_state.cc
index 54d909bd..3689b3a 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_state.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_state.cc
@@ -294,12 +294,17 @@
     case WM_EVENT_RESTORE: {
       // We special handle WM_EVENT_RESTORE event here.
       WindowStateType restore_state = window_state->GetRestoreWindowState();
-      if (restore_state == WindowStateType::kPrimarySnapped)
+      if (restore_state == WindowStateType::kPrimarySnapped) {
+        window_state->set_snap_action_source(
+            WindowSnapActionSource::kSnapByWindowStateRestore);
         DoTabletSnap(window_state, WM_EVENT_SNAP_PRIMARY);
-      else if (restore_state == WindowStateType::kSecondarySnapped)
+      } else if (restore_state == WindowStateType::kSecondarySnapped) {
+        window_state->set_snap_action_source(
+            WindowSnapActionSource::kSnapByWindowStateRestore);
         DoTabletSnap(window_state, WM_EVENT_SNAP_SECONDARY);
-      else
+      } else {
         UpdateWindow(window_state, restore_state, /*animate=*/true);
+      }
       break;
     }
     case WM_EVENT_SNAP_PRIMARY:
@@ -533,6 +538,7 @@
   }
   // If |window| can snap in split view, then snap |window| in |snap_position|.
   if (split_view_controller->CanSnapWindow(window)) {
+    window_state->RecordAndResetWindowSnapActionSource();
     split_view_controller->SnapWindow(window, snap_position);
     return;
   }
@@ -553,6 +559,8 @@
   }
 
   window_state->set_bounds_changed_by_user(true);
+  window_state->RecordAndResetWindowSnapActionSource();
+
   // A snap WMEvent will put the window in tablet split view.
   split_view_controller->OnWindowSnapWMEvent(window, snap_event_type);
 
diff --git a/ash/wm/window_state.cc b/ash/wm/window_state.cc
index ef043bcb..fe19f303 100644
--- a/ash/wm/window_state.cc
+++ b/ash/wm/window_state.cc
@@ -31,6 +31,7 @@
 #include "ash/wm/window_state_observer.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_event.h"
+#include "ash/wm/wm_metrics.h"
 #include "base/auto_reset.h"
 #include "base/containers/adapters.h"
 #include "base/containers/fixed_flat_map.h"
@@ -1189,4 +1190,10 @@
   return true;
 }
 
+void WindowState::RecordAndResetWindowSnapActionSource() {
+  base::UmaHistogramEnumeration(kWindowSnapActionSourceHistogram,
+                                snap_action_source_);
+  snap_action_source_ = WindowSnapActionSource::kOthers;
+}
+
 }  // namespace ash
diff --git a/ash/wm/window_state.h b/ash/wm/window_state.h
index 29d35e7..53203e7 100644
--- a/ash/wm/window_state.h
+++ b/ash/wm/window_state.h
@@ -11,6 +11,7 @@
 #include "ash/ash_export.h"
 #include "ash/display/persistent_window_info.h"
 #include "ash/wm/drag_details.h"
+#include "ash/wm/wm_metrics.h"
 #include "base/gtest_prod_util.h"
 #include "base/observer_list.h"
 #include "base/time/time.h"
@@ -370,6 +371,10 @@
   const DragDetails* drag_details() const { return drag_details_.get(); }
   DragDetails* drag_details() { return drag_details_.get(); }
 
+  void set_snap_action_source(WindowSnapActionSource type) {
+    snap_action_source_ = type;
+  }
+
   const std::vector<chromeos::WindowStateType>&
   window_state_restore_history_for_testing() const {
     return window_state_restore_history_;
@@ -512,6 +517,8 @@
 
   bool CanUnresizableSnapOnDisplay(display::Display display) const;
 
+  void RecordAndResetWindowSnapActionSource();
+
   // The owner of this window settings.
   aura::Window* window_;
   std::unique_ptr<WindowStateDelegate> delegate_;
@@ -576,6 +583,10 @@
   // can restore back to. See kWindowStateRestoreHistoryLayerMap in the cc file
   // for what window state types that can be put in the restore history stack.
   std::vector<chromeos::WindowStateType> window_state_restore_history_;
+
+  // This is used to record where the current snap window state change request
+  // comes from.
+  WindowSnapActionSource snap_action_source_ = WindowSnapActionSource::kOthers;
 };
 
 }  // namespace ash
diff --git a/ash/wm/window_state_unittest.cc b/ash/wm/window_state_unittest.cc
index c43d96d..b9ef138 100644
--- a/ash/wm/window_state_unittest.cc
+++ b/ash/wm/window_state_unittest.cc
@@ -8,16 +8,21 @@
 
 #include "ash/constants/app_types.h"
 #include "ash/metrics/pip_uma.h"
+#include "ash/public/cpp/accelerators.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/test/test_window_builder.h"
+#include "ash/wm/overview/overview_item.h"
+#include "ash/wm/overview/overview_test_util.h"
 #include "ash/wm/pip/pip_positioner.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
+#include "ash/wm/window_resizer.h"
 #include "ash/wm/window_state_util.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_event.h"
+#include "ash/wm/wm_metrics.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "chromeos/ui/base/window_state_type.h"
@@ -1280,6 +1285,77 @@
   EXPECT_EQ(window_state->GetStateType(), WindowStateType::kNormal);
 }
 
+TEST_F(WindowStateTest, WindowSnapActionSourceUmaMetrics) {
+  UpdateDisplay("800x600");
+  base::HistogramTester histograms;
+  std::unique_ptr<aura::Window> window(CreateAppWindow());
+  WindowState* window_state = WindowState::Get(window.get());
+
+  // Use WMEvent to directly snap the window.
+  WMEvent snap_left(WM_EVENT_SNAP_PRIMARY);
+  window_state->OnWMEvent(&snap_left);
+  histograms.ExpectBucketCount(kWindowSnapActionSourceHistogram,
+                               WindowSnapActionSource::kOthers, 1);
+  window_state->Maximize();
+
+  // Drag the window to the screen edge to snap.
+  std::unique_ptr<WindowResizer> resizer(CreateWindowResizer(
+      window.get(), gfx::PointF(), HTCAPTION, ::wm::WINDOW_MOVE_SOURCE_TOUCH));
+  resizer->Drag(gfx::PointF(0, 400), 0);
+  resizer->CompleteDrag();
+  resizer.reset();
+  histograms.ExpectBucketCount(kWindowSnapActionSourceHistogram,
+                               WindowSnapActionSource::kDragWindowToEdgeToSnap,
+                               1);
+  window_state->Maximize();
+
+  // Use keyboard to snap a window.
+  AcceleratorController::Get()->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_LEFT,
+                                                       {});
+  histograms.ExpectBucketCount(kWindowSnapActionSourceHistogram,
+                               WindowSnapActionSource::kKeyboardShortcutToSnap,
+                               1);
+  window_state->Maximize();
+
+  // Restore the maximized window to snap window state.
+  window_state->Restore();
+  histograms.ExpectBucketCount(
+      kWindowSnapActionSourceHistogram,
+      WindowSnapActionSource::kSnapByWindowStateRestore, 1);
+  window_state->Maximize();
+
+  // Drag or select overview window to snap window.
+  ui::test::EventGenerator* generator = GetEventGenerator();
+  EnterOverview();
+  ASSERT_TRUE(GetOverviewSession());
+  const gfx::Point center_point =
+      gfx::ToRoundedPoint(GetOverviewSession()
+                              ->GetOverviewItemForWindow(window.get())
+                              ->target_bounds()
+                              .CenterPoint());
+  generator->MoveMouseTo(center_point);
+  generator->DragMouseTo(gfx::Point(0, 400));
+  histograms.ExpectBucketCount(
+      kWindowSnapActionSourceHistogram,
+      WindowSnapActionSource::kDragOrSelectOverviewWindowToSnap, 1);
+  window_state->Maximize();
+
+  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
+  EXPECT_TRUE(Shell::Get()->tablet_mode_controller()->InTabletMode());
+
+  // Use keyboard to snap the window in tablet mode.
+  AcceleratorController::Get()->PerformActionIfEnabled(WINDOW_CYCLE_SNAP_LEFT,
+                                                       {});
+  histograms.ExpectBucketCount(kWindowSnapActionSourceHistogram,
+                               WindowSnapActionSource::kKeyboardShortcutToSnap,
+                               2);
+
+  // Auto-snap in splitview.
+  std::unique_ptr<aura::Window> window2(CreateAppWindow());
+  histograms.ExpectBucketCount(kWindowSnapActionSourceHistogram,
+                               WindowSnapActionSource::kAutoSnapBySplitview, 1);
+}
+
 // Test WindowStateTest functionalities with portrait display. This test is
 // parameterized to enable vertical layout or horizontal layout snap in
 // portrait display.
diff --git a/ash/wm/wm_metrics.h b/ash/wm/wm_metrics.h
new file mode 100644
index 0000000..b39adc0
--- /dev/null
+++ b/ash/wm/wm_metrics.h
@@ -0,0 +1,35 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WM_WM_METRICS_H_
+#define ASH_WM_WM_METRICS_H_
+
+namespace ash {
+
+// Used to record different ways to snap a window. Note this should be kept in
+// sync with WindowSnapActionSource enum in tools/metrics/histograms/enums.xml.
+enum class WindowSnapActionSource {
+  kDragWindowToEdgeToSnap,
+  kUseCaptionButtonToSnap,
+  kKeyboardShortcutToSnap,
+  kDragOrSelectOverviewWindowToSnap,
+  kLongPressOverviewButtonToSnap,
+  kDragUpFromShelfToSnap,
+  kDragDownFromTopToSnap,
+  kDragTabToSnap,
+  kAutoSnapBySplitview,
+  kSnapByWindowStateRestore,
+  kOthers,  // This can include any actions that's not covered above, e.g.,
+            // window snap by full restore, desk template, desk switch or user
+            // switch, etc
+  kMaxValue = kOthers,
+};
+
+// Used to save histogram metrics about how the user initiates window snapping.
+constexpr char kWindowSnapActionSourceHistogram[] =
+    "Ash.Wm.WindowSnapActionSource";
+
+}  // namespace ash
+
+#endif  // ASH_WM_WM_METRICS_H_
diff --git a/ash/wm/workspace/workspace_window_resizer.cc b/ash/wm/workspace/workspace_window_resizer.cc
index b827e89..17d46e0 100644
--- a/ash/wm/workspace/workspace_window_resizer.cc
+++ b/ash/wm/workspace/workspace_window_resizer.cc
@@ -866,10 +866,14 @@
     switch (snap_type_) {
       case SnapType::kPrimary:
         type = WM_EVENT_SNAP_PRIMARY;
+        window_state()->set_snap_action_source(
+            WindowSnapActionSource::kDragWindowToEdgeToSnap);
         base::RecordAction(base::UserMetricsAction("WindowDrag_MaximizeLeft"));
         break;
       case SnapType::kSecondary:
         type = WM_EVENT_SNAP_SECONDARY;
+        window_state()->set_snap_action_source(
+            WindowSnapActionSource::kDragWindowToEdgeToSnap);
         base::RecordAction(base::UserMetricsAction("WindowDrag_MaximizeRight"));
         break;
       case SnapType::kMaximize:
@@ -1655,6 +1659,9 @@
     case WindowStateType::kPrimarySnapped:
       if (window_state->CanSnap()) {
         window_state->SetRestoreBoundsInParent(restore_bounds_for_gesture_);
+        window_state->set_snap_action_source(
+            WindowSnapActionSource::kDragWindowToEdgeToSnap);
+
         const WMEvent event(WM_EVENT_SNAP_PRIMARY);
         window_state->OnWMEvent(&event);
       }
@@ -1662,6 +1669,9 @@
     case WindowStateType::kSecondarySnapped:
       if (window_state->CanSnap()) {
         window_state->SetRestoreBoundsInParent(restore_bounds_for_gesture_);
+        window_state->set_snap_action_source(
+            WindowSnapActionSource::kDragWindowToEdgeToSnap);
+
         const WMEvent event(WM_EVENT_SNAP_SECONDARY);
         window_state->OnWMEvent(&event);
       }
diff --git a/base/json/string_escape.cc b/base/json/string_escape.cc
index d1253e2..4fb9630 100644
--- a/base/json/string_escape.cc
+++ b/base/json/string_escape.cc
@@ -92,8 +92,7 @@
   for (int32_t i = 0; i < length; ++i) {
     uint32_t code_point;
     if (!ReadUnicodeCharacter(str.data(), length, &i, &code_point) ||
-        code_point == static_cast<decltype(code_point)>(CBU_SENTINEL) ||
-        !IsValidCodepoint(code_point)) {
+        code_point == static_cast<decltype(code_point)>(CBU_SENTINEL)) {
       code_point = kReplacementCodePoint;
       did_replacement = true;
     }
diff --git a/base/memory/raw_ptr.h b/base/memory/raw_ptr.h
index 0671814..3069c513 100644
--- a/base/memory/raw_ptr.h
+++ b/base/memory/raw_ptr.h
@@ -17,6 +17,7 @@
 #include "base/check.h"
 #include "base/compiler_specific.h"
 #include "base/dcheck_is_on.h"
+#include "base/trace_event/base_tracing_forward.h"
 #include "build/build_config.h"
 #include "build/buildflag.h"
 
@@ -828,6 +829,14 @@
     std::swap(lhs.wrapped_ptr_, rhs.wrapped_ptr_);
   }
 
+  // If T can be serialised into trace, its alias is also
+  // serialisable.
+  template <class U = T>
+  typename perfetto::check_traced_value_support<U>::type WriteIntoTrace(
+      perfetto::TracedValue&& context) const {
+    perfetto::WriteIntoTracedValue(std::move(context), get());
+  }
+
  private:
   // This getter is meant for situations where the pointer is meant to be
   // dereferenced. It is allowed to crash on nullptr (it may or may not),
diff --git a/base/memory/raw_ptr_unittest.cc b/base/memory/raw_ptr_unittest.cc
index b291d913..b25b1ce47 100644
--- a/base/memory/raw_ptr_unittest.cc
+++ b/base/memory/raw_ptr_unittest.cc
@@ -17,6 +17,10 @@
 #include "build/buildflag.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(ENABLE_BASE_TRACING)
+#include "third_party/perfetto/include/perfetto/test/traced_value_test_support.h"  // no-presubmit-check nogncheck
+#endif  // BUILDFLAG(ENABLE_BASE_TRACING)
+
 using testing::Test;
 
 static_assert(sizeof(raw_ptr<void>) == sizeof(void*),
@@ -962,6 +966,31 @@
             checked_derived2_ptr);
 }
 
+#if BUILDFLAG(ENABLE_BASE_TRACING)
+TEST_F(RawPtrTest, TracedValueSupport) {
+  // Serialise nullptr.
+  EXPECT_EQ(perfetto::TracedValueToString(raw_ptr<int>()), "0x0");
+
+  {
+    // If the pointer is non-null, its dereferenced value will be serialised.
+    int value = 42;
+    EXPECT_EQ(perfetto::TracedValueToString(raw_ptr<int>(&value)), "42");
+  }
+
+  struct WithTraceSupport {
+    void WriteIntoTrace(perfetto::TracedValue ctx) const {
+      std::move(ctx).WriteString("result");
+    }
+  };
+
+  {
+    WithTraceSupport value;
+    EXPECT_EQ(perfetto::TracedValueToString(raw_ptr<WithTraceSupport>(&value)),
+              "result");
+  }
+}
+#endif  // BUILDFLAG(ENABLE_BASE_TRACING)
+
 class PmfTestBase {
  public:
   int MemFunc(char, double) const { return 11; }
diff --git a/base/trace_event/base_tracing_forward.h b/base/trace_event/base_tracing_forward.h
index f72f9fde..677966b8 100644
--- a/base/trace_event/base_tracing_forward.h
+++ b/base/trace_event/base_tracing_forward.h
@@ -19,7 +19,7 @@
 class TracedValue;
 
 template <typename T>
-void WriteIntoTrace(TracedValue context, T&& value);
+void WriteIntoTracedValue(TracedValue context, T&& value);
 
 template <typename T, class = void>
 struct TraceFormatTraits;
diff --git a/base/types/DEPS b/base/types/DEPS
deleted file mode 100644
index d89c3e1..0000000
--- a/base/types/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
-  "-third_party",
-]
diff --git a/base/types/strong_alias.h b/base/types/strong_alias.h
index 8c148d4..fcd6716 100644
--- a/base/types/strong_alias.h
+++ b/base/types/strong_alias.h
@@ -10,6 +10,7 @@
 #include <utility>
 
 #include "base/template_util.h"
+#include "base/trace_event/base_tracing_forward.h"
 
 namespace base {
 
@@ -135,6 +136,14 @@
     }
   };
 
+  // If UnderlyingType can be serialised into trace, its alias is also
+  // serialisable.
+  template <class U = UnderlyingType>
+  typename perfetto::check_traced_value_support<U>::type WriteIntoTrace(
+      perfetto::TracedValue&& context) const {
+    perfetto::WriteIntoTracedValue(std::move(context), value_);
+  }
+
  protected:
   UnderlyingType value_;
 };
diff --git a/base/types/strong_alias_unittest.cc b/base/types/strong_alias_unittest.cc
index 94f3198..c9f559c 100644
--- a/base/types/strong_alias_unittest.cc
+++ b/base/types/strong_alias_unittest.cc
@@ -17,6 +17,10 @@
 #include "base/template_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(ENABLE_BASE_TRACING)
+#include "third_party/perfetto/include/perfetto/test/traced_value_test_support.h"  // no-presubmit-check nogncheck
+#endif  // BUILDFLAG(ENABLE_BASE_TRACING)
+
 namespace base {
 
 namespace {
@@ -381,4 +385,11 @@
       !base::internal::SupportsOstreamOperator<NonStreamableAlias>::value, "");
 }
 
+#if BUILDFLAG(ENABLE_BASE_TRACING)
+TEST(StrongAliasTest, TracedValueSupport) {
+  using IntAlias = StrongAlias<class FooTag, int>;
+  EXPECT_EQ(perfetto::TracedValueToString(IntAlias(42)), "42");
+}
+#endif  // BUILDFLAG(ENABLE_BASE_TRACING)
+
 }  // namespace base
diff --git a/build/sanitizers/tsan_suppressions.cc b/build/sanitizers/tsan_suppressions.cc
index f575398a..01f25b21 100644
--- a/build/sanitizers/tsan_suppressions.cc
+++ b/build/sanitizers/tsan_suppressions.cc
@@ -81,9 +81,6 @@
     // http://crbug.com/380554
     "deadlock:g_type_add_interface_static\n"
 
-    // http:://crbug.com/386385
-    "race:content::AppCacheStorageImpl::DatabaseTask::CallRunCompleted\n"
-
     // http://crbug.com/397022
     "deadlock:"
     "base::trace_event::TraceEventTestFixture_ThreadOnceBlocking_Test::"
diff --git a/build/toolchain/apple/linker_driver.py b/build/toolchain/apple/linker_driver.py
index 2afe63b..33809eb 100755
--- a/build/toolchain/apple/linker_driver.py
+++ b/build/toolchain/apple/linker_driver.py
@@ -51,9 +51,17 @@
 # -Wcrl,strippath,<strip_path>
 #    Sets the path to the strip to run with -Wcrl,strip, in which case
 #    `xcrun` is not used to invoke it.
+#
+# -Wcrl,pad_linkedit,<size>
+#    Pads the total size of the linker's output to the specified size in bytes,
+#    after running the linker and `strip`. Padding is added to the __LINKEDIT
+#    segment by the pad_linkedit.py script located in the same directory as this
+#    script. It is an error to attempt to reduce the size of the linked image
+#    using this option.
 
 
 class LinkerDriver(object):
+
     def __init__(self, args):
         """Creates a new linker driver.
 
@@ -74,6 +82,7 @@
             ('unstripped,', self.run_save_unstripped),
             ('strippath,', self.set_strip_path),
             ('strip,', self.run_strip),
+            ('pad_linkedit,', self.run_pad_linkedit),
         ]
 
         # Linker driver actions can modify the these values.
@@ -278,6 +287,21 @@
         self._strip_cmd = [strip_path]
         return []
 
+    def run_pad_linkedit(self, size_string):
+        """Linker driver action for -Wcrl,pad_linkedit,<size>.
+
+        Args:
+            size_string: string, the size to pass to pad_linkedit.py.
+
+        Returns:
+            list of string, Build step outputs.
+        """
+        pad_linkedit_command = (os.path.join(os.path.dirname(__file__),
+                                             'pad_linkedit.py'),
+                                self._get_linker_output(), size_string)
+        subprocess.check_call(pad_linkedit_command)
+        return []
+
 
 # Regular expressions matching log spam messages from dsymutil.
 DSYM_SPURIOUS_PATTERNS = [
diff --git a/build/toolchain/apple/pad_linkedit.py b/build/toolchain/apple/pad_linkedit.py
new file mode 100755
index 0000000..b41f186
--- /dev/null
+++ b/build/toolchain/apple/pad_linkedit.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python3
+# coding: utf-8
+
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Increases the size of a Mach-O image by adding padding to its __LINKEDIT
+# segment.
+
+import argparse
+import os
+import struct
+import sys
+
+# Constants from <mach-o/loader.h>.
+_MH_MAGIC = 0xfeedface
+_MH_MAGIC_64 = 0xfeedfacf
+_LC_SEGMENT = 0x1
+_LC_SEGMENT_64 = 0x19
+_LC_CODE_SIGNATURE = 0x1d
+_SEG_LINKEDIT = b'__LINKEDIT'.ljust(16, b'\x00')
+
+
+class PadLinkeditError(Exception):
+    pass
+
+
+def _struct_read_unpack(file, format_or_struct):
+    """Reads bytes from |file|, unpacking them via the struct module. This
+    function is provided for convenience: the number of bytes to read from
+    |file| is determined based on the size of data that the struct unpack
+    operation will consume.
+
+    Args:
+        file: The file object to read from.
+
+        format_or_struct: A string suitable for struct.unpack’s |format|
+            argument, or a struct.Struct object. This will be used to determine
+            the number of bytes to read and to perform the unpack.
+
+    Returns:
+        A tuple of unpacked items.
+    """
+
+    if isinstance(format_or_struct, struct.Struct):
+        struc = format_or_struct
+        return struc.unpack(file.read(struc.size))
+
+    format = format_or_struct
+    return struct.unpack(format, file.read(struct.calcsize(format)))
+
+
+def PadLinkedit(file, size):
+    """Takes |file|, a single-architecture (thin) Mach-O image, and increases
+    its size to |size| by adding padding (NUL bytes) to its __LINKEDIT segment.
+    If |file| is not a thin Mach-O image, if its structure is unexpected, if it
+    is already larger than |size|, or if it is already code-signed, raises
+    PadLinkeditError.
+
+    The image must already have a __LINKEDIT segment, the load command for the
+    __LINKEDIT segment must be the last segment load command in the image, and
+    the __LINKEDIT segment contents must be at the end of the file.
+
+    Args:
+        file: The file object to read from and modify.
+
+        size: The desired size of the file.
+
+    Returns:
+        None
+
+    Raises:
+        PadLinkeditError if |file| is not suitable for the operation.
+    """
+
+    file.seek(0, os.SEEK_END)
+    current_size = file.tell()
+    file.seek(0, os.SEEK_SET)
+
+    magic, = _struct_read_unpack(file, '<I')
+    if magic == _MH_MAGIC_64:
+        bits, endian = 64, '<'
+    elif magic == _MH_MAGIC:
+        bits, endian = 32, '<'
+    elif magic == struct.unpack('>I', struct.pack('<I', _MH_MAGIC_64)):
+        bits, endian = 64, '>'
+    elif magic == struct.unpack('>I', struct.pack('<I', _MH_MAGIC)):
+        bits, endian = 32, '>'
+    else:
+        raise PadLinkeditError('unrecognized magic', magic)
+
+    if bits == 64:
+        lc_segment = _LC_SEGMENT_64
+        segment_command_struct = struct.Struct(endian + '16s4Q4I')
+    else:
+        lc_segment = _LC_SEGMENT
+        segment_command_struct = struct.Struct(endian + '16s8I')
+
+    grow = size - current_size
+    if grow < 0:
+        raise PadLinkeditError('file would need to shrink', grow)
+
+    (cputype, cpusubtype, filetype, ncmds, sizeofcmds,
+     flags) = _struct_read_unpack(file,
+                                  endian + '6I' + ('4x' if bits == 64 else ''))
+
+    load_command_struct = struct.Struct(endian + '2I')
+    found_linkedit = False
+    segment_max_offset = 0
+
+    # Iterate through the load commands. It’s possible to consider |sizeofcmds|,
+    # but since the file is being edited in-place, that would just be a sanity
+    # check.
+    for load_command_index in range(ncmds):
+        cmd, cmdsize = _struct_read_unpack(file, load_command_struct)
+        consumed = load_command_struct.size
+        if cmd == lc_segment:
+            if found_linkedit:
+                raise PadLinkeditError('__LINKEDIT segment not last')
+
+            (segname, vmaddr, vmsize, fileoff, filesize, maxprot, initprot,
+             nsects, flags) = _struct_read_unpack(file, segment_command_struct)
+            consumed += segment_command_struct.size
+
+            if segname == _SEG_LINKEDIT:
+                found_linkedit = True
+
+                if fileoff < segment_max_offset:
+                    raise PadLinkeditError('__LINKEDIT data not last')
+                if fileoff + filesize != current_size:
+                    raise PadLinkeditError('__LINKEDIT data not at EOF')
+
+                vmsize += grow
+                filesize += grow
+                file.seek(-segment_command_struct.size, os.SEEK_CUR)
+                file.write(
+                    segment_command_struct.pack(segname, vmaddr, vmsize,
+                                                fileoff, filesize, maxprot,
+                                                initprot, nsects, flags))
+
+            segment_max_offset = max(segment_max_offset, fileoff + filesize)
+        elif cmd == _LC_CODE_SIGNATURE:
+            raise PadLinkeditError(
+                'modifying an already-signed image would render it unusable')
+
+        # Aside from the above, load commands aren’t being interpreted, or even
+        # read, so skip ahead to the next one.
+        file.seek(cmdsize - consumed, os.SEEK_CUR)
+
+    if not found_linkedit:
+        raise PadLinkeditError('no __LINKEDIT')
+
+    # Add the padding to the __LINKEDIT segment data.
+    file.seek(grow, os.SEEK_END)
+    file.truncate()
+
+
+def _main(args):
+    parser = argparse.ArgumentParser(
+        description=
+        'Increase the size of a Mach-O image by adding padding to its ' +
+        '__LINKEDIT segment.')
+    parser.add_argument('file', help='The Mach-O file to modify')
+    parser.add_argument('size',
+                        type=int,
+                        help='The desired final size of the file, in bytes')
+    parsed = parser.parse_args()
+
+    with open(parsed.file, 'r+b') as file:
+        PadLinkedit(file, parsed.size)
+
+
+if __name__ == '__main__':
+    sys.exit(_main(sys.argv[1:]))
diff --git a/cc/paint/image_transfer_cache_entry.cc b/cc/paint/image_transfer_cache_entry.cc
index abc74d4..1c963381 100644
--- a/cc/paint/image_transfer_cache_entry.cc
+++ b/cc/paint/image_transfer_cache_entry.cc
@@ -92,44 +92,6 @@
   return image;
 }
 
-// TODO(ericrk): Replace calls to this with calls to SkImage::makeTextureImage,
-// once that function handles colorspaces. https://crbug.com/834837
-sk_sp<SkImage> MakeTextureImage(
-    GrDirectContext* context,
-    sk_sp<SkImage> source_image,
-    absl::optional<TargetColorParams> target_color_params,
-    GrMipMapped mip_mapped) {
-  // Step 1: Upload image and generate mips if necessary. If we will be applying
-  // a color-space conversion, don't generate mips yet, instead do it after
-  // conversion, in step 3.
-  // NOTE: |target_color_space| is only passed over the transfer cache if needed
-  // (non-null, different from the source color space).
-  bool add_mips_after_color_conversion =
-      target_color_params && mip_mapped == GrMipMapped::kYes;
-  sk_sp<SkImage> uploaded_image = source_image->makeTextureImage(
-      context, add_mips_after_color_conversion ? GrMipMapped::kNo : mip_mapped,
-      SkBudgeted::kNo);
-
-  // Step 2: Apply a color-space conversion if necessary.
-  if (uploaded_image && target_color_params) {
-    // TODO(https://crbug.com/1286088): Pass a shared cache as a parameter.
-    gfx::ColorConversionSkFilterCache cache;
-    uploaded_image = cache.ConvertImage(
-        uploaded_image, target_color_params->color_space.ToSkColorSpace(),
-        target_color_params->sdr_max_luminance_nits,
-        target_color_params->hdr_max_luminance_relative, context);
-  }
-
-  // Step 3: If we had a colorspace conversion, we couldn't mipmap in step 1, so
-  // add mips here.
-  if (uploaded_image && add_mips_after_color_conversion) {
-    uploaded_image = uploaded_image->makeTextureImage(
-        context, GrMipMapped::kYes, SkBudgeted::kNo);
-  }
-
-  return uploaded_image;
-}
-
 base::CheckedNumeric<uint32_t> SafeSizeForPixmap(const SkPixmap& pixmap) {
   base::CheckedNumeric<uint32_t> safe_size;
   safe_size += sizeof(uint64_t);  // color type
@@ -491,6 +453,10 @@
   plane_config_ = SkYUVAInfo::PlaneConfig::kUnknown;
   reader.Read(&plane_config_);
 
+  const GrMipMapped mip_mapped_for_upload =
+      has_mips_ && !target_color_params ? GrMipMapped::kYes : GrMipMapped::kNo;
+  SkPixmap rgba_pixmap;
+  sk_sp<SkImage> rgba_pixmap_image;
   if (plane_config_ != SkYUVAInfo::PlaneConfig::kUnknown) {
     SkYUVAInfo::Subsampling subsampling = SkYUVAInfo::Subsampling::kUnknown;
     reader.Read(&subsampling);
@@ -524,18 +490,20 @@
       }
 
       DCHECK(!target_color_params);
-      sk_sp<SkImage> plane = MakeSkImage(pixmap, target_color_params);
+      sk_sp<SkImage> plane = SkImage::MakeFromRaster(pixmap, nullptr, nullptr);
       if (!plane) {
-        DLOG(ERROR) << "Failed to upload plane pixmap";
+        DLOG(ERROR) << "Failed to create image from plane pixmap";
+        return false;
+      }
+      plane = plane->makeTextureImage(context_, mip_mapped_for_upload,
+                                      SkBudgeted::kNo);
+      if (!plane) {
+        DLOG(ERROR) << "Failed to upload plane pixmap to texture image";
         return false;
       }
       DCHECK(plane->isTextureBacked());
-
+      plane->getBackendTexture(/*flushPendingGrContextIO=*/true);
       plane_sizes_.push_back(plane->textureSize());
-      size_ += plane_sizes_.back();
-
-      // |plane_images_| must be set for use in EnsureMips(), memory dumps, and
-      // unit tests.
       plane_images_.push_back(std::move(plane));
     }
     DCHECK(yuv_color_space_.has_value());
@@ -543,59 +511,78 @@
         context_, plane_images_, plane_config_, subsampling_.value(),
         yuv_color_space_.value(), decoded_color_space);
   } else {
-    SkPixmap pixmap;
-    if (!ReadPixmap(reader, pixmap)) {
+    if (!ReadPixmap(reader, rgba_pixmap)) {
       DLOG(ERROR) << "Failed to read pixmap";
       return false;
     }
-    fits_on_gpu_ = pixmap.width() <= max_size && pixmap.height() <= max_size;
-    image_ = MakeSkImage(pixmap, target_color_params);
-    if (image_)
-      size_ = image_->textureSize();
-  }
-
-  return true;
-}
-
-sk_sp<SkImage> ServiceImageTransferCacheEntry::MakeSkImage(
-    const SkPixmap& pixmap,
-    absl::optional<TargetColorParams> target_color_params) {
-  DCHECK(context_);
-  sk_sp<SkImage> image;
-  if (fits_on_gpu_) {
-    image = SkImage::MakeFromRaster(pixmap, nullptr, nullptr);
-    if (!image)
-      return nullptr;
-    image = MakeTextureImage(context_, std::move(image), target_color_params,
-                             has_mips_ ? GrMipMapped::kYes : GrMipMapped::kNo);
-  } else {
-    // If the image is on the CPU, no work is needed to generate mips.
-    has_mips_ = true;
-    sk_sp<SkImage> original =
-        SkImage::MakeFromRaster(pixmap, [](const void*, void*) {}, nullptr);
-    if (!original)
-      return nullptr;
-    if (target_color_params) {
-      // TODO(https://crbug.com/1286088): Pass a shared cache as a parameter.
-      gfx::ColorConversionSkFilterCache cache;
-      image = cache.ConvertImage(
-          original, target_color_params->color_space.ToSkColorSpace(),
-          target_color_params->sdr_max_luminance_nits,
-          target_color_params->hdr_max_luminance_relative, /*context=*/nullptr);
-      // If color space conversion is a noop, use original data.
-      if (image == original)
-        image = SkImage::MakeRasterCopy(pixmap);
+    rgba_pixmap_image = SkImage::MakeFromRaster(rgba_pixmap, nullptr, nullptr);
+    if (!rgba_pixmap_image) {
+      DLOG(ERROR) << "Failed to create image from plane pixmap";
+      return false;
+    }
+    fits_on_gpu_ =
+        rgba_pixmap.width() <= max_size && rgba_pixmap.height() <= max_size;
+    if (fits_on_gpu_) {
+      image_ = rgba_pixmap_image->makeTextureImage(
+          context, mip_mapped_for_upload, SkBudgeted::kNo);
+      if (!image_) {
+        DLOG(ERROR) << "Failed to upload pixmap to texture image";
+        return false;
+      }
     } else {
-      // No color conversion to do, use original data.
-      image = SkImage::MakeRasterCopy(pixmap);
+      // If the image is on the CPU, no work is needed to generate mips.
+      has_mips_ = true;
+      image_ = rgba_pixmap_image;
+    }
+  }
+  DCHECK(image_);
+
+  // Perform color conversion.
+  if (target_color_params) {
+    // TODO(https://crbug.com/1286088): Pass a shared cache as a parameter.
+    gfx::ColorConversionSkFilterCache cache;
+    image_ = cache.ConvertImage(
+        image_, target_color_params->color_space.ToSkColorSpace(),
+        target_color_params->sdr_max_luminance_nits,
+        target_color_params->hdr_max_luminance_relative,
+        fits_on_gpu_ ? context_ : nullptr);
+    if (!image_) {
+      DLOG(ERROR) << "Failed image color conversion";
+      return false;
+    }
+
+    // Color conversion converts to RGBA. Remove all YUV state.
+    plane_images_.clear();
+    plane_sizes_.clear();
+    plane_config_ = SkYUVAInfo::PlaneConfig::kUnknown;
+    plane_sizes_.clear();
+    subsampling_ = absl::nullopt;
+    yuv_color_space_ = absl::nullopt;
+
+    // If mipmaps were requested, create them after color conversion.
+    if (has_mips_ && fits_on_gpu_) {
+      image_ =
+          image_->makeTextureImage(context, GrMipMapped::kYes, SkBudgeted::kNo);
+      if (!image_) {
+        DLOG(ERROR) << "Failed to generate mipmaps after color conversion";
+        return false;
+      }
     }
   }
 
-  // Make sure the GPU work to create the backing texture is issued.
-  if (image)
-    image->getBackendTexture(true /* flushPendingGrContextIO */);
+  // If `image_` is still pointing at the original data from `rgba_pixmap`, make
+  // a copy of it, because `rgba_pixmap` is directly referencing the transfer
+  // buffer's memory, and will go away after this this call.
+  if (image_ == rgba_pixmap_image) {
+    image_ = SkImage::MakeRasterCopy(rgba_pixmap);
+    if (!image_) {
+      DLOG(ERROR) << "Failed to create raster copy";
+      return false;
+    }
+  }
 
-  return image;
+  size_ = image_->textureSize();
+  return true;
 }
 
 const sk_sp<SkImage>& ServiceImageTransferCacheEntry::GetPlaneImage(
@@ -636,23 +623,25 @@
         context_, mipped_planes, plane_config_, subsampling_.value(),
         yuv_color_space_.value(),
         image_->refColorSpace() /* image_color_space */);
-    if (!mipped_image)
+    if (!mipped_image) {
+      DLOG(ERROR) << "Failed to create YUV image from mipmapped planes";
       return;
+    }
     // Note that we cannot update |size_| because the transfer cache keeps track
     // of a total size that is not updated after EnsureMips(). The original size
     // is used when the image is deleted from the cache.
     plane_images_ = std::move(mipped_planes);
     plane_sizes_ = std::move(mipped_plane_sizes);
     image_ = std::move(mipped_image);
-    has_mips_ = true;
-    return;
+  } else {
+    sk_sp<SkImage> mipped_image =
+        image_->makeTextureImage(context_, GrMipMapped::kYes, SkBudgeted::kNo);
+    if (!mipped_image) {
+      DLOG(ERROR) << "Failed to mipmapped image";
+      return;
+    }
+    image_ = std::move(mipped_image);
   }
-
-  sk_sp<SkImage> mipped_image =
-      image_->makeTextureImage(context_, GrMipMapped::kYes, SkBudgeted::kNo);
-  if (!mipped_image)
-    return;
-  image_ = std::move(mipped_image);
   has_mips_ = true;
 }
 
diff --git a/cc/paint/image_transfer_cache_entry.h b/cc/paint/image_transfer_cache_entry.h
index cacf3df..5e810fbd 100644
--- a/cc/paint/image_transfer_cache_entry.h
+++ b/cc/paint/image_transfer_cache_entry.h
@@ -151,11 +151,10 @@
   }
 
  private:
-  sk_sp<SkImage> MakeSkImage(
-      const SkPixmap& pixmap,
-      absl::optional<TargetColorParams> target_color_params);
-
   raw_ptr<GrDirectContext> context_ = nullptr;
+  // The individual planes that are used by `image_` when `image_` is a YUVA
+  // image. The planes are kept around for use in EnsureMips(), memory dumps,
+  // and unit tests.
   std::vector<sk_sp<SkImage>> plane_images_;
   SkYUVAInfo::PlaneConfig plane_config_ = SkYUVAInfo::PlaneConfig::kUnknown;
   std::vector<size_t> plane_sizes_;
@@ -163,6 +162,8 @@
   absl::optional<SkYUVAInfo::Subsampling> subsampling_;
   absl::optional<SkYUVColorSpace> yuv_color_space_;
   bool has_mips_ = false;
+  // The value of `size_` is computed during deserialization and never updated
+  // (even if the size of the image changes due to mipmaps being requested).
   size_t size_ = 0;
   bool fits_on_gpu_ = false;
 };
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 5b57d134..e5d3e35 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -485,13 +485,15 @@
       "//chrome/common:version_header",
     ]
 
+    ldflags = []
+
     if (enable_stripping) {
       # At link time, preserve the global symbols specified in the .exports
       # file. All other global symbols will be marked as private. The default
       # //build/config/mac:strip_all config will then remove the remaining
       # local and debug symbols.
-      ldflags = [ "-Wl,-exported_symbols_list," +
-                  rebase_path("app/app.exports", root_build_dir) ]
+      ldflags += [ "-Wl,-exported_symbols_list," +
+                   rebase_path("app/app.exports", root_build_dir) ]
     }
 
     if (is_component_build) {
@@ -499,7 +501,7 @@
       # executable because dlopen() and loading all the dependent dylibs
       # is time-consuming, see https://crbug.com/1197495.
       deps += [ ":chrome_framework+link" ]
-      ldflags = [ "-Wl,-rpath,@executable_path/../Frameworks" ]
+      ldflags += [ "-Wl,-rpath,@executable_path/../Frameworks" ]
 
       # The Framework is packaged inside the .app bundle. But when using the
       # component build, all the dependent shared libraries of :chrome_dll are
@@ -513,6 +515,70 @@
     if (enable_chromium_updater) {
       deps += [ ":chromium_updater_privileged_helper" ]
     }
+
+    if (is_chrome_branded && is_official_build && current_cpu == "x64") {
+      # This is for https://crbug.com/1300598, and more generally,
+      # https://crbug.com/1297588 (and all of the associated bugs). It's
+      # horrible!
+      #
+      # When the main executable is updated on disk while the application is
+      # running, and the offset of the Mach-O image at the main executable's
+      # path changes from the offset that was determined when the executable was
+      # loaded, SecCode ceases to be able to work with the executable. This may
+      # be triggered when the product is updated on disk but the application has
+      # not yet relaunched. This affects SecCodeCopySelf and
+      # SecCodeCopyGuestWithAttributes. Bugs are evident even when validation
+      # (SecCodeCheckValidity) is not attempted.
+      #
+      # Practically, this is only a concern for fat (universal) files, because
+      # the offset of a Mach-O image in a thin (single-architecture) file is
+      # always 0. The branded product always ships a fat executable, and because
+      # some uses of SecCode are in OS code beyond Chrome's control, an effort
+      # is made to freeze the geometry of the branded (is_chrome_branded)
+      # for-public-release (is_official_build) main executable.
+      #
+      # The fat file is produced by installer/mac/universalizer.py. The x86_64
+      # slice always precedes the arm64 slice: lipo, as used by
+      # universalizer.py, always places the arm64 slice last. See Xcode 12.0
+      # https://github.com/apple-oss-distributions/cctools/blob/cctools-973.0.1/misc/lipo.c#L2672
+      # cmp_qsort, used by create_fat at #L962. universalizer.py ensures that
+      # the first slice in the file is located at a constant offset (16kB since
+      # 98.0.4758.80), but if the first slice's size changes, it can affect the
+      # offset of the second slice, the arm64 one, triggering SecCode-related
+      # bugs for arm64 users across updates.
+      #
+      # As quite a hack of a workaround, the offset of the arm64 slice within
+      # the fat main executable is fixed at a constant value by introducing
+      # padding to the x86_64 slice that precedes it. The arm64 slice needs to
+      # remain at offset 304kB (since 98.0.4758.80), so enough padding is added
+      # to the x86_64 slice to ensure that the arm64 slice lands where it needs
+      # to be when universalized. This padding needs to be added to the thin
+      # form of the x86_64 image before being fed to universalizer.py.
+      #
+      # To make things extra tricky, the final size of the x86_64 image is not
+      # known when it is linked, because code signing will contribute more data
+      # to the file. Typically, official code signing adds a non-constant
+      # 22-23kB. The non-constancy makes a precise computation of the required
+      # padding at this stage of the build impossible. Luckily, the size of the
+      # x86_64 image doesn't need to be so precise. The arm64 slice that follows
+      # it in the fat file will be 16kB-aligned, so it's enough to ensure that
+      # the x86_64 slice ends at an offset in the range (288kB, 304kB], or,
+      # since the x86_64 slice itself begins at offset 16kB, its total size once
+      # signed must be in the range (272kB, 288kB]. Targeting size 280kB, right
+      # in the middle of that range, and assuming an expected 23200-byte
+      # contribution from code signing, the unsigned x86_64 image should be
+      # padded to 263520 bytes. Code signing may then add any amount in the
+      # range (15008 bytes, 31392 bytes] and the result, when universalized,
+      # will have its arm64 slice at the required 304kB offset.
+      #
+      # -Wcrl,pad_linkedit runs //build/toolchain/apple/pad_linkedit.py, and can
+      # only increase the size of the image. It will raise an error on an
+      # attempt to decrease the size. Fortunately, the x86_64 main executable in
+      # this build configuration is currently (at the time of this writing)
+      # smaller than this size, and is expected to shrink even further in the
+      # future.
+      ldflags += [ "-Wcrl,pad_linkedit,263520" ]
+    }
   }
 
   if (verify_dynamic_libraries) {
diff --git a/chrome/android/expectations/monochrome_public_bundle.AndroidManifest.expected b/chrome/android/expectations/monochrome_public_bundle.AndroidManifest.expected
index 91e8b9dd..5f2f0d0 100644
--- a/chrome/android/expectations/monochrome_public_bundle.AndroidManifest.expected
+++ b/chrome/android/expectations/monochrome_public_bundle.AndroidManifest.expected
@@ -642,6 +642,7 @@
     </activity>  # DIFF-ANCHOR: 66a0be05
     <activity  # DIFF-ANCHOR: a1fac31f
         android:name="org.chromium.chrome.browser.webauth.authenticator.CableAuthenticatorActivity"
+        android:configChanges="density|fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
         android:excludeFromRecents="true"
         android:exported="true"
         android:label="@string/cablev2_activity_title"
diff --git a/chrome/android/expectations/trichrome_chrome_bundle.AndroidManifest.expected b/chrome/android/expectations/trichrome_chrome_bundle.AndroidManifest.expected
index 02ddb5fd..9e18493 100644
--- a/chrome/android/expectations/trichrome_chrome_bundle.AndroidManifest.expected
+++ b/chrome/android/expectations/trichrome_chrome_bundle.AndroidManifest.expected
@@ -615,6 +615,7 @@
     </activity>  # DIFF-ANCHOR: 66a0be05
     <activity  # DIFF-ANCHOR: a1fac31f
         android:name="org.chromium.chrome.browser.webauth.authenticator.CableAuthenticatorActivity"
+        android:configChanges="density|fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
         android:excludeFromRecents="true"
         android:exported="true"
         android:label="@string/cablev2_activity_title"
diff --git a/chrome/android/features/cablev2_authenticator/java/res/layout-sw600dp/cablev2_ble_enable.xml b/chrome/android/features/cablev2_authenticator/java/res/layout-sw600dp/cablev2_ble_enable.xml
index 298df85..39bd2cf 100644
--- a/chrome/android/features/cablev2_authenticator/java/res/layout-sw600dp/cablev2_ble_enable.xml
+++ b/chrome/android/features/cablev2_authenticator/java/res/layout-sw600dp/cablev2_ble_enable.xml
@@ -3,41 +3,49 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<LinearLayout
+<ScrollView
     xmlns:a="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     a:layout_width="match_parent"
     a:layout_height="match_parent"
-    a:orientation="vertical"
-    a:gravity="center"
-    tools:ignore="UseCompoundDrawables">
+    a:fillViewport="true"
+    a:scrollbars="none">
 
-  <TextView
+  <LinearLayout
       a:layout_width="match_parent"
       a:layout_height="wrap_content"
-      a:layout_marginTop="-104dp"
-      a:layout_marginLeft="24dp"
-      a:layout_marginRight="24dp"
-      a:gravity="center_horizontal"
-      a:text="@string/cablev2_ble_enable_title"
-      a:textSize="36sp" />
+      a:orientation="vertical"
+      a:gravity="center"
+      tools:ignore="UseCompoundDrawables">
 
-  <!-- This is a semantically-meaningless picture of a phone and BLE icon that
-  screen readers can ignore. Thus contentDescription is null.
+    <TextView
+        a:layout_width="match_parent"
+        a:layout_height="wrap_content"
+        a:layout_marginTop="-104dp"
+        a:layout_marginLeft="24dp"
+        a:layout_marginRight="24dp"
+        a:gravity="center_horizontal"
+        a:text="@string/cablev2_ble_enable_title"
+        a:textSize="36sp" />
 
-  The visual center of this image isn't the pixel-center, and thus a left margin
-  is used to tweak the horizontal centering. The tooling thinks that's an RTL
-  issue, but RTL mode doesn't reflect images so the padding still needs to
-  be on the left. tools:ignore is used to skip this warning. -->
-  <ImageView
-      a:layout_width="match_parent"
-      a:layout_height="212dp"
-      a:layout_marginTop="52dp"
-      a:layout_marginLeft="40dp"
-      a:layout_marginBottom="35dp"
-      a:contentDescription="@null"
-      a:gravity="center_horizontal"
-      a:src="@drawable/ble"
-      tools:ignore="RtlHardcoded"/>
+    <!-- This is a semantically-meaningless picture of a phone and BLE icon that
+    screen readers can ignore. Thus contentDescription is null.
 
-</LinearLayout>
+    The visual center of this image isn't the pixel-center, and thus a left margin
+    is used to tweak the horizontal centering. The tooling thinks that's an RTL
+    issue, but RTL mode doesn't reflect images so the padding still needs to
+    be on the left. tools:ignore is used to skip this warning. -->
+    <ImageView
+        a:layout_width="match_parent"
+        a:layout_height="212dp"
+        a:layout_marginTop="52dp"
+        a:layout_marginLeft="40dp"
+        a:layout_marginBottom="35dp"
+        a:contentDescription="@null"
+        a:gravity="center_horizontal"
+        a:src="@drawable/ble"
+        tools:ignore="RtlHardcoded"/>
+
+  </LinearLayout>
+
+</ScrollView>
diff --git a/chrome/android/features/cablev2_authenticator/java/res/layout/cablev2_ble_enable.xml b/chrome/android/features/cablev2_authenticator/java/res/layout/cablev2_ble_enable.xml
index ad1c43e..a823e35 100644
--- a/chrome/android/features/cablev2_authenticator/java/res/layout/cablev2_ble_enable.xml
+++ b/chrome/android/features/cablev2_authenticator/java/res/layout/cablev2_ble_enable.xml
@@ -3,38 +3,46 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<LinearLayout
+<ScrollView
     xmlns:a="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     a:layout_width="match_parent"
     a:layout_height="match_parent"
-    a:orientation="vertical"
-    tools:ignore="UseCompoundDrawables">
+    a:fillViewport="true"
+    a:scrollbars="none">
 
-  <TextView
-      style="@style/TextAppearance.Headline.Primary"
+  <LinearLayout
       a:layout_width="match_parent"
       a:layout_height="wrap_content"
-      a:layout_marginTop="104dp"
-      a:gravity="center_horizontal"
-      a:text="@string/cablev2_ble_enable_title"/>
+      a:orientation="vertical"
+      tools:ignore="UseCompoundDrawables">
 
-  <!-- This is a semantically-meaningless picture of a phone and BLE icon that
-  screen readers can ignore. Thus contentDescription is null.
+    <TextView
+        style="@style/TextAppearance.Headline.Primary"
+        a:layout_width="match_parent"
+        a:layout_height="wrap_content"
+        a:layout_marginTop="104dp"
+        a:gravity="center_horizontal"
+        a:text="@string/cablev2_ble_enable_title"/>
 
-  The visual center of this image isn't the pixel-center, and thus a left margin
-  is used to tweak the horizontal centering. The tooling thinks that's an RTL
-  issue, but RTL mode doesn't reflect images so the padding still needs to
-  be on the left. tools:ignore is used to skip this warning. -->
-  <ImageView
-      a:layout_width="match_parent"
-      a:layout_height="212dp"
-      a:layout_marginTop="52dp"
-      a:layout_marginLeft="40dp"
-      a:layout_marginBottom="35dp"
-      a:contentDescription="@null"
-      a:gravity="center_horizontal"
-      a:src="@drawable/ble"
-      tools:ignore="RtlHardcoded"/>
+    <!-- This is a semantically-meaningless picture of a phone and BLE icon that
+    screen readers can ignore. Thus contentDescription is null.
 
-</LinearLayout>
+    The visual center of this image isn't the pixel-center, and thus a left margin
+    is used to tweak the horizontal centering. The tooling thinks that's an RTL
+    issue, but RTL mode doesn't reflect images so the padding still needs to
+    be on the left. tools:ignore is used to skip this warning. -->
+    <ImageView
+        a:layout_width="match_parent"
+        a:layout_height="212dp"
+        a:layout_marginTop="52dp"
+        a:layout_marginLeft="40dp"
+        a:layout_marginBottom="35dp"
+        a:contentDescription="@null"
+        a:gravity="center_horizontal"
+        a:src="@drawable/ble"
+        tools:ignore="RtlHardcoded"/>
+
+  </LinearLayout>
+
+</ScrollView>
diff --git a/chrome/android/features/cablev2_authenticator/java/res/layout/cablev2_usb_attached.xml b/chrome/android/features/cablev2_authenticator/java/res/layout/cablev2_usb_attached.xml
index ba8f10f..936f54ab 100644
--- a/chrome/android/features/cablev2_authenticator/java/res/layout/cablev2_usb_attached.xml
+++ b/chrome/android/features/cablev2_authenticator/java/res/layout/cablev2_usb_attached.xml
@@ -3,40 +3,48 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
+<ScrollView
+    xmlns:a="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     a:layout_width="match_parent"
     a:layout_height="match_parent"
-    a:gravity="center"
-    a:orientation="vertical">
+    a:fillViewport="true"
+    a:scrollbars="none">
 
-  <TextView
-      a:id="@+id/usb_label"
-      xmlns:tools="http://schemas.android.com/tools"
-      style="@style/TextAppearance.Headline.Primary"
-      a:layout_width="match_parent"
-      a:layout_height="wrap_content"
-      a:layout_marginBottom="40dp"
-      a:gravity="center"
-      a:text="@string/cablev2_usb_discon_title" />
-
-  <ImageView
-      a:id="@+id/usb_icon_disconnect"
-      a:layout_height="203dp"
-      a:layout_width="296dp"
-      a:layout_marginLeft="100dp"
-      a:layout_marginRight="100dp"
-      a:layout_marginBottom="25dp"
-      a:contentDescription="@null"
-      a:gravity="center_horizontal"
-      a:src="@drawable/usb_conn_disconnect"/>
-
-  <TextView
-      a:id="@+id/usb_title"
-      xmlns:tools="http://schemas.android.com/tools"
-      style="@style/TextAppearance.TextLarge.Secondary"
+  <LinearLayout
       a:layout_width="match_parent"
       a:layout_height="wrap_content"
       a:gravity="center"
-      a:text="@string/cablev2_usb_discon_body" />
+      a:orientation="vertical">
 
-</LinearLayout>
+    <TextView
+        a:id="@+id/usb_label"
+        style="@style/TextAppearance.Headline.Primary"
+        a:layout_width="match_parent"
+        a:layout_height="wrap_content"
+        a:layout_marginBottom="40dp"
+        a:gravity="center"
+        a:text="@string/cablev2_usb_discon_title" />
+
+    <ImageView
+        a:id="@+id/usb_icon_disconnect"
+        a:layout_height="203dp"
+        a:layout_width="296dp"
+        a:layout_marginLeft="100dp"
+        a:layout_marginRight="100dp"
+        a:layout_marginBottom="25dp"
+        a:contentDescription="@null"
+        a:gravity="center_horizontal"
+        a:src="@drawable/usb_conn_disconnect"/>
+
+    <TextView
+        a:id="@+id/usb_title"
+        style="@style/TextAppearance.TextLarge.Secondary"
+        a:layout_width="match_parent"
+        a:layout_height="wrap_content"
+        a:gravity="center"
+        a:text="@string/cablev2_usb_discon_body" />
+
+  </LinearLayout>
+
+</ScrollView>
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceBackButtonTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceBackButtonTest.java
index 595726f..db42be3 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceBackButtonTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceBackButtonTest.java
@@ -56,6 +56,7 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.layouts.LayoutStateProvider;
+import org.chromium.chrome.browser.layouts.LayoutTestUtils;
 import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
@@ -66,7 +67,6 @@
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeApplicationTestUtils;
 import org.chromium.chrome.test.util.MenuUtils;
-import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.test.util.UiRestriction;
@@ -251,11 +251,9 @@
                         -> Assert.assertTrue(StartSurfaceUserData.getKeepTab(
                                 cta.getTabModelSelector().getCurrentTab())));
 
-        OverviewModeBehaviorWatcher overviewModeWatcher =
-                new OverviewModeBehaviorWatcher(cta.getLayoutManager(), true, false);
         StartSurfaceTestUtils.pressBack(mActivityTestRule);
         // Verifies the new Tab isn't deleted, and Start surface is shown.
-        overviewModeWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.TAB_SWITCHER);
         TabUiTestHelper.verifyTabModelTabCount(cta, 2, 0);
 
         // Verifies Chrome is closed.
@@ -295,12 +293,11 @@
         StartSurfaceTestUtils.waitForTabModel(cta);
         TabUiTestHelper.verifyTabModelTabCount(cta, 1, 0);
 
-        OverviewModeBehaviorWatcher hideWatcher = TabUiTestHelper.createOverviewHideWatcher(cta);
         onViewWaiting(withId(org.chromium.chrome.start_surface.R.id.search_box_text))
                 .perform(replaceText("about:blank"));
         onView(withId(org.chromium.chrome.start_surface.R.id.url_bar))
                 .perform(pressKey(KeyEvent.KEYCODE_ENTER));
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
         TabUiTestHelper.verifyTabModelTabCount(cta, 2, 0);
 
         TabUiTestHelper.mergeAllNormalTabsToAGroup(cta);
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
index c78cb70..5f27ba6 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
@@ -131,7 +131,6 @@
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.MenuUtils;
-import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
@@ -1735,11 +1734,10 @@
 
         // Click the chip and check the tab navigates back to the search result page.
         assertEquals(mUrl, ChromeTabUtils.getUrlStringOnUiThread(currentTab));
-        OverviewModeBehaviorWatcher hideWatcher = TabUiTestHelper.createOverviewHideWatcher(cta);
         onView(withId(R.id.page_info_button))
                 .check(waitForView(allOf(withText(expectedTerm), isDisplayed())));
         onView(withId(R.id.page_info_button)).perform(click());
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
         ChromeTabUtils.waitForTabPageLoaded(currentTab, searchUrl.get());
 
         // Verify the chip is gone.
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
index 7fe4494..9fb9987a 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
@@ -46,6 +46,7 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.layouts.LayoutStateProvider;
+import org.chromium.chrome.browser.layouts.LayoutTestUtils;
 import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.browser.native_page.ContextMenuManager;
 import org.chromium.chrome.browser.suggestions.SiteSuggestion;
@@ -56,7 +57,6 @@
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.chrome.test.util.browser.suggestions.SuggestionsDependenciesRule;
 import org.chromium.chrome.test.util.browser.suggestions.mostvisited.FakeMostVisitedSites;
@@ -164,9 +164,8 @@
             return;
         }
         // Press back button should close the tab opened from the Start surface.
-        OverviewModeBehaviorWatcher showWatcher = TabUiTestHelper.createOverviewShowWatcher(cta);
         StartSurfaceTestUtils.pressBack(mActivityTestRule);
-        showWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.TAB_SWITCHER);
         assertThat(cta.getTabModelSelector().getCurrentModel().getCount(), equalTo(1));
     }
 
@@ -300,9 +299,8 @@
 
         // Open the incognito tile using the context menu.
         TabUiTestHelper.verifyTabModelTabCount(cta, 1, 0);
-        OverviewModeBehaviorWatcher hideWatcher = TabUiTestHelper.createOverviewHideWatcher(cta);
         invokeContextMenu(tileView, ContextMenuManager.ContextMenuItemId.OPEN_IN_INCOGNITO_TAB);
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
         CriteriaHelper.pollUiThread(() -> !cta.getLayoutManager().overviewVisible());
         // Verifies a new incognito tab is created.
         TabUiTestHelper.verifyTabModelTabCount(cta, 1, 1);
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java
index 6364bbb..e21b534 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java
@@ -65,7 +65,6 @@
 import org.chromium.chrome.browser.toolbar.HomeButton;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.test.util.UiRestriction;
@@ -165,12 +164,11 @@
 
         onViewWaiting(withId(org.chromium.chrome.start_surface.R.id.secondary_tasks_surface_view));
 
-        OverviewModeBehaviorWatcher hideWatcher =
-                TabUiTestHelper.createOverviewHideWatcher(mActivityTestRule.getActivity());
         onViewWaiting(allOf(withParent(withId(org.chromium.chrome.tab_ui.R.id.tasks_surface_body)),
                               withId(org.chromium.chrome.tab_ui.R.id.tab_list_view)))
                 .perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(
+                mActivityTestRule.getActivity().getLayoutManager(), LayoutType.BROWSING);
     }
 
     @Test
@@ -330,10 +328,8 @@
                 () -> { Assert.assertNotEquals(tab1.getTitle(), tab2.getTitle()); });
 
         // Returns to the Start surface.
-        OverviewModeBehaviorWatcher overviewModeWatcher =
-                new OverviewModeBehaviorWatcher(cta.getLayoutManager(), true, false);
         StartSurfaceTestUtils.pressHomePageButton(cta);
-        overviewModeWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.TAB_SWITCHER);
         waitForView(allOf(
                 withParent(withId(org.chromium.chrome.tab_ui.R.id.carousel_tab_switcher_container)),
                 withId(org.chromium.chrome.tab_ui.R.id.tab_list_view)));
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index f5b5d9e..be516a4 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -95,7 +95,6 @@
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
-import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.chrome.test.util.browser.suggestions.SuggestionsDependenciesRule;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
@@ -224,9 +223,8 @@
         StartSurfaceTestUtils.pressBack(mActivityTestRule);
         onViewWaiting(allOf(withId(R.id.primary_tasks_surface_view), isDisplayed()));
 
-        OverviewModeBehaviorWatcher hideWatcher = TabUiTestHelper.createOverviewHideWatcher(cta);
         StartSurfaceTestUtils.clickFirstTabInCarousel();
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
     }
 
     @Test
@@ -280,9 +278,8 @@
         onView(withId(org.chromium.chrome.tab_ui.R.id.incognito_toggle_tabs))
                 .check(matches(withEffectiveVisibility(GONE)));
 
-        OverviewModeBehaviorWatcher hideWatcher = TabUiTestHelper.createOverviewHideWatcher(cta);
         StartSurfaceTestUtils.clickFirstTabInCarousel();
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
     }
 
     @Test
@@ -331,9 +328,8 @@
             return;
         }
 
-        OverviewModeBehaviorWatcher hideWatcher = TabUiTestHelper.createOverviewHideWatcher(cta);
         StartSurfaceTestUtils.clickFirstTabInCarousel();
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
     }
 
     @Test
@@ -385,9 +381,8 @@
         StartSurfaceTestUtils.pressBack(mActivityTestRule);
         onViewWaiting(withId(R.id.primary_tasks_surface_view));
 
-        OverviewModeBehaviorWatcher hideWatcher = TabUiTestHelper.createOverviewHideWatcher(cta);
         onViewWaiting(withId(org.chromium.chrome.tab_ui.R.id.single_tab_view)).perform(click());
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
     }
 
     @Test
@@ -450,10 +445,9 @@
         StartSurfaceTestUtils.waitForTabModel(cta);
         assertThat(cta.getTabModelSelector().getCurrentModel().getCount(), equalTo(1));
 
-        OverviewModeBehaviorWatcher hideWatcher = TabUiTestHelper.createOverviewHideWatcher(cta);
         onViewWaiting(withId(R.id.search_box_text)).perform(replaceText("about:blank"));
         onViewWaiting(withId(R.id.url_bar)).perform(pressKey(KeyEvent.KEYCODE_ENTER));
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
         assertThat(cta.getTabModelSelector().getCurrentModel().getCount(), equalTo(2));
 
         TestThreadUtils.runOnUiThreadBlocking(() -> cta.getTabCreator(false).launchNTP());
@@ -492,10 +486,9 @@
         }
         assertTrue(cta.getTabModelSelector().isIncognitoSelected());
 
-        OverviewModeBehaviorWatcher hideWatcher = TabUiTestHelper.createOverviewHideWatcher(cta);
         onViewWaiting(withId(R.id.search_box_text)).perform(replaceText("about:blank"));
         onView(withId(R.id.url_bar)).perform(pressKey(KeyEvent.KEYCODE_ENTER));
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
         assertThat(cta.getTabModelSelector().getCurrentModel().getCount(), equalTo(1));
     }
 
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java
index 33f8aed..180f6bad 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java
@@ -74,7 +74,6 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeApplicationTestUtils;
-import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
 import org.chromium.chrome.test.util.browser.suggestions.SuggestionsDependenciesRule;
 import org.chromium.chrome.test.util.browser.suggestions.mostvisited.FakeMostVisitedSites;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -386,7 +385,6 @@
      */
     public static void launchFirstMVTile(ChromeTabbedActivity cta, int currentTabCount) {
         TabUiTestHelper.verifyTabModelTabCount(cta, currentTabCount, 0);
-        OverviewModeBehaviorWatcher hideWatcher = TabUiTestHelper.createOverviewHideWatcher(cta);
         onViewWaiting(withId(org.chromium.chrome.tab_ui.R.id.mv_tiles_layout))
                 .perform(new ViewAction() {
                     @Override
@@ -405,7 +403,7 @@
                         mvTilesContainer.getChildAt(0).performClick();
                     }
                 });
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
         CriteriaHelper.pollUiThread(() -> !cta.getLayoutManager().overviewVisible());
         // Verifies a new Tab is created.
         TabUiTestHelper.verifyTabModelTabCount(cta, currentTabCount + 1, 0);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceCardView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceCardView.java
index 54616e1f..e2bfc4bd 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceCardView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceCardView.java
@@ -12,7 +12,6 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.tab_ui.R;
 
 /**
@@ -44,9 +43,8 @@
         mPreviousPriceInfoBox = (TextView) findViewById(R.id.previous_price);
         mPreviousPriceInfoBox.setPaintFlags(
                 mPreviousPriceInfoBox.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
-        mPriceInfoBox.setTextColor(ApiCompatibilityUtils.getColor(
-                getResources(), R.color.price_drop_annotation_text_green));
+        mPriceInfoBox.setTextColor(getContext().getColor(R.color.price_drop_annotation_text_green));
         mPreviousPriceInfoBox.setTextColor(
-                ApiCompatibilityUtils.getColor(getResources(), R.color.chip_text_color_secondary));
+                getContext().getColor(R.color.chip_text_color_secondary));
     }
 }
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java
index ca72b121..3e2c0b5 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java
@@ -57,6 +57,8 @@
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
+import org.chromium.chrome.browser.layouts.LayoutTestUtils;
+import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabSelectionType;
 import org.chromium.chrome.browser.tabmodel.TabModel;
@@ -65,7 +67,6 @@
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
-import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 import java.io.File;
@@ -107,12 +108,11 @@
      * @param cta  The current running activity.
      */
     public static void enterTabSwitcher(ChromeTabbedActivity cta) {
-        OverviewModeBehaviorWatcher showWatcher = createOverviewShowWatcher(cta);
         assertFalse(cta.getLayoutManager().overviewVisible());
         // TODO(crbug.com/1145271): Replace this with clicking tab switcher button via espresso.
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> { cta.findViewById(R.id.tab_switcher_button).performClick(); });
-        showWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.TAB_SWITCHER);
     }
 
     /**
@@ -120,10 +120,9 @@
      * @param cta  The current running activity.
      */
     public static void leaveTabSwitcher(ChromeTabbedActivity cta) {
-        OverviewModeBehaviorWatcher hideWatcher = createOverviewHideWatcher(cta);
         assertTrue(cta.getLayoutManager().overviewVisible());
         pressBack();
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
     }
 
     /**
@@ -162,10 +161,9 @@
      * @param index The index of the target tab.
      */
     static void clickNthTabInDialog(ChromeTabbedActivity cta, int index) {
-        OverviewModeBehaviorWatcher hideWatcher = createOverviewHideWatcher(cta);
         onView(allOf(withId(R.id.tab_list_view), withParent(withId(R.id.dialog_container_view))))
                 .perform(RecyclerViewActions.actionOnItemAtPosition(index, click()));
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
     }
 
     /**
@@ -390,20 +388,6 @@
     }
 
     /**
-     * Create a {@link OverviewModeBehaviorWatcher} to inspect overview show.
-     */
-    public static OverviewModeBehaviorWatcher createOverviewShowWatcher(ChromeTabbedActivity cta) {
-        return new OverviewModeBehaviorWatcher(cta.getLayoutManager(), true, false);
-    }
-
-    /**
-     * Create a {@link OverviewModeBehaviorWatcher} to inspect overview hide.
-     */
-    public static OverviewModeBehaviorWatcher createOverviewHideWatcher(ChromeTabbedActivity cta) {
-        return new OverviewModeBehaviorWatcher(cta.getLayoutManager(), false, true);
-    }
-
-    /**
      * @return whether animators are enabled on device by checking whether the animation duration
      * scale is set to 0.0.
      */
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index e1948a0f..ac19edf 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -896,13 +896,19 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="org.chromium.chrome.browser.webauth.authenticator.CableAuthenticatorActivity"
-            android:theme="@style/Theme.Chromium.Activity.Fullscreen"
-            android:label="@string/cablev2_activity_title"
-            android:permission="com.google.android.gms.auth.cryptauth.permission.CABLEV2_SERVER_LINK"
-            android:exported="true"
-            android:excludeFromRecents="true"
-            android:launchMode="singleTop">
+        <!-- configChanges is set here to prevent the destruction of the
+             activity after configuration changes. Since the bulk of this
+             activity's logic is in a feature module, restoring the activity
+             via a Bundle doesn't work. -->
+        <activity
+        android:name="org.chromium.chrome.browser.webauth.authenticator.CableAuthenticatorActivity"
+        android:configChanges="density|fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
+        android:theme="@style/Theme.Chromium.Activity.Fullscreen"
+        android:label="@string/cablev2_activity_title"
+        android:permission="com.google.android.gms.auth.cryptauth.permission.CABLEV2_SERVER_LINK"
+        android:exported="true"
+        android:excludeFromRecents="true"
+        android:launchMode="singleTop">
             <!-- This activity can be started by GMSCore, and is thus exported
             with a permission set, or can be started by the Android system in
             the case that a USB device is attached. -->
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java
index da247c1..43e9100 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ContentViewFocusTest.java
@@ -28,10 +28,11 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.layouts.LayoutTestUtils;
+import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
-import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
 import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.ScrollDirection;
 import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.SwipeHandler;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
@@ -173,10 +174,6 @@
     public void testHideSelectionOnPhoneTabSwitcher() throws Exception {
         mActivityTestRule.startMainActivityOnBlankPage();
         // Setup
-        OverviewModeBehaviorWatcher showWatcher = new OverviewModeBehaviorWatcher(
-                mActivityTestRule.getActivity().getLayoutManager(), true, false);
-        OverviewModeBehaviorWatcher hideWatcher = new OverviewModeBehaviorWatcher(
-                mActivityTestRule.getActivity().getLayoutManager(), false, true);
         View currentView = mActivityTestRule.getActivity().getActivityTab().getContentView();
         addFocusChangedListener(currentView);
 
@@ -186,7 +183,8 @@
         Assert.assertNotNull("'tab_switcher_button' view is not found.", tabSwitcherButton);
         TouchCommon.singleClickView(
                 mActivityTestRule.getActivity().findViewById(R.id.tab_switcher_button));
-        showWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(
+                mActivityTestRule.getActivity().getLayoutManager(), LayoutType.TAB_SWITCHER);
 
         // Make sure the view loses focus. It is immediately given focus back
         // because it's the only focusable view.
@@ -197,7 +195,8 @@
         Assert.assertNotNull("'tab_switcher_button' view is not found.", tabSwitcherButton);
         TouchCommon.singleClickView(
                 mActivityTestRule.getActivity().findViewById(R.id.tab_switcher_button));
-        hideWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(
+                mActivityTestRule.getActivity().getLayoutManager(), LayoutType.BROWSING);
 
         Assert.assertTrue("Content view didn't regain focus", blockForFocusChanged());
         Assert.assertFalse("Unexpected focus change", haveFocusChanges());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java
index 2754ec9d..8996752 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java
@@ -68,7 +68,6 @@
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.MenuUtils;
 import org.chromium.chrome.test.util.NewTabPageTestUtils;
-import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
 import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.ScrollDirection;
 import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.SwipeHandler;
 import org.chromium.components.javascript_dialogs.JavascriptTabModalDialog;
@@ -255,19 +254,18 @@
             Assert.assertEquals("Data file for TabsTest", title);
         });
         final int tabCount = mActivityTestRule.getActivity().getCurrentTabModel().getCount();
-        OverviewModeBehaviorWatcher overviewModeWatcher = new OverviewModeBehaviorWatcher(
-                mActivityTestRule.getActivity().getLayoutManager(), true, false);
         View tabSwitcherButton =
                 mActivityTestRule.getActivity().findViewById(R.id.tab_switcher_button);
         Assert.assertNotNull("'tab_switcher_button' view is not found", tabSwitcherButton);
         TouchCommon.singleClickView(tabSwitcherButton);
-        overviewModeWatcher.waitForBehavior();
-        overviewModeWatcher = new OverviewModeBehaviorWatcher(
-                mActivityTestRule.getActivity().getLayoutManager(), false, true);
+        LayoutTestUtils.waitForLayout(
+                mActivityTestRule.getActivity().getLayoutManager(), LayoutType.TAB_SWITCHER);
+
         View newTabButton = mActivityTestRule.getActivity().findViewById(R.id.new_tab_button);
         Assert.assertNotNull("'new_tab_button' view is not found", newTabButton);
         TouchCommon.singleClickView(newTabButton);
-        overviewModeWatcher.waitForBehavior();
+        LayoutTestUtils.waitForLayout(
+                mActivityTestRule.getActivity().getLayoutManager(), LayoutType.BROWSING);
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(
                 () -> Assert.assertEquals("The tab count is wrong", tabCount + 1,
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionUpdateMessageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionUpdateMessageTest.java
index 2cf244a..d6c240f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionUpdateMessageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/permissions/PermissionUpdateMessageTest.java
@@ -20,7 +20,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.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -32,10 +31,11 @@
 import org.chromium.components.browser_ui.site_settings.PermissionInfo;
 import org.chromium.components.content_settings.ContentSettingValues;
 import org.chromium.components.content_settings.ContentSettingsType;
-import org.chromium.components.messages.MessageContainer;
+import org.chromium.components.messages.MessagesTestHelper;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.permissions.AndroidPermissionDelegate;
 import org.chromium.ui.permissions.PermissionCallback;
 
@@ -87,9 +87,9 @@
                         -> new PermissionInfo(
                                 ContentSettingsType.GEOLOCATION, locationUrl, null, false));
 
-        mActivityTestRule.getActivity().getWindowAndroid().setAndroidPermissionDelegate(
-                new TestAndroidPermissionDelegate(
-                        null, Arrays.asList(Manifest.permission.ACCESS_FINE_LOCATION), null));
+        WindowAndroid windowAndroid = mActivityTestRule.getActivity().getWindowAndroid();
+        windowAndroid.setAndroidPermissionDelegate(new TestAndroidPermissionDelegate(
+                null, Arrays.asList(Manifest.permission.ACCESS_FINE_LOCATION), null));
         LocationSettingsTestUtil.setSystemLocationSettingEnabled(true);
 
         try {
@@ -100,14 +100,11 @@
                                     ContentSettingValues.ALLOW));
 
             mActivityTestRule.loadUrl(mTestServer.getURL(GEOLOCATION_PAGE));
-            CriteriaHelper.pollInstrumentationThread(
-                    () -> mActivityTestRule.getActivity().findViewById(R.id.message_container));
-            MessageContainer container =
-                    (MessageContainer) mActivityTestRule.getActivity().findViewById(
-                            R.id.message_container);
 
-            CriteriaHelper.pollInstrumentationThread(
-                    () -> container.getChildCount() > 0, "Message is not enqueued.");
+            CriteriaHelper.pollUiThread(() -> {
+                Criteria.checkThat("Message is not enqueued.",
+                        MessagesTestHelper.getMessageCount(windowAndroid), Matchers.is(1));
+            });
 
             final WebContents webContents = TestThreadUtils.runOnUiThreadBlockingNoException(
                     () -> mActivityTestRule.getActivity().getActivityTab().getWebContents());
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index c84e127..151965a 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2454,7 +2454,7 @@
       "//chrome/browser/ui/webui/chromeos/parent_access:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/vm:mojo_bindings",
       "//chrome/browser/ui/webui/settings/ash/os_apps_page/mojom",
-      "//chrome/browser/ui/webui/settings/chromeos/search:mojo_bindings",
+      "//chrome/browser/ui/webui/settings/ash/search:mojo_bindings",
       "//chrome/common/chromeos/extensions",
       "//chromeos/components/chromebox_for_meetings/buildflags",
       "//chromeos/components/local_search_service",
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
index a020ce9..4f6b23e 100644
--- a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
+++ b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
@@ -244,19 +244,13 @@
   // themselves track the current state of the monitoring settings and only
   // perform monitoring if it is active.
   if (install_attributes->IsCloudManaged()) {
-    managed_session_service_ =
-        std::make_unique<policy::ManagedSessionService>();
+    CreateManagedSessionServiceAndReporters();
     CreateStatusUploader(managed_session_service_.get());
     syslog_uploader_ =
         std::make_unique<SystemLogUploader>(nullptr, task_runner_);
     heartbeat_scheduler_ = std::make_unique<HeartbeatScheduler>(
         g_browser_process->gcm_driver(), client(), device_store_.get(),
         install_attributes->GetDeviceId(), task_runner_);
-    login_logout_reporter_ = ash::reporting::LoginLogoutReporter::Create(
-        managed_session_service_.get());
-    user_added_removed_reporter_ =
-        ::reporting::UserAddedRemovedReporter::Create(
-            managed_session_service_.get());
     metric_reporting_manager_ = reporting::MetricReportingManager::Create(
         managed_session_service_.get());
   }
@@ -264,6 +258,14 @@
   NotifyConnected();
 }
 
+void DeviceCloudPolicyManagerAsh::OnPolicyStoreReady(
+    chromeos::InstallAttributes* install_attributes) {
+  if (!install_attributes->IsCloudManaged()) {
+    return;
+  }
+  CreateManagedSessionServiceAndReporters();
+}
+
 void DeviceCloudPolicyManagerAsh::Unregister(UnregisterCallback callback) {
   if (!service()) {
     LOG(ERROR) << "Tried to unregister but DeviceCloudPolicyManagerAsh is "
@@ -333,4 +335,16 @@
       kDeviceStatusUploadFrequency);
 }
 
+void DeviceCloudPolicyManagerAsh::CreateManagedSessionServiceAndReporters() {
+  if (managed_session_service_) {
+    return;
+  }
+
+  managed_session_service_ = std::make_unique<policy::ManagedSessionService>();
+  login_logout_reporter_ = ash::reporting::LoginLogoutReporter::Create(
+      managed_session_service_.get());
+  user_added_removed_reporter_ = ::reporting::UserAddedRemovedReporter::Create(
+      managed_session_service_.get());
+}
+
 }  // namespace policy
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h
index 37934c5b..a45bea4 100644
--- a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h
+++ b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h
@@ -110,6 +110,9 @@
   void StartConnection(std::unique_ptr<CloudPolicyClient> client_to_connect,
                        chromeos::InstallAttributes* install_attributes);
 
+  // Called when policy store is ready.
+  void OnPolicyStoreReady(chromeos::InstallAttributes* install_attributes);
+
   // Sends the unregister request. |callback| is invoked with a boolean
   // parameter indicating the result when done.
   virtual void Unregister(UnregisterCallback callback);
@@ -153,6 +156,20 @@
     return machine_certificate_uploader_.get();
   }
 
+ protected:
+  // Object that monitors managed session related events used by reporting
+  // services, protected for testing.
+  std::unique_ptr<ManagedSessionService> managed_session_service_;
+
+  // Object that reports login/logout events to the server, protected for
+  // testing.
+  std::unique_ptr<ash::reporting::LoginLogoutReporter> login_logout_reporter_;
+
+  // Object that reports user added/removed events to the server, protected for
+  // testing.
+  std::unique_ptr<reporting::UserAddedRemovedReporter>
+      user_added_removed_reporter_;
+
  private:
   // Saves the state keys received from |session_manager_client_|.
   void OnStateKeysUpdated();
@@ -163,6 +180,10 @@
   // Factory function to create the StatusUploader.
   void CreateStatusUploader(ManagedSessionService* managed_session_service);
 
+  // Init |managed_session_service_| and reporting objects such as
+  // |login_logout_reporter_|, and |user_added_removed_reporter_|.
+  void CreateManagedSessionServiceAndReporters();
+
   // Points to the same object as the base CloudPolicyManager::store(), but with
   // actual device policy specific type.
   std::unique_ptr<DeviceCloudPolicyStoreAsh> device_store_;
@@ -183,17 +204,6 @@
   // the server, to monitor connectivity.
   std::unique_ptr<HeartbeatScheduler> heartbeat_scheduler_;
 
-  // Object that monitors managed session related events used by reporting
-  // services.
-  std::unique_ptr<ManagedSessionService> managed_session_service_;
-
-  // Object that reports login/logout events to the server.
-  std::unique_ptr<ash::reporting::LoginLogoutReporter> login_logout_reporter_;
-
-  // Object that reports user added/removed events to the server.
-  std::unique_ptr<reporting::UserAddedRemovedReporter>
-      user_added_removed_reporter_;
-
   // Object that initiates device metrics collection and reporting.
   std::unique_ptr<reporting::MetricReportingManager> metric_reporting_manager_;
 
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash_unittest.cc b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash_unittest.cc
index 6092148b..1e7218c 100644
--- a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash_unittest.cc
+++ b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash_unittest.cc
@@ -122,6 +122,18 @@
   }
 
   ~TestingDeviceCloudPolicyManagerAsh() override {}
+
+  ManagedSessionService* GetManagedSessionService() {
+    return managed_session_service_.get();
+  }
+
+  ash::reporting::LoginLogoutReporter* GetLoginLogoutReporter() {
+    return login_logout_reporter_.get();
+  }
+
+  reporting::UserAddedRemovedReporter* GetUserAddedRemovedReporter() {
+    return user_added_removed_reporter_.get();
+  }
 };
 
 class DeviceCloudPolicyManagerAshTest
@@ -140,11 +152,6 @@
         chromeos::system::kSerialNumberKeyForTest, "test_sn");
     fake_statistics_provider_.SetMachineStatistic(
         chromeos::system::kHardwareClassKey, "test_hw");
-    std::vector<std::string> state_keys;
-    state_keys.push_back("1");
-    state_keys.push_back("2");
-    state_keys.push_back("3");
-    session_manager_client_.set_server_backed_state_keys(state_keys);
     session_manager_client_.AddObserver(this);
   }
 
@@ -229,10 +236,23 @@
     ASSERT_EQ(chromeos::InstallAttributes::LOCK_SUCCESS, result);
   }
 
+  void AddStateKeys() {
+    std::vector<std::string> state_keys;
+    state_keys.push_back("1");
+    state_keys.push_back("2");
+    state_keys.push_back("3");
+    session_manager_client_.set_server_backed_state_keys(state_keys);
+  }
+
   void ConnectManager(bool expectExternalDataManagerConnectCall = true) {
     if (expectExternalDataManagerConnectCall) {
       EXPECT_CALL(*external_data_manager_, Connect(_));
     }
+    AddStateKeys();
+    InitDeviceCloudPolicyInitializer();
+  }
+
+  void InitDeviceCloudPolicyInitializer() {
     manager_->Initialize(&local_state_);
     policy::EnrollmentRequisitionManager::Initialize();
     initializer_ = std::make_unique<DeviceCloudPolicyInitializer>(
@@ -347,6 +367,9 @@
             job_type);
   // Should create a status uploader for reporting on enrolled devices.
   EXPECT_TRUE(manager_->GetStatusUploader());
+  EXPECT_TRUE(manager_->GetManagedSessionService());
+  EXPECT_TRUE(manager_->GetLoginLogoutReporter());
+  EXPECT_TRUE(manager_->GetUserAddedRemovedReporter());
   VerifyPolicyPopulated();
 
   ShutdownManager();
@@ -385,6 +408,9 @@
   // Should create a status provider for reporting on enrolled devices, even
   // those that aren't managed.
   EXPECT_TRUE(manager_->GetStatusUploader());
+  EXPECT_TRUE(manager_->GetManagedSessionService());
+  EXPECT_TRUE(manager_->GetLoginLogoutReporter());
+  EXPECT_TRUE(manager_->GetUserAddedRemovedReporter());
 
   // Switch back to ACTIVE, service the policy fetch and let it propagate.
   device_policy_->policy_data().set_state(em::PolicyData::ACTIVE);
@@ -416,11 +442,45 @@
   EXPECT_TRUE(manager_->policies().Equals(bundle));
   // Should not create a status provider for reporting on consumer devices.
   EXPECT_FALSE(manager_->GetStatusUploader());
+  EXPECT_FALSE(manager_->GetManagedSessionService());
+  EXPECT_FALSE(manager_->GetLoginLogoutReporter());
+  EXPECT_FALSE(manager_->GetUserAddedRemovedReporter());
 
   ShutdownManager();
   EXPECT_TRUE(manager_->policies().Equals(bundle));
 }
 
+TEST_F(DeviceCloudPolicyManagerAshTest, EnrolledDeviceNoStateKeysGenerated) {
+  LockDevice();
+  FlushDeviceSettings();
+  EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
+  EXPECT_TRUE(manager_->IsInitializationComplete(POLICY_DOMAIN_CHROME));
+  VerifyPolicyPopulated();
+
+  EXPECT_CALL(job_creation_handler_, OnJobCreation).Times(0);
+  AllowUninterestingRemoteCommandFetches();
+
+  EXPECT_FALSE(manager_->GetManagedSessionService());
+  EXPECT_FALSE(manager_->GetLoginLogoutReporter());
+  EXPECT_FALSE(manager_->GetUserAddedRemovedReporter());
+
+  InitDeviceCloudPolicyInitializer();
+
+  // Status uploader for reporting on enrolled devices is only created on
+  // connect call.
+  EXPECT_FALSE(manager_->GetStatusUploader());
+  // Managed session service and reporters are created when notified by
+  // |DeviceCloudPolicyInitializer| that the policy store is ready.
+  EXPECT_TRUE(manager_->GetManagedSessionService());
+  EXPECT_TRUE(manager_->GetLoginLogoutReporter());
+  EXPECT_TRUE(manager_->GetUserAddedRemovedReporter());
+
+  ShutdownManager();
+
+  EXPECT_EQ(store_->policy()->service_account_identity(),
+            PolicyBuilder::kFakeServiceAccountIdentity);
+}
+
 class DeviceCloudPolicyManagerAshObserverTest
     : public DeviceCloudPolicyManagerAshTest,
       public DeviceCloudPolicyManagerAsh::Observer {
@@ -521,6 +581,7 @@
       EXPECT_CALL(mock_attestation_flow_, GetCertificate(_, _, _, _, _, _))
           .WillOnce(WithArgs<5>(Invoke(CertCallbackSuccess)));
     }
+    AddStateKeys();
   }
 
   void ExpectFailedEnrollment(EnrollmentStatus::Status status) {
diff --git a/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.cc b/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.cc
index df4a1eda..2a09930 100644
--- a/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.cc
+++ b/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.cc
@@ -259,15 +259,24 @@
     return;
   }
 
+  if (!policy_store_->is_initialized() || !policy_store_->has_policy()) {
+    return;
+  }
+
+  if (!policy_manager_store_ready_notified_) {
+    policy_manager_store_ready_notified_ = true;
+    policy_manager_->OnPolicyStoreReady(install_attributes_);
+  }
+
   // Currently reven devices don't support sever-backed state keys, but they
   // also don't support FRE/AutoRE so don't block initialization of device
   // policy on state keys being available on reven.
   // TODO(b/208705225): Remove this special case when reven supports state keys.
   const bool allow_init_without_state_keys = ash::switches::IsRevenBranding();
+
   // TODO(b/181140445): If we had a separate state keys upload request to DM
   // Server we could drop the `state_keys_broker_->available()` requirement.
-  if (policy_store_->is_initialized() && policy_store_->has_policy() &&
-      (allow_init_without_state_keys || state_keys_broker_->available())) {
+  if (allow_init_without_state_keys || state_keys_broker_->available()) {
     StartConnection(CreateClient(enterprise_service_));
   }
 }
diff --git a/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.h b/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.h
index 143f9ab2..657713f 100644
--- a/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.h
+++ b/chrome/browser/ash/policy/enrollment/device_cloud_policy_initializer.h
@@ -103,6 +103,7 @@
   DeviceCloudPolicyManagerAsh* policy_manager_;
   chromeos::system::StatisticsProvider* statistics_provider_;
   bool is_initialized_ = false;
+  bool policy_manager_store_ready_notified_ = false;
 
   base::CallbackListSubscription state_keys_update_subscription_;
 
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index b455296fc..03509986 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -249,9 +249,9 @@
 #include "chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.h"
 #include "chrome/browser/ui/webui/nearby_share/public/mojom/nearby_share_settings.mojom.h"  // nogncheck crbug.com/1125897
 #include "chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search.mojom.h"
+#include "chrome/browser/ui/webui/settings/ash/search/user_action_recorder.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search.mojom.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/user_action_recorder.mojom.h"
 #include "chromeos/components/local_search_service/public/mojom/index.mojom.h"
 #include "chromeos/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
 #include "chromeos/services/cellular_setup/public/mojom/cellular_setup.mojom.h"
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 65e42e5e..792ef16 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -4795,6 +4795,9 @@
     "../ui/webui/settings/ash/calculator/size_calculator_test_api.h",
     "../ui/webui/settings/ash/os_apps_page/app_notification_handler_unittest.cc",
     "../ui/webui/settings/ash/os_apps_page/mojom/app_type_mojom_traits_unittest.cc",
+    "../ui/webui/settings/ash/search/per_session_settings_user_action_tracker_unittest.cc",
+    "../ui/webui/settings/ash/search/search_handler_unittest.cc",
+    "../ui/webui/settings/ash/search/search_tag_registry_unittest.cc",
     "../ui/webui/settings/chromeos/ambient_mode_handler_unittest.cc",
     "../ui/webui/settings/chromeos/bluetooth_handler_unittest.cc",
     "../ui/webui/settings/chromeos/change_picture_handler_unittest.cc",
@@ -4806,9 +4809,6 @@
     "../ui/webui/settings/chromeos/multidevice_handler_unittest.cc",
     "../ui/webui/settings/chromeos/os_settings_manager_unittest.cc",
     "../ui/webui/settings/chromeos/os_settings_section_unittest.cc",
-    "../ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker_unittest.cc",
-    "../ui/webui/settings/chromeos/search/search_handler_unittest.cc",
-    "../ui/webui/settings/chromeos/search/search_tag_registry_unittest.cc",
     "../ui/webui/settings/chromeos/settings_user_action_tracker_unittest.cc",
   ]
   if (use_cups) {
diff --git a/chrome/browser/extensions/activity_log/activity_log_policy.cc b/chrome/browser/extensions/activity_log/activity_log_policy.cc
index 4325583..2e36541 100644
--- a/chrome/browser/extensions/activity_log/activity_log_policy.cc
+++ b/chrome/browser/extensions/activity_log/activity_log_policy.cc
@@ -90,7 +90,7 @@
 
   // Strip query parameters, username/password, etc., from URLs.
   if (action->page_url().is_valid() || action->arg_url().is_valid()) {
-    url::Replacements<char> url_sanitizer;
+    GURL::Replacements url_sanitizer;
     url_sanitizer.ClearUsername();
     url_sanitizer.ClearPassword();
     url_sanitizer.ClearQuery();
diff --git a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_api.cc b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_api.cc
index 7971b8b..b570d193 100644
--- a/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_api.cc
+++ b/chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_api.cc
@@ -65,8 +65,8 @@
 
   safe_browsing::ReferrerChain referrer_chain;
   SafeBrowsingNavigationObserverManager::AttributionResult result =
-      navigation_observer_manager->IdentifyReferrerChainByWebContents(
-          contents, kReferrerUserGestureLimit, &referrer_chain);
+      navigation_observer_manager->IdentifyReferrerChainByRenderFrameHost(
+          contents->GetMainFrame(), kReferrerUserGestureLimit, &referrer_chain);
 
   // If the referrer chain is incomplete we'll append the most recent
   // navigations to referrer chain for diagnostic purposes. This only happens if
diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
index 094fe834..abf92f7 100644
--- a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
+++ b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
@@ -1198,8 +1198,11 @@
     return RespondNow(ArgumentList(
         api::webstore_private::GetReferrerChain::Results::Create("")));
 
-  content::WebContents* web_contents = GetSenderWebContents();
-  if (!web_contents) {
+  content::RenderFrameHost* rfh = render_frame_host();
+  content::RenderFrameHost* outermost_rfh =
+      rfh ? rfh->GetOutermostMainFrame() : nullptr;
+
+  if (!outermost_rfh) {
     return RespondNow(ErrorWithArguments(
         api::webstore_private::GetReferrerChain::Results::Create(""),
         kWebstoreUserCancelledError));
@@ -1211,8 +1214,8 @@
 
   safe_browsing::ReferrerChain referrer_chain;
   SafeBrowsingNavigationObserverManager::AttributionResult result =
-      navigation_observer_manager->IdentifyReferrerChainByWebContents(
-          web_contents, kExtensionReferrerUserGestureLimit, &referrer_chain);
+      navigation_observer_manager->IdentifyReferrerChainByRenderFrameHost(
+          outermost_rfh, kExtensionReferrerUserGestureLimit, &referrer_chain);
 
   // If the referrer chain is incomplete we'll append the most recent
   // navigations to referrer chain for diagnostic purposes. This only happens if
diff --git a/chrome/browser/file_select_helper.cc b/chrome/browser/file_select_helper.cc
index c815d354..d2a7e57 100644
--- a/chrome/browser/file_select_helper.cc
+++ b/chrome/browser/file_select_helper.cc
@@ -32,6 +32,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/file_select_listener.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"
 #include "content/public/browser/render_view_host.h"
@@ -634,6 +635,9 @@
   sb_service->download_protection_service()->CheckPPAPIDownloadRequest(
       requestor_url,
       render_frame_host_ ? render_frame_host_->GetLastCommittedURL() : GURL(),
+      render_frame_host_
+          ? render_frame_host_->GetOutermostMainFrame()->GetGlobalId()
+          : content::GlobalRenderFrameHostId(),
       WebContents::FromRenderFrameHost(render_frame_host_), default_file_path,
       alternate_extensions, profile_,
       base::BindOnce(
diff --git a/chrome/browser/metrics/family_user_metrics_provider_browsertest.cc b/chrome/browser/metrics/family_user_metrics_provider_browsertest.cc
index 3eb5f9b..e6b5f61 100644
--- a/chrome/browser/metrics/family_user_metrics_provider_browsertest.cc
+++ b/chrome/browser/metrics/family_user_metrics_provider_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/metrics/family_user_metrics_provider.h"
 
+#include "ash/components/settings/cros_settings_names.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
@@ -12,6 +13,8 @@
 #include "chrome/browser/ash/login/test/logged_in_user_mixin.h"
 #include "chrome/browser/ash/login/test/scoped_policy_update.h"
 #include "chrome/browser/ash/login/test/user_policy_mixin.h"
+#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
+#include "chrome/browser/ash/settings/stub_cros_settings_provider.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
@@ -215,6 +218,9 @@
         ->set_ephemeral_users_enabled(true);
 
     device_policy_update.reset();
+
+    scoped_testing_cros_settings_.device_settings()->SetBoolean(
+        ash::kReportDeviceLoginLogout, false);
   }
 
   // MixinBasedInProcessBrowserTest:
@@ -232,6 +238,8 @@
   ash::LoggedInUserMixin logged_in_user_mixin_{
       &mixin_host_, ash::LoggedInUserMixin::LogInType::kRegular,
       embedded_test_server(), this};
+
+  ash::ScopedTestingCrosSettings scoped_testing_cros_settings_;
 };
 
 // Tests that regular ephemeral users report 0 for number of secondary accounts.
diff --git a/chrome/browser/net/secure_dns_util.cc b/chrome/browser/net/secure_dns_util.cc
index 2280d42b..8821d9db 100644
--- a/chrome/browser/net/secure_dns_util.cc
+++ b/chrome/browser/net/secure_dns_util.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/net/secure_dns_util.h"
 
 #include <algorithm>
+#include <memory>
 #include <string>
 
 #include "base/check.h"
@@ -14,6 +15,7 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
 #include "build/build_config.h"
+#include "chrome/browser/net/dns_probe_runner.h"
 #include "chrome/common/chrome_features.h"
 #include "components/country_codes/country_codes.h"
 #include "components/embedder_support/pref_names.h"
@@ -22,10 +24,9 @@
 #include "net/dns/public/dns_config_overrides.h"
 #include "net/dns/public/dns_over_https_config.h"
 #include "net/dns/public/doh_provider_entry.h"
+#include "net/dns/public/secure_dns_mode.h"
 
-namespace chrome_browser_net {
-
-namespace secure_dns {
+namespace chrome_browser_net::secure_dns {
 
 namespace {
 
@@ -145,13 +146,17 @@
   UMA_HISTOGRAM_BOOLEAN("Net.DNS.UI.ProbeAttemptSuccess", success);
 }
 
-void ApplyConfig(net::DnsConfigOverrides* overrides,
-                 base::StringPiece doh_config) {
-  overrides->dns_over_https_config =
-      net::DnsOverHttpsConfig::FromString(doh_config);
-  CHECK(overrides->dns_over_https_config);  // `doh_config` must be valid.
+std::unique_ptr<DnsProbeRunner> MakeProbeRunner(
+    net::DnsOverHttpsConfig doh_config,
+    const DnsProbeRunner::NetworkContextGetter& network_context_getter) {
+  net::DnsConfigOverrides overrides;
+  overrides.search = std::vector<std::string>();
+  overrides.attempts = 1;
+  overrides.secure_dns_mode = net::SecureDnsMode::kSecure;
+  overrides.dns_over_https_config = std::move(doh_config);
+
+  return std::make_unique<DnsProbeRunner>(std::move(overrides),
+                                          network_context_getter);
 }
 
-}  // namespace secure_dns
-
-}  // namespace chrome_browser_net
+}  // namespace chrome_browser_net::secure_dns
diff --git a/chrome/browser/net/secure_dns_util.h b/chrome/browser/net/secure_dns_util.h
index 5e9b3c4..cd4d993 100644
--- a/chrome/browser/net/secure_dns_util.h
+++ b/chrome/browser/net/secure_dns_util.h
@@ -5,21 +5,18 @@
 #ifndef CHROME_BROWSER_NET_SECURE_DNS_UTIL_H_
 #define CHROME_BROWSER_NET_SECURE_DNS_UTIL_H_
 
+#include <memory>
 #include <vector>
 
 #include "base/strings/string_piece.h"
+#include "chrome/browser/net/dns_probe_runner.h"
+#include "net/dns/public/dns_over_https_config.h"
 #include "net/dns/public/doh_provider_entry.h"
 
-namespace net {
-struct DnsConfigOverrides;
-}  // namespace net
-
 class PrefRegistrySimple;
 class PrefService;
 
-namespace chrome_browser_net {
-
-namespace secure_dns {
+namespace chrome_browser_net::secure_dns {
 
 // Returns the subset of |providers| that are marked for use in the specified
 // country.
@@ -47,9 +44,10 @@
 void UpdateValidationHistogram(bool valid);
 void UpdateProbeHistogram(bool success);
 
-// Modifies `overrides` to use the DoH servers specified by `doh_config`.
-void ApplyConfig(net::DnsConfigOverrides* overrides,
-                 base::StringPiece doh_config);
+// Returns a DNS prober configured for testing DoH servers
+std::unique_ptr<DnsProbeRunner> MakeProbeRunner(
+    net::DnsOverHttpsConfig doh_config,
+    const DnsProbeRunner::NetworkContextGetter& network_context_getter);
 
 // Registers the backup preference required for the DNS probes setting reset.
 // TODO(crbug.com/1062698): Remove this once the privacy settings redesign
@@ -63,8 +61,6 @@
 // is fully launched.
 void MigrateProbesSettingToOrFromBackup(PrefService* prefs);
 
-}  // namespace secure_dns
-
-}  // namespace chrome_browser_net
+}  // namespace chrome_browser_net::secure_dns
 
 #endif  // CHROME_BROWSER_NET_SECURE_DNS_UTIL_H_
diff --git a/chrome/browser/net/secure_dns_util_unittest.cc b/chrome/browser/net/secure_dns_util_unittest.cc
index ee9bf61..955a38f 100644
--- a/chrome/browser/net/secure_dns_util_unittest.cc
+++ b/chrome/browser/net/secure_dns_util_unittest.cc
@@ -5,9 +5,13 @@
 #include "chrome/browser/net/secure_dns_util.h"
 
 #include <memory>
+#include <string>
+#include <vector>
 
+#include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
 #include "build/build_config.h"
 #include "chrome/common/chrome_features.h"
 #include "components/country_codes/country_codes.h"
@@ -17,14 +21,15 @@
 #include "net/dns/public/dns_config_overrides.h"
 #include "net/dns/public/dns_over_https_config.h"
 #include "net/dns/public/doh_provider_entry.h"
+#include "net/dns/public/secure_dns_mode.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/test/test_network_context.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using testing::ElementsAre;
 
-namespace chrome_browser_net {
-
-namespace secure_dns {
+namespace chrome_browser_net::secure_dns {
 
 class SecureDnsUtilTest : public testing::Test {};
 
@@ -72,31 +77,21 @@
   EXPECT_FALSE(prefs.GetBoolean(kAlternateErrorPagesBackup));
 }
 
-TEST(SecureDnsUtil, ApplyDohTemplatePost) {
-  std::string post_template("https://valid");
-  net::DnsConfigOverrides overrides;
-  ApplyConfig(&overrides, post_template);
+TEST(SecureDnsUtil, MakeProbeRunner) {
+  base::test::SingleThreadTaskEnvironment task_environment;
 
-  EXPECT_EQ(*net::DnsOverHttpsConfig::FromString(post_template),
-            overrides.dns_over_https_config);
-}
-
-TEST(SecureDnsUtil, ApplyTwoDohTemplates) {
-  std::string two_templates("https://valid https://valid/{?dns}");
-  net::DnsConfigOverrides overrides;
-  ApplyConfig(&overrides, two_templates);
-
-  EXPECT_EQ(*net::DnsOverHttpsConfig::FromString(two_templates),
-            overrides.dns_over_https_config);
-}
-
-TEST(SecureDnsUtil, ApplyDohTemplateGet) {
-  std::string get_template("https://valid/{?dns}");
-  net::DnsConfigOverrides overrides;
-  ApplyConfig(&overrides, get_template);
-
-  EXPECT_EQ(*net::DnsOverHttpsConfig::FromString(get_template),
-            overrides.dns_over_https_config);
+  auto doh_config = *net::DnsOverHttpsConfig::FromString(
+      "https://valid https://valid/{?dns}");
+  network::TestNetworkContext test_context;
+  auto prober = MakeProbeRunner(
+      doh_config,
+      base::BindLambdaForTesting(
+          [&]() -> network::mojom::NetworkContext* { return &test_context; }));
+  auto overrides = prober->GetConfigOverridesForTesting();
+  EXPECT_EQ(1, overrides.attempts);
+  EXPECT_EQ(std::vector<std::string>(), overrides.search);
+  EXPECT_EQ(net::SecureDnsMode::kSecure, overrides.secure_dns_mode);
+  EXPECT_EQ(doh_config, overrides.dns_over_https_config);
 }
 
 net::DohProviderEntry::List GetProvidersForTesting() {
@@ -199,6 +194,4 @@
   histograms.ExpectTotalCount(kUmaBase + ".Unselected", 1u);
 }
 
-}  // namespace secure_dns
-
-}  // namespace chrome_browser_net
+}  // namespace chrome_browser_net::secure_dns
diff --git a/chrome/browser/prerender/prerender_omnibox_ui_browsertest.cc b/chrome/browser/prerender/prerender_omnibox_ui_browsertest.cc
index ee1e629..1c5531c 100644
--- a/chrome/browser/prerender/prerender_omnibox_ui_browsertest.cc
+++ b/chrome/browser/prerender/prerender_omnibox_ui_browsertest.cc
@@ -15,7 +15,10 @@
 #include "chrome/browser/predictors/autocomplete_action_predictor_factory.h"
 #include "chrome/browser/prerender/prerender_manager.h"
 #include "chrome/browser/prerender/prerender_utils.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/safe_browsing/test_safe_browsing_navigation_observer_manager.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/location_bar/location_bar.h"
@@ -28,8 +31,10 @@
 #include "chrome/test/base/search_test_utils.h"
 #include "components/omnibox/browser/base_search_provider.h"
 #include "components/omnibox/browser/omnibox_edit_model.h"
+#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/search_engines/template_url_data.h"
 #include "components/search_engines/template_url_service.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/browser_test.h"
@@ -878,4 +883,81 @@
   EXPECT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId);
 }
 
+class PrerenderOmniboxReferrerChainUIBrowserTest
+    : public PrerenderOmniboxUIBrowserTest {
+ public:
+  void SetUpOnMainThread() override {
+    // Disable Safe Browsing service so we can directly control when
+    // SafeBrowsingNavigationObserverManager and SafeBrowsingNavigationObserver
+    // are instantiated.
+    browser()->profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled,
+                                                 false);
+    PrerenderOmniboxUIBrowserTest::SetUpOnMainThread();
+    observer_manager_ = std::make_unique<
+        safe_browsing::TestSafeBrowsingNavigationObserverManager>(browser());
+    observer_manager_->ObserveContents(
+        browser()->tab_strip_model()->GetActiveWebContents());
+  }
+
+  absl::optional<size_t> FindNavigationEventIndex(
+      const GURL& target_url,
+      content::GlobalRenderFrameHostId outermost_main_frame_id) {
+    return observer_manager_->navigation_event_list()->FindNavigationEvent(
+        base::Time::Now(), target_url, GURL(), SessionID::InvalidValue(),
+        outermost_main_frame_id,
+        (observer_manager_->navigation_event_list()->NavigationEventsSize() -
+         1));
+  }
+
+  safe_browsing::NavigationEvent* GetNavigationEvent(size_t index) {
+    return observer_manager_->navigation_event_list()
+        ->navigation_events()[index]
+        .get();
+  }
+
+  void TearDownOnMainThread() override { observer_manager_.reset(); }
+
+ private:
+  std::unique_ptr<safe_browsing::TestSafeBrowsingNavigationObserverManager>
+      observer_manager_;
+};
+
+IN_PROC_BROWSER_TEST_F(PrerenderOmniboxReferrerChainUIBrowserTest,
+                       PrerenderHasNoInitiator) {
+  Observe(GetActiveWebContents());
+  base::HistogramTester histogram_tester;
+  const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
+  ASSERT_TRUE(GetActiveWebContents());
+  ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), kInitialUrl));
+
+  content::test::PrerenderHostRegistryObserver registry_observer(
+      *GetActiveWebContents());
+
+  // Attempt to prerender a direct URL input.
+  ASSERT_TRUE(GetAutocompleteActionPredictor());
+  const GURL kPrerenderingUrl =
+      embedded_test_server()->GetURL("/empty.html?prerender");
+  AutocompleteMatch match;
+  match.type = AutocompleteMatchType::URL_WHAT_YOU_TYPED;
+  match.destination_url = kPrerenderingUrl;
+  GetAutocompleteActionPredictor()->StartPrerendering(
+      match, *GetActiveWebContents(), gfx::Size(50, 50));
+
+  registry_observer.WaitForTrigger(kPrerenderingUrl);
+  int host_id = prerender_helper().GetHostForUrl(kPrerenderingUrl);
+  ASSERT_NE(host_id, content::RenderFrameHost::kNoFrameTreeNodeId);
+  prerender_helper().WaitForPrerenderLoadCompletion(host_id);
+  // By using no id, we should get the most recent navigation event.
+  auto index = FindNavigationEventIndex(kPrerenderingUrl,
+                                        content::GlobalRenderFrameHostId());
+  ASSERT_TRUE(index);
+
+  // Since this was triggered by the omnibox (and hence by the user), we should
+  // have no initiator outermost main frame id. I.e., it would be incorrect to
+  // attribute this load to the document previously loaded in the outermost main
+  // frame.
+  auto* nav_event = GetNavigationEvent(*index);
+  EXPECT_FALSE(nav_event->initiator_outermost_main_frame_id);
+}
+
 }  // namespace
diff --git a/chrome/browser/privacy/secure_dns_bridge.cc b/chrome/browser/privacy/secure_dns_bridge.cc
index 341ee4b..aefdc1ef 100644
--- a/chrome/browser/privacy/secure_dns_bridge.cc
+++ b/chrome/browser/privacy/secure_dns_bridge.cc
@@ -13,6 +13,7 @@
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/bind.h"
+#include "base/check.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/synchronization/waitable_event.h"
 #include "chrome/browser/browser_process.h"
@@ -30,6 +31,7 @@
 #include "net/dns/public/dns_over_https_config.h"
 #include "net/dns/public/doh_provider_entry.h"
 #include "net/dns/public/secure_dns_mode.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 using base::android::JavaParamRef;
 using base::android::ScopedJavaLocalRef;
@@ -51,10 +53,13 @@
 // |waiter| when the probe has completed.  Must run on the UI thread.
 void RunProbe(base::WaitableEvent* waiter,
               bool* success,
-              net::DnsConfigOverrides overrides) {
+              const std::string& doh_config) {
+  absl::optional<net::DnsOverHttpsConfig> parsed =
+      net::DnsOverHttpsConfig::FromString(doh_config);
+  DCHECK(parsed.has_value());  // `doh_config` must be valid.
   auto* manager = g_browser_process->system_network_context_manager();
-  auto runner = std::make_unique<DnsProbeRunner>(
-      std::move(overrides),
+  auto runner = secure_dns::MakeProbeRunner(
+      std::move(*parsed),
       base::BindRepeating(&SystemNetworkContextManager::GetContext,
                           base::Unretained(manager)));
   auto* const runner_ptr = runner.get();
@@ -154,13 +159,6 @@
 static jboolean JNI_SecureDnsBridge_ProbeConfig(
     JNIEnv* env,
     const JavaParamRef<jstring>& doh_config) {
-  net::DnsConfigOverrides overrides;
-  overrides.search = std::vector<std::string>();
-  overrides.attempts = 1;
-  overrides.secure_dns_mode = net::SecureDnsMode::kSecure;
-  secure_dns::ApplyConfig(&overrides,
-                          base::android::ConvertJavaStringToUTF8(doh_config));
-
   // Android recommends converting async functions to blocking when using JNI:
   // https://developer.android.com/training/articles/perf-jni.
   // This function converts the DnsProbeRunner, which can only be created and
@@ -171,7 +169,8 @@
   bool success;
   bool posted = content::GetUIThreadTaskRunner({})->PostTask(
       FROM_HERE,
-      base::BindOnce(RunProbe, &waiter, &success, std::move(overrides)));
+      base::BindOnce(RunProbe, &waiter, &success,
+                     base::android::ConvertJavaStringToUTF8(doh_config)));
   DCHECK(posted);
   waiter.Wait();
 
diff --git a/chrome/browser/resources/new_tab_page/modules/modules.ts b/chrome/browser/resources/new_tab_page/modules/modules.ts
index a03b49e7..7ae21ce 100644
--- a/chrome/browser/resources/new_tab_page/modules/modules.ts
+++ b/chrome/browser/resources/new_tab_page/modules/modules.ts
@@ -145,15 +145,30 @@
       if (loadTimeData.getBoolean('modulesRedesignedLayoutEnabled')) {
         // Wrap pairs of sibling short modules in a container. All other
         // modules will be placed in a container of their own.
-        if (moduleContainer.classList.contains(SHORT_CLASS_NAME) &&
+        if ((moduleContainer.classList.contains(SHORT_CLASS_NAME) ||
+             moduleContainer.hidden) &&
             shortModuleSiblingsContainer) {
-          // Add current sibling short module to container which already
-          // contains the previous sibling short module by setting its parent
-          // to be 'shortModuleSiblingsContainer'.
+          // Add current sibling short module or hidden module to sibling
+          // container which already contains one or more other modules, by
+          // setting its parent to be 'shortModuleSiblingsContainer'. We add
+          // hidden modules to the sibling container, so if a user reverts a
+          // module from its hidden state, the module assumes its original
+          // position.
           moduleContainerParent = shortModuleSiblingsContainer;
           this.$.modules.appendChild(shortModuleSiblingsContainer);
-          shortModuleSiblingsContainer = null;
+          // If another visible short module is added, a visible tall module is
+          // next, or we've reached the end of our container list we stop adding
+          // to the container.
+          if (!moduleContainer.hidden ||
+              index + 1 !== moduleContainers.length &&
+                  moduleContainers[index + 1].classList.contains(
+                      TALL_CLASS_NAME) &&
+                  !moduleContainers[index + 1].hidden ||
+              index + 1 === moduleContainers.length) {
+            shortModuleSiblingsContainer = null;
+          }
         } else if (
+            !moduleContainer.hidden &&
             moduleContainer.classList.contains(SHORT_CLASS_NAME) &&
             index + 1 !== moduleContainers.length &&
             moduleContainers[index + 1].classList.contains(SHORT_CLASS_NAME)) {
@@ -325,6 +340,10 @@
           moduleWrapper.parentElement!.hidden =
               this.moduleDisabled_(moduleWrapper.module.descriptor.id);
         });
+    // Append modules again to accommodate for empty space from removed module.
+    const moduleContainers = [...this.shadowRoot!.querySelectorAll<HTMLElement>(
+        '.module-container')];
+    this.appendModuleContainers_(moduleContainers);
   }
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index 1b5e09f..103a292d 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -42,7 +42,6 @@
     deps = [
       ":copy_browser_settings_tsc",
       ":preprocess_gen_v3",
-      ":preprocess_mojo_v3",
       ":preprocess_v3",
       "../../../../../ui/webui/resources:preprocess",
       "../../nearby_share/shared:preprocess_v3",
@@ -94,18 +93,18 @@
 # OS Settings specific mojo files, bundled in optimized builds.
 preprocess_if_expr("preprocess_mojo_v3") {
   deps = [
+    "//chrome/browser/ui/webui/settings/ash/search:mojo_bindings_js",
     "//chrome/browser/ui/webui/settings/chromeos/constants:mojom_js",
-    "//chrome/browser/ui/webui/settings/chromeos/search:mojo_bindings_js",
   ]
-  in_folder = get_path_info("../../../ui/webui/settings/chromeos/", "gen_dir")
+  in_folder = get_path_info("../../../ui/webui/settings/", "gen_dir")
   out_folder = "$target_gen_dir/$preprocess_folder_v3"
   out_manifest = "$target_gen_dir/$preprocess_mojo_manifest"
   in_files = [
-    "constants/routes.mojom-lite.js",
-    "constants/setting.mojom-lite.js",
-    "search/search.mojom-lite.js",
-    "search/search_result_icon.mojom-lite.js",
-    "search/user_action_recorder.mojom-lite.js",
+    "ash/search/search.mojom-lite.js",
+    "ash/search/search_result_icon.mojom-lite.js",
+    "ash/search/user_action_recorder.mojom-lite.js",
+    "chromeos/constants/routes.mojom-lite.js",
+    "chromeos/constants/setting.mojom-lite.js",
   ]
 }
 
@@ -201,6 +200,7 @@
   input_files_base_dir = rebase_path(".", "//")
   deps = [
     ":preprocess_external_mojo",
+    ":preprocess_mojo_v3",
     "../../nearby_share:build_mojo_grdp",
   ]
   grdp_files = [ "$root_gen_dir/chrome/browser/resources/nearby_share/nearby_share_mojo_resources.grdp" ]
@@ -213,6 +213,11 @@
     "ui/webui/resources/cr_components/app_management/app_management.mojom-lite.js|app-management/app_management.mojom-lite.js",
     "chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom-lite.js|os_apps_page/app_notification_handler.mojom-lite.js",
     "components/services/app_service/public/mojom/types.mojom-lite.js|app-management/types.mojom-lite.js",
+    "ash/search/search.mojom-lite.js|search/search.mojom-lite.js",
+    "ash/search/search_result_icon.mojom-lite.js|search/search_result_icon.mojom-lite.js",
+    "ash/search/user_action_recorder.mojom-lite.js|search/user_action_recorder.mojom-lite.js",
+    "chromeos/constants/routes.mojom-lite.js|constants/routes.mojom-lite.js",
+    "chromeos/constants/setting.mojom-lite.js|constants/setting.mojom-lite.js",
     "../../nearby_share/shared/nearby_share_progress_bar_dark.json|nearby_share_progress_bar_dark.json",
     "../../nearby_share/shared/nearby_share_progress_bar_light.json|nearby_share_progress_bar_light.json",
     "../../nearby_share/shared/nearby_share_pulse_animation_dark.json|nearby_share_pulse_animation_dark.json",
@@ -234,7 +239,6 @@
     deps += [
       ":copy_browser_settings_tsc",
       ":preprocess_gen_v3",
-      ":preprocess_mojo_v3",
       ":preprocess_v3",
       "../../nearby_share/shared:build_v3_grdp",
       "//ui/webui/resources/cr_components/app_management:build_ts",
@@ -706,7 +710,7 @@
 
 js_library("metrics_recorder") {
   sources = [ "metrics_recorder.m.js" ]
-  deps = [ "//chrome/browser/ui/webui/settings/chromeos/search:mojo_bindings_js_library_for_compile" ]
+  deps = [ "//chrome/browser/ui/webui/settings/ash/search:mojo_bindings_js_library_for_compile" ]
 }
 
 js_library("os_icons.m") {
@@ -773,7 +777,7 @@
 
 js_library("search_handler") {
   deps = [
-    "//chrome/browser/ui/webui/settings/chromeos/search:mojo_bindings_js_library_for_compile",
+    "//chrome/browser/ui/webui/settings/ash/search:mojo_bindings_js_library_for_compile",
     "//ui/webui/resources/js:cr.m",
   ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_true_wireless_images.html b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_true_wireless_images.html
index 41df625..7c3b8f3 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_true_wireless_images.html
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_bluetooth_true_wireless_images.html
@@ -3,28 +3,36 @@
     align-items: center;
     display: flex;
     flex-direction: row;
-    justify-content: space-around;
-    margin: 7% 10%;
+    gap: 100px;
+    justify-content: center;
+    margin: 40px 0;
   }
 
   .image-wrapper {
-    padding-bottom: 16px;
+    height: 60px;
+    margin-bottom: 16px;
+    width: 60px;
   }
 
   .image-wrapper img {
-    border: 1px solid lightgrey;
+    border: 1px solid var(--cros-color-primary-dark);
     border-radius: 50%;
-    height: 70px;
-    width: 70px;
+    height: 100%;
+    width: 100%;
   }
 
   .battery-container {
     align-items: center;
     display: flex;
     flex-direction: column;
+    height: 124px;
     justify-content: center;
   }
 
+  bluetooth-battery-icon-percentage {
+    margin-bottom: 8px;
+  }
+
   #notConnectedLabel {
     color: gray;
   }
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index 9a4e6cd..13621dd3 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -474,12 +474,23 @@
       "test_safe_browsing_service.h",
     ]
 
+    if (!is_android) {
+      sources += [
+        "test_safe_browsing_navigation_observer_manager.cc",
+        "test_safe_browsing_navigation_observer_manager.h",
+      ]
+    }
+
     deps = [
       ":safe_browsing",
+      "//chrome/browser:browser",
+      "//chrome/browser/profiles:profile",
+      "//chrome/browser/ui:ui",
       "//chrome/common",
       "//chrome/common/safe_browsing:proto",
       "//components/enterprise/common/proto:connectors_proto",
       "//components/safe_browsing:buildflags",
+      "//components/safe_browsing/content/browser:browser",
       "//components/safe_browsing/content/browser:safe_browsing_blocking_page",
       "//components/safe_browsing/content/browser:safe_browsing_service",
       "//components/safe_browsing/core/browser/db:database_manager",
@@ -487,6 +498,7 @@
       "//components/safe_browsing/core/browser/db:v4_protocol_manager_util",
       "//content/public/browser",
       "//services/network:test_support",
+      "//testing/gtest",
     ]
   }
 
diff --git a/chrome/browser/safe_browsing/chrome_client_side_detection_host_delegate.cc b/chrome/browser/safe_browsing/chrome_client_side_detection_host_delegate.cc
index 05be7683..38fc36d 100644
--- a/chrome/browser/safe_browsing/chrome_client_side_detection_host_delegate.cc
+++ b/chrome/browser/safe_browsing/chrome_client_side_detection_host_delegate.cc
@@ -18,6 +18,7 @@
 #include "components/safe_browsing/core/browser/db/database_manager.h"
 #include "components/safe_browsing/core/browser/sync/safe_browsing_primary_account_token_fetcher.h"
 #include "components/safe_browsing/core/browser/sync/sync_utils.h"
+#include "content/public/browser/global_routing_id.h"
 
 namespace safe_browsing {
 
@@ -88,7 +89,8 @@
 
 void ChromeClientSideDetectionHostDelegate::AddReferrerChain(
     ClientPhishingRequest* verdict,
-    GURL current_url) {
+    GURL current_url,
+    const content::GlobalRenderFrameHostId& current_outermost_main_frame_id) {
   SafeBrowsingNavigationObserverManager* navigation_observer_manager =
       GetSafeBrowsingNavigationObserverManager();
   if (!navigation_observer_manager) {
@@ -97,6 +99,7 @@
   SafeBrowsingNavigationObserverManager::AttributionResult result =
       navigation_observer_manager->IdentifyReferrerChainByEventURL(
           current_url, SessionID::InvalidValue(),
+          current_outermost_main_frame_id,
           kCSDAttributionUserGestureLimitForExtendedReporting,
           verdict->mutable_referrer_chain());
 
diff --git a/chrome/browser/safe_browsing/chrome_client_side_detection_host_delegate.h b/chrome/browser/safe_browsing/chrome_client_side_detection_host_delegate.h
index 4052f28..f3d6b0ab 100644
--- a/chrome/browser/safe_browsing/chrome_client_side_detection_host_delegate.h
+++ b/chrome/browser/safe_browsing/chrome_client_side_detection_host_delegate.h
@@ -8,6 +8,7 @@
 #include "base/memory/raw_ptr.h"
 #include "components/safe_browsing/content/browser/client_side_detection_host.h"
 #include "components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.h"
+#include "content/public/browser/global_routing_id.h"
 
 namespace safe_browsing {
 
@@ -37,7 +38,9 @@
   scoped_refptr<BaseUIManager> GetSafeBrowsingUIManager() override;
   ClientSideDetectionService* GetClientSideDetectionService() override;
   void AddReferrerChain(ClientPhishingRequest* verdict,
-                        GURL current_url) override;
+                        GURL current_url,
+                        const content::GlobalRenderFrameHostId&
+                            current_outermost_main_frame_id) override;
 
   void SetNavigationObserverManagerForTesting(
       SafeBrowsingNavigationObserverManager* navigation_observer_manager) {
diff --git a/chrome/browser/safe_browsing/chrome_client_side_detection_host_delegate_unittest.cc b/chrome/browser/safe_browsing/chrome_client_side_detection_host_delegate_unittest.cc
index 59586f41..921d7aa 100644
--- a/chrome/browser/safe_browsing/chrome_client_side_detection_host_delegate_unittest.cc
+++ b/chrome/browser/safe_browsing/chrome_client_side_detection_host_delegate_unittest.cc
@@ -14,6 +14,7 @@
 #include "components/safe_browsing/content/browser/safe_browsing_navigation_observer.h"
 #include "components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.h"
 #include "components/safe_browsing/core/common/features.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/test/navigation_simulator.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -89,7 +90,8 @@
   csd_host_delegate->SetNavigationObserverManagerForTesting(
       navigation_observer_manager_);
   std::unique_ptr<ClientPhishingRequest> verdict(new ClientPhishingRequest);
-  csd_host_delegate->AddReferrerChain(verdict.get(), GURL("http://b.com/"));
+  csd_host_delegate->AddReferrerChain(verdict.get(), GURL("http://b.com/"),
+                                      content::GlobalRenderFrameHostId());
   ReferrerChain referrer_chain = verdict->referrer_chain();
 
   EXPECT_EQ(2, referrer_chain.size());
@@ -115,7 +117,8 @@
       std::make_unique<ChromeClientSideDetectionHostDelegate>(
           browser()->tab_strip_model()->GetWebContentsAt(0));
   std::unique_ptr<ClientPhishingRequest> verdict(new ClientPhishingRequest);
-  csd_host_delegate->AddReferrerChain(verdict.get(), GURL("http://b.com/"));
+  csd_host_delegate->AddReferrerChain(verdict.get(), GURL("http://b.com/"),
+                                      content::GlobalRenderFrameHostId());
   ReferrerChain referrer_chain = verdict->referrer_chain();
 
   EXPECT_EQ(0, referrer_chain.size());
diff --git a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.cc b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.cc
index 2290f74..fcc915a 100644
--- a/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.cc
+++ b/chrome/browser/safe_browsing/chrome_enterprise_url_lookup_service_unittest.cc
@@ -23,6 +23,7 @@
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/test/browser_task_environment.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
@@ -44,13 +45,15 @@
 class MockReferrerChainProvider : public ReferrerChainProvider {
  public:
   virtual ~MockReferrerChainProvider() = default;
-  MOCK_METHOD3(IdentifyReferrerChainByWebContents,
-               AttributionResult(content::WebContents* web_contents,
+  MOCK_METHOD3(IdentifyReferrerChainByRenderFrameHost,
+               AttributionResult(content::RenderFrameHost* rfh,
                                  int user_gesture_count_limit,
                                  ReferrerChain* out_referrer_chain));
-  MOCK_METHOD4(IdentifyReferrerChainByEventURL,
+  MOCK_METHOD5(IdentifyReferrerChainByEventURL,
                AttributionResult(const GURL& event_url,
                                  SessionID event_tab_id,
+                                 const content::GlobalRenderFrameHostId&
+                                     event_outermost_main_frame_id,
                                  int user_gesture_count_limit,
                                  ReferrerChain* out_referrer_chain));
   MOCK_METHOD3(IdentifyReferrerChainByPendingEventURL,
diff --git a/chrome/browser/safe_browsing/chrome_password_protection_service.cc b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
index 79207cf..711a790 100644
--- a/chrome/browser/safe_browsing/chrome_password_protection_service.cc
+++ b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
@@ -1349,7 +1349,8 @@
           profile_);
   SafeBrowsingNavigationObserverManager::AttributionResult result =
       navigation_observer_manager->IdentifyReferrerChainByEventURL(
-          event_url, event_tab_id, kPasswordEventAttributionUserGestureLimit,
+          event_url, event_tab_id, content::GlobalRenderFrameHostId(),
+          kPasswordEventAttributionUserGestureLimit,
           frame->mutable_referrer_chain());
   size_t referrer_chain_length = frame->referrer_chain().size();
   UMA_HISTOGRAM_COUNTS_100(
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection/download_protection_service.cc
index e7a9a1d..5c78aaf0 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service.cc
@@ -52,6 +52,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/download_item_utils.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/web_contents.h"
 #include "net/base/url_util.h"
 #include "net/cert/x509_util.h"
@@ -327,6 +328,7 @@
 void DownloadProtectionService::CheckPPAPIDownloadRequest(
     const GURL& requestor_url,
     const GURL& initiating_frame_url,
+    const content::GlobalRenderFrameHostId& initiating_outermost_main_frame_id,
     content::WebContents* web_contents,
     const base::FilePath& default_file_path,
     const std::vector<base::FilePath::StringType>& alternate_extensions,
@@ -341,9 +343,9 @@
     return;
   }
   std::unique_ptr<PPAPIDownloadRequest> request(new PPAPIDownloadRequest(
-      requestor_url, initiating_frame_url, web_contents, default_file_path,
-      alternate_extensions, profile, std::move(callback), this,
-      database_manager_));
+      requestor_url, initiating_frame_url, initiating_outermost_main_frame_id,
+      web_contents, default_file_path, alternate_extensions, profile,
+      std::move(callback), this, database_manager_));
   PPAPIDownloadRequest* request_copy = request.get();
   auto insertion_result = ppapi_download_requests_.insert(
       std::make_pair(request_copy, std::move(request)));
@@ -535,23 +537,27 @@
     return nullptr;
   }
 
+  content::RenderFrameHost* render_frame_host =
+      content::DownloadItemUtils::GetRenderFrameHost(&item);
+  content::RenderFrameHost* outermost_render_frame_host =
+      render_frame_host ? render_frame_host->GetOutermostMainFrame() : nullptr;
+  content::GlobalRenderFrameHostId frame_id =
+      outermost_render_frame_host ? outermost_render_frame_host->GetGlobalId()
+                                  : content::GlobalRenderFrameHostId();
+
   SessionID download_tab_id =
       sessions::SessionTabHelper::IdForTab(web_contents);
   // We look for the referrer chain that leads to the download url first.
   SafeBrowsingNavigationObserverManager::AttributionResult result =
       GetNavigationObserverManager(web_contents)
           ->IdentifyReferrerChainByEventURL(
-              item.GetURL(), download_tab_id,
+              item.GetURL(), download_tab_id, frame_id,
               GetDownloadAttributionUserGestureLimit(item),
               referrer_chain.get());
 
   // If no navigation event is found, this download is not triggered by regular
   // navigation (e.g. html5 file apis, etc). We look for the referrer chain
   // based on relevant RenderFrameHost instead.
-  content::RenderFrameHost* render_frame_host =
-      content::DownloadItemUtils::GetRenderFrameHost(&item);
-  content::RenderFrameHost* outermost_render_frame_host =
-      render_frame_host ? render_frame_host->GetOutermostMainFrame() : nullptr;
   if (result ==
           SafeBrowsingNavigationObserverManager::NAVIGATION_EVENT_NOT_FOUND &&
       web_contents && outermost_render_frame_host &&
@@ -559,8 +565,9 @@
     AddEventUrlToReferrerChain(item, outermost_render_frame_host,
                                referrer_chain.get());
     result = GetNavigationObserverManager(web_contents)
-                 ->IdentifyReferrerChainByWebContents(
-                     web_contents, GetDownloadAttributionUserGestureLimit(item),
+                 ->IdentifyReferrerChainByRenderFrameHost(
+                     outermost_render_frame_host,
+                     GetDownloadAttributionUserGestureLimit(item),
                      referrer_chain.get());
   }
 
@@ -607,8 +614,9 @@
   SafeBrowsingNavigationObserverManager::AttributionResult result =
       GetNavigationObserverManager(item.web_contents)
           ->IdentifyReferrerChainByHostingPage(
-              item.frame_url, tab_url, tab_id, item.has_user_gesture,
-              kDownloadAttributionUserGestureLimit, referrer_chain.get());
+              item.frame_url, tab_url, item.outermost_main_frame_id, tab_id,
+              item.has_user_gesture, kDownloadAttributionUserGestureLimit,
+              referrer_chain.get());
 
   UMA_HISTOGRAM_ENUMERATION(
       "SafeBrowsing.ReferrerAttributionResult.NativeFileSystemWriteAttribution",
@@ -637,6 +645,7 @@
 void DownloadProtectionService::AddReferrerChainToPPAPIClientDownloadRequest(
     content::WebContents* web_contents,
     const GURL& initiating_frame_url,
+    const content::GlobalRenderFrameHostId& initiating_outermost_main_frame_id,
     const GURL& initiating_main_frame_url,
     SessionID tab_id,
     bool has_user_gesture,
@@ -649,8 +658,9 @@
   SafeBrowsingNavigationObserverManager::AttributionResult result =
       GetNavigationObserverManager(web_contents)
           ->IdentifyReferrerChainByHostingPage(
-              initiating_frame_url, initiating_main_frame_url, tab_id,
-              has_user_gesture, kDownloadAttributionUserGestureLimit,
+              initiating_frame_url, initiating_main_frame_url,
+              initiating_outermost_main_frame_id, tab_id, has_user_gesture,
+              kDownloadAttributionUserGestureLimit,
               out_request->mutable_referrer_chain());
   UMA_HISTOGRAM_COUNTS_100(
       "SafeBrowsing.ReferrerURLChainSize.PPAPIDownloadAttribution",
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service.h b/chrome/browser/safe_browsing/download_protection/download_protection_service.h
index 973de09..75cb96dc 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service.h
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service.h
@@ -117,6 +117,8 @@
   virtual void CheckPPAPIDownloadRequest(
       const GURL& requestor_url,
       const GURL& initiating_frame_url,
+      const content::GlobalRenderFrameHostId&
+          initiating_outermost_main_frame_id,
       content::WebContents* web_contents,
       const base::FilePath& default_file_path,
       const std::vector<base::FilePath::StringType>& alternate_extensions,
@@ -304,6 +306,8 @@
   void AddReferrerChainToPPAPIClientDownloadRequest(
       content::WebContents* web_contents,
       const GURL& initiating_frame_url,
+      const content::GlobalRenderFrameHostId&
+          initiating_outermost_main_frame_id,
       const GURL& initiating_main_frame_url,
       SessionID tab_id,
       bool has_user_gesture,
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc b/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
index f9531006..441e683 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
@@ -101,6 +101,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/download_item_utils.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/page_navigator.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/navigation_simulator.h"
@@ -2512,7 +2513,8 @@
   std::vector<base::FilePath::StringType> alternate_extensions{
       FILE_PATH_LITERAL(".jpeg")};
   download_service_->CheckPPAPIDownloadRequest(
-      GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
+      GURL("http://example.com/foo"), GURL(),
+      content::GlobalRenderFrameHostId(), nullptr, default_file_path,
       alternate_extensions, profile(),
       base::BindOnce(&DownloadProtectionServiceTest::SyncCheckDoneCallback,
                      base::Unretained(this)));
@@ -2546,7 +2548,8 @@
     SetExtendedReportingPreference(true);
     RunLoop run_loop;
     download_service_->CheckPPAPIDownloadRequest(
-        GURL("http://example.com/foo"), GURL(), /*web_contents=*/nullptr,
+        GURL("http://example.com/foo"), GURL(),
+        content::GlobalRenderFrameHostId(), /*web_contents=*/nullptr,
         default_file_path, alternate_extensions, profile(),
         base::BindOnce(&DownloadProtectionServiceTest::CheckDoneCallback,
                        base::Unretained(this), run_loop.QuitClosure()));
@@ -2568,7 +2571,8 @@
   SetExtendedReportingPreference(false);
   RunLoop run_loop;
   download_service_->CheckPPAPIDownloadRequest(
-      GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
+      GURL("http://example.com/foo"), GURL(),
+      content::GlobalRenderFrameHostId(), nullptr, default_file_path,
       alternate_extensions, profile(),
       base::BindOnce(&DownloadProtectionServiceTest::CheckDoneCallback,
                      base::Unretained(this), run_loop.QuitClosure()));
@@ -2588,7 +2592,8 @@
 
   RunLoop run_loop;
   download_service_->CheckPPAPIDownloadRequest(
-      GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
+      GURL("http://example.com/foo"), GURL(),
+      content::GlobalRenderFrameHostId(), nullptr, default_file_path,
       alternate_extensions, profile(),
       base::BindOnce(&DownloadProtectionServiceTest::CheckDoneCallback,
                      base::Unretained(this), run_loop.QuitClosure()));
@@ -2607,7 +2612,8 @@
       .WillRepeatedly(Return(false));
   RunLoop run_loop;
   download_service_->CheckPPAPIDownloadRequest(
-      GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
+      GURL("http://example.com/foo"), GURL(),
+      content::GlobalRenderFrameHostId(), nullptr, default_file_path,
       alternate_extensions, profile(),
       base::BindOnce(&DownloadProtectionServiceTest::CheckDoneCallback,
                      base::Unretained(this), run_loop.QuitClosure()));
@@ -2626,7 +2632,8 @@
       .WillRepeatedly(Return(false));
   RunLoop run_loop;
   download_service_->CheckPPAPIDownloadRequest(
-      GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
+      GURL("http://example.com/foo"), GURL(),
+      content::GlobalRenderFrameHostId(), nullptr, default_file_path,
       alternate_extensions, profile(),
       base::BindOnce(&DownloadProtectionServiceTest::CheckDoneCallback,
                      base::Unretained(this), run_loop.QuitClosure()));
@@ -2645,7 +2652,8 @@
   download_service_->download_request_timeout_ms_ = 0;
   RunLoop run_loop;
   download_service_->CheckPPAPIDownloadRequest(
-      GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
+      GURL("http://example.com/foo"), GURL(),
+      content::GlobalRenderFrameHostId(), nullptr, default_file_path,
       alternate_extensions, profile(),
       base::BindOnce(&DownloadProtectionServiceTest::CheckDoneCallback,
                      base::Unretained(this), run_loop.QuitClosure()));
@@ -2674,8 +2682,8 @@
   const GURL kRequestorUrl("http://example.com/foo");
   RunLoop run_loop;
   download_service_->CheckPPAPIDownloadRequest(
-      kRequestorUrl, GURL(), nullptr, default_file_path, alternate_extensions,
-      profile(),
+      kRequestorUrl, GURL(), content::GlobalRenderFrameHostId(), nullptr,
+      default_file_path, alternate_extensions, profile(),
       base::BindOnce(&DownloadProtectionServiceTest::CheckDoneCallback,
                      base::Unretained(this), run_loop.QuitClosure()));
   run_loop.Run();
@@ -2704,8 +2712,9 @@
   std::vector<base::FilePath::StringType> alternate_extensions{
       FILE_PATH_LITERAL(".tmp"), FILE_PATH_LITERAL(".asdfasdf")};
   download_service_->CheckPPAPIDownloadRequest(
-      GURL("http://example.com/foo"), GURL(), web_contents.get(),
-      default_file_path, alternate_extensions, profile(),
+      GURL("http://example.com/foo"), GURL(),
+      content::GlobalRenderFrameHostId(), web_contents.get(), default_file_path,
+      alternate_extensions, profile(),
       base::BindOnce(&DownloadProtectionServiceTest::SyncCheckDoneCallback,
                      base::Unretained(this)));
   ASSERT_TRUE(IsResult(DownloadCheckResult::ALLOWLISTED_BY_POLICY));
diff --git a/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc b/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
index 8b37f98..1ffea9a 100644
--- a/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
+++ b/chrome/browser/safe_browsing/download_protection/ppapi_download_request.cc
@@ -20,6 +20,7 @@
 #include "components/sessions/content/session_tab_helper.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/web_contents.h"
 #include "google_apis/google_api_keys.h"
 #include "net/base/escape.h"
@@ -40,6 +41,7 @@
 PPAPIDownloadRequest::PPAPIDownloadRequest(
     const GURL& requestor_url,
     const GURL& initiating_frame_url,
+    const content::GlobalRenderFrameHostId& initiating_outermost_main_frame_id,
     content::WebContents* web_contents,
     const base::FilePath& default_file_path,
     const std::vector<base::FilePath::StringType>& alternate_extensions,
@@ -49,6 +51,7 @@
     scoped_refptr<SafeBrowsingDatabaseManager> database_manager)
     : requestor_url_(requestor_url),
       initiating_frame_url_(initiating_frame_url),
+      initiating_outermost_main_frame_id_(initiating_outermost_main_frame_id),
       initiating_main_frame_url_(
           web_contents ? web_contents->GetLastCommittedURL() : GURL()),
       tab_id_(sessions::SessionTabHelper::IdForTab(web_contents)),
@@ -208,8 +211,8 @@
   }
 
   service_->AddReferrerChainToPPAPIClientDownloadRequest(
-      web_contents_, initiating_frame_url_, initiating_main_frame_url_, tab_id_,
-      has_user_gesture_, &request);
+      web_contents_, initiating_frame_url_, initiating_outermost_main_frame_id_,
+      initiating_main_frame_url_, tab_id_, has_user_gesture_, &request);
 
   if (!request.SerializeToString(&client_download_request_data_)) {
     // More of an internal error than anything else. Note that the UNKNOWN
diff --git a/chrome/browser/safe_browsing/download_protection/ppapi_download_request.h b/chrome/browser/safe_browsing/download_protection/ppapi_download_request.h
index 4f2c5657..9ddafab 100644
--- a/chrome/browser/safe_browsing/download_protection/ppapi_download_request.h
+++ b/chrome/browser/safe_browsing/download_protection/ppapi_download_request.h
@@ -13,6 +13,7 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/safe_browsing/download_protection/download_protection_util.h"
 #include "components/sessions/core/session_id.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "url/gurl.h"
 
@@ -64,6 +65,8 @@
   PPAPIDownloadRequest(
       const GURL& requestor_url,
       const GURL& initiating_frame_url,
+      const content::GlobalRenderFrameHostId&
+          initiating_outermost_main_frame_id,
       content::WebContents* web_contents,
       const base::FilePath& default_file_path,
       const std::vector<base::FilePath::StringType>& alternate_extensions,
@@ -138,6 +141,9 @@
   // URL of the frame that hosts the PPAPI plugin.
   const GURL initiating_frame_url_;
 
+  // The id of the initiating outermost main frame.
+  const content::GlobalRenderFrameHostId initiating_outermost_main_frame_id_;
+
   // URL of the tab that contains the initialting_frame.
   const GURL initiating_main_frame_url_;
 
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
index 2be08ce..07dbe2b6 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/download_protection/download_protection_service.h"
 #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager_factory.h"
+#include "chrome/browser/safe_browsing/test_safe_browsing_navigation_observer_manager.h"
 #include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -30,8 +31,10 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/download_item_utils.h"
 #include "content/public/browser/download_manager.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/prerender_test_util.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/test_utils.h"
 #include "net/dns/mock_host_resolver.h"
@@ -51,8 +54,15 @@
 const char kMultiFrameTestURL[] =
     "/safe_browsing/download_protection/navigation_observer/"
     "navigation_observer_multi_frame_tests.html";
+const char kSubFrameTestURL[] =
+    "/safe_browsing/download_protection/navigation_observer/"
+    "iframe.html";
 const char kRedirectURL[] =
     "/safe_browsing/download_protection/navigation_observer/redirect.html";
+const char kEmptyURL[] = "/empty.html";
+const char kBasicIframeURL[] = "/iframe.html";
+// This is the src of the iframe in iframe.html above.
+const char kInitialSubframeUrl[] = "/title1.html";
 // Please update |kShortDataURL| too if you're changing |kDownloadDataURL|.
 const char kDownloadDataURL[] =
     "data:application/octet-stream;base64,a2poYWxrc2hkbGtoYXNka2xoYXNsa2RoYWxra"
@@ -153,89 +163,21 @@
   std::vector<DownloadItem*> items_seen_;
 };
 
-class InnerContentsCreationObserver : public content::WebContentsObserver {
- public:
-  InnerContentsCreationObserver(
-      content::WebContents* web_contents,
-      base::RepeatingCallback<void(content::WebContents*)>
-          on_inner_contents_created)
-      : content::WebContentsObserver(web_contents),
-        on_inner_contents_created_(on_inner_contents_created) {}
-
-  // WebContentsObserver:
-  void InnerWebContentsCreated(
-      content::WebContents* inner_web_contents) override {
-    on_inner_contents_created_.Run(inner_web_contents);
-  }
-
- private:
-  base::RepeatingCallback<void(content::WebContents*)>
-      on_inner_contents_created_;
-};
-
-// Test class to help create SafeBrowsingNavigationObservers for each
-// WebContents before they are actually installed through AttachTabHelper.
-class TestNavigationObserverManager
-    : public SafeBrowsingNavigationObserverManager,
-      public TabStripModelObserver {
- public:
-  explicit TestNavigationObserverManager(Browser* browser)
-      : SafeBrowsingNavigationObserverManager(browser->profile()->GetPrefs()) {
-    browser->tab_strip_model()->AddObserver(this);
-  }
-
-  void ObserveContents(content::WebContents* contents) {
-    ASSERT_TRUE(contents);
-    Profile* profile =
-        Profile::FromBrowserContext(contents->GetBrowserContext());
-    auto observer = std::make_unique<SafeBrowsingNavigationObserver>(
-        contents, HostContentSettingsMapFactory::GetForProfile(profile),
-        safe_browsing::SafeBrowsingNavigationObserverManagerFactory::
-            GetForBrowserContext(profile));
-    observer->SetObserverManagerForTesting(this);
-    observer_list_.push_back(std::move(observer));
-    inner_contents_creation_observers_.push_back(
-        std::make_unique<InnerContentsCreationObserver>(
-            contents,
-            base::BindRepeating(&TestNavigationObserverManager::ObserveContents,
-                                // Unretained is safe because this object owns
-                                // inner_contents_creation_observers_
-                                base::Unretained(this))));
-  }
-
-  // TabStripModelObserver:
-  void OnTabStripModelChanged(
-      TabStripModel* tab_strip_model,
-      const TabStripModelChange& change,
-      const TabStripSelectionChange& selection) override {
-    if (change.type() != TabStripModelChange::kInserted)
-      return;
-
-    for (const TabStripModelChange::ContentsWithIndex& tab :
-         change.GetInsert()->contents) {
-      ObserveContents(tab.contents);
-    }
-  }
-
-  TestNavigationObserverManager(const TestNavigationObserverManager&) = delete;
-  TestNavigationObserverManager& operator=(
-      const TestNavigationObserverManager&) = delete;
-
-  ~TestNavigationObserverManager() override = default;
-
- private:
-  std::vector<std::unique_ptr<SafeBrowsingNavigationObserver>> observer_list_;
-  std::vector<std::unique_ptr<InnerContentsCreationObserver>>
-      inner_contents_creation_observers_;
-};
-
 class SBNavigationObserverBrowserTest : public InProcessBrowserTest {
  public:
-  SBNavigationObserverBrowserTest() {
+  SBNavigationObserverBrowserTest()
+      : prerender_helper_(
+            base::BindRepeating(&SBNavigationObserverBrowserTest::web_contents,
+                                base::Unretained(this))) {
     scoped_feature_list_.InitAndDisableFeature(
         kOmitNonUserGesturesFromReferrerChain);
   }
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    InProcessBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     // Disable Safe Browsing service so we can directly control when
     // SafeBrowsingNavigationObserverManager and SafeBrowsingNavigationObserver
@@ -245,12 +187,16 @@
     ASSERT_TRUE(embedded_test_server()->Start());
     host_resolver()->AddRule("*", "127.0.0.1");
     observer_manager_ =
-        std::make_unique<TestNavigationObserverManager>(browser());
+        std::make_unique<TestSafeBrowsingNavigationObserverManager>(browser());
     observer_manager_->ObserveContents(
         browser()->tab_strip_model()->GetActiveWebContents());
     ASSERT_TRUE(InitialSetup());
   }
 
+  content::WebContents* web_contents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
   bool InitialSetup() {
     if (!browser())
       return false;
@@ -470,15 +416,21 @@
       ReferrerChain* referrer_chain) {
     SessionID download_tab_id = sessions::SessionTabHelper::IdForTab(
         content::DownloadItemUtils::GetWebContents(download));
+    content::RenderFrameHost* rfh =
+        content::DownloadItemUtils::GetRenderFrameHost(download);
+    content::GlobalRenderFrameHostId outermost_main_frame_id;
+    if (rfh)
+      outermost_main_frame_id = rfh->GetOutermostMainFrame()->GetGlobalId();
+
     auto result = observer_manager_->IdentifyReferrerChainByEventURL(
-        download->GetURL(), download_tab_id,
+        download->GetURL(), download_tab_id, outermost_main_frame_id,
         2,  // kDownloadAttributionUserGestureLimit
         referrer_chain);
     if (result ==
         SafeBrowsingNavigationObserverManager::NAVIGATION_EVENT_NOT_FOUND) {
       DCHECK_EQ(0, referrer_chain->size());
-      observer_manager_->IdentifyReferrerChainByWebContents(
-          content::DownloadItemUtils::GetWebContents(download),
+      observer_manager_->IdentifyReferrerChainByRenderFrameHost(
+          rfh,
           2,  // kDownloadAttributionUserGestureLimit
           referrer_chain);
     }
@@ -486,12 +438,25 @@
 
   void IdentifyReferrerChainForWebContents(content::WebContents* web_contents,
                                            ReferrerChain* referrer_chain) {
-    observer_manager_->IdentifyReferrerChainByWebContents(
-        web_contents,
+    observer_manager_->IdentifyReferrerChainByRenderFrameHost(
+        // We will assume the primary main frame here.
+        web_contents->GetMainFrame(),
         2,  // kDownloadAttributionUserGestureLimit
         referrer_chain);
   }
 
+  void IdentifyReferrerChainByEventURL(
+      const GURL& event_url,
+      SessionID event_tab_id,  // Invalid if tab id is unknown or not available.
+      const content::GlobalRenderFrameHostId&
+          event_outermost_main_frame_id,  // Can also be Invalid.
+      ReferrerChain* out_referrer_chain) {
+    observer_manager_->IdentifyReferrerChainByEventURL(
+        event_url, event_tab_id, event_outermost_main_frame_id,
+        2,  // user_gesture_count_limit
+        out_referrer_chain);
+  }
+
   // Identify referrer chain of a PPAPI download and populate |referrer_chain|.
   void IdentifyReferrerChainForPPAPIDownload(
       const GURL& initiating_frame_url,
@@ -502,7 +467,8 @@
     observer_manager_->OnUserGestureConsumed(web_contents);
     EXPECT_LE(observer_manager_->IdentifyReferrerChainByHostingPage(
                   initiating_frame_url, web_contents->GetLastCommittedURL(),
-                  tab_id, has_user_gesture,
+                  web_contents->GetMainFrame()->GetGlobalId(), tab_id,
+                  has_user_gesture,
                   2,  // kDownloadAttributionUserGestureLimit
                   referrer_chain),
               SafeBrowsingNavigationObserverManager::SUCCESS_LANDING_REFERRER);
@@ -557,27 +523,44 @@
                                                out_referrer_chain);
   }
 
+  NavigationEvent* GetNavigationEvent(size_t index) {
+    return observer_manager_->navigation_event_list()
+        ->navigation_events()[index]
+        .get();
+  }
+
+  absl::optional<size_t> FindNavigationEventIndex(
+      const GURL& target_url,
+      content::GlobalRenderFrameHostId outermost_main_frame_id) {
+    return observer_manager_->navigation_event_list()->FindNavigationEvent(
+        base::Time::Now(), target_url, GURL(), SessionID::InvalidValue(),
+        outermost_main_frame_id,
+        (observer_manager_->navigation_event_list()->NavigationEventsSize() -
+         1));
+  }
+
   void FindAndAddNavigationToReferrerChain(ReferrerChain* referrer_chain,
                                            const GURL& target_url) {
-    size_t nav_event_index =
-        observer_manager_->navigation_event_list()->FindNavigationEvent(
-            base::Time::Now(), target_url, GURL(), SessionID::InvalidValue(),
-            (observer_manager_->navigation_event_list()
-                 ->NavigationEventsSize() -
-             1));
-    if (static_cast<int>(nav_event_index) != -1) {
+    auto nav_event_index = FindNavigationEventIndex(
+        target_url, content::GlobalRenderFrameHostId());
+    if (nav_event_index) {
       auto* nav_event =
           observer_manager_->navigation_event_list()->GetNavigationEvent(
-              nav_event_index);
+              *nav_event_index);
       observer_manager_->AddToReferrerChain(referrer_chain, nav_event, GURL(),
                                             ReferrerChainEntry::EVENT_URL);
     }
   }
 
  protected:
-  std::unique_ptr<TestNavigationObserverManager> observer_manager_;
+  std::unique_ptr<TestSafeBrowsingNavigationObserverManager> observer_manager_;
+
+  content::test::PrerenderTestHelper& prerender_helper() {
+    return prerender_helper_;
+  }
 
  private:
+  content::test::PrerenderTestHelper prerender_helper_;
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
@@ -2570,6 +2553,279 @@
 }
 
 IN_PROC_BROWSER_TEST_F(SBNavigationObserverBrowserTest,
+                       NewWindowAndNavigateSubframe) {
+  std::string test_server_ip(embedded_test_server()->host_port_pair().host());
+  auto main_frame_url = embedded_test_server()->GetURL(kMultiFrameTestURL);
+  auto new_window_url = embedded_test_server()->GetURL(kBasicIframeURL);
+  auto new_window_subframe_url = embedded_test_server()->GetURL(kEmptyURL);
+  auto initial_subframe_url =
+      embedded_test_server()->GetURL(kInitialSubframeUrl);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
+
+  auto initiator_outermost_main_frame_id =
+      web_contents()->GetMainFrame()->GetGlobalId();
+
+  auto* initial_web_contents = web_contents();
+
+  ui_test_utils::UrlLoadObserver url_observer(
+      new_window_url, content::NotificationService::AllSources());
+  ASSERT_TRUE(
+      ExecJs(web_contents()->GetMainFrame(),
+             content::JsReplace("var w = window.open($1, 'New Window');",
+                                new_window_url)));
+  url_observer.Wait();
+  const int kExpectedNumberOfNavigations = 1;
+
+  content::TestNavigationObserver navigation_observer(
+      web_contents(), kExpectedNumberOfNavigations);
+  ASSERT_TRUE(
+      ExecJs(initial_web_contents->GetMainFrame(),
+             content::JsReplace("w.document.querySelector('IFRAME').src = $1;",
+                                new_window_subframe_url)));
+  navigation_observer.Wait();
+  EXPECT_EQ(new_window_subframe_url, navigation_observer.last_navigation_url());
+
+  auto target_main_frame_id = web_contents()->GetMainFrame()->GetGlobalId();
+
+  ReferrerChain referrer_chain;
+  IdentifyReferrerChainByEventURL(
+      new_window_url, sessions::SessionTabHelper::IdForTab(web_contents()),
+      content::GlobalRenderFrameHostId(), &referrer_chain);
+
+  auto index = FindNavigationEventIndex(new_window_url,
+                                        content::GlobalRenderFrameHostId());
+  ASSERT_TRUE(index);
+  auto* nav_event = GetNavigationEvent(*index);
+  EXPECT_EQ(initiator_outermost_main_frame_id,
+            nav_event->initiator_outermost_main_frame_id);
+  EXPECT_EQ(target_main_frame_id, nav_event->outermost_main_frame_id);
+
+  EXPECT_EQ(2, referrer_chain.size());
+
+  referrer_chain.Clear();
+  IdentifyReferrerChainByEventURL(
+      new_window_subframe_url,
+      sessions::SessionTabHelper::IdForTab(web_contents()),
+      content::GlobalRenderFrameHostId(), &referrer_chain);
+  EXPECT_EQ(4, referrer_chain.size());
+  VerifyReferrerChainEntry(
+      new_window_subframe_url,  // url
+      // TODO(crbug.com/1300014): this should be |new_window_url|.
+      GURL(),                         // main_frame_url
+      ReferrerChainEntry::EVENT_URL,  // type
+      test_server_ip,                 // ip_address
+      initial_subframe_url,           // referrer_url
+      new_window_url,                 // referrer_main_frame_url
+      false,                          // is_retargeting
+      std::vector<GURL>(),            // server redirects
+      ReferrerChainEntry::RENDERER_INITIATED_WITHOUT_USER_GESTURE,
+      referrer_chain.Get(0));
+
+  VerifyReferrerChainEntry(
+      initial_subframe_url,                 // url
+      new_window_url,                       // main_frame_url
+      ReferrerChainEntry::CLIENT_REDIRECT,  // type
+      test_server_ip,                       // ip_address
+      GURL(),                               // referrer_url
+      new_window_url,                       // referrer_main_frame_url
+      false,                                // is_retargeting
+      std::vector<GURL>(),                  // server redirects
+      ReferrerChainEntry::RENDERER_INITIATED_WITHOUT_USER_GESTURE,
+      referrer_chain.Get(1));
+
+  VerifyReferrerChainEntry(
+      new_window_url,                       // url
+      GURL(),                               // main_frame_url
+      ReferrerChainEntry::CLIENT_REDIRECT,  // type
+      test_server_ip,                       // ip_address
+      main_frame_url,                       // referrer_url
+      GURL(),                               // referrer_main_frame_url
+      true,                                 // is_retargeting
+      std::vector<GURL>(),                  // server redirects
+      ReferrerChainEntry::RENDERER_INITIATED_WITHOUT_USER_GESTURE,
+      referrer_chain.Get(2));
+
+  VerifyReferrerChainEntry(main_frame_url,  // url
+                           GURL(),          // main_frame_url
+                           ReferrerChainEntry::CLIENT_REDIRECT,  // type
+                           test_server_ip,                       // ip_address
+                           GURL(),                               // referrer_url
+                           GURL(),               // referrer_main_frame_url
+                           false,                // is_retargeting
+                           std::vector<GURL>(),  // server redirects
+                           ReferrerChainEntry::BROWSER_INITIATED,
+                           referrer_chain.Get(3));
+
+  index = FindNavigationEventIndex(new_window_subframe_url,
+                                   content::GlobalRenderFrameHostId());
+  ASSERT_TRUE(index);
+  nav_event = GetNavigationEvent(*index);
+  EXPECT_EQ(web_contents()->GetMainFrame()->GetGlobalId(),
+            nav_event->outermost_main_frame_id);
+
+  // If the initial main frame runs JS to initiate this subframe navigation, the
+  // the navigation request's initiator frame is in the current frame tree,
+  // not the frame in which the JS ran.
+  EXPECT_EQ(web_contents()->GetMainFrame()->GetGlobalId(),
+            nav_event->initiator_outermost_main_frame_id);
+}
+
+// This is similar to PrerenderReferrerChains, but navigates rather than
+// initiating the prerender to ensure that the referrer chains match.
+IN_PROC_BROWSER_TEST_F(SBNavigationObserverBrowserTest,
+                       ReferrerChainsMatchPrerender) {
+  std::string test_server_ip(embedded_test_server()->host_port_pair().host());
+  auto main_frame_url = embedded_test_server()->GetURL(kMultiFrameTestURL);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
+
+  auto next_url = embedded_test_server()->GetURL(kSubFrameTestURL);
+  content::TestNavigationObserver observer(web_contents(), 1);
+  ASSERT_TRUE(ExecJs(web_contents(),
+                     content::JsReplace("window.location = $1;", next_url)));
+  observer.Wait();
+
+  ReferrerChain referrer_chain;
+  IdentifyReferrerChainByEventURL(
+      next_url, sessions::SessionTabHelper::IdForTab(web_contents()),
+      web_contents()->GetMainFrame()->GetGlobalId(), &referrer_chain);
+
+  EXPECT_EQ(2, referrer_chain.size());
+
+  VerifyReferrerChainEntry(
+      next_url,                       // url
+      GURL(),                         // main_frame_url
+      ReferrerChainEntry::EVENT_URL,  // type
+      test_server_ip,                 // ip_address
+      main_frame_url,                 // referrer_url
+      GURL(),                         // referrer_main_frame_url
+      false,                          // is_retargeting
+      std::vector<GURL>(),            // server redirects
+      ReferrerChainEntry::RENDERER_INITIATED_WITHOUT_USER_GESTURE,
+      referrer_chain.Get(0));
+
+  VerifyReferrerChainEntry(main_frame_url,  // url
+                           GURL(),          // main_frame_url
+                           ReferrerChainEntry::CLIENT_REDIRECT,  // type
+                           test_server_ip,                       // ip_address
+                           GURL(),                               // referrer_url
+                           GURL(),               // referrer_main_frame_url
+                           false,                // is_retargeting
+                           std::vector<GURL>(),  // server redirects
+                           ReferrerChainEntry::BROWSER_INITIATED,
+                           referrer_chain.Get(1));
+}
+
+IN_PROC_BROWSER_TEST_F(SBNavigationObserverBrowserTest,
+                       PrerenderReferrerChains) {
+  std::string test_server_ip(embedded_test_server()->host_port_pair().host());
+  auto main_frame_url = embedded_test_server()->GetURL(kMultiFrameTestURL);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
+
+  auto prerendered_url = embedded_test_server()->GetURL(kSubFrameTestURL);
+  auto host_id = prerender_helper().AddPrerender(prerendered_url);
+  prerender_helper().WaitForPrerenderLoadCompletion(prerendered_url);
+
+  // The prerendered URL (iframe.html) will now be loaded twice within the
+  // WebContents. Once as the main frame of the prerendered page and once as a
+  // subframe of the primary page. Since the prerender was initiated after
+  // loading the primary page, it will be the most recent navigation event.
+
+  // Should find the event corresponding to the prerendered page. By passing an
+  // invalid RFH id, FindNavigationEvent does not attempt to match within a
+  // given outermost main frame but will instead match the most recent matching
+  // navigation event.
+  auto index_a = FindNavigationEventIndex(prerendered_url,
+                                          content::GlobalRenderFrameHostId());
+
+  // Since we're supplying the id for the primary page's outermost main frame,
+  // we should find the event for the primary page.
+  auto index_b = FindNavigationEventIndex(
+      prerendered_url, web_contents()->GetMainFrame()->GetGlobalId());
+
+  // Ensure that these indices are valid and not equal.
+  EXPECT_TRUE(index_a && index_b);
+  EXPECT_NE(*index_a, *index_b);
+
+  auto* prerendered_main_frame_host =
+      prerender_helper().GetPrerenderedMainFrameHost(host_id);
+  auto index_c = FindNavigationEventIndex(
+      prerendered_url,
+      prerendered_main_frame_host->GetOutermostMainFrame()->GetGlobalId());
+
+  // By explicitly supplying the outermost frame id for the prerendered page, we
+  // should also get the event corresponding to the prerendered page.
+  EXPECT_TRUE(index_c);
+  EXPECT_EQ(*index_a, *index_c);
+
+  // Build a full referrer chain for the prerendered page.
+  ReferrerChain referrer_chain;
+  IdentifyReferrerChainByEventURL(
+      prerendered_url, sessions::SessionTabHelper::IdForTab(web_contents()),
+      prerendered_main_frame_host->GetOutermostMainFrame()->GetGlobalId(),
+      &referrer_chain);
+
+  EXPECT_EQ(2, referrer_chain.size());
+
+  VerifyReferrerChainEntry(
+      prerendered_url,                // url
+      GURL(),                         // main_frame_url
+      ReferrerChainEntry::EVENT_URL,  // type
+      test_server_ip,                 // ip_address
+      main_frame_url,                 // referrer_url
+      GURL(),                         // referrer_main_frame_url
+      false,                          // is_retargeting
+      std::vector<GURL>(),            // server redirects
+      ReferrerChainEntry::RENDERER_INITIATED_WITHOUT_USER_GESTURE,
+      referrer_chain.Get(0));
+
+  VerifyReferrerChainEntry(main_frame_url,  // url
+                           GURL(),          // main_frame_url
+                           ReferrerChainEntry::CLIENT_REDIRECT,  // type
+                           test_server_ip,                       // ip_address
+                           GURL(),                               // referrer_url
+                           GURL(),               // referrer_main_frame_url
+                           false,                // is_retargeting
+                           std::vector<GURL>(),  // server redirects
+                           ReferrerChainEntry::BROWSER_INITIATED,
+                           referrer_chain.Get(1));
+
+  content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
+  prerender_helper().NavigatePrimaryPage(prerendered_url);
+  host_observer.WaitForActivation();
+
+  // Build a full referrer chain for the prerendered page following activation.
+  referrer_chain.Clear();
+  IdentifyReferrerChainByEventURL(
+      prerendered_url, sessions::SessionTabHelper::IdForTab(web_contents()),
+      web_contents()->GetMainFrame()->GetGlobalId(), &referrer_chain);
+
+  EXPECT_EQ(2, referrer_chain.size());
+
+  VerifyReferrerChainEntry(
+      prerendered_url,                // url
+      GURL(),                         // main_frame_url
+      ReferrerChainEntry::EVENT_URL,  // type
+      test_server_ip,                 // ip_address
+      main_frame_url,                 // referrer_url
+      GURL(),                         // referrer_main_frame_url
+      false,                          // is_retargeting
+      std::vector<GURL>(),            // server redirects
+      ReferrerChainEntry::RENDERER_INITIATED_WITHOUT_USER_GESTURE,
+      referrer_chain.Get(0));
+
+  VerifyReferrerChainEntry(main_frame_url,  // url
+                           GURL(),          // main_frame_url
+                           ReferrerChainEntry::CLIENT_REDIRECT,  // type
+                           test_server_ip,                       // ip_address
+                           GURL(),                               // referrer_url
+                           GURL(),               // referrer_main_frame_url
+                           false,                // is_retargeting
+                           std::vector<GURL>(),  // server redirects
+                           ReferrerChainEntry::BROWSER_INITIATED,
+                           referrer_chain.Get(1));
+}
+
+IN_PROC_BROWSER_TEST_F(SBNavigationObserverBrowserTest,
                        NavigateBackwardForward) {
   ASSERT_TRUE(ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(kSingleFrameTestURL)));
diff --git a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc
index ab45dc8..ad9f27d 100644
--- a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc
+++ b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc
@@ -33,6 +33,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/download_item_utils.h"
 #include "content/public/browser/download_manager.h"
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 
 using content::BrowserContext;
@@ -169,6 +170,7 @@
 
 void AndroidTelemetryService::FillReferrerChain(
     content::WebContents* web_contents,
+    content::RenderFrameHost* rfh,
     ClientSafeBrowsingReportRequest* report) {
   if (!SafeBrowsingNavigationObserverManager::IsEnabledAndReady(
           profile_->GetPrefs(), g_browser_process->safe_browsing_service())) {
@@ -188,8 +190,8 @@
           : nullptr;
   SafeBrowsingNavigationObserverManager::AttributionResult result =
       observer_manager
-          ? observer_manager->IdentifyReferrerChainByWebContents(
-                web_contents, kAndroidTelemetryUserGestureLimit,
+          ? observer_manager->IdentifyReferrerChainByRenderFrameHost(
+                rfh, kAndroidTelemetryUserGestureLimit,
                 report->mutable_referrer_chain())
           : SafeBrowsingNavigationObserverManager::NAVIGATION_EVENT_NOT_FOUND;
 
@@ -229,7 +231,12 @@
   // Fill referrer chain.
   content::WebContents* web_contents =
       content::DownloadItemUtils::GetWebContents(item);
-  FillReferrerChain(web_contents, report.get());
+  content::RenderFrameHost* rfh =
+      content::DownloadItemUtils::GetRenderFrameHost(item);
+  if (!rfh && web_contents)
+    rfh = web_contents->GetMainFrame();
+
+  FillReferrerChain(web_contents, rfh, report.get());
 
   // Fill DownloadItemInfo
   ClientSafeBrowsingReportRequest::DownloadItemInfo*
diff --git a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.h b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.h
index e5e1d42..ce28c701 100644
--- a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.h
+++ b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.h
@@ -20,6 +20,7 @@
 class PrefService;
 
 namespace content {
+class RenderFrameHost;
 class WebContents;
 }
 
@@ -88,8 +89,9 @@
   bool CanSendPing(download::DownloadItem* item);
 
   // Fill the referrer chain in |report| with the actual referrer chain for the
-  // given |web_contents|, as well as recent navigations.
+  // given |rfh|, as well as recent navigations.
   void FillReferrerChain(content::WebContents* web_contents,
+                         content::RenderFrameHost* rfh,
                          ClientSafeBrowsingReportRequest* report);
 
   // Sets the relevant fields in an instance of
diff --git a/chrome/browser/safe_browsing/test_safe_browsing_navigation_observer_manager.cc b/chrome/browser/safe_browsing/test_safe_browsing_navigation_observer_manager.cc
new file mode 100644
index 0000000..c8750d6
--- /dev/null
+++ b/chrome/browser/safe_browsing/test_safe_browsing_navigation_observer_manager.cc
@@ -0,0 +1,76 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/safe_browsing/test_safe_browsing_navigation_observer_manager.h"
+
+#include <memory>
+
+#include "build/build_config.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager_factory.h"
+#include "chrome/browser/ui/browser.h"
+#include "components/safe_browsing/content/browser/safe_browsing_navigation_observer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace safe_browsing {
+
+InnerContentsCreationObserver::InnerContentsCreationObserver(
+    content::WebContents* web_contents,
+    base::RepeatingCallback<void(content::WebContents*)>
+        on_inner_contents_created)
+    : content::WebContentsObserver(web_contents),
+      on_inner_contents_created_(on_inner_contents_created) {}
+
+InnerContentsCreationObserver::~InnerContentsCreationObserver() = default;
+
+// WebContentsObserver:
+void InnerContentsCreationObserver::InnerWebContentsCreated(
+    content::WebContents* inner_web_contents) {
+  on_inner_contents_created_.Run(inner_web_contents);
+}
+
+TestSafeBrowsingNavigationObserverManager::
+    TestSafeBrowsingNavigationObserverManager(Browser* browser)
+    : SafeBrowsingNavigationObserverManager(browser->profile()->GetPrefs()) {
+  browser->tab_strip_model()->AddObserver(this);
+}
+TestSafeBrowsingNavigationObserverManager::
+    ~TestSafeBrowsingNavigationObserverManager() = default;
+
+void TestSafeBrowsingNavigationObserverManager::ObserveContents(
+    content::WebContents* contents) {
+  ASSERT_TRUE(contents);
+  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
+  auto observer = std::make_unique<SafeBrowsingNavigationObserver>(
+      contents, HostContentSettingsMapFactory::GetForProfile(profile),
+      safe_browsing::SafeBrowsingNavigationObserverManagerFactory::
+          GetForBrowserContext(profile));
+  observer->SetObserverManagerForTesting(this);
+  observer_list_.push_back(std::move(observer));
+  inner_contents_creation_observers_.push_back(
+      std::make_unique<InnerContentsCreationObserver>(
+          contents,
+          base::BindRepeating(
+              &TestSafeBrowsingNavigationObserverManager::ObserveContents,
+              // Unretained is safe because this object owns
+              // inner_contents_creation_observers_
+              base::Unretained(this))));
+}
+
+// TabStripModelObserver:
+void TestSafeBrowsingNavigationObserverManager::OnTabStripModelChanged(
+    TabStripModel* tab_strip_model,
+    const TabStripModelChange& change,
+    const TabStripSelectionChange& selection) {
+  if (change.type() != TabStripModelChange::kInserted)
+    return;
+
+  for (const TabStripModelChange::ContentsWithIndex& tab :
+       change.GetInsert()->contents) {
+    ObserveContents(tab.contents);
+  }
+}
+
+}  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/test_safe_browsing_navigation_observer_manager.h b/chrome/browser/safe_browsing/test_safe_browsing_navigation_observer_manager.h
new file mode 100644
index 0000000..68e91d500
--- /dev/null
+++ b/chrome/browser/safe_browsing/test_safe_browsing_navigation_observer_manager.h
@@ -0,0 +1,68 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SAFE_BROWSING_TEST_SAFE_BROWSING_NAVIGATION_OBSERVER_MANAGER_H_
+#define CHROME_BROWSER_SAFE_BROWSING_TEST_SAFE_BROWSING_NAVIGATION_OBSERVER_MANAGER_H_
+
+#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
+#include "components/safe_browsing/content/browser/safe_browsing_navigation_observer.h"
+#include "components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.h"
+
+class Browser;
+
+namespace safe_browsing {
+
+class InnerContentsCreationObserver : public content::WebContentsObserver {
+ public:
+  InnerContentsCreationObserver(
+      content::WebContents* web_contents,
+      base::RepeatingCallback<void(content::WebContents*)>
+          on_inner_contents_created);
+
+  ~InnerContentsCreationObserver() override;
+
+  // WebContentsObserver:
+  void InnerWebContentsCreated(
+      content::WebContents* inner_web_contents) override;
+
+ private:
+  base::RepeatingCallback<void(content::WebContents*)>
+      on_inner_contents_created_;
+};
+
+// Test class to help create SafeBrowsingNavigationObservers for each
+// WebContents before they are actually installed through AttachTabHelper.
+class TestSafeBrowsingNavigationObserverManager
+    : public SafeBrowsingNavigationObserverManager,
+      public TabStripModelObserver {
+ public:
+  explicit TestSafeBrowsingNavigationObserverManager(Browser* browser);
+  TestSafeBrowsingNavigationObserverManager(
+      const TestSafeBrowsingNavigationObserverManager&) = delete;
+  TestSafeBrowsingNavigationObserverManager& operator=(
+      const TestSafeBrowsingNavigationObserverManager&) = delete;
+
+  ~TestSafeBrowsingNavigationObserverManager() override;
+
+  void ObserveContents(content::WebContents* contents);
+
+  // TabStripModelObserver:
+  void OnTabStripModelChanged(
+      TabStripModel* tab_strip_model,
+      const TabStripModelChange& change,
+      const TabStripSelectionChange& selection) override;
+
+  NavigationEventList* navigation_event_list() {
+    return SafeBrowsingNavigationObserverManager::navigation_event_list();
+  }
+
+ private:
+  std::vector<std::unique_ptr<SafeBrowsingNavigationObserver>> observer_list_;
+  std::vector<std::unique_ptr<InnerContentsCreationObserver>>
+      inner_contents_creation_observers_;
+};
+
+}  // namespace safe_browsing
+
+#endif  // CHROME_BROWSER_SAFE_BROWSING_TEST_SAFE_BROWSING_NAVIGATION_OBSERVER_MANAGER_H_
diff --git a/chrome/browser/safe_browsing/threat_details_unittest.cc b/chrome/browser/safe_browsing/threat_details_unittest.cc
index 22fd3f1..ac6295e 100644
--- a/chrome/browser/safe_browsing/threat_details_unittest.cc
+++ b/chrome/browser/safe_browsing/threat_details_unittest.cc
@@ -213,14 +213,16 @@
 
 class MockReferrerChainProvider : public ReferrerChainProvider {
  public:
-  virtual ~MockReferrerChainProvider() {}
-  MOCK_METHOD3(IdentifyReferrerChainByWebContents,
-               AttributionResult(content::WebContents* web_contents,
+  virtual ~MockReferrerChainProvider() = default;
+  MOCK_METHOD3(IdentifyReferrerChainByRenderFrameHost,
+               AttributionResult(content::RenderFrameHost* rfh,
                                  int user_gesture_count_limit,
                                  ReferrerChain* out_referrer_chain));
-  MOCK_METHOD4(IdentifyReferrerChainByEventURL,
+  MOCK_METHOD5(IdentifyReferrerChainByEventURL,
                AttributionResult(const GURL& event_url,
                                  SessionID event_tab_id,
+                                 const content::GlobalRenderFrameHostId&
+                                     outermost_event_main_frame_id,
                                  int user_gesture_count_limit,
                                  ReferrerChain* out_referrer_chain));
   MOCK_METHOD3(IdentifyReferrerChainByPendingEventURL,
@@ -563,7 +565,8 @@
   returned_referrer_chain.Add()->set_url(kReferrerURL);
   returned_referrer_chain.Add()->set_url(kSecondRedirectURL);
   EXPECT_CALL(*referrer_chain_provider_,
-              IdentifyReferrerChainByWebContents(web_contents(), _, _))
+              IdentifyReferrerChainByRenderFrameHost(
+                  web_contents()->GetMainFrame(), _, _))
       .WillOnce(DoAll(SetArgPointee<2>(returned_referrer_chain),
                       Return(ReferrerChainProvider::SUCCESS)));
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 2267662..1bf5e2e 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2777,6 +2777,13 @@
       "webui/settings/ash/calculator/size_calculator.h",
       "webui/settings/ash/os_apps_page/app_notification_handler.cc",
       "webui/settings/ash/os_apps_page/app_notification_handler.h",
+      "webui/settings/ash/search/per_session_settings_user_action_tracker.cc",
+      "webui/settings/ash/search/per_session_settings_user_action_tracker.h",
+      "webui/settings/ash/search/search_concept.h",
+      "webui/settings/ash/search/search_handler.cc",
+      "webui/settings/ash/search/search_handler.h",
+      "webui/settings/ash/search/search_tag_registry.cc",
+      "webui/settings/ash/search/search_tag_registry.h",
       "webui/settings/chromeos/about_section.cc",
       "webui/settings/chromeos/about_section.h",
       "webui/settings/chromeos/accessibility_handler.cc",
@@ -2890,13 +2897,6 @@
       "webui/settings/chromeos/quick_unlock_handler.h",
       "webui/settings/chromeos/reset_section.cc",
       "webui/settings/chromeos/reset_section.h",
-      "webui/settings/chromeos/search/per_session_settings_user_action_tracker.cc",
-      "webui/settings/chromeos/search/per_session_settings_user_action_tracker.h",
-      "webui/settings/chromeos/search/search_concept.h",
-      "webui/settings/chromeos/search/search_handler.cc",
-      "webui/settings/chromeos/search/search_handler.h",
-      "webui/settings/chromeos/search/search_tag_registry.cc",
-      "webui/settings/chromeos/search/search_tag_registry.h",
       "webui/settings/chromeos/search_section.cc",
       "webui/settings/chromeos/search_section.h",
       "webui/settings/chromeos/server_printer_url_util.cc",
@@ -3024,7 +3024,7 @@
       "//chrome/browser/ui/webui/nearby_share:mojom",
       "//chrome/browser/ui/webui/nearby_share/public/mojom",
       "//chrome/browser/ui/webui/settings/ash/os_apps_page/mojom",
-      "//chrome/browser/ui/webui/settings/chromeos/search:mojo_bindings",
+      "//chrome/browser/ui/webui/settings/ash/search:mojo_bindings",
       "//chrome/browser/web_applications",
       "//chrome/services/file_util/public/cpp",
       "//chromeos/assistant:buildflags",
diff --git a/chrome/browser/ui/app_list/search/os_settings_provider.cc b/chrome/browser/ui/app_list/search/os_settings_provider.cc
index 5f346e1..8e0d739d 100644
--- a/chrome/browser/ui/app_list/search/os_settings_provider.cc
+++ b/chrome/browser/ui/app_list/search/os_settings_provider.cc
@@ -21,10 +21,10 @@
 #include "chrome/browser/ui/app_list/search/common/icon_constants.h"
 #include "chrome/browser/ui/app_list/search/search_tags_util.h"
 #include "chrome/browser/ui/settings_window_manager_chromeos.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/hierarchy.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_manager.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_manager_factory.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_handler.h"
 #include "chrome/browser/web_applications/web_app_id_constants.h"
 #include "chrome/common/chrome_features.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
diff --git a/chrome/browser/ui/app_list/search/os_settings_provider.h b/chrome/browser/ui/app_list/search/os_settings_provider.h
index 7449024a..bae525f 100644
--- a/chrome/browser/ui/app_list/search/os_settings_provider.h
+++ b/chrome/browser/ui/app_list/search/os_settings_provider.h
@@ -13,7 +13,7 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_forward.h"
 #include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/app_list/search/search_provider.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search.mojom.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search.mojom.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/icon_types.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
diff --git a/chrome/browser/ui/views/extensions/extension_popup.cc b/chrome/browser/ui/views/extensions/extension_popup.cc
index fcad195..13283dd8 100644
--- a/chrome/browser/ui/views/extensions/extension_popup.cc
+++ b/chrome/browser/ui/views/extensions/extension_popup.cc
@@ -31,6 +31,32 @@
 constexpr gfx::Size ExtensionPopup::kMinSize;
 constexpr gfx::Size ExtensionPopup::kMaxSize;
 
+// A helper class to scope the observation of DevToolsAgentHosts. We can't just
+// use base::ScopedObservation here because that requires a specific source
+// object, where as DevToolsAgentHostObservers are added to a singleton list.
+// The `observer_` passed into this object will be registered as an observer
+// for this object's lifetime.
+class ExtensionPopup::ScopedDevToolsAgentHostObservation {
+ public:
+  ScopedDevToolsAgentHostObservation(
+      content::DevToolsAgentHostObserver* observer)
+      : observer_(observer) {
+    content::DevToolsAgentHost::AddObserver(observer_);
+  }
+
+  ScopedDevToolsAgentHostObservation(
+      const ScopedDevToolsAgentHostObservation&) = delete;
+  ScopedDevToolsAgentHostObservation& operator=(
+      const ScopedDevToolsAgentHostObservation&) = delete;
+
+  ~ScopedDevToolsAgentHostObservation() {
+    content::DevToolsAgentHost::RemoveObserver(observer_);
+  }
+
+ private:
+  content::DevToolsAgentHostObserver* observer_;
+};
+
 // static
 void ExtensionPopup::ShowPopup(
     std::unique_ptr<extensions::ExtensionViewHost> host,
@@ -64,8 +90,6 @@
 }
 
 ExtensionPopup::~ExtensionPopup() {
-  content::DevToolsAgentHost::RemoveObserver(this);
-
   // The ExtensionPopup may close before it was ever shown. If so, indicate such
   // through the callback.
   if (shown_callback_)
@@ -167,10 +191,11 @@
 
     extension_host_observation_.Reset();
     host_.reset();
-    // Stop observing the registry immediately to prevent any subsequent
-    // notifications, since Widget::Close is asynchronous.
+    // Stop observing the registry and devtools immediately to prevent any
+    // subsequent notifications, since Widget::Close is asynchronous.
     DCHECK(extension_registry_observation_.IsObserving());
     extension_registry_observation_.Reset();
+    scoped_devtools_observation_.reset();
 
     GetWidget()->Close();
   }
@@ -192,18 +217,14 @@
 
 void ExtensionPopup::DevToolsAgentHostAttached(
     content::DevToolsAgentHost* agent_host) {
+  DCHECK(host_);
   if (host_->host_contents() == agent_host->GetWebContents())
     show_action_ = PopupShowAction::kShowAndInspect;
 }
 
 void ExtensionPopup::DevToolsAgentHostDetached(
     content::DevToolsAgentHost* agent_host) {
-  // If the extension's page is open it will be closed when the extension
-  // is uninstalled, and if DevTools are attached, we will be notified here.
-  // But because OnExtensionUnloaded was already called, |host_| is
-  // no longer valid.
-  if (!host_)
-    return;
+  DCHECK(host_);
   if (host_->host_contents() == agent_host->GetWebContents())
     show_action_ = PopupShowAction::kShow;
 }
@@ -244,7 +265,8 @@
   // See comments in OnWidgetActivationChanged().
   set_close_on_deactivate(false);
 
-  content::DevToolsAgentHost::AddObserver(this);
+  scoped_devtools_observation_ =
+      std::make_unique<ScopedDevToolsAgentHostObservation>(this);
   host_->browser()->tab_strip_model()->AddObserver(this);
 
   // Listen for the containing view calling window.close();
diff --git a/chrome/browser/ui/views/extensions/extension_popup.h b/chrome/browser/ui/views/extensions/extension_popup.h
index 48e8c9cf..532d052 100644
--- a/chrome/browser/ui/views/extensions/extension_popup.h
+++ b/chrome/browser/ui/views/extensions/extension_popup.h
@@ -121,6 +121,8 @@
   void OnExtensionHostShouldClose(extensions::ExtensionHost* host) override;
 
  private:
+  class ScopedDevToolsAgentHostObservation;
+
   ExtensionPopup(std::unique_ptr<extensions::ExtensionViewHost> host,
                  views::View* anchor_view,
                  views::BubbleBorder::Arrow arrow,
@@ -149,6 +151,9 @@
   PopupShowAction show_action_;
 
   ShowPopupCallback shown_callback_;
+
+  std::unique_ptr<ScopedDevToolsAgentHostObservation>
+      scoped_devtools_observation_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_EXTENSIONS_EXTENSION_POPUP_H_
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
index c8d4205..11de087 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
@@ -857,8 +857,8 @@
     // there are no other buttons at the end.
     ProfileMenuViewBase::ActionableItem::kEditProfileButton};
 
-// TODO(crbug.com/1298490): flaky on Windows.
-#if BUILDFLAG(IS_WIN)
+// TODO(crbug.com/1298490): flaky on Windows and Mac
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
 #define MAYBE_ProfileMenuClickTest_SyncPaused \
   DISABLED_ProfileMenuClickTest_SyncPaused
 #else
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index 9a42c3bd..b6f9760970 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -104,13 +104,6 @@
         this, controller->browser()->tab_menu_model_delegate(),
         controller->model_, controller->tabstrip_->GetModelIndexOf(tab));
 
-    // If IPH is showing, continue into the menu. IsCommandIdAlerted()
-    // is called on |menu_runner_| construction, and we check
-    // |tab_groups_promo_handle_| there. So we must do this first.
-    tab_groups_promo_handle_ =
-        controller->browser()->window()->CloseFeaturePromoAndContinue(
-            feature_engagement::kIPHDesktopTabGroupsNewGroupFeature);
-
     // Because we use "new" badging for feature promos, we cannot use system-
     // native context menus. (See crbug.com/1109256.)
     const int run_flags =
@@ -143,11 +136,9 @@
 
   bool IsCommandIdAlerted(int command_id) const override {
     return command_id == TabStripModel::CommandAddToNewGroup &&
-           tab_groups_promo_handle_;
-  }
-
-  void MenuClosed(ui::SimpleMenuModel*) override {
-    tab_groups_promo_handle_.Release();
+           controller_->GetBrowser()->window()->IsFeaturePromoActive(
+               feature_engagement::kIPHDesktopTabGroupsNewGroupFeature,
+               /* include_continued_promos =*/true);
   }
 
   bool GetAcceleratorForCommandId(int command_id,
@@ -184,9 +175,6 @@
 
   // A pointer back to our hosting controller, for command state information.
   raw_ptr<BrowserTabStripController> controller_;
-
-  // Handle we keep if showing menu IPH for tab groups.
-  FeaturePromoController::PromoHandle tab_groups_promo_handle_;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/views/tabs/tab_container.cc b/chrome/browser/ui/views/tabs/tab_container.cc
index 3889c63..7ddc358 100644
--- a/chrome/browser/ui/views/tabs/tab_container.cc
+++ b/chrome/browser/ui/views/tabs/tab_container.cc
@@ -5,8 +5,11 @@
 #include "chrome/browser/ui/views/tabs/tab_container.h"
 
 #include "base/bits.h"
+#include "base/containers/adapters.h"
 #include "chrome/browser/ui/layout_constants.h"
+#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/tabs/tab.h"
+#include "chrome/browser/ui/views/tabs/tab_drag_context.h"
 #include "chrome/browser/ui/views/tabs/tab_group_header.h"
 #include "chrome/browser/ui/views/tabs/tab_group_highlight.h"
 #include "chrome/browser/ui/views/tabs/tab_group_underline.h"
@@ -237,9 +240,11 @@
 }
 
 TabContainer::TabContainer(TabStripController* controller,
-                           TabHoverCardController* hover_card_controller)
+                           TabHoverCardController* hover_card_controller,
+                           TabDragContext* drag_context)
     : controller_(controller),
       hover_card_controller_(hover_card_controller),
+      drag_context_(drag_context),
       bounds_animator_(this),
       layout_helper_(std::make_unique<TabStripLayoutHelper>(
           controller,
@@ -256,6 +261,11 @@
   RemoveAllChildViews();
 }
 
+void TabContainer::SetAvailableWidthCallback(
+    base::RepeatingCallback<int()> available_width_callback) {
+  available_width_callback_ = available_width_callback;
+}
+
 Tab* TabContainer::AddTab(std::unique_ptr<Tab> tab,
                           int model_index,
                           TabPinned pinned) {
@@ -332,7 +342,7 @@
     group_views->second->UpdateBounds();
 }
 
-int TabContainer::GetModelIndexOf(const TabSlotView* slot_view) {
+int TabContainer::GetModelIndexOf(const TabSlotView* slot_view) const {
   return tabs_view_model_.GetIndexOfView(slot_view);
 }
 
@@ -477,6 +487,20 @@
   PreferredSizeChanged();
 }
 
+int TabContainer::CalculateAvailableWidthForTabs() const {
+  return override_available_width_for_tabs_.value_or(
+      GetAvailableWidthForTabContainer());
+}
+
+int TabContainer::GetAvailableWidthForTabContainer() const {
+  // Falls back to views::View::GetAvailableSize() when
+  // |available_width_callback_| is not defined, e.g. when tab scrolling is
+  // disabled.
+  return available_width_callback_
+             ? available_width_callback_.Run()
+             : parent()->GetAvailableSize(this).width().value();
+}
+
 void TabContainer::SnapToIdealBounds() {
   for (int i = 0; i < GetTabCount(); ++i)
     GetTabAtModelIndex(i)->SetBoundsRect(tabs_view_model_.ideal_bounds(i));
@@ -551,6 +575,31 @@
   override_available_width_for_tabs_.reset();
 }
 
+void TabContainer::SetTabSlotVisibility() {
+  bool last_tab_visible = false;
+  absl::optional<tab_groups::TabGroupId> last_tab_group = absl::nullopt;
+  std::vector<Tab*> tabs = layout_helper()->GetTabs();
+  for (Tab* tab : base::Reversed(tabs)) {
+    absl::optional<tab_groups::TabGroupId> current_group = tab->group();
+    if (current_group != last_tab_group && last_tab_group.has_value()) {
+      TabGroupViews* group_view =
+          group_views().at(last_tab_group.value()).get();
+      group_view->header()->SetVisible(last_tab_visible);
+      group_view->underline()->SetVisible(last_tab_visible);
+    }
+    last_tab_visible = ShouldTabBeVisible(tab);
+    last_tab_group = tab->closing() ? absl::nullopt : current_group;
+
+    // Collapsed tabs disappear once they've reached their minimum size. This
+    // is different than very small non-collapsed tabs, because in that case
+    // the tab (and its favicon) must still be visible.
+    bool is_collapsed = (current_group.has_value() &&
+                         controller_->IsGroupCollapsed(current_group.value()) &&
+                         tab->bounds().width() <= TabStyle::GetTabOverlap());
+    tab->SetVisible(is_collapsed ? false : last_tab_visible);
+  }
+}
+
 void TabContainer::OnTabWillBeRemovedAt(int model_index, bool was_active) {
   // The tab at |model_index| has already been removed from the model, but is
   // still in |tabs_view_model_|.  Index math with care!
@@ -767,9 +816,67 @@
   return nullptr;
 }
 
+bool TabContainer::ShouldTabBeVisible(const Tab* tab) const {
+  // When the tabstrip is scrollable, it can grow to accommodate any number of
+  // tabs, so tabs can never become clipped.
+  // N.B. Tabs can still be not-visible because they're in a collapsed group,
+  // but that's handled elsewhere.
+  // N.B. This is separate from the tab being potentially scrolled offscreen -
+  // this solely determines whether the tab should be clipped for the
+  // pre-scrolling overflow behavior.
+  if (base::FeatureList::IsEnabled(features::kScrollableTabStrip))
+    return true;
+
+  // Detached tabs should always be invisible (as they close).
+  if (tab->detached())
+    return false;
+
+  // If the tab would be clipped by the trailing edge of the strip, even if the
+  // tabstrip were resized to its greatest possible width, it shouldn't be
+  // visible.
+  int right_edge = tab->bounds().right();
+  const int tabstrip_right = tab->dragging()
+                                 ? drag_context_->GetTabDragAreaWidth()
+                                 : GetAvailableWidthForTabContainer();
+  if (right_edge > tabstrip_right)
+    return false;
+
+  // Non-clipped dragging tabs should always be visible.
+  if (tab->dragging())
+    return true;
+
+  // Let all non-clipped closing tabs be visible.  These will probably finish
+  // closing before the user changes the active tab, so there's little reason to
+  // try and make the more complex logic below apply.
+  if (tab->closing())
+    return true;
+
+  // Now we need to check whether the tab isn't currently clipped, but could
+  // become clipped if we changed the active tab, widening either this tab or
+  // the tabstrip portion before it.
+
+  // Pinned tabs don't change size when activated, so any tab in the pinned tab
+  // region is safe.
+  if (tab->data().pinned)
+    return true;
+
+  // If the active tab is on or before this tab, we're safe.
+  if (controller_->GetActiveIndex() <= GetModelIndexOf(tab))
+    return true;
+
+  // We need to check what would happen if the active tab were to move to this
+  // tab or before. If animating, we want to use the target bounds in this
+  // calculation.
+  if (bounds_animator_.IsAnimating())
+    right_edge = bounds_animator_.GetTargetBounds(tab).right();
+  return (right_edge + layout_helper_->active_tab_width() -
+          layout_helper_->inactive_tab_width()) <= tabstrip_right;
+}
+
 bool TabContainer::IsValidModelIndex(int model_index) const {
   return controller_->IsValidIndex(model_index);
 }
 
 BEGIN_METADATA(TabContainer, views::View)
+ADD_READONLY_PROPERTY_METADATA(int, AvailableWidthForTabContainer)
 END_METADATA
diff --git a/chrome/browser/ui/views/tabs/tab_container.h b/chrome/browser/ui/views/tabs/tab_container.h
index ce85e76..aafd9aa 100644
--- a/chrome/browser/ui/views/tabs/tab_container.h
+++ b/chrome/browser/ui/views/tabs/tab_container.h
@@ -22,16 +22,21 @@
 class TabStrip;
 class TabGroupHeader;
 class TabHoverCardController;
+class TabDragContext;
 
 // A View that contains a sequence of Tabs for the TabStrip.
 class TabContainer : public views::View, public views::ViewTargeterDelegate {
  public:
   METADATA_HEADER(TabContainer);
 
-  TabContainer(TabStripController* controller_,
-               TabHoverCardController* hover_card_controller_);
+  TabContainer(TabStripController* controller,
+               TabHoverCardController* hover_card_controller,
+               TabDragContext* drag_context);
   ~TabContainer() override;
 
+  void SetAvailableWidthCallback(
+      base::RepeatingCallback<int()> available_width_callback);
+
   Tab* AddTab(std::unique_ptr<Tab> tab, int model_index, TabPinned pinned);
   void MoveTab(Tab* tab, int from_model_index, int to_model_index);
 
@@ -51,7 +56,7 @@
 
   void UpdateTabGroupVisuals(tab_groups::TabGroupId group_id);
 
-  int GetModelIndexOf(const TabSlotView* slot_view);
+  int GetModelIndexOf(const TabSlotView* slot_view) const;
 
   views::ViewModelT<Tab>* tabs_view_model() { return &tabs_view_model_; }
 
@@ -80,6 +85,14 @@
   // currently set in ideal_bounds.
   void AnimateToIdealBounds();
 
+  // Calculates the width that can be occupied by the tabs in the container.
+  // This can differ from GetAvailableWidthForTabContainer() when in tab closing
+  // mode.
+  int CalculateAvailableWidthForTabs() const;
+
+  // Returns the total width available for the TabContainer's use.
+  int GetAvailableWidthForTabContainer() const;
+
   // Teleports the tabs to their ideal bounds.
   // NOTE: this does *not* invoke UpdateIdealBounds, it uses the bounds
   // currently set in ideal_bounds.
@@ -91,6 +104,10 @@
   void EnterTabClosingMode(absl::optional<int> override_width);
   void ExitTabClosingMode();
 
+  // Sets the visibility state of all tabs and group headers (if any) based on
+  // ShouldTabBeVisible().
+  void SetTabSlotVisibility();
+
   bool in_tab_close() { return in_tab_close_; }
   absl::optional<int> override_available_width_for_tabs() {
     return override_available_width_for_tabs_;
@@ -144,6 +161,13 @@
   // If no tabs are hit, returns null.
   Tab* FindTabHitByPoint(const gfx::Point& point);
 
+  // Returns true if the tab is not partly or fully clipped (due to overflow),
+  // and the tab couldn't become partly clipped due to changing the selected tab
+  // (for example, if currently the strip has the last tab selected, and
+  // changing that to the first tab would cause |tab| to be pushed over enough
+  // to clip).
+  bool ShouldTabBeVisible(const Tab* tab) const;
+
   bool IsValidModelIndex(int model_index) const;
 
   std::map<tab_groups::TabGroupId, std::unique_ptr<TabGroupViews>> group_views_;
@@ -162,6 +186,8 @@
 
   TabHoverCardController* hover_card_controller_;
 
+  TabDragContext* drag_context_;
+
   // Responsible for animating tabs in response to model changes.
   views::BoundsAnimator bounds_animator_;
 
@@ -178,6 +204,8 @@
   // true, remove animations preserve current tab bounds, making tabs move more
   // predictably in case the user wants to perform more mouse-based actions.
   bool in_tab_close_ = false;
+
+  base::RepeatingCallback<int()> available_width_callback_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_CONTAINER_H_
diff --git a/chrome/browser/ui/views/tabs/tab_container_unittest.cc b/chrome/browser/ui/views/tabs/tab_container_unittest.cc
index c6d1bb0..b484f5fa 100644
--- a/chrome/browser/ui/views/tabs/tab_container_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_container_unittest.cc
@@ -22,7 +22,8 @@
 
     tab_strip_controller_ = std::make_unique<FakeBaseTabStripController>();
     tab_container_ = std::make_unique<TabContainer>(
-        tab_strip_controller_.get(), nullptr /*hover_card_controller_*/);
+        tab_strip_controller_.get(), nullptr /*hover_card_controller*/,
+        nullptr /*drag_context*/);
     tab_controller_ = std::make_unique<FakeTabController>();
   }
 
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
index 11d1440..d8cf19d4 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
@@ -871,7 +871,7 @@
   GURL domain_url;
   // Use committed URL to determine if no page has yet loaded, since the title
   // can be blank for some web pages.
-  if (tab->data().last_committed_url.is_empty()) {
+  if (!tab->data().last_committed_url.is_valid()) {
     domain_url = tab->data().visible_url;
     title = tab->data().IsCrashed()
                 ? l10n_util::GetStringUTF16(IDS_HOVER_CARD_CRASHED_TITLE)
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc b/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
index 57e9261..703ce1f 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
@@ -333,7 +333,7 @@
                                            const Tab* intended_tab) {
   // Make sure the hover card isn't accidentally shown if it's already visible
   // or if the anchor is gone or changed.
-  if (hover_card_ || !TargetTabIsValid() || target_tab_ != intended_tab)
+  if (hover_card_ || target_tab_ != intended_tab || !TargetTabIsValid())
     return;
 
   CreateHoverCard(target_tab_);
@@ -544,8 +544,13 @@
 }
 
 bool TabHoverCardController::TargetTabIsValid() const {
+  // There are a bunch of conditions under which a tab may no longer be valid,
+  // including no longer belonging to the same tabstrip, being dragged or
+  // detached, or just not being visible. We need to be vigilant about invalid
+  // tabs due to e.g. crbug.com/1295601.
   return target_tab_ && tab_strip_->GetModelIndexOf(target_tab_) >= 0 &&
-         !target_tab_->closing();
+         !target_tab_->closing() && !target_tab_->detached() &&
+         !target_tab_->dragging() && target_tab_->GetVisible();
 }
 
 void TabHoverCardController::OnCardFullyVisible() {
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 4dafa99..3c0a63f 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -383,8 +383,9 @@
     // 2) If the tabstrip is wider than the tab strip region (and thus is
     // scrollable), returning the tabstrip width allows tabs to be dragged
     // anywhere within the tabstrip, not just in the leftmost region of it.
-    return std::max(tab_strip_->GetAvailableWidthForTabStrip(),
-                    tab_strip_->width());
+    return std::max(
+        tab_strip_->tab_container_->GetAvailableWidthForTabContainer(),
+        tab_strip_->width());
   }
 
   int TabDragAreaBeginX() const override {
@@ -523,7 +524,7 @@
           ->SetVisible(true);
     }
 
-    tab_strip_->SetTabSlotVisibility();
+    tab_strip_->tab_container_->SetTabSlotVisibility();
     tab_strip_->SchedulePaint();
   }
 
@@ -571,7 +572,7 @@
         view->SetBoundsRect(new_bounds);
       }
     }
-    tab_strip_->SetTabSlotVisibility();
+    tab_strip_->tab_container_->SetTabSlotVisibility();
     // The rightmost tab may have moved, which would change the tabstrip's
     // preferred width.
     tab_strip_->PreferredSizeChanged();
@@ -782,10 +783,11 @@
 TabStrip::TabStrip(std::unique_ptr<TabStripController> controller)
     : controller_(std::move(controller)),
       hover_card_controller_(std::make_unique<TabHoverCardController>(this)),
+      drag_context_(std::make_unique<TabDragContextImpl>(this)),
       tab_container_(AddChildView(
           std::make_unique<TabContainer>(controller_.get(),
-                                         hover_card_controller_.get()))),
-      drag_context_(std::make_unique<TabDragContextImpl>(this)) {
+                                         hover_card_controller_.get(),
+                                         drag_context_.get()))) {
   // TODO(pbos): This is probably incorrect, the background of individual tabs
   // depend on their selected state. This should probably be pushed down into
   // tabs.
@@ -823,7 +825,7 @@
 
 void TabStrip::SetAvailableWidthCallback(
     base::RepeatingCallback<int()> available_width_callback) {
-  available_width_callback_ = available_width_callback;
+  tab_container_->SetAvailableWidthCallback(available_width_callback);
 }
 
 // static
@@ -1003,7 +1005,8 @@
 
   // If the tab strip won't be scrollable after the current tabstrip animations
   // complete, scroll animation wouldn't be meaningful.
-  if (ideal_bounds(GetTabCount() - 1).right() <= GetAvailableWidthForTabStrip())
+  if (ideal_bounds(GetTabCount() - 1).right() <=
+      tab_container_->GetAvailableWidthForTabContainer())
     return;
 
   if (tab_scrolling_animation_)
@@ -1171,63 +1174,6 @@
   ShiftGroupRelative(group, 1);
 }
 
-bool TabStrip::ShouldTabBeVisible(const Tab* tab) const {
-  // When the tabstrip is scrollable, it can grow to accommodate any number of
-  // tabs, so tabs can never become clipped.
-  // N.B. Tabs can still be not-visible because they're in a collapsed group,
-  // but that's handled elsewhere.
-  // N.B. This is separate from the tab being potentially scrolled offscreen -
-  // this solely determines whether the tab should be clipped for the
-  // pre-scrolling overflow behavior.
-  if (base::FeatureList::IsEnabled(features::kScrollableTabStrip))
-    return true;
-
-  // Detached tabs should always be invisible (as they close).
-  if (tab->detached())
-    return false;
-
-  // If the tab would be clipped by the trailing edge of the strip, even if the
-  // tabstrip were resized to its greatest possible width, it shouldn't be
-  // visible.
-  int right_edge = tab->bounds().right();
-  const int tabstrip_right = tab->dragging()
-                                 ? drag_context_->GetTabDragAreaWidth()
-                                 : GetAvailableWidthForTabStrip();
-  if (right_edge > tabstrip_right)
-    return false;
-
-  // Non-clipped dragging tabs should always be visible.
-  if (tab->dragging())
-    return true;
-
-  // Let all non-clipped closing tabs be visible.  These will probably finish
-  // closing before the user changes the active tab, so there's little reason to
-  // try and make the more complex logic below apply.
-  if (tab->closing())
-    return true;
-
-  // Now we need to check whether the tab isn't currently clipped, but could
-  // become clipped if we changed the active tab, widening either this tab or
-  // the tabstrip portion before it.
-
-  // Pinned tabs don't change size when activated, so any tab in the pinned tab
-  // region is safe.
-  if (tab->data().pinned)
-    return true;
-
-  // If the active tab is on or before this tab, we're safe.
-  if (controller_->GetActiveIndex() <= GetModelIndexOf(tab))
-    return true;
-
-  // We need to check what would happen if the active tab were to move to this
-  // tab or before. If animating, we want to use the target bounds in this
-  // calculation.
-  if (IsAnimating())
-    right_edge = tab_container_->bounds_animator().GetTargetBounds(tab).right();
-  return (right_edge + GetActiveTabWidth() - GetInactiveTabWidth()) <=
-         tabstrip_right;
-}
-
 bool TabStrip::ShouldDrawStrokes() const {
   // If the controller says we can't draw strokes, don't.
   if (!controller_->CanDrawStrokes())
@@ -1812,7 +1758,14 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// TabStrip, views::AccessiblePaneView overrides:
+// TabStrip, views::View overrides:
+
+views::SizeBounds TabStrip::GetAvailableSize(const views::View* child) const {
+  // We can only reach here if SetAvailableWidthCallback() was never called,
+  // e.g. if tab scrolling is disabled. Defer to our parent.
+  DCHECK(child == tab_container_);
+  return parent()->GetAvailableSize(this);
+}
 
 void TabStrip::Layout() {
   if (base::FeatureList::IsEnabled(features::kScrollableTabStrip)) {
@@ -1823,23 +1776,25 @@
     // It should never be smaller than its minimum width.
     const int min_width = GetMinimumSize().width();
     // If it can, it should fit within the tab strip region.
-    const int available_width = available_width_callback_.Run();
+    const int available_width =
+        tab_container_->GetAvailableWidthForTabContainer();
     // It should be as wide as possible subject to the above constraints.
     const int width = std::min(max_width, std::max(min_width, available_width));
     SetBounds(0, 0, width, GetLayoutConstant(TAB_HEIGHT));
-    SetTabSlotVisibility();
+    tab_container_->SetTabSlotVisibility();
   }
 
   tab_container_->SetBounds(0, 0, width(), height());
 
   if (IsAnimating()) {
     // Hide tabs that have animated at least partially out of the clip region.
-    SetTabSlotVisibility();
+    tab_container_->SetTabSlotVisibility();
     return;
   }
 
   // Only do a layout if our size or the available width changed.
-  const int available_width = GetAvailableWidthForTabStrip();
+  const int available_width =
+      tab_container_->GetAvailableWidthForTabContainer();
   if (last_layout_size_ == size() && last_available_width_ == available_width)
     return;
   if (drag_context_->IsDragSessionActive())
@@ -2125,7 +2080,7 @@
 }
 
 void TabStrip::CompleteAnimationAndLayout() {
-  last_available_width_ = GetAvailableWidthForTabStrip();
+  last_available_width_ = tab_container_->GetAvailableWidthForTabContainer();
   last_layout_size_ = size();
 
   tab_container_->bounds_animator().Cancel();
@@ -2135,36 +2090,10 @@
   UpdateIdealBounds();
   tab_container_->SnapToIdealBounds();
 
-  SetTabSlotVisibility();
+  tab_container_->SetTabSlotVisibility();
   SchedulePaint();
 }
 
-void TabStrip::SetTabSlotVisibility() {
-  bool last_tab_visible = false;
-  absl::optional<tab_groups::TabGroupId> last_tab_group = absl::nullopt;
-  std::vector<Tab*> tabs = tab_container_->layout_helper()->GetTabs();
-  for (Tab* tab : base::Reversed(tabs)) {
-    absl::optional<tab_groups::TabGroupId> current_group = tab->group();
-    if (current_group != last_tab_group && last_tab_group.has_value()) {
-      TabGroupViews* group_view =
-          tab_container_->group_views().at(last_tab_group.value()).get();
-      group_view->header()->SetVisible(last_tab_visible);
-      group_view->underline()->SetVisible(last_tab_visible);
-    }
-    last_tab_visible = ShouldTabBeVisible(tab);
-    last_tab_group = tab->closing() ? absl::nullopt : current_group;
-
-    // Collapsed tabs disappear once they've reached their minimum size. This
-    // is different than very small non-collapsed tabs, because in that case
-    // the tab (and its favicon) must still be visible.
-    bool is_collapsed =
-        (current_group.has_value() &&
-         controller()->IsGroupCollapsed(current_group.value()) &&
-         tab->bounds().width() <= TabStyle::GetTabOverlap());
-    tab->SetVisible(is_collapsed ? false : last_tab_visible);
-  }
-}
-
 int TabStrip::GetActiveTabWidth() const {
   return tab_container_->layout_helper()->active_tab_width();
 }
@@ -2659,23 +2588,13 @@
   // Update |last_available_width_| in case there is a different amount of
   // available width than there was in the last layout (e.g. if the tabstrip
   // is currently hidden).
-  last_available_width_ = GetAvailableWidthForTabStrip();
+  last_available_width_ = tab_container_->GetAvailableWidthForTabContainer();
 
-  const int available_width_for_tabs = CalculateAvailableWidthForTabs();
+  const int available_width_for_tabs =
+      tab_container_->CalculateAvailableWidthForTabs();
   tab_container_->layout_helper()->UpdateIdealBounds(available_width_for_tabs);
 }
 
-int TabStrip::CalculateAvailableWidthForTabs() const {
-  return tab_container_->override_available_width_for_tabs().value_or(
-      GetAvailableWidthForTabStrip());
-}
-
-int TabStrip::GetAvailableWidthForTabStrip() const {
-  return available_width_callback_
-             ? available_width_callback_.Run()
-             : parent()->GetAvailableSize(this).width().value();
-}
-
 void TabStrip::StartResizeLayoutAnimation() {
   PrepareForAnimation();
   UpdateIdealBounds();
@@ -2821,5 +2740,4 @@
 ADD_READONLY_PROPERTY_METADATA(float, HoverOpacityForRadialHighlight)
 ADD_READONLY_PROPERTY_METADATA(int, ActiveTabWidth)
 ADD_READONLY_PROPERTY_METADATA(int, InactiveTabWidth)
-ADD_READONLY_PROPERTY_METADATA(int, AvailableWidthForTabStrip)
 END_METADATA
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index 22b9b3d..21183fb 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -189,13 +189,6 @@
   // Attempts to move the specified group to the right.
   void ShiftGroupRight(const tab_groups::TabGroupId& group);
 
-  // Returns true if the tab is not partly or fully clipped (due to overflow),
-  // and the tab couldn't become partly clipped due to changing the selected tab
-  // (for example, if currently the strip has the last tab selected, and
-  // changing that to the first tab would cause |tab| to be pushed over enough
-  // to clip).
-  bool ShouldTabBeVisible(const Tab* tab) const;
-
   // Returns whether or not strokes should be drawn around and under the tabs.
   bool ShouldDrawStrokes() const;
 
@@ -326,6 +319,7 @@
   void MouseMovedOutOfHost() override;
 
   // views::View:
+  views::SizeBounds GetAvailableSize(const View* child) const override;
   void Layout() override;
   void ChildPreferredSizeChanged(views::View* child) override;
   gfx::Size GetMinimumSize() const override;
@@ -438,10 +432,6 @@
   // Invoked from Layout if the size changes or layout is really needed.
   void CompleteAnimationAndLayout();
 
-  // Sets the visibility state of all tabs and group headers (if any) based on
-  // ShouldTabBeVisible().
-  void SetTabSlotVisibility();
-
   // Returns the current width of the active tab.
   int GetActiveTabWidth() const;
 
@@ -523,13 +513,6 @@
   // use this information for other purposes - see AnimateToIdealBounds.
   void UpdateIdealBounds();
 
-  // Calculates the width that can be occupied by the tabs in the strip. This
-  // can differ from GetAvailableWidthForTabStrip() when in tab closing mode.
-  int CalculateAvailableWidthForTabs() const;
-
-  // Returns the total width available for the TabStrip's use.
-  int GetAvailableWidthForTabStrip() const;
-
   // Starts various types of TabStrip animations.
   void StartResizeLayoutAnimation();
   void StartPinnedTabAnimation();
@@ -583,11 +566,11 @@
 
   std::unique_ptr<TabHoverCardController> hover_card_controller_;
 
+  std::unique_ptr<TabDragContextImpl> drag_context_;
+
   // The View parent for the tabs and the various group views.
   TabContainer* tab_container_;
 
-  base::RepeatingCallback<int()> available_width_callback_;
-
   // Responsible for animating the scroll of the tab strip.
   std::unique_ptr<gfx::LinearAnimation> tab_scrolling_animation_;
 
@@ -649,8 +632,6 @@
           base::BindRepeating(&TabStrip::OnTouchUiChanged,
                               base::Unretained(this)));
 
-  std::unique_ptr<TabDragContextImpl> drag_context_;
-
   TabContextMenuController context_menu_controller_{this};
 };
 
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_mac_win_linux.cc b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_mac_win_linux.cc
index 711d2b6..00a8eb7 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_mac_win_linux.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_mac_win_linux.cc
@@ -88,7 +88,7 @@
                        CheckNavigateToAppSettingsFromChromeAppsWorks) {
   helper_.InstallCreateShortcutWindowed("SiteA");
   helper_.CheckAppInListWindowed("SiteA");
-  helper_.LaunchAppSettingsFromChromeApps("SiteA");
+  helper_.OpenAppSettingsFromChromeApps("SiteA");
   helper_.CheckBrowserNavigationIsAppSettings("SiteA");
   helper_.UninstallFromMenu("SiteA");
   helper_.CheckAppNotInList("SiteA");
@@ -99,12 +99,20 @@
   helper_.InstallCreateShortcutWindowed("SiteA");
   helper_.CheckAppInListWindowed("SiteA");
   helper_.LaunchFromChromeApps("SiteA");
-  helper_.LaunchAppSettingsFromAppMenu("SiteA");
+  helper_.OpenAppSettingsFromAppMenu("SiteA");
   helper_.CheckBrowserNavigationIsAppSettings("SiteA");
   helper_.UninstallFromMenu("SiteA");
   helper_.CheckAppNotInList("SiteA");
 }
 
+IN_PROC_BROWSER_TEST_F(WebAppIntegrationBrowserTestMacWinLinux,
+                       CheckUninstallFromAppSettingsWorks) {
+  helper_.InstallCreateShortcutWindowed("SiteA");
+  helper_.CheckAppInListWindowed("SiteA");
+  helper_.UninstallFromAppSettings("SiteA");
+  helper_.CheckAppNotInList("SiteA");
+}
+
 // Generated tests:
 
 IN_PROC_BROWSER_TEST_F(
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
index 4a29e926..e5820a7 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
@@ -76,6 +76,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/test_utils.h"
 #include "content/public/test/test_web_ui.h"
 #include "extensions/browser/extension_dialog_auto_confirm.h"
 #include "net/dns/mock_host_resolver.h"
@@ -303,6 +304,18 @@
              : absl::make_optional<AppState>(it->second);
 }
 
+#if !BUILDFLAG(IS_CHROMEOS)
+AppManagementPageHandler CreateAppManagementPageHandler(Profile* profile) {
+  mojo::PendingReceiver<app_management::mojom::Page> page;
+  mojo::Remote<app_management::mojom::PageHandler> handler;
+  static auto delegate =
+      WebAppSettingsUI::CreateAppManagementPageHandlerDelegate(profile);
+  return AppManagementPageHandler(handler.BindNewPipeAndPassReceiver(),
+                                  page.InitWithNewPipeAndPassRemote(), profile,
+                                  *delegate);
+}
+#endif
+
 }  // anonymous namespace
 
 BrowserState::BrowserState(
@@ -819,7 +832,7 @@
   AfterStateChangeAction();
 }
 
-void WebAppIntegrationTestDriver::LaunchAppSettingsFromAppMenu(
+void WebAppIntegrationTestDriver::OpenAppSettingsFromAppMenu(
     const std::string& site_mode) {
 #if !BUILDFLAG(IS_CHROMEOS)
   BeforeStateChangeAction(__FUNCTION__);
@@ -857,7 +870,7 @@
 #endif
 }
 
-void WebAppIntegrationTestDriver::LaunchAppSettingsFromChromeApps(
+void WebAppIntegrationTestDriver::OpenAppSettingsFromChromeApps(
     const std::string& site_mode) {
 #if !BUILDFLAG(IS_CHROMEOS)
   BeforeStateChangeAction(__FUNCTION__);
@@ -1011,13 +1024,7 @@
   sync_bridge.SetAppUserDisplayMode(app_id, blink::mojom::DisplayMode::kBrowser,
                                     true);
 #else
-  mojo::PendingReceiver<app_management::mojom::Page> page;
-  mojo::Remote<app_management::mojom::PageHandler> handler;
-  auto delegate =
-      WebAppSettingsUI::CreateAppManagementPageHandlerDelegate(profile());
-  AppManagementPageHandler app_management_page_handler(
-      handler.BindNewPipeAndPassReceiver(), page.InitWithNewPipeAndPassRemote(),
-      profile(), *delegate);
+  auto app_management_page_handler = CreateAppManagementPageHandler(profile());
   app_management_page_handler.SetWindowMode(app_id,
                                             apps::mojom::WindowMode::kBrowser);
 #endif
@@ -1038,13 +1045,7 @@
   sync_bridge.SetAppUserDisplayMode(
       app_id, blink::mojom::DisplayMode::kStandalone, true);
 #else
-  mojo::PendingReceiver<app_management::mojom::Page> page;
-  mojo::Remote<app_management::mojom::PageHandler> handler;
-  auto delegate =
-      WebAppSettingsUI::CreateAppManagementPageHandlerDelegate(profile());
-  AppManagementPageHandler app_management_page_handler(
-      handler.BindNewPipeAndPassReceiver(), page.InitWithNewPipeAndPassRemote(),
-      profile(), *delegate);
+  auto app_management_page_handler = CreateAppManagementPageHandler(profile());
   app_management_page_handler.SetWindowMode(app_id,
                                             apps::mojom::WindowMode::kWindow);
 #endif
@@ -1131,6 +1132,44 @@
   AfterStateChangeAction();
 }
 
+void WebAppIntegrationTestDriver::UninstallFromAppSettings(
+    const std::string& site_mode) {
+#if !BUILDFLAG(IS_CHROMEOS)
+  BeforeStateChangeAction(__FUNCTION__);
+  absl::optional<AppState> app_state = GetAppBySiteMode(
+      before_state_change_action_state_.get(), profile(), site_mode);
+  ASSERT_TRUE(app_state.has_value())
+      << "No app installed for site: " << site_mode;
+  auto app_id = app_state->id;
+  WebAppTestUninstallObserver uninstall_observer(profile());
+  uninstall_observer.BeginListening({app_id});
+
+  auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
+  if (web_contents->GetURL() !=
+      GURL("chrome://app-settings/" + app_state->id)) {
+    OpenAppSettingsFromChromeApps(site_mode);
+    CheckBrowserNavigationIsAppSettings("SiteA");
+  }
+
+  web_contents = browser()->tab_strip_model()->GetActiveWebContents();
+  content::WebContentsDestroyedWatcher destroyed_watcher(web_contents);
+
+  extensions::ScopedTestDialogAutoConfirm auto_confirm(
+      extensions::ScopedTestDialogAutoConfirm::ACCEPT);
+  auto app_management_page_handler = CreateAppManagementPageHandler(profile());
+  app_management_page_handler.Uninstall(app_id);
+
+  uninstall_observer.Wait();
+
+  // Wait for app settings page to be closed.
+  destroyed_watcher.Wait();
+
+  AfterStateChangeAction();
+#else
+  NOTREACHED() << "Not implemented on Chrome OS.";
+#endif
+}
+
 void WebAppIntegrationTestDriver::UninstallFromMenu(
     const std::string& site_mode) {
   BeforeStateChangeAction(__FUNCTION__);
@@ -1990,16 +2029,10 @@
       before_state_change_action_state_.get(), profile(), site_mode);
   ASSERT_TRUE(app_state.has_value())
       << "No app installed for site: " << site_mode;
-  mojo::PendingReceiver<app_management::mojom::Page> page;
-  mojo::Remote<app_management::mojom::PageHandler> handler;
-  auto delegate =
-      WebAppSettingsUI::CreateAppManagementPageHandlerDelegate(profile());
   base::RunLoop run_loop;
   web_app::SetRunOnOsLoginOsHooksChangedCallbackForTesting(
       run_loop.QuitClosure());
-  AppManagementPageHandler app_management_page_handler(
-      handler.BindNewPipeAndPassReceiver(), page.InitWithNewPipeAndPassRemote(),
-      profile(), *delegate);
+  auto app_management_page_handler = CreateAppManagementPageHandler(profile());
   app_management_page_handler.SetRunOnOsLoginMode(app_state->id, login_mode);
   run_loop.Run();
 #endif
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.h b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.h
index f6643e4..995e9df 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.h
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.h
@@ -166,8 +166,8 @@
   void LaunchFromLaunchIcon(const std::string& site_mode);
   void LaunchFromMenuOption(const std::string& site_mode);
   void LaunchFromShortcut(const std::string& site_mode);
-  void LaunchAppSettingsFromChromeApps(const std::string& site_mode);
-  void LaunchAppSettingsFromAppMenu(const std::string& site_mode);
+  void OpenAppSettingsFromChromeApps(const std::string& site_mode);
+  void OpenAppSettingsFromAppMenu(const std::string& site_mode);
   void NavigateBrowser(const std::string& site_mode);
   void NavigatePwaSiteATo(const std::string& site_mode);
   void NavigateNotfoundUrl();
@@ -184,6 +184,7 @@
   void SyncTurnOn();
   void UninstallFromList(const std::string& site_mode);
   void UninstallFromMenu(const std::string& site_mode);
+  void UninstallFromAppSettings(const std::string& site_mode);
   void UninstallPolicyApp(const std::string& site_mode);
   void UninstallFromOs(const std::string& site_mode);
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/BUILD.gn b/chrome/browser/ui/webui/settings/ash/search/BUILD.gn
similarity index 87%
rename from chrome/browser/ui/webui/settings/chromeos/search/BUILD.gn
rename to chrome/browser/ui/webui/settings/ash/search/BUILD.gn
index 0065095..58ce0fb 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search/BUILD.gn
+++ b/chrome/browser/ui/webui/settings/ash/search/BUILD.gn
@@ -15,7 +15,7 @@
   ]
 
   public_deps = [
-    "../constants:mojom",
+    "//chrome/browser/ui/webui/settings/chromeos/constants:mojom",
     "//mojo/public/mojom/base",
   ]
 }
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/OWNERS b/chrome/browser/ui/webui/settings/ash/search/OWNERS
similarity index 100%
rename from chrome/browser/ui/webui/settings/chromeos/search/OWNERS
rename to chrome/browser/ui/webui/settings/ash/search/OWNERS
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker.cc b/chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker.cc
similarity index 97%
rename from chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker.cc
rename to chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker.cc
index 7f568b2..6d0272a 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker.cc
+++ b/chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker.h"
+#include "chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker.h"
 
 #include "base/metrics/histogram_functions.h"
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker.h b/chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker.h
similarity index 88%
rename from chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker.h
rename to chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker.h
index da5c3706..932a617 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker.h
+++ b/chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_SEARCH_PER_SESSION_SETTINGS_USER_ACTION_TRACKER_H_
-#define CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_SEARCH_PER_SESSION_SETTINGS_USER_ACTION_TRACKER_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SEARCH_PER_SESSION_SETTINGS_USER_ACTION_TRACKER_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SEARCH_PER_SESSION_SETTINGS_USER_ACTION_TRACKER_H_
 
 #include "base/time/time.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -64,4 +64,4 @@
 }  // namespace settings
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_SEARCH_PER_SESSION_SETTINGS_USER_ACTION_TRACKER_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SEARCH_PER_SESSION_SETTINGS_USER_ACTION_TRACKER_H_
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker_unittest.cc b/chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker_unittest.cc
similarity index 98%
rename from chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker_unittest.cc
rename to chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker_unittest.cc
index 74f716e..96640f1 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker_unittest.cc
+++ b/chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker.h"
+#include "chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker.h"
 
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/search.mojom b/chrome/browser/ui/webui/settings/ash/search/search.mojom
similarity index 98%
rename from chrome/browser/ui/webui/settings/chromeos/search/search.mojom
rename to chrome/browser/ui/webui/settings/ash/search/search.mojom
index d63ae03..a3c098d 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search/search.mojom
+++ b/chrome/browser/ui/webui/settings/ash/search/search.mojom
@@ -6,7 +6,7 @@
 
 import "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom";
 import "chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom";
-import "chrome/browser/ui/webui/settings/chromeos/search/search_result_icon.mojom";
+import "chrome/browser/ui/webui/settings/ash/search/search_result_icon.mojom";
 import "mojo/public/mojom/base/string16.mojom";
 
 // Describes the type of settings result.
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/search_concept.h b/chrome/browser/ui/webui/settings/ash/search/search_concept.h
similarity index 88%
rename from chrome/browser/ui/webui/settings/chromeos/search/search_concept.h
rename to chrome/browser/ui/webui/settings/ash/search/search_concept.h
index 156e6c81..45a35d1 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search/search_concept.h
+++ b/chrome/browser/ui/webui/settings/ash/search/search_concept.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_SEARCH_SEARCH_CONCEPT_H_
-#define CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_SEARCH_SEARCH_CONCEPT_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SEARCH_SEARCH_CONCEPT_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SEARCH_SEARCH_CONCEPT_H_
 
+#include "chrome/browser/ui/webui/settings/ash/search/search.mojom.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_result_icon.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_identifier.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search.mojom.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_result_icon.mojom.h"
 
 namespace chromeos {
 namespace settings {
@@ -71,4 +71,4 @@
 }  // namespace settings
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_SEARCH_SEARCH_CONCEPT_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SEARCH_SEARCH_CONCEPT_H_
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/search_handler.cc b/chrome/browser/ui/webui/settings/ash/search/search_handler.cc
similarity index 97%
rename from chrome/browser/ui/webui/settings/chromeos/search/search_handler.cc
rename to chrome/browser/ui/webui/settings/ash/search/search_handler.cc
index 34fc3c59..f8fb158 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search/search_handler.cc
+++ b/chrome/browser/ui/webui/settings/ash/search/search_handler.cc
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_handler.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_handler.h"
 
 #include <algorithm>
 
 #include "base/strings/string_number_conversions.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_concept.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_result_icon.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/hierarchy.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_sections.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_concept.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_result_icon.mojom.h"
 #include "chrome/grit/generated_resources.h"
 #include "chromeos/components/local_search_service/public/cpp/local_search_service_proxy.h"
 #include "ui/base/l10n/l10n_util.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/search_handler.h b/chrome/browser/ui/webui/settings/ash/search/search_handler.h
similarity index 91%
rename from chrome/browser/ui/webui/settings/chromeos/search/search_handler.h
rename to chrome/browser/ui/webui/settings/ash/search/search_handler.h
index 3dbffcf..28effd6 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search/search_handler.h
+++ b/chrome/browser/ui/webui/settings/ash/search/search_handler.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_SEARCH_SEARCH_HANDLER_H_
-#define CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_SEARCH_SEARCH_HANDLER_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SEARCH_SEARCH_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SEARCH_SEARCH_HANDLER_H_
 
 #include <vector>
 
 #include "base/gtest_prod_util.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search.mojom.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search.mojom.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chromeos/components/local_search_service/public/cpp/local_search_service_proxy.h"
 #include "chromeos/components/local_search_service/public/mojom/index.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -119,4 +119,4 @@
 }  // namespace settings
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_SEARCH_SEARCH_HANDLER_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SEARCH_SEARCH_HANDLER_H_
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/search_handler_unittest.cc b/chrome/browser/ui/webui/settings/ash/search/search_handler_unittest.cc
similarity index 97%
rename from chrome/browser/ui/webui/settings/chromeos/search/search_handler_unittest.cc
rename to chrome/browser/ui/webui/settings/ash/search/search_handler_unittest.cc
index c298276..757f81f12 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search/search_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/ash/search/search_handler_unittest.cc
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_handler.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_handler.h"
 
 #include "base/no_destructor.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/task_environment.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search.mojom-test-utils.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/fake_hierarchy.h"
 #include "chrome/browser/ui/webui/settings/chromeos/fake_os_settings_sections.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search.mojom-test-utils.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/grit/generated_resources.h"
 #include "chromeos/components/local_search_service/public/cpp/local_search_service_proxy.h"
 #include "mojo/public/cpp/bindings/remote.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/search_result_icon.mojom b/chrome/browser/ui/webui/settings/ash/search/search_result_icon.mojom
similarity index 100%
rename from chrome/browser/ui/webui/settings/chromeos/search/search_result_icon.mojom
rename to chrome/browser/ui/webui/settings/ash/search/search_result_icon.mojom
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.cc b/chrome/browser/ui/webui/settings/ash/search/search_tag_registry.cc
similarity index 97%
rename from chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.cc
rename to chrome/browser/ui/webui/settings/ash/search/search_tag_registry.cc
index 1772dcb..17de7c6 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.cc
+++ b/chrome/browser/ui/webui/settings/ash/search/search_tag_registry.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 
 #include <algorithm>
 #include <sstream>
@@ -10,7 +10,7 @@
 #include "base/feature_list.h"
 #include "base/strings/string_number_conversions.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_concept.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_concept.h"
 #include "chromeos/components/local_search_service/public/cpp/local_search_service_proxy.h"
 #include "ui/base/l10n/l10n_util.h"
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h b/chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h
similarity index 94%
rename from chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h
rename to chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h
index 46e9e2b..d9f00d5 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h
+++ b/chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_SEARCH_SEARCH_TAG_REGISTRY_H_
-#define CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_SEARCH_SEARCH_TAG_REGISTRY_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SEARCH_SEARCH_TAG_REGISTRY_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SEARCH_SEARCH_TAG_REGISTRY_H_
 
 #include <unordered_map>
 #include <utility>
@@ -79,6 +79,7 @@
   // returned by the LocalSearchService. If no metadata is available, null
   // is returned.
   const SearchConcept* GetTagMetadata(const std::string& result_id) const;
+
  private:
   FRIEND_TEST_ALL_PREFIXES(SearchTagRegistryTest, AddAndRemove);
 
@@ -108,4 +109,4 @@
 }  // namespace settings
 }  // namespace chromeos
 
-#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_SEARCH_SEARCH_TAG_REGISTRY_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_ASH_SEARCH_SEARCH_TAG_REGISTRY_H_
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry_unittest.cc b/chrome/browser/ui/webui/settings/ash/search/search_tag_registry_unittest.cc
similarity index 96%
rename from chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry_unittest.cc
rename to chrome/browser/ui/webui/settings/ash/search/search_tag_registry_unittest.cc
index 5611dab7..bd4e862 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry_unittest.cc
+++ b/chrome/browser/ui/webui/settings/ash/search/search_tag_registry_unittest.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 
 #include "base/no_destructor.h"
 #include "base/test/task_environment.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_concept.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_concept.h"
 #include "chrome/grit/generated_resources.h"
 #include "chromeos/components/local_search_service/public/mojom/index.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/search/user_action_recorder.mojom b/chrome/browser/ui/webui/settings/ash/search/user_action_recorder.mojom
similarity index 100%
rename from chrome/browser/ui/webui/settings/chromeos/search/user_action_recorder.mojom
rename to chrome/browser/ui/webui/settings/ash/search/user_action_recorder.mojom
diff --git a/chrome/browser/ui/webui/settings/chromeos/BUILD.gn b/chrome/browser/ui/webui/settings/chromeos/BUILD.gn
index c86ea04..0fc7523 100644
--- a/chrome/browser/ui/webui/settings/chromeos/BUILD.gn
+++ b/chrome/browser/ui/webui/settings/chromeos/BUILD.gn
@@ -5,8 +5,8 @@
 group("mojom_js") {
   public_deps = [
     "constants:mojom_js",
-    "search:mojo_bindings_js",
     "//chrome/browser/ui/webui/nearby_share/public/mojom:mojom_js",
+    "//chrome/browser/ui/webui/settings/ash/search:mojo_bindings_js",
     "//ui/webui/resources/cr_components/app_management:mojo_bindings_js",
   ]
 }
diff --git a/chrome/browser/ui/webui/settings/chromeos/about_section.cc b/chrome/browser/ui/webui/settings/chromeos/about_section.cc
index f676a04f..55e65de 100644
--- a/chrome/browser/ui/webui/settings/chromeos/about_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/about_section.cc
@@ -19,8 +19,8 @@
 #include "chrome/browser/obsolete_system/obsolete_system.h"
 #include "chrome/browser/ui/webui/management/management_ui.h"
 #include "chrome/browser/ui/webui/settings/about_handler.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/device_name_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/version/version_ui.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/channel_info.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/accessibility_section.cc b/chrome/browser/ui/webui/settings/chromeos/accessibility_section.cc
index 4933f39..165f9e5 100644
--- a/chrome/browser/ui/webui/settings/chromeos/accessibility_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/accessibility_section.cc
@@ -21,9 +21,9 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/speech/extension_api/tts_engine_extension_observer_chromeos.h"
 #include "chrome/browser/ui/webui/settings/accessibility_main_handler.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/captions_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/accessibility_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/switch_access_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/tts_handler.h"
 #include "chrome/browser/ui/webui/settings/font_handler.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/apps_section.cc b/chrome/browser/ui/webui/settings/chromeos/apps_section.cc
index 4774000..77ab8af 100644
--- a/chrome/browser/ui/webui/settings/chromeos/apps_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/apps_section.cc
@@ -19,10 +19,10 @@
 #include "chrome/browser/ash/plugin_vm/plugin_vm_pref_names.h"
 #include "chrome/browser/ash/plugin_vm/plugin_vm_util.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/android_apps_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/guest_os_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/plugin_vm_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/url_constants.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/bluetooth_section.cc b/chrome/browser/ui/webui/settings/chromeos/bluetooth_section.cc
index c108923..cc4a119 100644
--- a/chrome/browser/ui/webui/settings/chromeos/bluetooth_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/bluetooth_section.cc
@@ -10,12 +10,12 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/no_destructor.h"
 #include "chrome/browser/ui/webui/chromeos/bluetooth_shared_load_time_data_provider.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search.mojom.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_result_icon.mojom.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/bluetooth_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search.mojom.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_result_icon.mojom.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/crostini_section.cc b/chrome/browser/ui/webui/settings/chromeos/crostini_section.cc
index 21fa9f10..75d0089 100644
--- a/chrome/browser/ui/webui/settings/chromeos/crostini_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/crostini_section.cc
@@ -16,9 +16,9 @@
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/crostini_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/guest_os_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/generated_resources.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/date_time_section.cc b/chrome/browser/ui/webui/settings/chromeos/date_time_section.cc
index 9c65bcd..5280753 100644
--- a/chrome/browser/ui/webui/settings/chromeos/date_time_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/date_time_section.cc
@@ -12,8 +12,8 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ash/system/timezone_util.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/date_time_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/generated_resources.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_section.cc b/chrome/browser/ui/webui/settings/chromeos/device_section.cc
index 486d5062..7be9b02 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_section.cc
@@ -16,13 +16,13 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ash/login/demo_mode/demo_session.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/device_display_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/device_pointer_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/device_power_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_features_util.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/url_constants.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/files_section.cc b/chrome/browser/ui/webui/settings/chromeos/files_section.cc
index ff4ff98..7f1bd7b 100644
--- a/chrome/browser/ui/webui/settings/chromeos/files_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/files_section.cc
@@ -9,7 +9,7 @@
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ui/webui/chromeos/smb_shares/smb_handler.h"
 #include "chrome/browser/ui/webui/chromeos/smb_shares/smb_shares_localized_strings_provider.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/generated_resources.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/hierarchy.h b/chrome/browser/ui/webui/settings/chromeos/hierarchy.h
index 8ca5a79..9051051 100644
--- a/chrome/browser/ui/webui/settings/chromeos/hierarchy.h
+++ b/chrome/browser/ui/webui/settings/chromeos/hierarchy.h
@@ -10,10 +10,10 @@
 #include <utility>
 #include <vector>
 
+#include "chrome/browser/ui/webui/settings/ash/search/search.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_identifier.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace chromeos {
diff --git a/chrome/browser/ui/webui/settings/chromeos/internet_section.cc b/chrome/browser/ui/webui/settings/chromeos/internet_section.cc
index caab949..0982b45 100644
--- a/chrome/browser/ui/webui/settings/chromeos/internet_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/internet_section.cc
@@ -14,9 +14,9 @@
 #include "base/no_destructor.h"
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/ui/webui/chromeos/cellular_setup/cellular_setup_localized_strings_provider.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/internet_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/url_constants.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/kerberos_section.cc b/chrome/browser/ui/webui/settings/chromeos/kerberos_section.cc
index 4bda75a..caa6029e 100644
--- a/chrome/browser/ui/webui/settings/chromeos/kerberos_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/kerberos_section.cc
@@ -6,8 +6,8 @@
 
 #include "base/no_destructor.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/kerberos_accounts_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_data_source.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/languages_section.cc b/chrome/browser/ui/webui/settings/chromeos/languages_section.cc
index 943f7d3..6bc185f2 100644
--- a/chrome/browser/ui/webui/settings/chromeos/languages_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/languages_section.cc
@@ -10,8 +10,8 @@
 #include "base/no_destructor.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_features_util.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/languages_handler.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/url_constants.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_section.cc b/chrome/browser/ui/webui/settings/chromeos/multidevice_section.cc
index 767f6c3..bfb0598d 100644
--- a/chrome/browser/ui/webui/settings/chromeos/multidevice_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_section.cc
@@ -23,8 +23,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/ash/session_controller_client_impl.h"
 #include "chrome/browser/ui/webui/nearby_share/shared_resources.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/url_constants.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_manager.cc b/chrome/browser/ui/webui/settings/chromeos/os_settings_manager.cc
index f7af139b..7ee91f9 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_manager.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_manager.cc
@@ -7,10 +7,10 @@
 #include "base/bind.h"
 #include "base/feature_list.h"
 #include "chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_handler.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/hierarchy.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_sections.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/settings_user_action_tracker.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "ui/base/l10n/l10n_util.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_section.h b/chrome/browser/ui/webui/settings/chromeos/os_settings_section.h
index 6ea23f8..49119bbf 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_section.h
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_section.h
@@ -11,10 +11,10 @@
 #include "base/containers/span.h"
 #include "base/gtest_prod_util.h"
 #include "base/values.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search.mojom.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_concept.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search.mojom.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_concept.h"
 
 class Profile;
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc
index 79eebde..e781f7bd 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.cc
@@ -18,11 +18,11 @@
 #include "chrome/browser/nearby_sharing/nearby_sharing_service_impl.h"
 #include "chrome/browser/ui/webui/managed_ui_handler.h"
 #include "chrome/browser/ui/webui/settings/ash/os_apps_page/app_notification_handler.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/device_storage_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_manager.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_manager_factory.h"
 #include "chrome/browser/ui/webui/settings/chromeos/pref_names.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/settings_user_action_tracker.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/webui_url_constants.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h
index 28600c2..b49fbf7 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h
@@ -13,7 +13,7 @@
 #include "chrome/browser/ui/webui/nearby_share/nearby_share.mojom.h"
 #include "chrome/browser/ui/webui/nearby_share/public/mojom/nearby_share_settings.mojom.h"
 #include "chrome/browser/ui/webui/settings/ash/os_apps_page/mojom/app_notification_handler.mojom-forward.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/user_action_recorder.mojom-forward.h"
+#include "chrome/browser/ui/webui/settings/ash/search/user_action_recorder.mojom-forward.h"
 #include "chrome/browser/ui/webui/webui_load_timer.h"
 #include "chromeos/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom-forward.h"
 #include "chromeos/services/cellular_setup/public/mojom/cellular_setup.mojom-forward.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/people_section.cc b/chrome/browser/ui/webui/settings/chromeos/people_section.cc
index 11229c41a..546d5217 100644
--- a/chrome/browser/ui/webui/settings/chromeos/people_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/people_section.cc
@@ -27,12 +27,12 @@
 #include "chrome/browser/supervised_user/supervised_user_service.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/webui/chromeos/sync/os_sync_handler.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/account_manager_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/fingerprint_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_features_util.h"
 #include "chrome/browser/ui/webui/settings/chromeos/parental_controls_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/quick_unlock_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/people_handler.h"
 #include "chrome/browser/ui/webui/settings/profile_info_handler.h"
 #include "chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/personalization_section.cc b/chrome/browser/ui/webui/settings/chromeos/personalization_section.cc
index ed47d5f..9d508f3 100644
--- a/chrome/browser/ui/webui/settings/chromeos/personalization_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/personalization_section.cc
@@ -12,11 +12,11 @@
 #include "base/no_destructor.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/ambient_mode_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/change_picture_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_features_util.h"
 #include "chrome/browser/ui/webui/settings/chromeos/personalization_hub_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/wallpaper_handler.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/chrome_features.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/printing_section.cc b/chrome/browser/ui/webui/settings/chromeos/printing_section.cc
index 0ac876f..fd472f7 100644
--- a/chrome/browser/ui/webui/settings/chromeos/printing_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/printing_section.cc
@@ -5,8 +5,8 @@
 #include "chrome/browser/ui/webui/settings/chromeos/printing_section.h"
 
 #include "base/no_destructor.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/url_constants.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc b/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc
index e79dfa7..84b5ed8 100644
--- a/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc
@@ -15,10 +15,10 @@
 #include "chrome/browser/ash/login/quick_unlock/quick_unlock_utils.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_features_util.h"
 #include "chrome/browser/ui/webui/settings/chromeos/peripheral_data_access_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/settings_secure_dns_handler.h"
 #include "chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.h"
 #include "chrome/browser/ui/webui/webui_util.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/reset_section.cc b/chrome/browser/ui/webui/settings/chromeos/reset_section.cc
index 3f14606..ef7fcfff 100644
--- a/chrome/browser/ui/webui/settings/chromeos/reset_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/reset_section.cc
@@ -6,7 +6,7 @@
 
 #include "base/no_destructor.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/reset_settings_handler.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/grit/chromium_strings.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/search_section.cc b/chrome/browser/ui/webui/settings/chromeos/search_section.cc
index 60150addd..d3c2fdaf 100644
--- a/chrome/browser/ui/webui/settings/chromeos/search_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/search_section.cc
@@ -15,8 +15,8 @@
 #include "chrome/browser/ash/assistant/assistant_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/chromeos/assistant_optin/assistant_optin_utils.h"
+#include "chrome/browser/ui/webui/settings/ash/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
 #include "chrome/browser/ui/webui/settings/search_engines_handler.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/url_constants.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/settings_user_action_tracker.h b/chrome/browser/ui/webui/settings/chromeos/settings_user_action_tracker.h
index f05770d..ff57567 100644
--- a/chrome/browser/ui/webui/settings/chromeos/settings_user_action_tracker.h
+++ b/chrome/browser/ui/webui/settings/chromeos/settings_user_action_tracker.h
@@ -8,9 +8,9 @@
 #include <memory>
 
 #include "base/gtest_prod_util.h"
+#include "chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker.h"
+#include "chrome/browser/ui/webui/settings/ash/search/user_action_recorder.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/user_action_recorder.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/settings_user_action_tracker_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/settings_user_action_tracker_unittest.cc
index 0adb5d6..9641684 100644
--- a/chrome/browser/ui/webui/settings/chromeos/settings_user_action_tracker_unittest.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/settings_user_action_tracker_unittest.cc
@@ -6,12 +6,12 @@
 
 #include "base/test/metrics/histogram_tester.h"
 #include "base/values.h"
+#include "chrome/browser/ui/webui/settings/ash/search/per_session_settings_user_action_tracker.h"
+#include "chrome/browser/ui/webui/settings/ash/search/user_action_recorder.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/fake_hierarchy.h"
 #include "chrome/browser/ui/webui/settings/chromeos/fake_os_settings_section.h"
 #include "chrome/browser/ui/webui/settings/chromeos/fake_os_settings_sections.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/per_session_settings_user_action_tracker.h"
-#include "chrome/browser/ui/webui/settings/chromeos/search/user_action_recorder.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chromeos {
diff --git a/chrome/browser/ui/webui/settings/settings_secure_dns_handler.cc b/chrome/browser/ui/webui/settings/settings_secure_dns_handler.cc
index 2b3d729..56aa0c6 100644
--- a/chrome/browser/ui/webui/settings/settings_secure_dns_handler.cc
+++ b/chrome/browser/ui/webui/settings/settings_secure_dns_handler.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/check.h"
 #include "base/rand_util.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/net/secure_dns_config.h"
@@ -25,6 +26,7 @@
 #include "net/dns/public/doh_provider_entry.h"
 #include "net/dns/public/secure_dns_mode.h"
 #include "net/dns/public/util.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace secure_dns = chrome_browser_net::secure_dns;
@@ -190,15 +192,12 @@
 
   probe_callback_id_ = args[0].GetString();
   const std::string& doh_config = args[1].GetString();
-
-  net::DnsConfigOverrides overrides;
-  overrides.search = std::vector<std::string>();
-  overrides.attempts = 1;
-  overrides.secure_dns_mode = net::SecureDnsMode::kSecure;
-  secure_dns::ApplyConfig(&overrides, doh_config);
   DCHECK(!runner_);
-  runner_ = std::make_unique<chrome_browser_net::DnsProbeRunner>(
-      overrides, network_context_getter_);
+  absl::optional<net::DnsOverHttpsConfig> parsed =
+      net::DnsOverHttpsConfig::FromString(doh_config);
+  DCHECK(parsed.has_value());  // `doh_config` must be valid.
+  runner_ =
+      secure_dns::MakeProbeRunner(std::move(*parsed), network_context_getter_);
   runner_->RunProbe(base::BindOnce(&SecureDnsHandler::OnProbeComplete,
                                    base::Unretained(this)));
 }
diff --git a/chrome/browser/webauthn/android/java/res/layout-sw600dp/cablev2_error.xml b/chrome/browser/webauthn/android/java/res/layout-sw600dp/cablev2_error.xml
index bc238c4..2bf14e9 100644
--- a/chrome/browser/webauthn/android/java/res/layout-sw600dp/cablev2_error.xml
+++ b/chrome/browser/webauthn/android/java/res/layout-sw600dp/cablev2_error.xml
@@ -3,87 +3,97 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
+<ScrollView
+    xmlns:a="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     a:layout_width="match_parent"
     a:layout_height="match_parent"
-    a:gravity="center"
-    a:orientation="vertical">
+    a:fillViewport="true"
+    a:scrollbars="none">
 
   <LinearLayout
       a:layout_width="match_parent"
-      a:layout_weight="2"
-      a:layout_height="0dp"
-      a:layout_marginTop="70dp"
-      a:orientation="vertical"
-      a:gravity="center">
+      a:layout_height="wrap_content"
+      a:gravity="center"
+      a:orientation="vertical">
 
-    <TextView
-        a:id="@+id/error_title"
-        a:textSize="36sp"
+    <LinearLayout
         a:layout_width="match_parent"
-        a:layout_height="wrap_content"
-        a:layout_marginTop="104dp"
-        a:gravity="center_horizontal"
-        a:text="@string/cablev2_error_title"/>
+        a:layout_weight="2"
+        a:layout_height="0dp"
+        a:layout_marginTop="70dp"
+        a:orientation="vertical"
+        a:gravity="center">
 
-    <!-- This is a semantically-meaningless picture of a sad tab that
-         screen readers can ignore. Thus contentDescription is null. -->
-    <ImageView
-        a:layout_width="210dp"
-        a:layout_height="128dp"
-        a:layout_marginTop="52dp"
-        a:layout_marginBottom="35dp"
-        a:contentDescription="@null"
-        a:gravity="center_horizontal"
-        a:src="@drawable/error_icon"/>
+      <TextView
+          a:id="@+id/error_title"
+          a:textSize="36sp"
+          a:layout_width="match_parent"
+          a:layout_height="wrap_content"
+          a:layout_marginTop="104dp"
+          a:gravity="center_horizontal"
+          a:text="@string/cablev2_error_title"/>
 
-    <TextView
-        a:id="@+id/error_description"
-        style="@style/TextAppearance.TextMedium.Secondary"
-        a:layout_marginTop="4dp"
-        a:layout_marginLeft="40dp"
-        a:layout_marginRight="40dp"
-        a:layout_width="wrap_content"
-        a:layout_height="wrap_content"
-        a:gravity="center_horizontal"/>
+      <!-- This is a semantically-meaningless picture of a sad tab that
+           screen readers can ignore. Thus contentDescription is null. -->
+      <ImageView
+          a:layout_width="210dp"
+          a:layout_height="128dp"
+          a:layout_marginTop="52dp"
+          a:layout_marginBottom="35dp"
+          a:contentDescription="@null"
+          a:gravity="center_horizontal"
+          a:src="@drawable/error_icon"/>
 
-    <TextView
-        a:id="@+id/error_code"
-        style="@style/TextAppearance.TextMedium.Disabled"
-        a:layout_marginTop="4dp"
-        a:layout_marginLeft="40dp"
-        a:layout_marginRight="40dp"
-        a:layout_width="wrap_content"
-        a:layout_height="wrap_content"
-        a:gravity="center_horizontal"/>
+      <TextView
+          a:id="@+id/error_description"
+          style="@style/TextAppearance.TextMedium.Secondary"
+          a:layout_marginTop="4dp"
+          a:layout_marginLeft="40dp"
+          a:layout_marginRight="40dp"
+          a:layout_width="wrap_content"
+          a:layout_height="wrap_content"
+          a:gravity="center_horizontal"/>
 
-    <!-- not shown by default. The visibility is toggled by code when needed -->
-    <org.chromium.ui.widget.ButtonCompat
-        a:id="@+id/error_settings_button"
-        style="@style/TextButton"
-        a:layout_width="wrap_content"
-        a:layout_height="wrap_content"
-        a:gravity="center_horizontal"
-        a:layout_marginTop="20dp"
-        a:visibility="invisible"
-        a:text="@string/qr_code_open_settings_label"/>
+      <TextView
+          a:id="@+id/error_code"
+          style="@style/TextAppearance.TextMedium.Disabled"
+          a:layout_marginTop="4dp"
+          a:layout_marginLeft="40dp"
+          a:layout_marginRight="40dp"
+          a:layout_width="wrap_content"
+          a:layout_height="wrap_content"
+          a:gravity="center_horizontal"/>
 
-  </LinearLayout>
+      <!-- not shown by default. The visibility is toggled by code when needed -->
+      <org.chromium.ui.widget.ButtonCompat
+          a:id="@+id/error_settings_button"
+          style="@style/TextButton"
+          a:layout_width="wrap_content"
+          a:layout_height="wrap_content"
+          a:gravity="center_horizontal"
+          a:layout_marginTop="20dp"
+          a:visibility="invisible"
+          a:text="@string/qr_code_open_settings_label"/>
 
-  <LinearLayout
-      a:layout_width="match_parent"
-      a:layout_height="0dp"
-      a:layout_weight="1"
-      a:gravity="bottom|center">
+    </LinearLayout>
 
-    <org.chromium.ui.widget.ButtonCompat
-        a:id="@+id/error_close"
-        style="@style/TextButton"
-        a:layout_width="wrap_content"
-        a:layout_height="wrap_content"
-        a:gravity="bottom|center"
-        a:layout_marginBottom="20dp"
-        a:text="@string/cablev2_error_close"/>
+    <LinearLayout
+        a:layout_width="match_parent"
+        a:layout_height="0dp"
+        a:layout_weight="1"
+        a:gravity="bottom|center">
 
-  </LinearLayout>
-</LinearLayout>
+      <org.chromium.ui.widget.ButtonCompat
+          a:id="@+id/error_close"
+          style="@style/TextButton"
+          a:layout_width="wrap_content"
+          a:layout_height="wrap_content"
+          a:gravity="bottom|center"
+          a:layout_marginBottom="20dp"
+          a:text="@string/cablev2_error_close"/>
+
+    </LinearLayout>
+    </LinearLayout>
+
+</ScrollView>
diff --git a/chrome/browser/webauthn/android/java/res/layout-sw600dp/cablev2_spinner.xml b/chrome/browser/webauthn/android/java/res/layout-sw600dp/cablev2_spinner.xml
index f7703322..f1e31ca 100644
--- a/chrome/browser/webauthn/android/java/res/layout-sw600dp/cablev2_spinner.xml
+++ b/chrome/browser/webauthn/android/java/res/layout-sw600dp/cablev2_spinner.xml
@@ -3,56 +3,66 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
+
+<ScrollView
+    xmlns:a="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     a:layout_width="match_parent"
     a:layout_height="match_parent"
-    a:orientation="vertical"
-    a:gravity="center"
-    xmlns:tools="http://schemas.android.com/tools">
+    a:fillViewport="true"
+    a:scrollbars="none">
 
-  <TextView
+  <LinearLayout
       a:layout_width="match_parent"
       a:layout_height="wrap_content"
-      a:layout_marginTop="-104dp"
-      a:layout_marginLeft="24dp"
-      a:layout_marginRight="24dp"
-      a:gravity="center_horizontal"
-      a:text="@string/cablev2_serverlink_connecting_to_your_device"
-      a:textSize="36sp" />
+      a:orientation="vertical"
+      a:gravity="center">
 
-  <RelativeLayout
-      a:layout_width="wrap_content"
-      a:layout_height="wrap_content"
-      a:layoutDirection="ltr"
-      a:layout_marginTop="52dp"
-      a:layout_marginLeft="24dp"
-      a:layout_marginRight="24dp"
-      a:layout_gravity="center_horizontal">
-    <ImageView
-        a:id="@+id/spinner"
-        a:contentDescription="@null"
+    <TextView
+        a:layout_width="match_parent"
+        a:layout_height="wrap_content"
+        a:layout_marginTop="-104dp"
+        a:layout_marginLeft="24dp"
+        a:layout_marginRight="24dp"
+        a:gravity="center_horizontal"
+        a:text="@string/cablev2_serverlink_connecting_to_your_device"
+        a:textSize="36sp" />
+
+    <RelativeLayout
         a:layout_width="wrap_content"
         a:layout_height="wrap_content"
-        a:layout_gravity="center"
-        a:layout_centerInParent="true"
-        a:layout_centerVertical="true" />
-    <ImageView
-        a:contentDescription="@null"
-        a:layout_height="wrap_content"
+        a:layoutDirection="ltr"
+        a:layout_marginTop="52dp"
+        a:layout_marginLeft="24dp"
+        a:layout_marginRight="24dp"
+        a:layout_gravity="center_horizontal">
+      <ImageView
+          a:id="@+id/spinner"
+          a:contentDescription="@null"
+          a:layout_width="wrap_content"
+          a:layout_height="wrap_content"
+          a:layout_gravity="center"
+          a:layout_centerInParent="true"
+          a:layout_centerVertical="true" />
+      <ImageView
+          a:contentDescription="@null"
+          a:layout_height="wrap_content"
+          a:layout_width="wrap_content"
+          a:src="@drawable/ic_lock_googblue_48dp"
+          a:layout_centerInParent="true"
+          a:layout_centerVertical="true" />
+    </RelativeLayout>
+
+    <TextView
+        a:id="@+id/status_text"
         a:layout_width="wrap_content"
-        a:src="@drawable/ic_lock_googblue_48dp"
-        a:layout_centerInParent="true"
-        a:layout_centerVertical="true" />
-  </RelativeLayout>
+        a:layout_height="wrap_content"
+        a:layout_marginTop="4dp"
+        a:layout_marginBottom="16dp"
+        a:layout_gravity="center_horizontal"
+        a:padding="0px"
+        a:textSize="24sp" />
 
-  <TextView
-      a:id="@+id/status_text"
-      a:layout_width="wrap_content"
-      a:layout_height="wrap_content"
-      a:layout_marginTop="4dp"
-      a:layout_gravity="center_horizontal"
-      a:padding="0px"
-      a:textSize="24sp" />
+  </LinearLayout>
 
-</LinearLayout>
+</ScrollView>
diff --git a/chrome/browser/webauthn/android/java/res/layout/cablev2_error.xml b/chrome/browser/webauthn/android/java/res/layout/cablev2_error.xml
index 70e6c1a7..ede138dc 100644
--- a/chrome/browser/webauthn/android/java/res/layout/cablev2_error.xml
+++ b/chrome/browser/webauthn/android/java/res/layout/cablev2_error.xml
@@ -3,77 +3,87 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
+<ScrollView
+    xmlns:a="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     a:layout_width="match_parent"
     a:layout_height="match_parent"
-    a:gravity="center"
-    a:orientation="vertical">
-
-  <TextView
-      a:id="@+id/error_title"
-      style="@style/TextAppearance.Headline.Primary"
-      a:layout_width="match_parent"
-      a:layout_height="wrap_content"
-      a:layout_marginTop="104dp"
-      a:gravity="center_horizontal"
-      a:text="@string/cablev2_error_title"/>
-
-  <!-- This is a semantically-meaningless picture of a sad tab that
-       screen readers can ignore. Thus contentDescription is null. -->
-  <ImageView
-      a:layout_width="210dp"
-      a:layout_height="128dp"
-      a:layout_marginTop="52dp"
-      a:layout_marginBottom="35dp"
-      a:contentDescription="@null"
-      a:gravity="center_horizontal"
-      a:src="@drawable/error_icon"/>
-
-  <TextView
-      a:id="@+id/error_description"
-      style="@style/TextAppearance.TextMedium.Secondary"
-      a:layout_marginTop="4dp"
-      a:layout_marginLeft="40dp"
-      a:layout_marginRight="40dp"
-      a:layout_width="wrap_content"
-      a:layout_height="wrap_content"
-      a:gravity="center_horizontal"/>
-
-  <TextView
-      a:id="@+id/error_code"
-      style="@style/TextAppearance.TextMedium.Disabled"
-      a:layout_marginTop="4dp"
-      a:layout_marginLeft="40dp"
-      a:layout_marginRight="40dp"
-      a:layout_width="wrap_content"
-      a:layout_height="wrap_content"
-      a:gravity="center_horizontal"/>
-
-    <!-- not shown by default. The visibility is toggled by code when needed -->
-    <org.chromium.ui.widget.ButtonCompat
-        a:id="@+id/error_settings_button"
-        style="@style/TextButton"
-        a:layout_width="wrap_content"
-        a:layout_height="wrap_content"
-        a:gravity="center_horizontal"
-        a:layout_marginTop="20dp"
-        a:visibility="invisible"
-        a:text="@string/qr_code_open_settings_label"/>
+    a:fillViewport="true"
+    a:scrollbars="none">
 
   <LinearLayout
       a:layout_width="match_parent"
-      a:layout_height="0dp"
-      a:layout_weight="1"
-      a:gravity="bottom|center">
+      a:layout_height="wrap_content"
+      a:gravity="center"
+      a:orientation="vertical">
 
-    <org.chromium.ui.widget.ButtonCompat
-        a:id="@+id/error_close"
-        style="@style/TextButton"
+    <TextView
+        a:id="@+id/error_title"
+        style="@style/TextAppearance.Headline.Primary"
+        a:layout_width="match_parent"
+        a:layout_height="wrap_content"
+        a:layout_marginTop="104dp"
+        a:gravity="center_horizontal"
+        a:text="@string/cablev2_error_title"/>
+
+    <!-- This is a semantically-meaningless picture of a sad tab that
+         screen readers can ignore. Thus contentDescription is null. -->
+    <ImageView
+        a:layout_width="210dp"
+        a:layout_height="128dp"
+        a:layout_marginTop="52dp"
+        a:layout_marginBottom="35dp"
+        a:contentDescription="@null"
+        a:gravity="center_horizontal"
+        a:src="@drawable/error_icon"/>
+
+    <TextView
+        a:id="@+id/error_description"
+        style="@style/TextAppearance.TextMedium.Secondary"
+        a:layout_marginTop="4dp"
+        a:layout_marginLeft="40dp"
+        a:layout_marginRight="40dp"
         a:layout_width="wrap_content"
         a:layout_height="wrap_content"
-        a:gravity="bottom|center"
-        a:layout_marginBottom="20dp"
-        a:text="@string/cablev2_error_close"/>
+        a:gravity="center_horizontal"/>
 
+    <TextView
+        a:id="@+id/error_code"
+        style="@style/TextAppearance.TextMedium.Disabled"
+        a:layout_marginTop="4dp"
+        a:layout_marginLeft="40dp"
+        a:layout_marginRight="40dp"
+        a:layout_width="wrap_content"
+        a:layout_height="wrap_content"
+        a:gravity="center_horizontal"/>
+
+      <!-- not shown by default. The visibility is toggled by code when needed -->
+      <org.chromium.ui.widget.ButtonCompat
+          a:id="@+id/error_settings_button"
+          style="@style/TextButton"
+          a:layout_width="wrap_content"
+          a:layout_height="wrap_content"
+          a:gravity="center_horizontal"
+          a:layout_marginTop="20dp"
+          a:visibility="invisible"
+          a:text="@string/qr_code_open_settings_label"/>
+
+    <LinearLayout
+        a:layout_width="match_parent"
+        a:layout_height="0dp"
+        a:layout_weight="1"
+        a:gravity="bottom|center">
+
+      <org.chromium.ui.widget.ButtonCompat
+          a:id="@+id/error_close"
+          style="@style/TextButton"
+          a:layout_width="wrap_content"
+          a:layout_height="wrap_content"
+          a:gravity="bottom|center"
+          a:layout_marginBottom="20dp"
+          a:text="@string/cablev2_error_close"/>
+
+    </LinearLayout>
   </LinearLayout>
-</LinearLayout>
+
+</ScrollView>
diff --git a/chrome/browser/webauthn/android/java/res/layout/cablev2_spinner.xml b/chrome/browser/webauthn/android/java/res/layout/cablev2_spinner.xml
index 0c9420d..abbd458 100644
--- a/chrome/browser/webauthn/android/java/res/layout/cablev2_spinner.xml
+++ b/chrome/browser/webauthn/android/java/res/layout/cablev2_spinner.xml
@@ -3,55 +3,64 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
+<ScrollView
+    xmlns:a="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     a:layout_width="match_parent"
     a:layout_height="match_parent"
-    a:orientation="vertical"
-    xmlns:tools="http://schemas.android.com/tools">
+    a:fillViewport="true"
+    a:scrollbars="none">
 
-  <TextView
+  <LinearLayout
       a:layout_width="match_parent"
       a:layout_height="wrap_content"
-      a:layout_marginTop="104dp"
-      a:layout_marginLeft="24dp"
-      a:layout_marginRight="24dp"
-      a:gravity="center_horizontal"
-      a:text="@string/cablev2_serverlink_connecting_to_your_device"
-      a:textSize="24sp" />
+      a:orientation="vertical">
 
-  <RelativeLayout
-      a:layout_width="wrap_content"
-      a:layout_height="wrap_content"
-      a:layoutDirection="ltr"
-      a:layout_marginTop="52dp"
-      a:layout_marginLeft="24dp"
-      a:layout_marginRight="24dp"
-      a:layout_gravity="center_horizontal">
-    <ImageView
-        a:id="@+id/spinner"
-        a:contentDescription="@null"
+    <TextView
+        a:layout_width="match_parent"
+        a:layout_height="wrap_content"
+        a:layout_marginTop="104dp"
+        a:layout_marginLeft="24dp"
+        a:layout_marginRight="24dp"
+        a:gravity="center_horizontal"
+        a:text="@string/cablev2_serverlink_connecting_to_your_device"
+        a:textSize="24sp" />
+
+    <RelativeLayout
         a:layout_width="wrap_content"
         a:layout_height="wrap_content"
-        a:layout_gravity="center"
-        a:layout_centerInParent="true"
-        a:layout_centerVertical="true" />
-    <ImageView
-        a:contentDescription="@null"
-        a:layout_height="wrap_content"
+        a:layoutDirection="ltr"
+        a:layout_marginTop="52dp"
+        a:layout_marginLeft="24dp"
+        a:layout_marginRight="24dp"
+        a:layout_gravity="center_horizontal">
+      <ImageView
+          a:id="@+id/spinner"
+          a:contentDescription="@null"
+          a:layout_width="wrap_content"
+          a:layout_height="wrap_content"
+          a:layout_gravity="center"
+          a:layout_centerInParent="true"
+          a:layout_centerVertical="true" />
+      <ImageView
+          a:contentDescription="@null"
+          a:layout_height="wrap_content"
+          a:layout_width="wrap_content"
+          a:src="@drawable/ic_lock_googblue_48dp"
+          a:layout_centerInParent="true"
+          a:layout_centerVertical="true" />
+    </RelativeLayout>
+
+    <TextView
+        a:id="@+id/status_text"
         a:layout_width="wrap_content"
-        a:src="@drawable/ic_lock_googblue_48dp"
-        a:layout_centerInParent="true"
-        a:layout_centerVertical="true" />
-  </RelativeLayout>
+        a:layout_height="wrap_content"
+        a:layout_marginTop="4dp"
+        a:layout_marginBottom="16dp"
+        a:layout_gravity="center_horizontal"
+        a:padding="0px"
+        a:textSize="14sp" />
 
-  <TextView
-      a:id="@+id/status_text"
-      a:layout_width="wrap_content"
-      a:layout_height="wrap_content"
-      a:layout_marginTop="4dp"
-      a:layout_gravity="center_horizontal"
-      a:padding="0px"
-      a:textSize="14sp" />
+  </LinearLayout>
 
-</LinearLayout>
+</ScrollView>
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 13304b3..96116ede 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1645725546-03a3f2a67e880246435a3a270215ca8761470300.profdata
+chrome-win32-main-1645736298-0bbb2e587ea8d357893033f7560c7f635d8bdf91.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 40c2ced..e05b36e 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1645725546-41e700cf464c36c3bdc664410054cc53cd6bbb0a.profdata
+chrome-win64-main-1645736298-1f88af4fba2e513be7f01a82d64e31bce7ea806d.profdata
diff --git a/chrome/renderer/DEPS b/chrome/renderer/DEPS
index 066439c..8a70ec3 100644
--- a/chrome/renderer/DEPS
+++ b/chrome/renderer/DEPS
@@ -28,7 +28,7 @@
   "+components/feed/feed_feature_list.h",
   "+components/grit",
   "+components/guest_view/renderer",
-  "+components/history_clusters/core/features.h",
+  "+components/history_clusters/core/config.h",
   "+components/metrics/child_call_stack_profile_collector.h",
   "+components/nacl/common",
   "+components/nacl/renderer",
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 2292fe1..6ba41fd 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -81,7 +81,7 @@
 #include "components/error_page/common/localized_error.h"
 #include "components/feed/buildflags.h"
 #include "components/grit/components_scaled_resources.h"
-#include "components/history_clusters/core/features.h"
+#include "components/history_clusters/core/config.h"
 #include "components/network_hints/renderer/web_prescient_networking_impl.h"
 #include "components/no_state_prefetch/common/prerender_url_loader_throttle.h"
 #include "components/no_state_prefetch/renderer/no_state_prefetch_client.h"
@@ -585,8 +585,9 @@
   const bool search_result_extractor_enabled =
       base::FeatureList::IsEnabled(features::kContinuousSearch);
 #else
+  history_clusters::OverrideWithFinch(RenderThread::Get()->GetLocale());
   const bool search_result_extractor_enabled =
-      history_clusters::IsJourneysEnabled(RenderThread::Get()->GetLocale());
+      history_clusters::GetConfig().is_journeys_enabled;
 #endif
   if (render_frame->IsMainFrame() && search_result_extractor_enabled) {
     continuous_search::SearchResultExtractorImpl::Create(render_frame);
diff --git a/chrome/renderer/media/flash_embed_rewrite.cc b/chrome/renderer/media/flash_embed_rewrite.cc
index 9d99c1b1..416af5b 100644
--- a/chrome/renderer/media/flash_embed_rewrite.cc
+++ b/chrome/renderer/media/flash_embed_rewrite.cc
@@ -56,8 +56,8 @@
   std::string path = corrected_url.path();
   path.replace(path.find("/v/"), 3, "/embed/");
 
-  url::Replacements<char> r;
-  r.SetPath(path.c_str(), url::Component(0, path.length()));
+  GURL::Replacements r;
+  r.SetPathStr(path);
 
   return corrected_url.ReplaceComponents(r);
 }
@@ -73,8 +73,8 @@
   int replace_length = path.find("/swf/video/") == 0 ? 11 : 5;
   path.replace(0, replace_length, "/embed/video/");
 
-  url::Replacements<char> r;
-  r.SetPath(path.c_str(), url::Component(0, path.length()));
+  GURL::Replacements r;
+  r.SetPathStr(path);
 
   return url.ReplaceComponents(r);
 }
diff --git a/chrome/test/android/BUILD.gn b/chrome/test/android/BUILD.gn
index abfdfcd..bce8795 100644
--- a/chrome/test/android/BUILD.gn
+++ b/chrome/test/android/BUILD.gn
@@ -274,7 +274,6 @@
     "javatests/src/org/chromium/chrome/test/util/MenuUtils.java",
     "javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java",
     "javatests/src/org/chromium/chrome/test/util/OmniboxTestUtils.java",
-    "javatests/src/org/chromium/chrome/test/util/OverviewModeBehaviorWatcher.java",
     "javatests/src/org/chromium/chrome/test/util/RecentTabsPageTestUtils.java",
     "javatests/src/org/chromium/chrome/test/util/SadTabRule.java",
     "javatests/src/org/chromium/chrome/test/util/TabStripUtils.java",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OverviewModeBehaviorWatcher.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OverviewModeBehaviorWatcher.java
deleted file mode 100644
index 5b04acf..0000000
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/OverviewModeBehaviorWatcher.java
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.test.util;
-
-import org.hamcrest.Matchers;
-
-import org.chromium.base.test.util.Criteria;
-import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.chrome.browser.compositor.layouts.EmptyOverviewModeObserver;
-import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
-import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior.OverviewModeObserver;
-import org.chromium.content_public.browser.test.util.TestThreadUtils;
-
-/**
- * Checks and waits for certain overview mode events to happen.  Can be used to block test threads
- * until certain overview mode state criteria are met.
- */
-public class OverviewModeBehaviorWatcher {
-    private final OverviewModeBehavior mOverviewModeBehavior;
-    private final OverviewModeObserver mOverviewModeObserver;
-    private boolean mWaitingForShow;
-    private boolean mWaitingForHide;
-
-    /**
-     * Creates an instance of an {@link OverviewModeBehaviorWatcher}.  Note that at this point
-     * it will be registered for overview mode events.
-     *
-     * @param behavior          The {@link OverviewModeBehavior} to watch.
-     * @param waitForShow Whether or not to wait for overview mode to finish showing.
-     * @param waitForHide Whether or not to wait for overview mode to finish hiding.
-     */
-    public OverviewModeBehaviorWatcher(OverviewModeBehavior behavior, boolean waitForShow,
-            boolean waitForHide) {
-        mOverviewModeBehavior = behavior;
-        mOverviewModeObserver = new EmptyOverviewModeObserver() {
-            @Override
-            public void onOverviewModeFinishedShowing() {
-                mWaitingForShow = false;
-            }
-
-            @Override
-            public void onOverviewModeFinishedHiding() {
-                mWaitingForHide = false;
-            }
-        };
-
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> mOverviewModeBehavior.addOverviewModeObserver(mOverviewModeObserver));
-
-        mWaitingForShow = waitForShow;
-        mWaitingForHide = waitForHide;
-    }
-
-    /**
-     * Blocks until all of the expected events have occurred.  Once the events of this class
-     * are met it will always return immediately from {@link #waitForBehavior()}.
-     */
-    public void waitForBehavior() {
-        try {
-            CriteriaHelper.pollUiThread(() -> {
-                Criteria.checkThat(
-                        "OverviewModeObserver#onOverviewModeFinishedShowing() not called.",
-                        mWaitingForShow, Matchers.is(false));
-                Criteria.checkThat(
-                        "OverviewModeObserver#onOverviewModeFinishedHiding() not called.",
-                        mWaitingForHide, Matchers.is(false));
-            });
-        } finally {
-            TestThreadUtils.runOnUiThreadBlocking(
-                    () -> mOverviewModeBehavior.removeOverviewModeObserver(mOverviewModeObserver));
-        }
-    }
-}
diff --git a/chrome/test/data/pdf/page_change_test.js b/chrome/test/data/pdf/page_change_test.js
index 28c63da..806ffff3 100644
--- a/chrome/test/data/pdf/page_change_test.js
+++ b/chrome/test/data/pdf/page_change_test.js
@@ -24,12 +24,23 @@
   simulateFormFocusChange(false);
 }
 
-
 /** @return {number} */
 function getCurrentPage() {
   return getViewer().viewport.getMostVisiblePage();
 }
 
+function getAllPossibleKeyModifiers() {
+  const modifiers = ['shift', 'ctrl', 'alt', 'meta'];
+  modifiers.push(
+      ['shift', 'ctrl'], ['shift', 'alt'], ['shift', 'meta'], ['ctrl', 'alt'],
+      ['ctrl', 'meta'], ['alt', 'meta']);
+  modifiers.push(
+      ['shift', 'ctrl', 'alt'], ['shift', 'ctrl', 'meta'],
+      ['shift', 'alt', 'meta'], ['ctrl', 'alt', 'meta']);
+  modifiers.push(['shift', 'ctrl', 'alt', 'meta']);
+  return modifiers;
+}
+
 const tests = [
   /**
    * Test that the left/right arrows change page back and forth.
@@ -68,16 +79,30 @@
   },
 
   /**
-   * Test that when the document.documentElement is in fit to page, pressing
-   * page up/page down changes page back/forth.
+   * Test that when the PDF Viewer is in fit-to-page mode:
+   *  - Pressing page up/page down changes page back/forth.
+   *  - Pressing any modifiers + page up/page down does not.
    */
   function testPageDownInFitPage() {
     getViewer().viewport.fitToPage();
 
+    // Modifiers + Page down -> Does not change the page.
+    const modifiers = getAllPossibleKeyModifiers();
+    for (const mods of modifiers) {
+      pressAndReleaseKeyOn(document.documentElement, 34, mods, 'PageDown');
+      chrome.test.assertEq(0, getCurrentPage());
+    }
+
     // Page down -> Go to page 2.
     pressAndReleaseKeyOn(document.documentElement, 34, '', 'PageDown');
     chrome.test.assertEq(1, getCurrentPage());
 
+    // Modifiers + Page up -> Does not change the page.
+    for (const mods of modifiers) {
+      pressAndReleaseKeyOn(document.documentElement, 33, mods, 'PageUp');
+      chrome.test.assertEq(1, getCurrentPage());
+    }
+
     // Page up -> Back to page 1.
     pressAndReleaseKeyOn(document.documentElement, 33, '', 'PageUp');
     chrome.test.assertEq(0, getCurrentPage());
diff --git a/chrome/test/data/webui/new_tab_page/modules/modules_test.ts b/chrome/test/data/webui/new_tab_page/modules/modules_test.ts
index d18733a..e8488d3 100644
--- a/chrome/test/data/webui/new_tab_page/modules/modules_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/modules_test.ts
@@ -138,6 +138,9 @@
         },
       ]);
 
+      callbackRouterRemote.setDisabledModules(false, []);
+      await callbackRouterRemote.$.flushForTesting();
+
       // Assert.
       const modules =
           Array.from(modulesElement.shadowRoot!.querySelectorAll('#modules'));
@@ -163,6 +166,157 @@
            ModuleWrapperElement)
               .module.element);
     });
+
+    test('modules can be disabled and restored', async () => {
+      // Arrange.
+      let restoreCalled = false;
+      const moduleArray = [];
+      for (let i = 0; i < 3; ++i) {
+        let module = createElement();
+        moduleArray.push(module);
+      }
+      const fooDescriptor = new ModuleDescriptorV2(
+          'foo', 'foo', ModuleHeight.SHORT, async () => createElement());
+      const barDescriptor = new ModuleDescriptorV2(
+          'bar', 'bar', ModuleHeight.SHORT, async () => createElement());
+      const bazDescriptor = new ModuleDescriptorV2(
+          'baz', 'baz', ModuleHeight.SHORT, async () => createElement());
+      moduleRegistry.setResultFor(
+          'getDescriptors', [fooDescriptor, barDescriptor, bazDescriptor]);
+
+      // Act.
+      const modulesElement = await createModulesElement([
+        {
+          descriptor: fooDescriptor,
+          element: moduleArray[0]!,
+        },
+        {
+          descriptor: barDescriptor,
+          element: moduleArray[1]!,
+        },
+        {
+          descriptor: bazDescriptor,
+          element: moduleArray[2]!,
+        }
+      ]);
+
+      callbackRouterRemote.setDisabledModules(false, []);
+      await callbackRouterRemote.$.flushForTesting();
+
+      // Assert.
+      const modules =
+          Array.from(modulesElement.shadowRoot!.querySelectorAll('#modules'));
+      const moduleWrappers =
+          modulesElement.shadowRoot!.querySelectorAll('ntp-module-wrapper');
+      const moduleWrapperContainers =
+          modulesElement.shadowRoot!.querySelectorAll('.module-container');
+      let shortModuleSiblingsContainers =
+          modulesElement.shadowRoot!.querySelectorAll(
+              '.short-module-siblings-container');
+      assertEquals(3, moduleWrappers.length);
+      assertEquals(3, moduleWrapperContainers.length);
+      assertEquals(1, shortModuleSiblingsContainers.length);
+      assertEquals(modules[0]!.children[0], shortModuleSiblingsContainers[0]);
+      assertEquals(
+          moduleArray[0],
+          (shortModuleSiblingsContainers[0]!.children[0]!.children[0] as
+           ModuleWrapperElement)
+              .module.element);
+      assertEquals(
+          moduleArray[1],
+          (shortModuleSiblingsContainers[0]!.children[1]!.children[0] as
+           ModuleWrapperElement)
+              .module.element);
+      assertNotStyle(moduleWrappers[0]!, 'display', 'none');
+      assertNotStyle(moduleWrapperContainers[0]!, 'display', 'none');
+      assertFalse(modulesElement.$.removeModuleToast.open);
+
+      // Act.
+      moduleWrappers[0]!.dispatchEvent(new CustomEvent('disable-module', {
+        bubbles: true,
+        composed: true,
+        detail: {
+          message: 'Foo',
+          restoreCallback: () => {
+            restoreCalled = true;
+          },
+        },
+      }));
+
+      // Assert.
+      assertDeepEquals(['foo', true], handler.getArgs('setModuleDisabled')[0]);
+
+      // Act.
+      callbackRouterRemote.setDisabledModules(false, ['foo']);
+      await callbackRouterRemote.$.flushForTesting();
+
+      // Assert.
+      shortModuleSiblingsContainers =
+          modulesElement.shadowRoot!.querySelectorAll(
+              '.short-module-siblings-container');
+      assertEquals(1, shortModuleSiblingsContainers.length);
+      assertEquals(modules[0]!.children[1], shortModuleSiblingsContainers[0]);
+      assertEquals(
+          moduleArray[1],
+          (shortModuleSiblingsContainers[0]!.children[0]!.children[0] as
+           ModuleWrapperElement)
+              .module.element);
+      assertEquals(
+          moduleArray[2],
+          (shortModuleSiblingsContainers[0]!.children[1]!.children[0] as
+           ModuleWrapperElement)
+              .module.element);
+      assertNotStyle(moduleWrappers[0]!, 'display', 'none');
+      assertStyle(moduleWrapperContainers[0]!, 'display', 'none');
+      assertTrue(modulesElement.$.removeModuleToast.open);
+      assertEquals(
+          'Foo', modulesElement.$.removeModuleToastMessage.textContent!.trim());
+      assertEquals(1, metrics.count('NewTabPage.Modules.Disabled', 'foo'));
+      assertEquals(
+          1, metrics.count('NewTabPage.Modules.Disabled.ModuleRequest', 'foo'));
+      assertFalse(restoreCalled);
+
+      // Act.
+      modulesElement.$.undoRemoveModuleButton.click();
+
+      // // Assert.
+      assertDeepEquals(['foo', false], handler.getArgs('setModuleDisabled')[1]);
+
+      // Act.
+      callbackRouterRemote.setDisabledModules(false, []);
+      await callbackRouterRemote.$.flushForTesting();
+
+      // Assert.
+      shortModuleSiblingsContainers =
+          modulesElement.shadowRoot!.querySelectorAll(
+              '.short-module-siblings-container');
+      assertEquals(1, shortModuleSiblingsContainers.length);
+      assertEquals(modules[0]!.children[0], shortModuleSiblingsContainers[0]);
+      assertEquals(
+          moduleArray[0],
+          (shortModuleSiblingsContainers[0]!.children[0]!.children[0] as
+           ModuleWrapperElement)
+              .module.element);
+      assertEquals(
+          moduleArray[1],
+          (shortModuleSiblingsContainers[0]!.children[1]!.children[0] as
+           ModuleWrapperElement)
+              .module.element);
+      assertNotStyle(moduleWrappers[0]!, 'display', 'none');
+      assertNotStyle(moduleWrapperContainers[0]!, 'display', 'none');
+      assertFalse(modulesElement.$.removeModuleToast.open);
+      assertTrue(restoreCalled);
+      assertEquals(1, metrics.count('NewTabPage.Modules.Enabled', 'foo'));
+      assertEquals(1, metrics.count('NewTabPage.Modules.Enabled.Toast', 'foo'));
+
+      // // Act.
+      window.dispatchEvent(new KeyboardEvent('keydown', {
+        key: 'z',
+        ctrlKey: true,
+      }));
+
+      // Assert: no crash.
+    });
   });
 
   test('modules can be dismissed and restored', async () => {
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index d52f574..46317f5 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -18,14 +18,14 @@
 
 js_library("fake_user_action_recorder") {
   deps = [
-    "//chrome/browser/ui/webui/settings/chromeos/search:mojo_bindings_js_library_for_compile",
+    "//chrome/browser/ui/webui/settings/ash/search:mojo_bindings_js_library_for_compile",
     "//ui/webui/resources/js:cr",
   ]
 }
 
 js_library("fake_settings_search_handler") {
   deps = [
-    "//chrome/browser/ui/webui/settings/chromeos/search:mojo_bindings_js_library_for_compile",
+    "//chrome/browser/ui/webui/settings/ash/search:mojo_bindings_js_library_for_compile",
     "//ui/webui/resources/js:cr",
   ]
 }
diff --git a/chrome/test/ppapi/ppapi_filechooser_browsertest.cc b/chrome/test/ppapi/ppapi_filechooser_browsertest.cc
index 154bd7d9..685fbf2 100644
--- a/chrome/test/ppapi/ppapi_filechooser_browsertest.cc
+++ b/chrome/test/ppapi/ppapi_filechooser_browsertest.cc
@@ -19,6 +19,7 @@
 #include "chrome/test/ppapi/ppapi_test_select_file_dialog_factory.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/services/quarantine/test_support.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/test/browser_test.h"
 #include "ppapi/shared_impl/test_utils.h"
 
@@ -54,6 +55,8 @@
   void CheckPPAPIDownloadRequest(
       const GURL& requestor_url,
       const GURL& initiating_frame_url_unused,
+      const content::GlobalRenderFrameHostId&
+          initiating_outermost_main_frame_id,
       content::WebContents* web_contents_unused,
       const base::FilePath& default_file_path,
       const std::vector<base::FilePath::StringType>& alternate_extensions,
diff --git a/chrome/updater/win/task_scheduler_unittest.cc b/chrome/updater/win/task_scheduler_unittest.cc
index d0d65c1..574aed5 100644
--- a/chrome/updater/win/task_scheduler_unittest.cc
+++ b/chrome/updater/win/task_scheduler_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/updater/win/task_scheduler.h"
 
+#include <lmsname.h>
 #include <mstask.h>
 #include <security.h>
 #include <shlobj.h>
@@ -70,6 +71,7 @@
     EXPECT_TRUE(task_scheduler_->DeleteTask(kTaskName1));
     EXPECT_TRUE(task_scheduler_->DeleteTask(kTaskName2));
     ASSERT_FALSE(IsProcessRunning(kTestProcessExecutableName));
+    EXPECT_TRUE(IsServiceRunning(SERVICE_SCHEDULE));
   }
 
   void TearDown() override {
diff --git a/chrome/updater/win/win_util.cc b/chrome/updater/win/win_util.cc
index 2d0d834..01ee3d0 100644
--- a/chrome/updater/win/win_util.cc
+++ b/chrome/updater/win/win_util.cc
@@ -677,4 +677,33 @@
        msi_installer.value(), L"\" /log \"", msi_installer.value(), L".log\""});
 }
 
+bool IsServiceRunning(const std::wstring& service_name) {
+  ScopedScHandle scm(::OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT));
+  if (!scm.IsValid()) {
+    LOG(ERROR) << "::OpenSCManager failed. service_name: " << service_name
+               << ", error: " << std::hex << HRESULTFromLastError();
+    return false;
+  }
+
+  ScopedScHandle service(
+      ::OpenService(scm.Get(), service_name.c_str(), SERVICE_QUERY_STATUS));
+  if (!service.IsValid()) {
+    LOG(ERROR) << "::OpenService failed. service_name: " << service_name
+               << ", error: " << std::hex << HRESULTFromLastError();
+    return false;
+  }
+
+  SERVICE_STATUS status = {0};
+  if (!::QueryServiceStatus(service.Get(), &status)) {
+    LOG(ERROR) << "::QueryServiceStatus failed. service_name: " << service_name
+               << ", error: " << std::hex << HRESULTFromLastError();
+    return false;
+  }
+
+  VLOG(1) << "IsServiceRunning. service_name: " << service_name
+          << ", status: " << std::hex << status.dwCurrentState;
+  return status.dwCurrentState == SERVICE_RUNNING ||
+         status.dwCurrentState == SERVICE_START_PENDING;
+}
+
 }  // namespace updater
diff --git a/chrome/updater/win/win_util.h b/chrome/updater/win/win_util.h
index bcc7e61..55899a0 100644
--- a/chrome/updater/win/win_util.h
+++ b/chrome/updater/win/win_util.h
@@ -37,6 +37,27 @@
 
 namespace updater {
 
+class ScHandleTraits {
+ public:
+  using Handle = SC_HANDLE;
+
+  ScHandleTraits() = delete;
+  ScHandleTraits(const ScHandleTraits&) = delete;
+  ScHandleTraits& operator=(const ScHandleTraits&) = delete;
+
+  static bool CloseHandle(SC_HANDLE handle) {
+    return ::CloseServiceHandle(handle) != FALSE;
+  }
+
+  static bool IsHandleValid(SC_HANDLE handle) { return handle != nullptr; }
+
+  static SC_HANDLE NullHandle() { return nullptr; }
+};
+
+using ScopedScHandle =
+    base::win::GenericScopedHandle<ScHandleTraits,
+                                   base::win::DummyVerifierTraits>;
+
 class ProcessFilterName : public base::ProcessFilter {
  public:
   explicit ProcessFilterName(const std::wstring& process_name);
@@ -199,6 +220,9 @@
 std::wstring BuildMsiCommandLine(const std::wstring& arguments,
                                  const base::FilePath& msi_installer);
 
+// Returns `true` if the service specified is currently running or starting.
+bool IsServiceRunning(const std::wstring& service_name);
+
 }  // namespace updater
 
 #endif  // CHROME_UPDATER_WIN_WIN_UTIL_H_
diff --git a/chromecast/cast_core/BUILD.gn b/chromecast/cast_core/BUILD.gn
index fb98901e3..8ef9c5e 100644
--- a/chromecast/cast_core/BUILD.gn
+++ b/chromecast/cast_core/BUILD.gn
@@ -16,6 +16,7 @@
     "//chromecast/browser:browser",
     "//chromecast/browser:prefs_simple",
     "//chromecast/browser:simple_main_parts",
+    "//chromecast/cast_core/grpc:grpc_factory_linux",
     "//chromecast/cast_core/runtime/browser:browser_simple",
     "//chromecast/cast_core/runtime/renderer",
     "//chromecast/utility:simple_client",
@@ -40,6 +41,7 @@
     ":all_unit_tests",
     "//chromecast/browser:prefs_simple",
     "//chromecast/browser:simple_main_parts",
+    "//chromecast/cast_core/grpc:grpc_factory_linux",
     "//chromecast/cast_core/runtime/browser:browser_simple",
     "//chromecast/cast_core/runtime/renderer",
     "//mojo/core/test:run_all_unittests",
diff --git a/chromecast/cast_core/grpc/BUILD.gn b/chromecast/cast_core/grpc/BUILD.gn
index a235ce2..83cf12da 100644
--- a/chromecast/cast_core/grpc/BUILD.gn
+++ b/chromecast/cast_core/grpc/BUILD.gn
@@ -5,6 +5,26 @@
 import("//chromecast/chromecast.gni")
 import("//third_party/cast_core/public/src/proto/proto.gni")
 
+cast_source_set("grpc_factory") {
+  sources = [
+    "grpc_server_builder.cc",
+    "grpc_server_builder.h",
+  ]
+
+  public_deps = [ "//third_party/grpc:grpc++" ]
+}
+
+cast_source_set("grpc_factory_linux") {
+  sources = [ "grpc_factory_linux.cc" ]
+
+  deps = [
+    ":grpc_factory",
+    "//base",
+    "//third_party/abseil-cpp:absl",
+    "//third_party/grpc:grpc++",
+  ]
+}
+
 cast_source_set("grpc") {
   sources = [
     "cancellable_reactor.h",
@@ -30,6 +50,7 @@
   ]
 
   public_deps = [
+    ":grpc_factory",
     "//base",
     "//third_party/abseil-cpp:absl",
     "//third_party/grpc:grpc++",
diff --git a/chromecast/cast_core/grpc/grpc_factory.h b/chromecast/cast_core/grpc/grpc_factory.h
new file mode 100644
index 0000000..dd4baf4
--- /dev/null
+++ b/chromecast/cast_core/grpc/grpc_factory.h
@@ -0,0 +1,20 @@
+#ifndef THIRD_PARTY_CASTLITE_COMMON_GRPC_GRPC_FACTORY_H_
+#define THIRD_PARTY_CASTLITE_COMMON_GRPC_GRPC_FACTORY_H_
+
+#include "chromecast/cast_core/grpc/grpc_server_builder.h"
+
+namespace cast {
+namespace utils {
+
+// This class holds a set of factory methods that should be overridden per
+// specific platform (ie Android, Linux etc).
+class GrpcFactory {
+ public:
+  // Creates an instance of GrpcServerBuilder.
+  static std::unique_ptr<GrpcServerBuilder> CreateServerBuilder();
+};
+
+}  // namespace utils
+}  // namespace cast
+
+#endif  // THIRD_PARTY_CASTLITE_COMMON_GRPC_GRPC_FACTORY_H_
diff --git a/chromecast/cast_core/grpc/grpc_factory_linux.cc b/chromecast/cast_core/grpc/grpc_factory_linux.cc
new file mode 100644
index 0000000..ebc79d5
--- /dev/null
+++ b/chromecast/cast_core/grpc/grpc_factory_linux.cc
@@ -0,0 +1,58 @@
+#include "base/sequence_checker.h"
+#include "chromecast/cast_core/grpc/grpc_factory.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace cast {
+namespace utils {
+
+namespace {
+
+// Linux implementation of the |GrpcServerBuilder|.
+class GrpcServerBuilderLinux : public GrpcServerBuilder {
+ public:
+  GrpcServerBuilderLinux() { server_builder_.emplace(); }
+  ~GrpcServerBuilderLinux() override = default;
+
+  // Implements GrpcServerBuilder APIs.
+  GrpcServerBuilder& AddListeningPort(
+      const std::string& endpoint,
+      std::shared_ptr<grpc::ServerCredentials> creds,
+      int* selected_port = nullptr) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(server_builder_);
+    DCHECK(creds);
+    server_builder_->AddListeningPort(endpoint, std::move(creds),
+                                      selected_port);
+    return *this;
+  }
+
+  GrpcServerBuilder& RegisterCallbackGenericService(
+      grpc::CallbackGenericService* service) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(server_builder_);
+    DCHECK(service);
+    server_builder_->RegisterCallbackGenericService(service);
+    return *this;
+  }
+
+  std::unique_ptr<grpc::Server> BuildAndStart() override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(server_builder_) << "gRPC server was already built";
+    auto server = std::move(server_builder_).value().BuildAndStart();
+    server_builder_.reset();
+    return server;
+  }
+
+ private:
+  SEQUENCE_CHECKER(sequence_checker_);
+  absl::optional<grpc::ServerBuilder> server_builder_;
+};
+
+}  // namespace
+
+std::unique_ptr<GrpcServerBuilder> GrpcFactory::CreateServerBuilder() {
+  return std::make_unique<GrpcServerBuilderLinux>();
+}
+
+}  // namespace utils
+}  // namespace cast
diff --git a/chromecast/cast_core/grpc/grpc_server.cc b/chromecast/cast_core/grpc/grpc_server.cc
index c5222c3..9bcc73bb 100644
--- a/chromecast/cast_core/grpc/grpc_server.cc
+++ b/chromecast/cast_core/grpc/grpc_server.cc
@@ -12,6 +12,8 @@
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
 #include "chromecast/cast_core/grpc/grpc_call_options.h"
+#include "chromecast/cast_core/grpc/grpc_factory.h"
+#include "chromecast/cast_core/grpc/grpc_server_builder.h"
 
 namespace cast {
 namespace utils {
@@ -67,9 +69,10 @@
   DCHECK(!server_) << "Server is already running";
   DCHECK(server_reactor_tracker_) << "Server was alreadys shutdown";
 
-  server_ = grpc::ServerBuilder()
-                .AddListeningPort(std::string(endpoint),
-                                  grpc::InsecureServerCredentials())
+  auto builder = GrpcFactory::CreateServerBuilder();
+  server_ = builder
+                ->AddListeningPort(std::string(endpoint),
+                                   grpc::InsecureServerCredentials())
                 .RegisterCallbackGenericService(this)
                 .BuildAndStart();
   DCHECK(server_) << "Failed to start server";
diff --git a/chromecast/cast_core/grpc/grpc_server_builder.cc b/chromecast/cast_core/grpc/grpc_server_builder.cc
new file mode 100644
index 0000000..dc44f36
--- /dev/null
+++ b/chromecast/cast_core/grpc/grpc_server_builder.cc
@@ -0,0 +1,13 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromecast/cast_core/grpc/grpc_server_builder.h"
+
+namespace cast {
+namespace utils {
+
+GrpcServerBuilder::~GrpcServerBuilder() = default;
+
+}  // namespace utils
+}  // namespace cast
diff --git a/chromecast/cast_core/grpc/grpc_server_builder.h b/chromecast/cast_core/grpc/grpc_server_builder.h
new file mode 100644
index 0000000..1bc08bf
--- /dev/null
+++ b/chromecast/cast_core/grpc/grpc_server_builder.h
@@ -0,0 +1,43 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMECAST_CAST_CORE_GRPC_GRPC_SERVER_BUILDER_H_
+#define CHROMECAST_CAST_CORE_GRPC_GRPC_SERVER_BUILDER_H_
+
+#include <grpcpp/grpcpp.h>
+
+#include <memory>
+#include <string>
+
+namespace cast {
+namespace utils {
+
+// An grpc::Server builder interface which enables injection of additional logic
+// to the vanilla grpc::ServerBuilder. For example, enabling gRPC on Android via
+// IPC binder.
+class GrpcServerBuilder {
+ public:
+  virtual ~GrpcServerBuilder();
+
+  // Create an instance of GrpcServerBuilder. May be overridden per platform.
+  static std::unique_ptr<GrpcServerBuilder> Create();
+
+  // Adds a gRPC server listening port.
+  virtual GrpcServerBuilder& AddListeningPort(
+      const std::string& endpoint,
+      std::shared_ptr<grpc::ServerCredentials> creds,
+      int* selected_port = nullptr) = 0;
+
+  // Registers a generic service that uses the callback API.
+  virtual GrpcServerBuilder& RegisterCallbackGenericService(
+      grpc::CallbackGenericService* service) = 0;
+
+  // Builds and starts the gRPC server.
+  virtual std::unique_ptr<grpc::Server> BuildAndStart() = 0;
+};
+
+}  // namespace utils
+}  // namespace cast
+
+#endif  // CHROMECAST_CAST_CORE_GRPC_GRPC_SERVER_BUILDER_H_
diff --git a/chromeos/dbus/userdataauth/fake_userdataauth_client.cc b/chromeos/dbus/userdataauth/fake_userdataauth_client.cc
index 40190730..81a0352 100644
--- a/chromeos/dbus/userdataauth/fake_userdataauth_client.cc
+++ b/chromeos/dbus/userdataauth/fake_userdataauth_client.cc
@@ -16,6 +16,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "chromeos/dbus/cryptohome/UserDataAuth.pb.h"
 #include "chromeos/dbus/cryptohome/rpc.pb.h"
 
 namespace chromeos {
@@ -303,6 +304,169 @@
   ReturnProtobufMethodCallback(reply, std::move(callback));
 }
 
+void FakeUserDataAuthClient::PrepareGuestVault(
+    const ::user_data_auth::PrepareGuestVaultRequest& request,
+    PrepareGuestVaultCallback callback) {
+  ::user_data_auth::PrepareGuestVaultReply reply;
+
+  cryptohome::AccountIdentifier account;
+  account.set_account_id(kGuestUserName);
+  reply.set_sanitized_username(GetStubSanitizedUsername(account));
+
+  ReturnProtobufMethodCallback(reply, std::move(callback));
+}
+
+void FakeUserDataAuthClient::PrepareEphemeralVault(
+    const ::user_data_auth::PrepareEphemeralVaultRequest& request,
+    PrepareEphemeralVaultCallback callback) {
+  ::user_data_auth::PrepareEphemeralVaultReply reply;
+
+  cryptohome::AccountIdentifier account;
+  auto auth_session = auth_sessions_.find(request.auth_session_id());
+  if (auth_session == auth_sessions_.end()) {
+    LOG(ERROR) << "AuthSession not found";
+    reply.set_sanitized_username(std::string());
+    reply.set_error(::user_data_auth::CryptohomeErrorCode::
+                        CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN);
+  } else {
+    account = auth_session->second.account;
+    reply.set_sanitized_username(GetStubSanitizedUsername(account));
+    if (!auth_session->second.authenticated) {
+      LOG(ERROR) << "AuthSession is not authenticated";
+      reply.set_error(::user_data_auth::CryptohomeErrorCode::
+                          CRYPTOHOME_ERROR_INVALID_ARGUMENT);
+    }
+  }
+
+  ReturnProtobufMethodCallback(reply, std::move(callback));
+}
+
+void FakeUserDataAuthClient::CreatePersistentUser(
+    const ::user_data_auth::CreatePersistentUserRequest& request,
+    CreatePersistentUserCallback callback) {
+  ::user_data_auth::CreatePersistentUserReply reply;
+
+  auto auth_session = auth_sessions_.find(request.auth_session_id());
+  if (auth_session == auth_sessions_.end()) {
+    LOG(ERROR) << "AuthSession not found";
+    reply.set_sanitized_username(std::string());
+    reply.set_error(::user_data_auth::CryptohomeErrorCode::
+                        CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN);
+  } else if (UserExists(auth_session->second.account)) {
+    LOG(ERROR) << "User already exists"
+               << GetStubSanitizedUsername(auth_session->second.account);
+    reply.set_error(::user_data_auth::CryptohomeErrorCode::
+                        CRYPTOHOME_ERROR_MOUNT_MOUNT_POINT_BUSY);
+  } else {
+    auth_session->second.authenticated = true;
+    AddExistingUser(auth_session->second.account);
+  }
+
+  ReturnProtobufMethodCallback(reply, std::move(callback));
+}
+
+void FakeUserDataAuthClient::PreparePersistentVault(
+    const ::user_data_auth::PreparePersistentVaultRequest& request,
+    PreparePersistentVaultCallback callback) {
+  ::user_data_auth::PreparePersistentVaultReply reply;
+
+  auto error = ::user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_NOT_SET;
+  auto* authenticated_auth_session =
+      GetAuthenticatedAuthSession(request.auth_session_id(), &error);
+
+  if (authenticated_auth_session == nullptr) {
+    reply.set_error(error);
+  } else if (!UserExists(authenticated_auth_session->account)) {
+    reply.set_error(::user_data_auth::CryptohomeErrorCode::
+                        CRYPTOHOME_ERROR_ACCOUNT_NOT_FOUND);
+  }
+
+  ReturnProtobufMethodCallback(reply, std::move(callback));
+}
+
+void FakeUserDataAuthClient::InvalidateAuthSession(
+    const ::user_data_auth::InvalidateAuthSessionRequest& request,
+    InvalidateAuthSessionCallback callback) {
+  ::user_data_auth::InvalidateAuthSessionReply reply;
+  auto auth_session = auth_sessions_.find(request.auth_session_id());
+  if (auth_session == auth_sessions_.end()) {
+    LOG(ERROR) << "AuthSession not found";
+    reply.set_error(::user_data_auth::CryptohomeErrorCode::
+                        CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN);
+  } else {
+    auth_sessions_.erase(auth_session);
+  }
+  ReturnProtobufMethodCallback(reply, std::move(callback));
+}
+
+void FakeUserDataAuthClient::ExtendAuthSession(
+    const ::user_data_auth::ExtendAuthSessionRequest& request,
+    ExtendAuthSessionCallback callback) {
+  ::user_data_auth::ExtendAuthSessionReply reply;
+
+  auto error = ::user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_NOT_SET;
+  GetAuthenticatedAuthSession(request.auth_session_id(), &error);
+  reply.set_error(error);
+
+  ReturnProtobufMethodCallback(reply, std::move(callback));
+}
+
+void FakeUserDataAuthClient::AddAuthFactor(
+    const ::user_data_auth::AddAuthFactorRequest& request,
+    AddAuthFactorCallback callback) {
+  ::user_data_auth::AddAuthFactorReply reply;
+
+  auto error = ::user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_NOT_SET;
+  GetAuthenticatedAuthSession(request.auth_session_id(), &error);
+  reply.set_error(error);
+
+  ReturnProtobufMethodCallback(reply, std::move(callback));
+}
+
+void FakeUserDataAuthClient::AuthenticateAuthFactor(
+    const ::user_data_auth::AuthenticateAuthFactorRequest& request,
+    AuthenticateAuthFactorCallback callback) {
+  last_authenticate_auth_factor_request_ = request;
+  ::user_data_auth::AuthenticateAuthFactorReply reply;
+
+  const std::string auth_session_id = request.auth_session_id();
+  const auto auth_session = auth_sessions_.find(auth_session_id);
+  if (auth_session == auth_sessions_.end()) {
+    LOG(ERROR) << "AuthSession not found";
+    reply.set_error(::user_data_auth::CryptohomeErrorCode::
+                        CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN);
+  } else if (auth_session->second.authenticated) {
+    LOG(WARNING) << "AuthSession is already authenticated";
+  } else {
+    auth_session->second.authenticated = true;
+  }
+  ReturnProtobufMethodCallback(reply, std::move(callback));
+}
+
+void FakeUserDataAuthClient::UpdateAuthFactor(
+    const ::user_data_auth::UpdateAuthFactorRequest& request,
+    UpdateAuthFactorCallback callback) {
+  ::user_data_auth::UpdateAuthFactorReply reply;
+
+  auto error = ::user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_NOT_SET;
+  GetAuthenticatedAuthSession(request.auth_session_id(), &error);
+  reply.set_error(error);
+
+  ReturnProtobufMethodCallback(reply, std::move(callback));
+}
+
+void FakeUserDataAuthClient::RemoveAuthFactor(
+    const ::user_data_auth::RemoveAuthFactorRequest& request,
+    RemoveAuthFactorCallback callback) {
+  ::user_data_auth::RemoveAuthFactorReply reply;
+
+  auto error = ::user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_NOT_SET;
+  GetAuthenticatedAuthSession(request.auth_session_id(), &error);
+  reply.set_error(error);
+
+  ReturnProtobufMethodCallback(reply, std::move(callback));
+}
+
 void FakeUserDataAuthClient::WaitForServiceToBeAvailable(
     chromeos::WaitForServiceToBeAvailableCallback callback) {
   if (service_is_available_ || service_reported_not_available_) {
@@ -424,6 +588,29 @@
   return base::PathExists(GetUserProfileDir(account_id));
 }
 
+const FakeUserDataAuthClient::AuthSessionData*
+FakeUserDataAuthClient::GetAuthenticatedAuthSession(
+    const std::string& auth_session_id,
+    ::user_data_auth::CryptohomeErrorCode* error) const {
+  auto auth_session = auth_sessions_.find(auth_session_id);
+
+  // Check if the token refers to a valid AuthSession.
+  if (auth_session == auth_sessions_.end()) {
+    LOG(ERROR) << "AuthSession not found";
+    *error = ::user_data_auth::CryptohomeErrorCode::
+        CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN;
+  }
+
+  // Check if the AuthSession is properly authenticated.
+  if (!auth_session->second.authenticated) {
+    LOG(ERROR) << "AuthSession is not authenticated";
+    *error = ::user_data_auth::CryptohomeErrorCode::
+        CRYPTOHOME_ERROR_INVALID_ARGUMENT;
+  }
+
+  return &auth_session->second;
+}
+
 void FakeUserDataAuthClient::AddExistingUser(
     const cryptohome::AccountIdentifier& account_id) {
   existing_users_.insert(account_id);
diff --git a/chromeos/dbus/userdataauth/fake_userdataauth_client.h b/chromeos/dbus/userdataauth/fake_userdataauth_client.h
index a6908e9..c5e4bba 100644
--- a/chromeos/dbus/userdataauth/fake_userdataauth_client.h
+++ b/chromeos/dbus/userdataauth/fake_userdataauth_client.h
@@ -95,6 +95,35 @@
       AuthenticateAuthSessionCallback callback) override;
   void AddCredentials(const ::user_data_auth::AddCredentialsRequest& request,
                       AddCredentialsCallback callback) override;
+  void PrepareGuestVault(
+      const ::user_data_auth::PrepareGuestVaultRequest& request,
+      PrepareGuestVaultCallback callback) override;
+  void PrepareEphemeralVault(
+      const ::user_data_auth::PrepareEphemeralVaultRequest& request,
+      PrepareEphemeralVaultCallback callback) override;
+  void CreatePersistentUser(
+      const ::user_data_auth::CreatePersistentUserRequest& request,
+      CreatePersistentUserCallback callback) override;
+  void PreparePersistentVault(
+      const ::user_data_auth::PreparePersistentVaultRequest& request,
+      PreparePersistentVaultCallback callback) override;
+  void InvalidateAuthSession(
+      const ::user_data_auth::InvalidateAuthSessionRequest& request,
+      InvalidateAuthSessionCallback callback) override;
+  void ExtendAuthSession(
+      const ::user_data_auth::ExtendAuthSessionRequest& request,
+      ExtendAuthSessionCallback callback) override;
+  void AddAuthFactor(const ::user_data_auth::AddAuthFactorRequest& request,
+                     AddAuthFactorCallback callback) override;
+  void AuthenticateAuthFactor(
+      const ::user_data_auth::AuthenticateAuthFactorRequest& request,
+      AuthenticateAuthFactorCallback callback) override;
+  void UpdateAuthFactor(
+      const ::user_data_auth::UpdateAuthFactorRequest& request,
+      UpdateAuthFactorCallback callback) override;
+  void RemoveAuthFactor(
+      const ::user_data_auth::RemoveAuthFactorRequest& request,
+      RemoveAuthFactorCallback callback) override;
 
   // Mount() related setter/getters.
 
@@ -177,6 +206,12 @@
     return last_authenticate_auth_session_request_.authorization();
   }
 
+  // AuthenticateAuthFactor() related:
+  const ::user_data_auth::AuthenticateAuthFactorRequest&
+  get_last_authenticate_auth_factor_request() {
+    return last_authenticate_auth_factor_request_;
+  }
+
   // WaitForServiceToBeAvailable() related:
 
   // Changes the behavior of WaitForServiceToBeAvailable(). This method runs
@@ -227,6 +262,13 @@
   // Check whether user with given id exists
   bool UserExists(const cryptohome::AccountIdentifier& account_id) const;
 
+  // The method takes serialized auth session id and returns an authenticated
+  // auth session associated with the id. If the session is missing or not
+  // authenticated, |nullptr| is returned.
+  const AuthSessionData* GetAuthenticatedAuthSession(
+      const std::string& auth_session_id,
+      ::user_data_auth::CryptohomeErrorCode* error) const;
+
   // Mount() related fields.
   ::user_data_auth::CryptohomeErrorCode cryptohome_error_ =
       ::user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_NOT_SET;
@@ -277,6 +319,11 @@
   ::user_data_auth::AuthenticateAuthSessionRequest
       last_authenticate_auth_session_request_;
 
+  // The AuthenticateAuthFactorRequest passed in for the last
+  // AuthenticateAuthFactor() call.
+  ::user_data_auth::AuthenticateAuthFactorRequest
+      last_authenticate_auth_factor_request_;
+
   // The auth sessions on file.
   base::flat_map<std::string, AuthSessionData> auth_sessions_;
 
diff --git a/chromeos/dbus/userdataauth/userdataauth_client.cc b/chromeos/dbus/userdataauth/userdataauth_client.cc
index 1b399ae..091fbd37 100644
--- a/chromeos/dbus/userdataauth/userdataauth_client.cc
+++ b/chromeos/dbus/userdataauth/userdataauth_client.cc
@@ -235,6 +235,85 @@
                     std::move(callback));
   }
 
+  void PrepareGuestVault(
+      const ::user_data_auth::PrepareGuestVaultRequest& request,
+      PrepareGuestVaultCallback callback) override {
+    CallProtoMethod(::user_data_auth::kPrepareGuestVault,
+                    ::user_data_auth::kUserDataAuthInterface, request,
+                    std::move(callback));
+  }
+
+  void PrepareEphemeralVault(
+      const ::user_data_auth::PrepareEphemeralVaultRequest& request,
+      PrepareEphemeralVaultCallback callback) override {
+    CallProtoMethod(::user_data_auth::kPrepareEphemeralVault,
+                    ::user_data_auth::kUserDataAuthInterface, request,
+                    std::move(callback));
+  }
+
+  void CreatePersistentUser(
+      const ::user_data_auth::CreatePersistentUserRequest& request,
+      CreatePersistentUserCallback callback) override {
+    CallProtoMethod(::user_data_auth::kCreatePersistentUser,
+                    ::user_data_auth::kUserDataAuthInterface, request,
+                    std::move(callback));
+  }
+
+  void PreparePersistentVault(
+      const ::user_data_auth::PreparePersistentVaultRequest& request,
+      PreparePersistentVaultCallback callback) override {
+    CallProtoMethod(::user_data_auth::kPreparePersistentVault,
+                    ::user_data_auth::kUserDataAuthInterface, request,
+                    std::move(callback));
+  }
+
+  void InvalidateAuthSession(
+      const ::user_data_auth::InvalidateAuthSessionRequest& request,
+      InvalidateAuthSessionCallback callback) override {
+    CallProtoMethod(::user_data_auth::kInvalidateAuthSession,
+                    ::user_data_auth::kUserDataAuthInterface, request,
+                    std::move(callback));
+  }
+
+  void ExtendAuthSession(
+      const ::user_data_auth::ExtendAuthSessionRequest& request,
+      ExtendAuthSessionCallback callback) override {
+    CallProtoMethod(::user_data_auth::kExtendAuthSession,
+                    ::user_data_auth::kUserDataAuthInterface, request,
+                    std::move(callback));
+  }
+
+  void AddAuthFactor(const ::user_data_auth::AddAuthFactorRequest& request,
+                     AddAuthFactorCallback callback) override {
+    CallProtoMethod(::user_data_auth::kAddAuthFactor,
+                    ::user_data_auth::kUserDataAuthInterface, request,
+                    std::move(callback));
+  }
+
+  void AuthenticateAuthFactor(
+      const ::user_data_auth::AuthenticateAuthFactorRequest& request,
+      AuthenticateAuthFactorCallback callback) override {
+    CallProtoMethod(::user_data_auth::kAuthenticateAuthFactor,
+                    ::user_data_auth::kUserDataAuthInterface, request,
+                    std::move(callback));
+  }
+
+  void UpdateAuthFactor(
+      const ::user_data_auth::UpdateAuthFactorRequest& request,
+      UpdateAuthFactorCallback callback) override {
+    CallProtoMethod(::user_data_auth::kUpdateAuthFactor,
+                    ::user_data_auth::kUserDataAuthInterface, request,
+                    std::move(callback));
+  }
+
+  void RemoveAuthFactor(
+      const ::user_data_auth::RemoveAuthFactorRequest& request,
+      RemoveAuthFactorCallback callback) override {
+    CallProtoMethod(::user_data_auth::kRemoveAuthFactor,
+                    ::user_data_auth::kUserDataAuthInterface, request,
+                    std::move(callback));
+  }
+
  private:
   // Calls cryptohomed's |method_name| method in |interface_name| interface,
   // passing in |request| as input with |timeout_ms|. Once the (asynchronous)
diff --git a/chromeos/dbus/userdataauth/userdataauth_client.h b/chromeos/dbus/userdataauth/userdataauth_client.h
index 67d9b13f..8d98518e 100644
--- a/chromeos/dbus/userdataauth/userdataauth_client.h
+++ b/chromeos/dbus/userdataauth/userdataauth_client.h
@@ -69,6 +69,26 @@
       DBusMethodCallback<::user_data_auth::AuthenticateAuthSessionReply>;
   using AddCredentialsCallback =
       DBusMethodCallback<::user_data_auth::AddCredentialsReply>;
+  using PrepareGuestVaultCallback =
+      DBusMethodCallback<::user_data_auth::PrepareGuestVaultReply>;
+  using PrepareEphemeralVaultCallback =
+      DBusMethodCallback<::user_data_auth::PrepareEphemeralVaultReply>;
+  using CreatePersistentUserCallback =
+      DBusMethodCallback<::user_data_auth::CreatePersistentUserReply>;
+  using PreparePersistentVaultCallback =
+      DBusMethodCallback<::user_data_auth::PreparePersistentVaultReply>;
+  using InvalidateAuthSessionCallback =
+      DBusMethodCallback<::user_data_auth::InvalidateAuthSessionReply>;
+  using ExtendAuthSessionCallback =
+      DBusMethodCallback<::user_data_auth::ExtendAuthSessionReply>;
+  using AddAuthFactorCallback =
+      DBusMethodCallback<::user_data_auth::AddAuthFactorReply>;
+  using AuthenticateAuthFactorCallback =
+      DBusMethodCallback<::user_data_auth::AuthenticateAuthFactorReply>;
+  using UpdateAuthFactorCallback =
+      DBusMethodCallback<::user_data_auth::UpdateAuthFactorReply>;
+  using RemoveAuthFactorCallback =
+      DBusMethodCallback<::user_data_auth::RemoveAuthFactorReply>;
 
   // Not copyable or movable.
   UserDataAuthClient(const UserDataAuthClient&) = delete;
@@ -190,6 +210,67 @@
       const ::user_data_auth::AddCredentialsRequest& request,
       AddCredentialsCallback callback) = 0;
 
+  // This request is intended to happen when a user wants
+  // to login to ChromeOS as a guest.
+  virtual void PrepareGuestVault(
+      const ::user_data_auth::PrepareGuestVaultRequest& request,
+      PrepareGuestVaultCallback callback) = 0;
+
+  // This request is intended when a policy (either device or enterprise)
+  // has enabled ephemeral users. An ephemeral user is created
+  // in a memory filesystem only and is never actually persisted to disk.
+  virtual void PrepareEphemeralVault(
+      const ::user_data_auth::PrepareEphemeralVaultRequest& request,
+      PrepareEphemeralVaultCallback callback) = 0;
+
+  // This will create user directories needed to store
+  // keys and download policies. This will usually be called when a new user is
+  // registering.
+  virtual void CreatePersistentUser(
+      const ::user_data_auth::CreatePersistentUserRequest& request,
+      CreatePersistentUserCallback callback) = 0;
+
+  // This makes available user directories for them to use.
+  virtual void PreparePersistentVault(
+      const ::user_data_auth::PreparePersistentVaultRequest& request,
+      PreparePersistentVaultCallback callback) = 0;
+
+  // This call is used to invalidate an AuthSession
+  // once the need for one no longer exists.
+  virtual void InvalidateAuthSession(
+      const ::user_data_auth::InvalidateAuthSessionRequest& request,
+      InvalidateAuthSessionCallback callback) = 0;
+
+  // This call is used to extend the duration of
+  //  AuthSession that it should be valid for.
+  virtual void ExtendAuthSession(
+      const ::user_data_auth::ExtendAuthSessionRequest& request,
+      ExtendAuthSessionCallback callback) = 0;
+
+  // This call adds an AuthFactor for a user. The call goes
+  // through an authenticated AuthSession.
+  virtual void AddAuthFactor(
+      const ::user_data_auth::AddAuthFactorRequest& request,
+      AddAuthFactorCallback callback) = 0;
+
+  // This will Authenticate an existing AuthFactor.
+  // This call will authenticate an AuthSession.
+  virtual void AuthenticateAuthFactor(
+      const ::user_data_auth::AuthenticateAuthFactorRequest& request,
+      AuthenticateAuthFactorCallback callback) = 0;
+
+  // This call will be used in the case of a user wanting
+  // to update an AuthFactor. (E.g. Changing pin or password).
+  virtual void UpdateAuthFactor(
+      const ::user_data_auth::UpdateAuthFactorRequest& request,
+      UpdateAuthFactorCallback callback) = 0;
+
+  // This is called when a user wants to remove an
+  // AuthFactor.
+  virtual void RemoveAuthFactor(
+      const ::user_data_auth::RemoveAuthFactorRequest& request,
+      RemoveAuthFactorCallback callback) = 0;
+
  protected:
   // Initialize/Shutdown should be used instead.
   UserDataAuthClient();
diff --git a/components/browsing_topics/OWNERS b/components/browsing_topics/OWNERS
new file mode 100644
index 0000000..077aa37f
--- /dev/null
+++ b/components/browsing_topics/OWNERS
@@ -0,0 +1,2 @@
+yaoxia@chromium.org
+jkarlin@chromium.org
diff --git a/components/browsing_topics/common/BUILD.gn b/components/browsing_topics/common/BUILD.gn
new file mode 100644
index 0000000..b48c2bc
--- /dev/null
+++ b/components/browsing_topics/common/BUILD.gn
@@ -0,0 +1,16 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+component("common") {
+  output_name = "browsing_topics_common"
+
+  defines = [ "IS_BROWSING_TOPICS_COMMON_IMPL" ]
+
+  sources = [
+    "common_types.cc",
+    "common_types.h",
+  ]
+
+  deps = [ "//base" ]
+}
diff --git a/components/browsing_topics/common/common_types.cc b/components/browsing_topics/common/common_types.cc
new file mode 100644
index 0000000..cf7db18c
--- /dev/null
+++ b/components/browsing_topics/common/common_types.cc
@@ -0,0 +1,23 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/browsing_topics/common/common_types.h"
+
+namespace browsing_topics {
+
+ApiUsageContextQueryResult::ApiUsageContextQueryResult() = default;
+
+ApiUsageContextQueryResult::ApiUsageContextQueryResult(
+    std::vector<ApiUsageContext> api_usage_contexts)
+    : success(true), api_usage_contexts(std::move(api_usage_contexts)) {}
+
+ApiUsageContextQueryResult::ApiUsageContextQueryResult(
+    ApiUsageContextQueryResult&& other) = default;
+
+ApiUsageContextQueryResult& ApiUsageContextQueryResult::operator=(
+    ApiUsageContextQueryResult&& other) = default;
+
+ApiUsageContextQueryResult::~ApiUsageContextQueryResult() = default;
+
+}  // namespace browsing_topics
diff --git a/components/browsing_topics/common/common_types.h b/components/browsing_topics/common/common_types.h
new file mode 100644
index 0000000..2377712
--- /dev/null
+++ b/components/browsing_topics/common/common_types.h
@@ -0,0 +1,46 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_BROWSING_TOPICS_COMMON_COMMON_TYPES_H_
+#define COMPONENTS_BROWSING_TOPICS_COMMON_COMMON_TYPES_H_
+
+#include <vector>
+
+#include "base/component_export.h"
+#include "base/time/time.h"
+#include "base/types/strong_alias.h"
+
+namespace browsing_topics {
+
+using HashedHost = base::StrongAlias<class HashedHostTag, int64_t>;
+using HashedDomain = base::StrongAlias<class HashedHostTag, int64_t>;
+
+struct COMPONENT_EXPORT(BROWSING_TOPICS_COMMON) ApiUsageContext {
+  HashedDomain hashed_context_domain;
+  HashedHost hashed_top_host;
+  base::Time time;
+};
+
+struct COMPONENT_EXPORT(BROWSING_TOPICS_COMMON) ApiUsageContextQueryResult {
+  ApiUsageContextQueryResult();
+  explicit ApiUsageContextQueryResult(
+      std::vector<ApiUsageContext> api_usage_contexts);
+
+  ApiUsageContextQueryResult(const ApiUsageContextQueryResult&) = delete;
+  ApiUsageContextQueryResult& operator=(const ApiUsageContextQueryResult&) =
+      delete;
+
+  ApiUsageContextQueryResult(ApiUsageContextQueryResult&&);
+  ApiUsageContextQueryResult& operator=(ApiUsageContextQueryResult&&);
+
+  ~ApiUsageContextQueryResult();
+
+  bool success = false;
+
+  std::vector<ApiUsageContext> api_usage_contexts;
+};
+
+}  // namespace browsing_topics
+
+#endif  // COMPONENTS_BROWSING_TOPICS_COMMON_COMMON_TYPES_H_
diff --git a/components/history_clusters/core/BUILD.gn b/components/history_clusters/core/BUILD.gn
index 836ce2a5..fc926888 100644
--- a/components/history_clusters/core/BUILD.gn
+++ b/components/history_clusters/core/BUILD.gn
@@ -20,6 +20,8 @@
 static_library("core") {
   sources = [
     "clustering_backend.h",
+    "config.cc",
+    "config.h",
     "features.cc",
     "features.h",
     "history_clusters_db_tasks.cc",
@@ -87,7 +89,7 @@
 source_set("unit_tests") {
   testonly = true
   sources = [
-    "features_unittest.cc",
+    "config_unittest.cc",
     "history_clusters_db_tasks_unittest.cc",
     "history_clusters_service_unittest.cc",
     "history_clusters_util_unittest.cc",
diff --git a/components/history_clusters/core/config.cc b/components/history_clusters/core/config.cc
new file mode 100644
index 0000000..af42d9e
--- /dev/null
+++ b/components/history_clusters/core/config.cc
@@ -0,0 +1,66 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/history_clusters/core/config.h"
+
+#include "base/containers/contains.h"
+#include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/strings/string_piece_forward.h"
+#include "base/strings/string_split.h"
+#include "build/build_config.h"
+#include "components/history_clusters/core/features.h"
+#include "components/history_clusters/core/on_device_clustering_features.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace history_clusters {
+
+static Config* s_config = nullptr;
+
+Config::Config() = default;
+Config::Config(const Config& other) = default;
+Config::~Config() = default;
+
+// Override any parameters that may be provided by Finch.
+void OverrideWithFinch(const std::string& application_locale) {
+  if (s_config)
+    return;
+
+  s_config = new Config;
+
+  if (!base::FeatureList::IsEnabled(internal::kJourneys)) {
+    s_config->is_journeys_enabled = false;
+  } else {
+    // Default to "", because defaulting it to a specific locale makes it hard
+    // to allow all locales, since the FeatureParam code interprets an empty
+    // string as undefined, and instead returns the default value.
+    const base::FeatureParam<std::string> kLocaleOrLanguageAllowlist{
+        &internal::kJourneys, "JourneysLocaleOrLanguageAllowlist", ""};
+
+    // Allow comma and colon as delimiters to the language list.
+    auto allowlist =
+        base::SplitString(kLocaleOrLanguageAllowlist.Get(),
+                          ",:", base::WhitespaceHandling::TRIM_WHITESPACE,
+                          base::SplitResult::SPLIT_WANT_NONEMPTY);
+
+    // Allow any exact locale matches, and also allow any users where the
+    // primary language subtag, e.g. "en" from "en-US" to match any element of
+    // the list.
+    s_config->is_journeys_enabled =
+        allowlist.empty() || base::Contains(allowlist, application_locale) ||
+        base::Contains(allowlist, l10n_util::GetLanguage(application_locale));
+  }
+}
+
+void ResetConfigForTesting() {
+  s_config = nullptr;
+}
+
+const Config& GetConfig() {
+  DCHECK(s_config);
+
+  return *s_config;
+}
+
+}  // namespace history_clusters
\ No newline at end of file
diff --git a/components/history_clusters/core/config.h b/components/history_clusters/core/config.h
new file mode 100644
index 0000000..4ce487cb
--- /dev/null
+++ b/components/history_clusters/core/config.h
@@ -0,0 +1,35 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_HISTORY_CLUSTERS_CORE_CONFIG_H_
+#define COMPONENTS_HISTORY_CLUSTERS_CORE_CONFIG_H_
+
+#include <string>
+
+namespace history_clusters {
+
+// The default configuration. Always use |GetConfig()| to get the current
+// configuration.
+struct Config {
+  //
+  bool is_journeys_enabled = false;
+
+  Config();
+  Config(const Config& other);
+  ~Config();
+};
+
+// Gets the current configuration. OverrideWithFinch() must have been called
+// before GetConfig() is called.
+const Config& GetConfig();
+
+// Override any parameters that may be provided by Finch.
+void OverrideWithFinch(const std::string& application_locale);
+
+// Resets the static config object for testing.
+void ResetConfigForTesting();
+
+}  // namespace history_clusters
+
+#endif  // COMPONENTS_HISTORY_CLUSTERS_CORE_CONFIG_H_
\ No newline at end of file
diff --git a/components/history_clusters/core/config_unittest.cc b/components/history_clusters/core/config_unittest.cc
new file mode 100644
index 0000000..f056d93
--- /dev/null
+++ b/components/history_clusters/core/config_unittest.cc
@@ -0,0 +1,93 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/history_clusters/core/config.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "components/history_clusters/core/features.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace history_clusters {
+
+TEST(HistoryClustersConfigTest, PlainEnabled) {
+  const struct {
+    const std::string locale;
+    bool expected_is_journeys_enabled;
+  } kLocaleTestCases[] = {{"", false},
+                          {"en", false},
+                          {"fr", false},
+                          {"zh-TW", false},
+                          {" random junk ", false}};
+
+  for (const auto& test : kLocaleTestCases) {
+    ResetConfigForTesting();
+    OverrideWithFinch(test.locale);
+
+    EXPECT_EQ(test.expected_is_journeys_enabled,
+              GetConfig().is_journeys_enabled)
+        << test.locale;
+  }
+}
+
+TEST(HistoryClustersConfigTest, OmniboxAction) {
+  base::test::ScopedFeatureList features;
+  features.InitWithFeatures({internal::kJourneys, kOmniboxAction}, {});
+
+  const struct {
+    const std::string locale;
+    bool expected_is_journeys_enabled;
+  } kLocaleTestCases[] = {{"", true},
+                          {"en", true},
+                          {"fr", true},
+                          {"zh-TW", true},
+                          {" random junk ", true}};
+
+  for (const auto& test : kLocaleTestCases) {
+    ResetConfigForTesting();
+    OverrideWithFinch(test.locale);
+
+    EXPECT_EQ(test.expected_is_journeys_enabled,
+              GetConfig().is_journeys_enabled)
+        << test.locale;
+  }
+}
+
+TEST(HistoryClustersConfigTest, LocaleOrLanguageAllowlist) {
+  base::test::ScopedFeatureList features;
+  features.InitWithFeaturesAndParameters(
+      {{
+           internal::kJourneys,
+           // Test that we're tolerant of spaces, colons, whole locales, as well
+           // as primary language subcodes.
+           {{"JourneysLocaleOrLanguageAllowlist", "en, fr:de:zh-TW"}},
+       },
+       {kOmniboxAction, {}}},
+      {});
+
+  const struct {
+    const std::string locale;
+    bool expected_is_journeys_enabled;
+  } kLocaleTestCases[] = {{"", false},
+                          {"en", true},
+                          {"en-US", true},
+                          {"fr", true},
+                          {" random junk ", false},
+                          {"de", true},
+                          {"el", false},
+                          {"zh-TW", true},
+                          {"zh", false},
+                          {"zh-CN", false}};
+
+  for (const auto& test : kLocaleTestCases) {
+    ResetConfigForTesting();
+
+    OverrideWithFinch(test.locale);
+
+    EXPECT_EQ(test.expected_is_journeys_enabled,
+              GetConfig().is_journeys_enabled)
+        << test.locale;
+  }
+}
+
+}  // namespace history_clusters
diff --git a/components/history_clusters/core/features.cc b/components/history_clusters/core/features.cc
index c234463..8395829 100644
--- a/components/history_clusters/core/features.cc
+++ b/components/history_clusters/core/features.cc
@@ -25,30 +25,6 @@
 
 }  // namespace
 
-bool IsJourneysEnabled(const std::string& locale) {
-  if (!base::FeatureList::IsEnabled(internal::kJourneys))
-    return false;
-
-  // Allow comma and colon as delimiters to the language list.
-  auto allowlist =
-      base::SplitString(kLocaleOrLanguageAllowlist.Get(),
-                        ",:", base::WhitespaceHandling::TRIM_WHITESPACE,
-                        base::SplitResult::SPLIT_WANT_NONEMPTY);
-  if (allowlist.empty())
-    return true;
-
-  // Allow any exact locale matches, and also allow any users where the primary
-  // language subtag, e.g. "en" from "en-US" to match any element of the list.
-  return base::Contains(allowlist, locale) ||
-         base::Contains(allowlist, l10n_util::GetLanguage(locale));
-}
-
-// Default to "", because defaulting it to a specific locale makes it hard to
-// allow all locales, since the FeatureParam code interprets an empty string as
-// undefined, and instead returns the default value.
-const base::FeatureParam<std::string> kLocaleOrLanguageAllowlist{
-    &internal::kJourneys, "JourneysLocaleOrLanguageAllowlist", ""};
-
 const base::FeatureParam<int> kMaxVisitsToCluster{
     &internal::kJourneys, "JourneysMaxVisitsToCluster", 1000};
 
diff --git a/components/history_clusters/core/features.h b/components/history_clusters/core/features.h
index 827f4aea..f1fd5453 100644
--- a/components/history_clusters/core/features.h
+++ b/components/history_clusters/core/features.h
@@ -13,23 +13,6 @@
 
 // Params & helpers functions
 
-// Returns true if Journeys in the Chrome History WebUI is enabled.
-// Callers with access to `HistoryClustersService` should use
-// `HistoryClustersService::IsJourneysEnabled` which has precomputed this value
-// with the g_browser_process locale. Renderer process callers will have to
-// use this function directly.
-bool IsJourneysEnabled(const std::string& application_locale);
-
-// A comma (or colon) separated list of allowed locales and languages for which
-// Journeys is enabled. If this string is empty, any application locale or
-// language is allowed. If this string is non-empty, then the either the user's
-// system locale or primary language subtag must match one of the elements for
-// Journeys to be enabled.
-//
-// For example, "en,zh-TW" would mark English language users from any country,
-// and Chinese language users from Taiwan as on the allowlist.
-extern const base::FeatureParam<std::string> kLocaleOrLanguageAllowlist;
-
 // The max number of visits to use for each clustering iteration. This limits
 // the number of visits sent to the clustering backend per batch.
 extern const base::FeatureParam<int> kMaxVisitsToCluster;
diff --git a/components/history_clusters/core/features_unittest.cc b/components/history_clusters/core/features_unittest.cc
deleted file mode 100644
index 663e69aa..0000000
--- a/components/history_clusters/core/features_unittest.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/history_clusters/core/features.h"
-
-#include "base/test/scoped_feature_list.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace history_clusters {
-
-TEST(HistoryClustersFeaturesTest, PlainEnabled) {
-  EXPECT_FALSE(IsJourneysEnabled(""));
-  EXPECT_FALSE(IsJourneysEnabled("en"));
-  EXPECT_FALSE(IsJourneysEnabled("fr"));
-  EXPECT_FALSE(IsJourneysEnabled("zh-TW"));
-  EXPECT_FALSE(IsJourneysEnabled(" random junk "));
-
-  base::test::ScopedFeatureList features;
-  features.InitWithFeatures({internal::kJourneys, kOmniboxAction}, {});
-
-  EXPECT_TRUE(IsJourneysEnabled(""));
-  EXPECT_TRUE(IsJourneysEnabled("en"));
-  EXPECT_TRUE(IsJourneysEnabled("fr"));
-  EXPECT_TRUE(IsJourneysEnabled("zh-TW"));
-  EXPECT_TRUE(IsJourneysEnabled(" random junk "));
-}
-
-TEST(HistoryClustersFeaturesTest, LocaleOrLanguageAllowlist) {
-  base::test::ScopedFeatureList features;
-  features.InitWithFeaturesAndParameters(
-      {{
-           internal::kJourneys,
-           // Test that we're tolerant of spaces, colons, whole locales, as well
-           // as primary language subcodes.
-           {{"JourneysLocaleOrLanguageAllowlist", "en, fr:de:zh-TW"}},
-       },
-       {kOmniboxAction, {}}},
-      {});
-
-  EXPECT_FALSE(IsJourneysEnabled(""));
-  EXPECT_TRUE(IsJourneysEnabled("en"));
-  EXPECT_TRUE(IsJourneysEnabled("en-US"));
-  EXPECT_TRUE(IsJourneysEnabled("fr"));
-  EXPECT_FALSE(IsJourneysEnabled(" random junk "));
-  EXPECT_TRUE(IsJourneysEnabled("de"));
-  EXPECT_FALSE(IsJourneysEnabled("el"));
-  EXPECT_TRUE(IsJourneysEnabled("zh-TW"));
-  EXPECT_FALSE(IsJourneysEnabled("zh"));
-  EXPECT_FALSE(IsJourneysEnabled("zh-CN"));
-}
-
-}  // namespace history_clusters
diff --git a/components/history_clusters/core/history_clusters_service.cc b/components/history_clusters/core/history_clusters_service.cc
index 2ac8254..5c07328 100644
--- a/components/history_clusters/core/history_clusters_service.cc
+++ b/components/history_clusters/core/history_clusters_service.cc
@@ -30,6 +30,7 @@
 #include "components/history/core/browser/history_database.h"
 #include "components/history/core/browser/history_db_task.h"
 #include "components/history/core/browser/history_types.h"
+#include "components/history_clusters/core/config.h"
 #include "components/history_clusters/core/features.h"
 #include "components/history_clusters/core/history_clusters_buildflags.h"
 #include "components/history_clusters/core/history_clusters_db_tasks.h"
@@ -190,10 +191,9 @@
     optimization_guide::EntityMetadataProvider* entity_metadata_provider,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     site_engagement::SiteEngagementScoreProvider* engagement_score_provider)
-    : is_journeys_enabled_(
-          ::history_clusters::IsJourneysEnabled(application_locale)),
-      history_service_(history_service),
-      visit_deletion_observer_(this) {
+    : history_service_(history_service), visit_deletion_observer_(this) {
+  InitializeConfig(application_locale);
+
   DCHECK(history_service_);
 
   visit_deletion_observer_.AttachToHistoryService(history_service);
@@ -207,12 +207,22 @@
 
 HistoryClustersService::~HistoryClustersService() = default;
 
+// static
+void HistoryClustersService::InitializeConfig(
+    const std::string& application_locale) {
+  OverrideWithFinch(application_locale);
+}
+
 base::WeakPtr<HistoryClustersService> HistoryClustersService::GetWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
 }
 
 void HistoryClustersService::Shutdown() {}
 
+bool HistoryClustersService::IsJourneysEnabled() const {
+  return GetConfig().is_journeys_enabled;
+}
+
 void HistoryClustersService::AddObserver(Observer* obs) {
   observers_.AddObserver(obs);
 }
diff --git a/components/history_clusters/core/history_clusters_service.h b/components/history_clusters/core/history_clusters_service.h
index f502362..6c079184 100644
--- a/components/history_clusters/core/history_clusters_service.h
+++ b/components/history_clusters/core/history_clusters_service.h
@@ -104,7 +104,7 @@
   // Returns true if the Journeys feature is enabled for the current application
   // locale. This is a cached wrapper of `IsJourneysEnabled()` within features.h
   // that's already evaluated against the g_browser_process application locale.
-  bool IsJourneysEnabled() const { return is_journeys_enabled_; }
+  bool IsJourneysEnabled() const;
 
   // Used to add and remove observers.
   void AddObserver(Observer* obs);
@@ -174,6 +174,10 @@
   // Clears `all_keywords_cache_` and cancels any pending tasks to populate it.
   void ClearKeywordCache();
 
+  // Initializes the history cluster config object if it has not been already
+  // initialized.
+  static void InitializeConfig(const std::string& application_locale);
+
  private:
   friend class HistoryClustersServiceTestApi;
 
@@ -202,9 +206,6 @@
                         QueryClustersCallback callback,
                         std::vector<history::Cluster> clusters) const;
 
-  // True if the Journeys feature is enabled for the application locale.
-  const bool is_journeys_enabled_;
-
   // Non-owning pointer, but never nullptr.
   history::HistoryService* const history_service_;
 
diff --git a/components/messages/android/messages_feature.cc b/components/messages/android/messages_feature.cc
index 081cee51..ede8673 100644
--- a/components/messages/android/messages_feature.cc
+++ b/components/messages/android/messages_feature.cc
@@ -35,7 +35,7 @@
         "save_password_message_dismiss_duration_ms", 0};
 
 const base::Feature kMessagesForAndroidPermissionUpdate{
-    "MessagesForAndroidPermissionUpdate", base::FEATURE_DISABLED_BY_DEFAULT};
+    "MessagesForAndroidPermissionUpdate", base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kMessagesForAndroidPopupBlocked{
     "MessagesForAndroidPopupBlocked", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/components/safe_browsing/content/browser/client_side_detection_host.cc b/components/safe_browsing/content/browser/client_side_detection_host.cc
index 5cfb4e65..49a84af 100644
--- a/components/safe_browsing/content/browser/client_side_detection_host.cc
+++ b/components/safe_browsing/content/browser/client_side_detection_host.cc
@@ -422,6 +422,9 @@
   }
 
   current_url_ = navigation_handle->GetURL();
+  current_outermost_main_frame_id_ = navigation_handle->GetRenderFrameHost()
+                                         ->GetOutermostMainFrame()
+                                         ->GetGlobalId();
 
   // Check whether we can cassify the current URL for phishing.
   classification_request_ = new ShouldClassifyUrlRequest(
@@ -554,7 +557,8 @@
 
     if (IsEnhancedProtectionEnabled(*delegate_->GetPrefs()) &&
         base::FeatureList::IsEnabled(kClientSideDetectionReferrerChain)) {
-      delegate_->AddReferrerChain(verdict.get(), current_url_);
+      delegate_->AddReferrerChain(verdict.get(), current_url_,
+                                  current_outermost_main_frame_id_);
     }
 
     base::UmaHistogramBoolean("SBClientPhishing.LocalModelDetectsPhishing",
diff --git a/components/safe_browsing/content/browser/client_side_detection_host.h b/components/safe_browsing/content/browser/client_side_detection_host.h
index 6fbe46f..f3190bcf 100644
--- a/components/safe_browsing/content/browser/client_side_detection_host.h
+++ b/components/safe_browsing/content/browser/client_side_detection_host.h
@@ -20,6 +20,7 @@
 #include "components/safe_browsing/core/browser/db/database_manager.h"
 #include "components/safe_browsing/core/browser/safe_browsing_token_fetcher.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
@@ -29,6 +30,10 @@
 class TickClock;
 }
 
+namespace content {
+struct GlobalRenderFrameHostId;
+}
+
 namespace safe_browsing {
 class ClientPhishingRequest;
 class ClientSideDetectionService;
@@ -58,7 +63,9 @@
     virtual scoped_refptr<BaseUIManager> GetSafeBrowsingUIManager() = 0;
     virtual ClientSideDetectionService* GetClientSideDetectionService() = 0;
     virtual void AddReferrerChain(ClientPhishingRequest* verdict,
-                                  GURL current_url) = 0;
+                                  GURL current_url,
+                                  const content::GlobalRenderFrameHostId&
+                                      current_outermost_main_frame_id) = 0;
   };
 
   // The caller keeps ownership of the tab object and is responsible for
@@ -196,6 +203,8 @@
   scoped_refptr<ShouldClassifyUrlRequest> classification_request_;
   // The current URL
   GURL current_url_;
+  // The current outermost main frame's id.
+  content::GlobalRenderFrameHostId current_outermost_main_frame_id_;
   // A map from the live RenderFrameHosts to their PhishingDetector. These
   // correspond to the `phishing_detector_receiver_` in the
   // PhishingClassifierDelegate.
diff --git a/components/safe_browsing/content/browser/safe_browsing_navigation_observer.cc b/components/safe_browsing/content/browser/safe_browsing_navigation_observer.cc
index f01b6ca..422a2e7 100644
--- a/components/safe_browsing/content/browser/safe_browsing_navigation_observer.cc
+++ b/components/safe_browsing/content/browser/safe_browsing_navigation_observer.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/metrics/histogram_functions.h"
 #include "base/time/time.h"
 #include "components/page_info/page_info_ui.h"
 #include "components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.h"
@@ -30,47 +31,19 @@
 
 // SafeBrowsingNavigationObserver::NavigationEvent-----------------------------
 NavigationEvent::NavigationEvent()
-    : source_url(),
-      source_main_frame_url(),
-      original_request_url(),
-      source_tab_id(SessionID::InvalidValue()),
+    : source_tab_id(SessionID::InvalidValue()),
       target_tab_id(SessionID::InvalidValue()),
       last_updated(base::Time::Now()),
       navigation_initiation(ReferrerChainEntry::UNDEFINED),
       has_committed(false),
       maybe_launched_by_external_application() {}
 
-NavigationEvent::NavigationEvent(NavigationEvent&& nav_event)
-    : source_url(std::move(nav_event.source_url)),
-      source_main_frame_url(std::move(nav_event.source_main_frame_url)),
-      original_request_url(std::move(nav_event.original_request_url)),
-      server_redirect_urls(std::move(nav_event.server_redirect_urls)),
-      source_tab_id(std::move(nav_event.source_tab_id)),
-      target_tab_id(std::move(nav_event.target_tab_id)),
-      last_updated(nav_event.last_updated),
-      navigation_initiation(nav_event.navigation_initiation),
-      has_committed(nav_event.has_committed),
-      maybe_launched_by_external_application(
-          nav_event.maybe_launched_by_external_application) {}
-
+NavigationEvent::NavigationEvent(NavigationEvent&& nav_event) = default;
 NavigationEvent::NavigationEvent(const NavigationEvent& nav_event) = default;
+NavigationEvent& NavigationEvent::operator=(NavigationEvent&& nav_event) =
+    default;
 
-NavigationEvent& NavigationEvent::operator=(NavigationEvent&& nav_event) {
-  source_url = std::move(nav_event.source_url);
-  source_main_frame_url = std::move(nav_event.source_main_frame_url);
-  original_request_url = std::move(nav_event.original_request_url);
-  source_tab_id = nav_event.source_tab_id;
-  target_tab_id = nav_event.target_tab_id;
-  last_updated = nav_event.last_updated;
-  navigation_initiation = nav_event.navigation_initiation;
-  has_committed = nav_event.has_committed;
-  maybe_launched_by_external_application =
-      nav_event.maybe_launched_by_external_application;
-  server_redirect_urls = std::move(nav_event.server_redirect_urls);
-  return *this;
-}
-
-NavigationEvent::~NavigationEvent() {}
+NavigationEvent::~NavigationEvent() = default;
 
 // SafeBrowsingNavigationObserver --------------------------------------------
 
@@ -109,7 +82,7 @@
   content_settings_observation_.Observe(host_content_settings_map);
 }
 
-SafeBrowsingNavigationObserver::~SafeBrowsingNavigationObserver() {}
+SafeBrowsingNavigationObserver::~SafeBrowsingNavigationObserver() = default;
 
 void SafeBrowsingNavigationObserver::OnUserInteraction() {
   GetObserverManager()->RecordUserGestureForWebContents(web_contents());
@@ -151,6 +124,7 @@
   nav_event->source_tab_id =
       sessions::SessionTabHelper::IdForTab(navigation_handle->GetWebContents());
   SetNavigationSourceMainFrameUrl(navigation_handle, nav_event.get());
+  SetNavigationOutermostMainFrameIds(navigation_handle, nav_event.get());
 
   std::unique_ptr<NavigationEvent> pending_nav_event =
       std::make_unique<NavigationEvent>(*nav_event);
@@ -203,6 +177,25 @@
   nav_event->has_committed = navigation_handle->HasCommitted();
   nav_event->target_tab_id =
       sessions::SessionTabHelper::IdForTab(navigation_handle->GetWebContents());
+
+  // TODO(crbug.com/1254770) Non-MPArch portals cause issues for the outermost
+  // main frame logic. Since they do not create navigation events for
+  // activation, there is a an unaccounted-for shift in outermost main frame at
+  // that point. For now, we will not set outermost main frame ids for portals
+  // so they will continue to match. In future, once portals have been converted
+  // to MPArch, this will not be necessary.
+  if (!web_contents()->IsPortal() && nav_event->is_outermost_main_frame &&
+      nav_event->has_committed) {
+    auto* rfh = navigation_handle->GetRenderFrameHost();
+    // We set the outermost main frame id here rather than in DidStartNavigation
+    // because in that function, we don't yet have a RenderFrameHost, so if
+    // we're the outermost main frame, we won't yet have an id.
+    nav_event->outermost_main_frame_id =
+        rfh->GetOutermostMainFrame()->GetGlobalId();
+  }
+  DCHECK(web_contents()->IsPortal() || !nav_event->has_committed ||
+         nav_event->outermost_main_frame_id);
+
   nav_event->last_updated = base::Time::Now();
 
   GetObserverManager()->RecordNavigationEvent(
@@ -264,6 +257,11 @@
                   navigation_handle->GetInitiatorProcessID(),
                   navigation_handle->GetInitiatorFrameToken().value())
             : nullptr;
+
+    base::UmaHistogramBoolean(
+        "SafeBrowsing.NavigationObserver.MissingInitiatorRenderFrameHostPortal",
+        !initiator_frame_host);
+
     // TODO(https://crbug.com/1074422): Handle the case where the initiator
     // RenderFrameHost is gone.
     if (initiator_frame_host) {
@@ -307,19 +305,38 @@
 void SafeBrowsingNavigationObserver::SetNavigationSourceUrl(
     content::NavigationHandle* navigation_handle,
     NavigationEvent* nav_event) {
-  // If there was a URL previously committed in the current RenderFrameHost,
-  // set it as the source url of this navigation. Otherwise, this is the
-  // first url going to commit in this frame.
-  content::RenderFrameHost* current_frame_host =
-      content::RenderFrameHost::FromID(
-          navigation_handle->GetPreviousRenderFrameHostId());
   // For browser initiated navigation (e.g. from address bar or bookmark), we
   // don't fill the source_url to prevent attributing navigation to the last
   // committed navigation.
-  if (navigation_handle->IsRendererInitiated() && current_frame_host &&
-      current_frame_host->GetLastCommittedURL().is_valid()) {
+  if (!navigation_handle->IsRendererInitiated())
+    return;
+
+  if (navigation_handle->IsInPrerenderedMainFrame()) {
+    // A prerendered page can only be initiated by the primary page, so we will
+    // grab the last committed URL from the primary main frame directly. Note
+    // that this cannot be obtained from the previous RFH since this is empty
+    // for prerenders. While this differs from the semantics for source_url
+    // (which is indeed related to the last committed URL in the frame into
+    // which we are navigating), it matches what source_url would have been had
+    // we been navigating normally since we will activate in the primary main
+    // frame.
     nav_event->source_url = SafeBrowsingNavigationObserverManager::ClearURLRef(
-        current_frame_host->GetLastCommittedURL());
+        navigation_handle->GetWebContents()
+            ->GetMainFrame()
+            ->GetLastCommittedURL());
+  } else {
+    // If there was a URL previously committed in the current RenderFrameHost,
+    // set it as the source url of this navigation. Otherwise, this is the
+    // first url going to commit in this frame.
+    content::RenderFrameHost* current_frame_host =
+        content::RenderFrameHost::FromID(
+            navigation_handle->GetPreviousRenderFrameHostId());
+    if (current_frame_host &&
+        current_frame_host->GetLastCommittedURL().is_valid()) {
+      nav_event->source_url =
+          SafeBrowsingNavigationObserverManager::ClearURLRef(
+              current_frame_host->GetLastCommittedURL());
+    }
   }
 }
 
@@ -337,6 +354,40 @@
   }
 }
 
+void SafeBrowsingNavigationObserver::SetNavigationOutermostMainFrameIds(
+    content::NavigationHandle* navigation_handle,
+    NavigationEvent* nav_event) {
+  // TODO(crbug.com/1254770) Non-MPArch portals cause issues for the outermost
+  // main frame logic. Since they do not create navigation events for
+  // activation, there is a an unaccounted-for shift in outermost main frame at
+  // that point. For now, we will not set outermost main frame ids for portals
+  // so they will continue to match. In future, once portals have been converted
+  // to MPArch, this will not be necessary.
+  if (web_contents()->IsPortal())
+    return;
+
+  auto* outer_rfh = navigation_handle->GetParentFrameOrOuterDocument();
+  nav_event->is_outermost_main_frame = !outer_rfh;
+
+  if (outer_rfh) {
+    nav_event->outermost_main_frame_id =
+        outer_rfh->GetOutermostMainFrame()->GetGlobalId();
+  }
+
+  if (navigation_handle->IsRendererInitiated()) {
+    auto* initiator_frame_host =
+        navigation_handle->GetInitiatorFrameToken().has_value()
+            ? content::RenderFrameHost::FromFrameToken(
+                  navigation_handle->GetInitiatorProcessID(),
+                  navigation_handle->GetInitiatorFrameToken().value())
+            : nullptr;
+    if (initiator_frame_host) {
+      nav_event->initiator_outermost_main_frame_id =
+          initiator_frame_host->GetOutermostMainFrame()->GetGlobalId();
+    }
+  }
+}
+
 SafeBrowsingNavigationObserverManager*
 SafeBrowsingNavigationObserver::GetObserverManager() {
   return observer_manager_;
diff --git a/components/safe_browsing/content/browser/safe_browsing_navigation_observer.h b/components/safe_browsing/content/browser/safe_browsing_navigation_observer.h
index 0a3a2662..24c3782 100644
--- a/components/safe_browsing/content/browser/safe_browsing_navigation_observer.h
+++ b/components/safe_browsing/content/browser/safe_browsing_navigation_observer.h
@@ -16,6 +16,7 @@
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/safe_browsing/core/common/proto/csd.pb.h"
 #include "components/sessions/core/session_id.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "url/gurl.h"
 
@@ -58,6 +59,21 @@
   // Which tab this request url is targeting to.
   SessionID target_tab_id;
 
+  // RFH ID of the outermost main frame of the frame which initiated this
+  // navigation. This can only differ from outermost_main_frame_id if
+  // |is_outermost_main_frame| is true, however differing values does not imply
+  // that we're in the outermost main frame (we could be navigating within the
+  // current RFH).
+  content::GlobalRenderFrameHostId initiator_outermost_main_frame_id;
+
+  // RFH ID of the outermost main frame of the frame where this navigation takes
+  // place. If this navigation is occurring in the outermost main frame, then
+  // this is not known until commit.
+  content::GlobalRenderFrameHostId outermost_main_frame_id;
+
+  // Whether this navigation is happening in the outermost main frame.
+  bool is_outermost_main_frame = false;
+
   // When this NavigationEvent was last updated.
   base::Time last_updated;
 
@@ -174,6 +190,9 @@
   void SetNavigationSourceMainFrameUrl(
       content::NavigationHandle* navigation_handle,
       NavigationEvent* nav_event);
+  void SetNavigationOutermostMainFrameIds(
+      content::NavigationHandle* navigation_handle,
+      NavigationEvent* nav_event);
 
   // Map keyed on NavigationHandle* to keep track of all the ongoing
   // navigation events. NavigationHandle pointers are owned by
diff --git a/components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.cc b/components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.cc
index f864e1ad..a44ffb8 100644
--- a/components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.cc
+++ b/components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.cc
@@ -23,6 +23,7 @@
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/core/common/utils.h"
 #include "components/sessions/content/session_tab_helper.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/navigation_details.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
@@ -123,19 +124,20 @@
   DCHECK_GT(size_limit_, 0U);
 }
 
-NavigationEventList::~NavigationEventList() {}
+NavigationEventList::~NavigationEventList() = default;
 
-size_t NavigationEventList::FindNavigationEvent(
+absl::optional<size_t> NavigationEventList::FindNavigationEvent(
     const base::Time& last_event_timestamp,
     const GURL& target_url,
     const GURL& target_main_frame_url,
     SessionID target_tab_id,
+    const content::GlobalRenderFrameHostId& outermost_main_frame_id,
     size_t start_index) {
   if (target_url.is_empty() && target_main_frame_url.is_empty())
-    return -1;
+    return absl::nullopt;
 
   if (navigation_events_.size() == 0)
-    return -1;
+    return absl::nullopt;
 
   // If target_url is empty, we should back trace navigation based on its
   // main frame URL instead.
@@ -150,10 +152,24 @@
       continue;
     }
 
-    // If tab id is not valid, we only compare url, otherwise we compare both.
-    if (nav_event->GetDestinationUrl() == search_url &&
-        (!target_tab_id.is_valid() ||
-         nav_event->target_tab_id == target_tab_id)) {
+    const bool valid_tab_id =
+        !target_tab_id.is_valid() || nav_event->target_tab_id == target_tab_id;
+
+    const bool potentially_valid_outermost_main_frame_id =
+        !outermost_main_frame_id || !nav_event->outermost_main_frame_id ||
+        nav_event->is_outermost_main_frame ||
+        nav_event->outermost_main_frame_id == outermost_main_frame_id;
+
+    const bool valid_outermost_main_frame_id =
+        !outermost_main_frame_id ||
+        nav_event->outermost_main_frame_id == outermost_main_frame_id;
+
+    // If tab id is valid, we require it match the nav event (similar for frame
+    // tree node id). In all cases, we require that the URLs match.
+    if (nav_event->GetDestinationUrl() == search_url && valid_tab_id &&
+        potentially_valid_outermost_main_frame_id) {
+      size_t result_index = current_index;
+
       // If both source_url and source_main_frame_url are empty, we should check
       // if a retargeting navigation caused this navigation. In this case, we
       // skip this navigation event and looks for the retargeting navigation
@@ -162,28 +178,44 @@
           nav_event->source_main_frame_url.is_empty()) {
         size_t retargeting_nav_event_index = FindRetargetingNavigationEvent(
             nav_event->last_updated, nav_event->target_tab_id, start_index);
-        if (static_cast<int>(retargeting_nav_event_index) == -1)
-          return current_index;
-        // If there is a server redirection immediately after retargeting, we
-        // need to adjust our search url to the original request.
-        auto* retargeting_nav_event =
-            GetNavigationEvent(retargeting_nav_event_index);
-        if (!nav_event->server_redirect_urls.empty()) {
-          // Adjust retargeting navigation event's attributes.
-          retargeting_nav_event->server_redirect_urls.push_back(
-              std::move(search_url));
-        } else {
-          // The retargeting_nav_event original request url is unreliable, since
-          // that navigation can be canceled.
-          retargeting_nav_event->original_request_url = std::move(search_url);
+        if (static_cast<int>(retargeting_nav_event_index) != -1) {
+          // If there is a server redirection immediately after retargeting, we
+          // need to adjust our search url to the original request.
+          auto* retargeting_nav_event =
+              GetNavigationEvent(retargeting_nav_event_index);
+          if (!nav_event->server_redirect_urls.empty()) {
+            // Adjust retargeting navigation event's attributes.
+            retargeting_nav_event->server_redirect_urls.push_back(
+                std::move(search_url));
+          } else {
+            // The retargeting_nav_event original request url is unreliable,
+            // since that navigation can be canceled.
+            retargeting_nav_event->original_request_url = std::move(search_url);
+          }
+          result_index = retargeting_nav_event_index;
         }
-        return retargeting_nav_event_index;
-      } else {
-        return current_index;
       }
+
+      // We didn't have a strict main frame id match, so we will look for a
+      // better match first.
+      if (!valid_outermost_main_frame_id && current_index > 0) {
+        auto alternate_index = FindNavigationEvent(
+            last_event_timestamp, target_url, target_main_frame_url,
+            target_tab_id, outermost_main_frame_id, current_index - 1);
+        if (alternate_index) {
+          auto* alternate_event = GetNavigationEvent(*alternate_index);
+          // Found a strict match.
+          if (alternate_event->outermost_main_frame_id ==
+              outermost_main_frame_id) {
+            result_index = *alternate_index;
+          }
+        }
+      }
+
+      return result_index;
     }
   }
-  return -1;
+  return absl::nullopt;
 }
 
 NavigationEvent* NavigationEventList::FindPendingNavigationEvent(
@@ -295,7 +327,7 @@
 // static
 GURL SafeBrowsingNavigationObserverManager::ClearURLRef(const GURL& url) {
   if (url.has_ref()) {
-    url::Replacements<char> replacements;
+    GURL::Replacements replacements;
     replacements.ClearRef();
     return url.ReplaceComponents(replacements);
   }
@@ -444,6 +476,7 @@
 SafeBrowsingNavigationObserverManager::IdentifyReferrerChainByEventURL(
     const GURL& event_url,
     SessionID event_tab_id,
+    const content::GlobalRenderFrameHostId& outermost_main_frame_id,
     int user_gesture_count_limit,
     ReferrerChain* out_referrer_chain) {
   SCOPED_UMA_HISTOGRAM_TIMER(
@@ -451,20 +484,21 @@
   if (!event_url.is_valid())
     return INVALID_URL;
 
-  size_t nav_event_index = navigation_event_list_.FindNavigationEvent(
+  auto nav_event_index = navigation_event_list_.FindNavigationEvent(
       base::Time::Now(), ClearURLRef(event_url), GURL(), event_tab_id,
+      outermost_main_frame_id,
       navigation_event_list_.NavigationEventsSize() - 1);
-  if (static_cast<int>(nav_event_index) == -1) {
+  if (!nav_event_index) {
     // We cannot find a single navigation event related to this event.
     return NAVIGATION_EVENT_NOT_FOUND;
   }
 
-  auto* nav_event = navigation_event_list_.GetNavigationEvent(nav_event_index);
+  auto* nav_event = navigation_event_list_.GetNavigationEvent(*nav_event_index);
   AttributionResult result = SUCCESS;
   AddToReferrerChain(out_referrer_chain, nav_event, GURL(),
                      ReferrerChainEntry::EVENT_URL);
   int user_gesture_count = 0;
-  GetRemainingReferrerChain(nav_event_index, user_gesture_count,
+  GetRemainingReferrerChain(*nav_event_index, user_gesture_count,
                             user_gesture_count_limit, out_referrer_chain,
                             &result);
   bool omit_non_user_gestures_is_enabled = base::FeatureList::IsEnabled(
@@ -525,28 +559,33 @@
 }
 
 SafeBrowsingNavigationObserverManager::AttributionResult
-SafeBrowsingNavigationObserverManager::IdentifyReferrerChainByWebContents(
-    content::WebContents* web_contents,
+SafeBrowsingNavigationObserverManager::IdentifyReferrerChainByRenderFrameHost(
+    content::RenderFrameHost* render_frame_host,
     int user_gesture_count_limit,
     ReferrerChain* out_referrer_chain) {
   SCOPED_UMA_HISTOGRAM_TIMER(
-      "SafeBrowsing.NavigationObserver.IdentifyReferrerChainByWebContentsTime");
-  if (!web_contents)
+      "SafeBrowsing.NavigationObserver."
+      "IdentifyReferrerChainByRenderFrameHostTime");
+  if (!render_frame_host)
     return INVALID_URL;
-  GURL last_committed_url = web_contents->GetLastCommittedURL();
+  GURL last_committed_url = render_frame_host->GetLastCommittedURL();
   if (!last_committed_url.is_valid())
     return INVALID_URL;
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(render_frame_host);
   bool has_user_gesture = HasUserGesture(web_contents);
   SessionID tab_id = sessions::SessionTabHelper::IdForTab(web_contents);
   return IdentifyReferrerChainByHostingPage(
-      ClearURLRef(last_committed_url), GURL(), tab_id, has_user_gesture,
-      user_gesture_count_limit, out_referrer_chain);
+      ClearURLRef(last_committed_url), GURL(),
+      render_frame_host->GetOutermostMainFrame()->GetGlobalId(), tab_id,
+      has_user_gesture, user_gesture_count_limit, out_referrer_chain);
 }
 
 SafeBrowsingNavigationObserverManager::AttributionResult
 SafeBrowsingNavigationObserverManager::IdentifyReferrerChainByHostingPage(
     const GURL& initiating_frame_url,
     const GURL& initiating_main_frame_url,
+    const content::GlobalRenderFrameHostId& initiating_outermost_main_frame_id,
     SessionID tab_id,
     bool has_user_gesture,
     int user_gesture_count_limit,
@@ -554,16 +593,22 @@
   if (!initiating_frame_url.is_valid())
     return INVALID_URL;
 
-  int nav_event_index = navigation_event_list_.FindNavigationEvent(
+  auto* rfh =
+      content::RenderFrameHost::FromID(initiating_outermost_main_frame_id);
+  DCHECK(!initiating_outermost_main_frame_id ||
+         (rfh && rfh->GetOutermostMainFrame() == rfh));
+
+  auto nav_event_index = navigation_event_list_.FindNavigationEvent(
       base::Time::Now(), ClearURLRef(initiating_frame_url),
       ClearURLRef(initiating_main_frame_url), tab_id,
+      initiating_outermost_main_frame_id,
       navigation_event_list_.NavigationEventsSize() - 1);
-  if (static_cast<int>(nav_event_index) == -1) {
+  if (!nav_event_index) {
     // We cannot find a single navigation event related to this hosting page.
     return NAVIGATION_EVENT_NOT_FOUND;
   }
 
-  auto* nav_event = navigation_event_list_.GetNavigationEvent(nav_event_index);
+  auto* nav_event = navigation_event_list_.GetNavigationEvent(*nav_event_index);
 
   AttributionResult result = SUCCESS;
 
@@ -580,7 +625,7 @@
                        ReferrerChainEntry::CLIENT_REDIRECT);
   }
 
-  GetRemainingReferrerChain(nav_event_index, user_gesture_count,
+  GetRemainingReferrerChain(*nav_event_index, user_gesture_count,
                             user_gesture_count_limit, out_referrer_chain,
                             &result);
 
@@ -629,6 +674,23 @@
             source_render_frame_host->GetOutermostMainFrame()
                 ->GetLastCommittedURL());
   }
+
+  // TODO(crbug.com/1254770) Non-MPArch portals cause issues for the outermost
+  // main frame logic. Since they do not create navigation events for
+  // activation, there is a an unaccounted-for shift in outermost main frame at
+  // that point. For now, we will not set outermost main frame ids for portals
+  // so they will continue to match. In future, once portals have been converted
+  // to MPArch, this will not be necessary.
+  if (!target_web_contents->IsPortal()) {
+    if (source_render_frame_host) {
+      nav_event->initiator_outermost_main_frame_id =
+          source_render_frame_host->GetOutermostMainFrame()->GetGlobalId();
+    }
+    nav_event->outermost_main_frame_id = target_web_contents->GetMainFrame()
+                                             ->GetOutermostMainFrame()
+                                             ->GetGlobalId();
+  }
+
   nav_event->source_tab_id =
       sessions::SessionTabHelper::IdForTab(source_web_contents);
   nav_event->original_request_url = cleaned_target_url;
@@ -852,14 +914,17 @@
   while (current_user_gesture_count < user_gesture_count_limit) {
     // Back trace to the next nav_event that was initiated by the user.
     while (!last_nav_event_traced->IsUserInitiated()) {
-      last_nav_event_traced_index = navigation_event_list_.FindNavigationEvent(
+      auto nav_event_index = navigation_event_list_.FindNavigationEvent(
           last_nav_event_traced->last_updated,
           last_nav_event_traced->source_url,
           last_nav_event_traced->source_main_frame_url,
           last_nav_event_traced->source_tab_id,
+          last_nav_event_traced->initiator_outermost_main_frame_id,
           last_nav_event_traced_index - 1);
-      if (static_cast<int>(last_nav_event_traced_index) == -1)
+      if (!nav_event_index)
         return;
+
+      last_nav_event_traced_index = *nav_event_index;
       last_nav_event_traced = navigation_event_list_.GetNavigationEvent(
           last_nav_event_traced_index);
       AddToReferrerChain(out_referrer_chain, last_nav_event_traced,
@@ -876,13 +941,16 @@
 
     current_user_gesture_count++;
 
-    last_nav_event_traced_index = navigation_event_list_.FindNavigationEvent(
+    auto nav_event_index = navigation_event_list_.FindNavigationEvent(
         last_nav_event_traced->last_updated, last_nav_event_traced->source_url,
         last_nav_event_traced->source_main_frame_url,
-        last_nav_event_traced->source_tab_id, last_nav_event_traced_index - 1);
-    if (static_cast<int>(last_nav_event_traced_index) == -1)
+        last_nav_event_traced->source_tab_id,
+        last_nav_event_traced->initiator_outermost_main_frame_id,
+        last_nav_event_traced_index - 1);
+    if (!nav_event_index)
       return;
 
+    last_nav_event_traced_index = *nav_event_index;
     last_nav_event_traced =
         navigation_event_list_.GetNavigationEvent(last_nav_event_traced_index);
     AddToReferrerChain(out_referrer_chain, last_nav_event_traced,
diff --git a/components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.h b/components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.h
index 7c66e59..05679df 100644
--- a/components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.h
+++ b/components/safe_browsing/content/browser/safe_browsing_navigation_observer_manager.h
@@ -17,6 +17,7 @@
 #include "components/safe_browsing/core/common/proto/csd.pb.h"
 #include "components/sessions/core/session_id.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/protobuf/src/google/protobuf/repeated_field.h"
 #include "url/gurl.h"
 
@@ -24,6 +25,7 @@
 
 namespace content {
 class NavigationHandle;
+struct GlobalRenderFrameHostId;
 }
 
 namespace safe_browsing {
@@ -70,7 +72,7 @@
 
   // Finds the index of the most recent navigation event that navigated to
   // |target_url| and  its associated |target_main_frame_url| in the tab with
-  // ID |target_tab_id|. Returns -1 if event is not found.
+  // ID |target_tab_id|. Returns an empty optional if event is not found.
   // If navigation happened in the main frame, |target_url| and
   // |target_main_frame_url| are the same.
   // If |target_url| is empty, we use its main frame url (a.k.a.
@@ -90,12 +92,22 @@
   // In this case, FindNavigationEvent() will think url2 in Window B is the
   // referrer of about::blank in Window C since this navigation is more recent.
   // However, it does not prevent us to attribute url1 in Window A as the cause
-  // of all these navigations. Returns -1 if an event is not found.
-  size_t FindNavigationEvent(const base::Time& last_event_timestamp,
-                             const GURL& target_url,
-                             const GURL& target_main_frame_url,
-                             SessionID target_tab_id,
-                             size_t start_index);
+  // of all these navigations. Returns an empty optional if an event is not
+  // found.
+  //
+  // If an |outermost_main_frame_id| is supplied, the function attempts to find
+  // a navigation event per the logic described above with the additional
+  // constraint that the |outermost_main_frame_id| match. If there is no such
+  // event, it will return the first main frame event that matches the other
+  // criteria. And if there is still no matching event, the function will return
+  // an empty optional.
+  absl::optional<size_t> FindNavigationEvent(
+      const base::Time& last_event_timestamp,
+      const GURL& target_url,
+      const GURL& target_main_frame_url,
+      SessionID target_tab_id,
+      const content::GlobalRenderFrameHostId& outermost_main_frame_id,
+      size_t start_index);
 
   // Finds the the navigation event in the |pending_navigation_events_| map that
   // has the same destination URL as the |target_url|. If there are multiple
@@ -222,6 +234,8 @@
   AttributionResult IdentifyReferrerChainByEventURL(
       const GURL& event_url,
       SessionID event_tab_id,  // Invalid if tab id is unknown or not available.
+      const content::GlobalRenderFrameHostId&
+          event_outermost_main_frame_id,  // Can also be Invalid.
       int user_gesture_count_limit,
       ReferrerChain* out_referrer_chain) override;
 
@@ -239,14 +253,14 @@
       int user_gesture_count_limit,
       ReferrerChain* out_referrer_chain) override;
 
-  // Based on the |web_contents| associated with an event, traces back the
+  // Based on the |render_frame_host| associated with an event, traces back the
   // observed NavigationEvents in |navigation_event_list_| to identify the
   // sequence of navigations leading to the event hosting page, with the
   // coverage limited to |user_gesture_count_limit| number of user gestures.
   // Then converts these identified NavigationEvents into ReferrerChainEntrys
   // and appends them to |out_referrer_chain|.
-  AttributionResult IdentifyReferrerChainByWebContents(
-      content::WebContents* web_contents,
+  AttributionResult IdentifyReferrerChainByRenderFrameHost(
+      content::RenderFrameHost* render_frame_host,
       int user_gesture_count_limit,
       ReferrerChain* out_referrer_chain) override;
 
@@ -261,6 +275,8 @@
   AttributionResult IdentifyReferrerChainByHostingPage(
       const GURL& initiating_frame_url,
       const GURL& initiating_main_frame_url,
+      const content::GlobalRenderFrameHostId&
+          initiating_outermost_main_frame_id,
       SessionID tab_id,
       bool has_user_gesture,
       int user_gesture_count_limit,
@@ -287,6 +303,11 @@
   void AppendRecentNavigations(size_t recent_navigation_count,
                                ReferrerChain* out_referrer_chain);
 
+ protected:
+  NavigationEventList* navigation_event_list() {
+    return &navigation_event_list_;
+  }
+
  private:
   friend class TestNavigationObserverManager;
   friend class SBNavigationObserverBrowserTest;
@@ -303,10 +324,6 @@
   typedef std::unordered_map<std::string, std::vector<ResolvedIPAddress>>
       HostToIpMap;
 
-  NavigationEventList* navigation_event_list() {
-    return &navigation_event_list_;
-  }
-
   HostToIpMap* host_to_ip_map() { return &host_to_ip_map_; }
 
   // Remove stale entries from navigation_event_list_ if they are older than
diff --git a/components/safe_browsing/content/browser/safe_browsing_navigation_observer_unittest.cc b/components/safe_browsing/content/browser/safe_browsing_navigation_observer_unittest.cc
index f6b0ebaf..ae9d1500 100644
--- a/components/safe_browsing/content/browser/safe_browsing_navigation_observer_unittest.cc
+++ b/components/safe_browsing/content/browser/safe_browsing_navigation_observer_unittest.cc
@@ -15,6 +15,7 @@
 #include "components/safe_browsing/core/common/features.h"
 #include "components/sessions/content/session_tab_helper.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/mock_navigation_handle.h"
 #include "content/public/test/navigation_simulator.h"
@@ -192,10 +193,10 @@
 TEST_F(SBNavigationObserverTest, TestNavigationEventList) {
   NavigationEventList events(3);
 
-  EXPECT_EQ(-1, static_cast<int>(events.FindNavigationEvent(
-                    base::Time::Now(), GURL("http://invalid.com"), GURL(),
-                    SessionID::InvalidValue(),
-                    navigation_event_list()->navigation_events().size() - 1)));
+  EXPECT_FALSE(events.FindNavigationEvent(
+      base::Time::Now(), GURL("http://invalid.com"), GURL(),
+      SessionID::InvalidValue(), content::GlobalRenderFrameHostId(),
+      navigation_event_list()->navigation_events().size() - 1));
   EXPECT_EQ(0U, events.CleanUpNavigationEvents());
   EXPECT_EQ(0U, events.NavigationEventsSize());
 
@@ -209,11 +210,13 @@
       CreateNavigationEventUniquePtr(GURL("http://foo1.com"), now));
   EXPECT_EQ(2U, events.NavigationEventsSize());
   // FindNavigationEvent should return the latest matching event.
-  int index = events.FindNavigationEvent(
+  auto index = events.FindNavigationEvent(
       base::Time::Now(), GURL("http://foo1.com"), GURL(),
-      SessionID::InvalidValue(), events.navigation_events().size() - 1);
+      SessionID::InvalidValue(), content::GlobalRenderFrameHostId(),
+      events.navigation_events().size() - 1);
+  EXPECT_TRUE(index);
 
-  EXPECT_EQ(now, events.GetNavigationEvent(index)->last_updated);
+  EXPECT_EQ(now, events.GetNavigationEvent(*index)->last_updated);
   // One event should get removed.
   EXPECT_EQ(1U, events.CleanUpNavigationEvents());
   EXPECT_EQ(1U, events.NavigationEventsSize());
@@ -292,10 +295,66 @@
   ASSERT_EQ(6U, navigation_event_list()->NavigationEventsSize());
   ReferrerChain referrer_chain;
   navigation_observer_manager_->IdentifyReferrerChainByEventURL(
-      GURL("http://A.com/"), SessionID::InvalidValue(), 10, &referrer_chain);
+      GURL("http://A.com/"), SessionID::InvalidValue(),
+      content::GlobalRenderFrameHostId(), 10, &referrer_chain);
   ASSERT_EQ(6, referrer_chain.size());
 }
 
+TEST_F(SBNavigationObserverTest, TestFindEventInCorrectOutermostFrame) {
+  NavigationEventList events(4);
+
+  // Add 2 events to the list.
+  base::Time now = base::Time::Now();
+  const int child_id = 1;
+  const int frame_id_a = 2;
+  const int frame_id_b = 3;
+
+  auto outermost_main_frame_id_a =
+      content::GlobalRenderFrameHostId(child_id, frame_id_a);
+
+  auto event_page_a =
+      CreateNavigationEventUniquePtr(GURL("http://foo1.com"), now);
+  event_page_a->outermost_main_frame_id = outermost_main_frame_id_a;
+  events.RecordNavigationEvent(std::move(event_page_a));
+
+  auto event_subframe_a = CreateNavigationEventUniquePtr(
+      GURL("http://foo1.com/subframe.html"), now);
+  event_subframe_a->outermost_main_frame_id = outermost_main_frame_id_a;
+  events.RecordNavigationEvent(std::move(event_subframe_a));
+
+  auto outermost_main_frame_id_b =
+      content::GlobalRenderFrameHostId(child_id, frame_id_b);
+
+  auto event_page_b =
+      CreateNavigationEventUniquePtr(GURL("http://foo1.com/bar.html"), now);
+  event_page_b->outermost_main_frame_id = outermost_main_frame_id_b;
+  events.RecordNavigationEvent(std::move(event_page_b));
+
+  auto event_subframe_b = CreateNavigationEventUniquePtr(
+      GURL("http://foo1.com/subframe.html"), now);
+  event_subframe_b->outermost_main_frame_id = outermost_main_frame_id_b;
+  events.RecordNavigationEvent(std::move(event_subframe_b));
+
+  // Should match outermost main frame id, where possible.
+  EXPECT_EQ(
+      1U, *events.FindNavigationEvent(
+              base::Time::Now(), GURL("http://foo1.com/subframe.html"), GURL(),
+              SessionID::InvalidValue(), outermost_main_frame_id_a,
+              events.NavigationEventsSize() - 1));
+  EXPECT_EQ(
+      3U, *events.FindNavigationEvent(
+              base::Time::Now(), GURL("http://foo1.com/subframe.html"), GURL(),
+              SessionID::InvalidValue(), outermost_main_frame_id_b,
+              events.NavigationEventsSize() - 1));
+
+  // Should match the most recent if main_frame_id is not given.
+  EXPECT_EQ(
+      3U, *events.FindNavigationEvent(
+              base::Time::Now(), GURL("http://foo1.com/subframe.html"), GURL(),
+              SessionID::InvalidValue(), content::GlobalRenderFrameHostId(),
+              events.NavigationEventsSize() - 1));
+}
+
 TEST_F(SBNavigationObserverTest, BasicNavigationAndCommit) {
   // Navigation in current tab.
   NavigateAndCommit(GURL("http://foo/1"), ui::PAGE_TRANSITION_AUTO_BOOKMARK);
@@ -414,9 +473,10 @@
   // Verifies all stale and invalid navigation events are removed.
   ASSERT_EQ(2U, navigation_event_list()->NavigationEventsSize());
   ASSERT_EQ(1U, navigation_event_list()->PendingNavigationEventsSize());
-  EXPECT_EQ(-1, static_cast<int>(navigation_event_list()->FindNavigationEvent(
-                    base::Time::Now(), url_1, GURL(), SessionID::InvalidValue(),
-                    navigation_event_list()->NavigationEventsSize() - 1)));
+  EXPECT_FALSE(navigation_event_list()->FindNavigationEvent(
+      base::Time::Now(), url_1, GURL(), SessionID::InvalidValue(),
+      content::GlobalRenderFrameHostId(),
+      navigation_event_list()->NavigationEventsSize() - 1));
   EXPECT_EQ(nullptr,
             navigation_event_list()->FindPendingNavigationEvent(url_1));
 }
@@ -582,7 +642,8 @@
 
   ReferrerChain referrer_chain;
   navigation_observer_manager_->IdentifyReferrerChainByEventURL(
-      GURL("http://A.com"), SessionID::InvalidValue(), 10, &referrer_chain);
+      GURL("http://A.com"), SessionID::InvalidValue(),
+      content::GlobalRenderFrameHostId(), 10, &referrer_chain);
 
   ASSERT_EQ(3, referrer_chain.size());
 
@@ -659,7 +720,8 @@
 
   ReferrerChain referrer_chain;
   navigation_observer_manager_->IdentifyReferrerChainByEventURL(
-      GURL("http://A.com/"), SessionID::InvalidValue(), 10, &referrer_chain);
+      GURL("http://A.com/"), SessionID::InvalidValue(),
+      content::GlobalRenderFrameHostId(), 10, &referrer_chain);
 
   int utm_counter = 10;
   GURL expected_current_url = GURL("http://A.com");
@@ -722,7 +784,8 @@
 
   ReferrerChain referrer_chain;
   navigation_observer_manager_->IdentifyReferrerChainByEventURL(
-      GURL("http://b.com/"), SessionID::InvalidValue(), 10, &referrer_chain);
+      GURL("http://b.com/"), SessionID::InvalidValue(),
+      content::GlobalRenderFrameHostId(), 10, &referrer_chain);
 
   ASSERT_EQ(1, referrer_chain.size());
 
@@ -755,7 +818,8 @@
 
   ReferrerChain referrer_chain;
   navigation_observer_manager_->IdentifyReferrerChainByEventURL(
-      GURL("http://b.com/"), SessionID::InvalidValue(), 10, &referrer_chain);
+      GURL("http://b.com/"), SessionID::InvalidValue(),
+      content::GlobalRenderFrameHostId(), 10, &referrer_chain);
 
   EXPECT_EQ(2, referrer_chain.size());
 }
@@ -796,8 +860,8 @@
 
   ReferrerChain referrer_chain;
   navigation_observer_manager_->IdentifyReferrerChainByEventURL(
-      GURL("http://example.com/c"), SessionID::InvalidValue(), 10,
-      &referrer_chain);
+      GURL("http://example.com/c"), SessionID::InvalidValue(),
+      content::GlobalRenderFrameHostId(), 10, &referrer_chain);
 
   ASSERT_EQ(1, referrer_chain.size());
 
diff --git a/components/safe_browsing/content/browser/threat_details.cc b/components/safe_browsing/content/browser/threat_details.cc
index e252991..90f855d 100644
--- a/components/safe_browsing/content/browser/threat_details.cc
+++ b/components/safe_browsing/content/browser/threat_details.cc
@@ -897,8 +897,10 @@
     return;
   }
 
-  referrer_chain_provider_->IdentifyReferrerChainByWebContents(
-      web_contents(), kThreatDetailsUserGestureLimit,
+  // We would have cancelled a prerender if it was blocked, so we can use the
+  // primary main frame here.
+  referrer_chain_provider_->IdentifyReferrerChainByRenderFrameHost(
+      web_contents()->GetMainFrame(), kThreatDetailsUserGestureLimit,
       report_->mutable_referrer_chain());
 }
 
diff --git a/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc b/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
index 1a58295..0d70d97 100644
--- a/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
+++ b/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
@@ -33,6 +33,7 @@
 #include "components/safe_browsing/core/common/features.h"
 #include "components/safe_browsing/core/common/proto/csd.pb.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
+#include "content/public/browser/global_routing_id.h"
 #include "services/network/public/mojom/cookie_manager.mojom.h"
 #if BUILDFLAG(FULL_SAFE_BROWSING)
 #include "components/enterprise/common/proto/connectors.pb.h"
@@ -2442,7 +2443,8 @@
 
   ReferrerChain referrer_chain;
   provider->IdentifyReferrerChainByEventURL(
-      GURL(url_string), SessionID::InvalidValue(), 2, &referrer_chain);
+      GURL(url_string), SessionID::InvalidValue(),
+      content::GlobalRenderFrameHostId(), 2, &referrer_chain);
 
   base::ListValue referrer_list;
   for (const ReferrerChainEntry& entry : referrer_chain) {
diff --git a/components/safe_browsing/core/browser/realtime/url_lookup_service_unittest.cc b/components/safe_browsing/core/browser/realtime/url_lookup_service_unittest.cc
index 41d6d88..5617781 100644
--- a/components/safe_browsing/core/browser/realtime/url_lookup_service_unittest.cc
+++ b/components/safe_browsing/core/browser/realtime/url_lookup_service_unittest.cc
@@ -70,13 +70,15 @@
 class MockReferrerChainProvider : public ReferrerChainProvider {
  public:
   virtual ~MockReferrerChainProvider() = default;
-  MOCK_METHOD3(IdentifyReferrerChainByWebContents,
-               AttributionResult(content::WebContents* web_contents,
+  MOCK_METHOD3(IdentifyReferrerChainByRenderFrameHost,
+               AttributionResult(content::RenderFrameHost* rfh,
                                  int user_gesture_count_limit,
                                  ReferrerChain* out_referrer_chain));
-  MOCK_METHOD4(IdentifyReferrerChainByEventURL,
+  MOCK_METHOD5(IdentifyReferrerChainByEventURL,
                AttributionResult(const GURL& event_url,
                                  SessionID event_tab_id,
+                                 const content::GlobalRenderFrameHostId&
+                                     event_outermost_main_frame_id,
                                  int user_gesture_count_limit,
                                  ReferrerChain* out_referrer_chain));
   MOCK_METHOD3(IdentifyReferrerChainByPendingEventURL,
diff --git a/components/safe_browsing/core/browser/referrer_chain_provider.h b/components/safe_browsing/core/browser/referrer_chain_provider.h
index e51a742..bef26948 100644
--- a/components/safe_browsing/core/browser/referrer_chain_provider.h
+++ b/components/safe_browsing/core/browser/referrer_chain_provider.h
@@ -10,7 +10,8 @@
 #include "url/gurl.h"
 
 namespace content {
-class WebContents;
+class RenderFrameHost;
+struct GlobalRenderFrameHostId;
 }
 
 namespace safe_browsing {
@@ -33,14 +34,15 @@
     ATTRIBUTION_FAILURE_TYPE_MAX
   };
 
-  virtual AttributionResult IdentifyReferrerChainByWebContents(
-      content::WebContents* web_contents,
+  virtual AttributionResult IdentifyReferrerChainByRenderFrameHost(
+      content::RenderFrameHost* render_frame_host,
       int user_gesture_count_limit,
       ReferrerChain* out_referrer_chain) = 0;
 
   virtual AttributionResult IdentifyReferrerChainByEventURL(
       const GURL& event_url,
       SessionID event_tab_id,
+      const content::GlobalRenderFrameHostId& event_outermost_main_frame_id,
       int user_gesture_count_limit,
       ReferrerChain* out_referrer_chain) = 0;
 
diff --git a/components/safe_browsing/core/common/features.cc b/components/safe_browsing/core/common/features.cc
index 7eb74545..b506c2ca 100644
--- a/components/safe_browsing/core/common/features.cc
+++ b/components/safe_browsing/core/common/features.cc
@@ -98,7 +98,13 @@
                                           base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kTailoredSecurityIntegration{
-    "TailoredSecurityIntegration", base::FEATURE_DISABLED_BY_DEFAULT};
+  "TailoredSecurityIntegration",
+#if BUILDFLAG(IS_ANDROID)
+      base::FEATURE_DISABLED_BY_DEFAULT
+#else
+      base::FEATURE_ENABLED_BY_DEFAULT
+#endif
+};
 
 const base::Feature kOmitNonUserGesturesFromReferrerChain{
     "SafeBrowsingOmitNonUserGesturesFromReferrerChain",
diff --git a/components/safe_browsing/core/common/safe_browsing_prefs.cc b/components/safe_browsing/core/common/safe_browsing_prefs.cc
index efc273d..f63da778 100644
--- a/components/safe_browsing/core/common/safe_browsing_prefs.cc
+++ b/components/safe_browsing/core/common/safe_browsing_prefs.cc
@@ -55,7 +55,7 @@
   if (!url.is_valid() || !url.IsStandard())
     return GURL();
 
-  url::Replacements<char> replacements;
+  GURL::Replacements replacements;
   replacements.ClearUsername();
   replacements.ClearPassword();
   replacements.ClearQuery();
diff --git a/components/sync/BUILD.gn b/components/sync/BUILD.gn
index 2acdb0c..54a46aa 100644
--- a/components/sync/BUILD.gn
+++ b/components/sync/BUILD.gn
@@ -129,7 +129,6 @@
   sources = [
     "base/client_tag_hash_unittest.cc",
     "base/model_type_unittest.cc",
-    "base/ordinal_unittest.cc",
     "base/protobuf_unittest.cc",
     "base/sync_prefs_unittest.cc",
     "base/sync_util_unittest.cc",
@@ -190,6 +189,7 @@
     "model/mutable_data_batch_unittest.cc",
     "model/processor_entity_tracker_unittest.cc",
     "model/processor_entity_unittest.cc",
+    "model/string_ordinal_unittest.cc",
     "model/sync_change_unittest.cc",
     "model/sync_data_unittest.cc",
     "model/sync_error_unittest.cc",
diff --git a/components/sync/base/BUILD.gn b/components/sync/base/BUILD.gn
index f7539be..a56ed25b 100644
--- a/components/sync/base/BUILD.gn
+++ b/components/sync/base/BUILD.gn
@@ -35,7 +35,6 @@
     "logging.h",
     "model_type.cc",
     "model_type.h",
-    "ordinal.h",
     "passphrase_enums.cc",
     "passphrase_enums.h",
     "pref_names.h",
diff --git a/components/sync/base/DEPS b/components/sync/base/DEPS
index 395d1e5..2483803f 100644
--- a/components/sync/base/DEPS
+++ b/components/sync/base/DEPS
@@ -8,7 +8,4 @@
   "+net/base/net_errors.h",
   "+net/http/http_status_code.h",
   "+ui/base",
-  # TODO(crbug.com/1300525): Remove this once ordinal_unittest.cc is moved to
-  # model/.
-  "+components/sync/model/string_ordinal.h",
 ]
diff --git a/components/sync/base/ordinal.h b/components/sync/base/ordinal.h
deleted file mode 100644
index 7314f1fe..0000000
--- a/components/sync/base/ordinal.h
+++ /dev/null
@@ -1,512 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_SYNC_BASE_ORDINAL_H_
-#define COMPONENTS_SYNC_BASE_ORDINAL_H_
-
-#include <stdint.h>
-
-#include <algorithm>
-#include <string>
-
-#include "base/check_op.h"
-#include "base/json/string_escape.h"
-
-namespace mojo {
-template <typename DataViewType, typename T>
-struct StructTraits;
-}
-
-namespace syncer {
-
-namespace mojom {
-class StringOrdinalDataView;
-}
-
-// An Ordinal<T> is an object that can be used for ordering. The
-// Ordinal<T> class has an unbounded dense strict total order, which
-// mean for any Ordinal<T>s a, b and c:
-//
-//  - a < b and b < c implies a < c (transitivity);
-//  - exactly one of a < b, b < a and a = b holds (trichotomy);
-//  - if a < b, there is a Ordinal<T> x such that a < x < b (density);
-//  - there are Ordinals<T> x and y such that x < a < y (unboundedness).
-//
-// This means that when Ordinal<T> is used for sorting a list, if any
-// item changes its position in the list, only its Ordinal<T> value
-// has to change to represent the new order, and all the other values
-// can stay the same.
-//
-// An Ordinal<T> is internally represented as an array of bytes, so it
-// can be serialized to and deserialized from disk.
-//
-// The Traits class should look like the following:
-//
-//   // Don't forget to #include <stdint.h> and <stddef.h>.
-//   struct MyOrdinalTraits {
-//     // There must be at least two distinct values greater than kZeroDigit
-//     // and less than kMaxDigit.
-//     static const uint8_t kZeroDigit = '0';
-//     static const uint8_t kMaxDigit = '9';
-//     // kMinLength must be positive.
-//     static const size_t kMinLength = 1;
-//   };
-//
-// An Ordinal<T> is valid iff its corresponding string has at least
-// kMinLength characters, does not contain any characters less than
-// kZeroDigit or greater than kMaxDigit, is not all zero digits, and
-// does not have any unnecessary trailing zero digits.
-//
-// Note that even if the native char type is signed, strings still
-// compare as if their they are unsigned.  (This is explicitly in
-// C++11 but not in C++98, even though all implementations do so
-// anyway in practice.)  Thus, it is safe to use any byte range for
-// Ordinal<T>s.
-template <typename Traits>
-class Ordinal {
- public:
-  // Functors for use with STL algorithms and containers.
-  class LessThanFn {
-   public:
-    LessThanFn();
-
-    bool operator()(const Ordinal<Traits>& lhs,
-                    const Ordinal<Traits>& rhs) const;
-  };
-
-  class EqualsFn {
-   public:
-    EqualsFn();
-
-    bool operator()(const Ordinal<Traits>& lhs,
-                    const Ordinal<Traits>& rhs) const;
-  };
-
-  // Creates an Ordinal from the given string of bytes. The Ordinal
-  // may be valid or invalid.
-  explicit Ordinal(const std::string& bytes);
-
-  // Creates an invalid Ordinal.
-  Ordinal();
-
-  // Creates a valid initial Ordinal. This is called to create the first
-  // element of Ordinal list (i.e. before we have any other values we can
-  // generate from).
-  static Ordinal CreateInitialOrdinal();
-
-  // Returns true iff this Ordinal is valid.  This takes constant
-  // time.
-  bool IsValid() const;
-
-  // Returns true iff |*this| == |other| or |*this| and |other|
-  // are both invalid.
-  bool EqualsOrBothInvalid(const Ordinal& other) const;
-
-  // Returns a printable string representation of the Ordinal suitable
-  // for logging.
-  std::string ToDebugString() const;
-
-  // All remaining functions can only be called if IsValid() holds.
-  // It is an error to call them if IsValid() is false.
-
-  // Order-related functions.
-
-  // Returns true iff |*this| < |other|.
-  bool LessThan(const Ordinal& other) const;
-
-  // Returns true iff |*this| > |other|.
-  bool GreaterThan(const Ordinal& other) const;
-
-  // Returns true iff |*this| == |other| (i.e. |*this| < |other| and
-  // |other| < |*this| are both false).
-  bool Equals(const Ordinal& other) const;
-
-  // Given |*this| != |other|, returns a Ordinal x such that
-  // min(|*this|, |other|) < x < max(|*this|, |other|). It is an error
-  // to call this function when |*this| == |other|.
-  Ordinal CreateBetween(const Ordinal& other) const;
-
-  // Returns a Ordinal |x| such that |x| < |*this|.
-  Ordinal CreateBefore() const;
-
-  // Returns a Ordinal |x| such that |*this| < |x|.
-  Ordinal CreateAfter() const;
-
-  // Returns the string of bytes representing the Ordinal.  It is
-  // guaranteed that an Ordinal constructed from the returned string
-  // will be valid.
-  std::string ToInternalValue() const;
-
-  // Use of copy constructor and default assignment for this class is allowed.
-
-  // Constants for Ordinal digits.
-  static const uint8_t kZeroDigit = Traits::kZeroDigit;
-  static const uint8_t kMaxDigit = Traits::kMaxDigit;
-  static const size_t kMinLength = Traits::kMinLength;
-  static const uint8_t kOneDigit = kZeroDigit + 1;
-  static const uint8_t kMidDigit = kOneDigit + (kMaxDigit - kOneDigit) / 2;
-  static const unsigned int kMidDigitValue = kMidDigit - kZeroDigit;
-  static const unsigned int kMaxDigitValue = kMaxDigit - kZeroDigit;
-  static const unsigned int kRadix = kMaxDigitValue + 1;
-
-  static_assert(kOneDigit > kZeroDigit, "incorrect ordinal one digit");
-  static_assert(kMidDigit > kOneDigit, "incorrect ordinal mid digit");
-  static_assert(kMaxDigit > kMidDigit, "incorrect ordinal max digit");
-  static_assert(kMinLength > 0, "incorrect ordinal min length");
-  static_assert(kMidDigitValue > 1, "incorrect ordinal mid digit");
-  static_assert(kMaxDigitValue > kMidDigitValue, "incorrect ordinal max digit");
-  static_assert(kRadix == kMaxDigitValue + 1, "incorrect ordinal radix");
-
- private:
-  friend struct mojo::StructTraits<syncer::mojom::StringOrdinalDataView,
-                                   Ordinal<Traits>>;
-
-  // Returns true iff the given byte string satisfies the criteria for
-  // a valid Ordinal.
-  static bool IsValidOrdinalBytes(const std::string& bytes);
-
-  // Returns the length that bytes.substr(0, length) would be with
-  // trailing zero digits removed.
-  static size_t GetLengthWithoutTrailingZeroDigits(const std::string& bytes,
-                                                   size_t length);
-
-  // Returns the digit at position i, padding with zero digits if
-  // required.
-  static uint8_t GetDigit(const std::string& bytes, size_t i);
-
-  // Returns the digit value at position i, padding with 0 if required.
-  static int GetDigitValue(const std::string& bytes, size_t i);
-
-  // Adds the given value to |bytes| at position i, carrying when
-  // necessary.  Returns the left-most carry.
-  static int AddDigitValue(std::string* bytes, size_t i, int digit_value);
-
-  // Returns the proper length |bytes| should be resized to, i.e. the
-  // smallest length such that |bytes| is still greater than
-  // |lower_bound| and is still valid.  |bytes| should be greater than
-  // |lower_bound|.
-  static size_t GetProperLength(const std::string& lower_bound,
-                                const std::string& bytes);
-
-  // Compute the midpoint ordinal byte string that is between |start|
-  // and |end|.
-  static std::string ComputeMidpoint(const std::string& start,
-                                     const std::string& end);
-
-  // Create a Ordinal that is lexigraphically greater than |start| and
-  // lexigraphically less than |end|. The returned Ordinal will be roughly
-  // between |start| and |end|.
-  static Ordinal<Traits> CreateOrdinalBetween(const Ordinal<Traits>& start,
-                                              const Ordinal<Traits>& end);
-
-  // The internal byte string representation of the Ordinal.  Never
-  // changes after construction except for assignment.
-  std::string bytes_;
-
-  // A cache of the result of IsValidOrdinalBytes(bytes_).
-  bool is_valid_;
-};
-
-template <typename Traits>
-const uint8_t Ordinal<Traits>::kZeroDigit;
-template <typename Traits>
-const uint8_t Ordinal<Traits>::kMaxDigit;
-template <typename Traits>
-const size_t Ordinal<Traits>::kMinLength;
-template <typename Traits>
-const uint8_t Ordinal<Traits>::kOneDigit;
-template <typename Traits>
-const uint8_t Ordinal<Traits>::kMidDigit;
-template <typename Traits>
-const unsigned int Ordinal<Traits>::kMidDigitValue;
-template <typename Traits>
-const unsigned int Ordinal<Traits>::kMaxDigitValue;
-template <typename Traits>
-const unsigned int Ordinal<Traits>::kRadix;
-
-template <typename Traits>
-Ordinal<Traits>::LessThanFn::LessThanFn() = default;
-
-template <typename Traits>
-bool Ordinal<Traits>::LessThanFn::operator()(const Ordinal<Traits>& lhs,
-                                             const Ordinal<Traits>& rhs) const {
-  return lhs.LessThan(rhs);
-}
-
-template <typename Traits>
-Ordinal<Traits>::EqualsFn::EqualsFn() = default;
-
-template <typename Traits>
-bool Ordinal<Traits>::EqualsFn::operator()(const Ordinal<Traits>& lhs,
-                                           const Ordinal<Traits>& rhs) const {
-  return lhs.Equals(rhs);
-}
-
-template <typename Traits>
-bool operator==(const Ordinal<Traits>& lhs, const Ordinal<Traits>& rhs) {
-  return lhs.EqualsOrBothInvalid(rhs);
-}
-
-template <typename Traits>
-bool operator!=(const Ordinal<Traits>& lhs, const Ordinal<Traits>& rhs) {
-  return !(lhs == rhs);
-}
-
-template <typename Traits>
-Ordinal<Traits>::Ordinal(const std::string& bytes)
-    : bytes_(bytes), is_valid_(IsValidOrdinalBytes(bytes_)) {}
-
-template <typename Traits>
-Ordinal<Traits>::Ordinal() : is_valid_(false) {}
-
-template <typename Traits>
-Ordinal<Traits> Ordinal<Traits>::CreateInitialOrdinal() {
-  std::string bytes(Traits::kMinLength, kZeroDigit);
-  bytes[0] = kMidDigit;
-  return Ordinal(bytes);
-}
-
-template <typename Traits>
-bool Ordinal<Traits>::IsValid() const {
-  DCHECK_EQ(IsValidOrdinalBytes(bytes_), is_valid_);
-  return is_valid_;
-}
-
-template <typename Traits>
-bool Ordinal<Traits>::EqualsOrBothInvalid(const Ordinal& other) const {
-  if (!IsValid() && !other.IsValid())
-    return true;
-
-  if (!IsValid() || !other.IsValid())
-    return false;
-
-  return Equals(other);
-}
-
-template <typename Traits>
-std::string Ordinal<Traits>::ToDebugString() const {
-  std::string debug_string =
-      base::EscapeBytesAsInvalidJSONString(bytes_, false /* put_in_quotes */);
-  if (!is_valid_) {
-    debug_string = "INVALID[" + debug_string + "]";
-  }
-  return debug_string;
-}
-
-template <typename Traits>
-bool Ordinal<Traits>::LessThan(const Ordinal& other) const {
-  CHECK(IsValid());
-  CHECK(other.IsValid());
-  return bytes_ < other.bytes_;
-}
-
-template <typename Traits>
-bool Ordinal<Traits>::GreaterThan(const Ordinal& other) const {
-  CHECK(IsValid());
-  CHECK(other.IsValid());
-  return bytes_ > other.bytes_;
-}
-
-template <typename Traits>
-bool Ordinal<Traits>::Equals(const Ordinal& other) const {
-  CHECK(IsValid());
-  CHECK(other.IsValid());
-  return bytes_ == other.bytes_;
-}
-
-template <typename Traits>
-Ordinal<Traits> Ordinal<Traits>::CreateBetween(const Ordinal& other) const {
-  CHECK(IsValid());
-  CHECK(other.IsValid());
-  CHECK(!Equals(other));
-
-  if (LessThan(other)) {
-    return CreateOrdinalBetween(*this, other);
-  } else {
-    return CreateOrdinalBetween(other, *this);
-  }
-}
-
-template <typename Traits>
-Ordinal<Traits> Ordinal<Traits>::CreateBefore() const {
-  CHECK(IsValid());
-  // Create the smallest valid Ordinal of the appropriate length
-  // to be the minimum boundary.
-  const size_t length = bytes_.length();
-  std::string start(length, kZeroDigit);
-  start[length - 1] = kOneDigit;
-  if (start == bytes_) {
-    start[length - 1] = kZeroDigit;
-    start += kOneDigit;
-  }
-
-  // Even though |start| is already a valid Ordinal that is less
-  // than |*this|, we don't return it because we wouldn't have much space in
-  // front of it to insert potential future values.
-  return CreateBetween(Ordinal(start));
-}
-
-template <typename Traits>
-Ordinal<Traits> Ordinal<Traits>::CreateAfter() const {
-  CHECK(IsValid());
-  // Create the largest valid Ordinal of the appropriate length to be
-  // the maximum boundary.
-  std::string end(bytes_.length(), kMaxDigit);
-  if (end == bytes_)
-    end += kMaxDigit;
-
-  // Even though |end| is already a valid Ordinal that is greater than
-  // |*this|, we don't return it because we wouldn't have much space after
-  // it to insert potential future values.
-  return CreateBetween(Ordinal(end));
-}
-
-template <typename Traits>
-std::string Ordinal<Traits>::ToInternalValue() const {
-  CHECK(IsValid());
-  return bytes_;
-}
-
-template <typename Traits>
-bool Ordinal<Traits>::IsValidOrdinalBytes(const std::string& bytes) {
-  const size_t length = bytes.length();
-  if (length < kMinLength)
-    return false;
-
-  bool found_non_zero = false;
-  for (size_t i = 0; i < length; ++i) {
-    const uint8_t byte = bytes[i];
-    if (byte < kZeroDigit || byte > kMaxDigit)
-      return false;
-    if (byte > kZeroDigit)
-      found_non_zero = true;
-  }
-  if (!found_non_zero)
-    return false;
-
-  if (length > kMinLength) {
-    const uint8_t last_byte = bytes[length - 1];
-    if (last_byte == kZeroDigit)
-      return false;
-  }
-
-  return true;
-}
-
-template <typename Traits>
-size_t Ordinal<Traits>::GetLengthWithoutTrailingZeroDigits(
-    const std::string& bytes,
-    size_t length) {
-  DCHECK(!bytes.empty());
-  DCHECK_GT(length, 0U);
-
-  size_t end_position =
-      bytes.find_last_not_of(static_cast<char>(kZeroDigit), length - 1);
-
-  // If no non kZeroDigit is found then the string is a string of all zeros
-  // digits so we return 0 as the correct length.
-  if (end_position == std::string::npos)
-    return 0;
-
-  return end_position + 1;
-}
-
-template <typename Traits>
-uint8_t Ordinal<Traits>::GetDigit(const std::string& bytes, size_t i) {
-  return (i < bytes.length()) ? bytes[i] : kZeroDigit;
-}
-
-template <typename Traits>
-int Ordinal<Traits>::GetDigitValue(const std::string& bytes, size_t i) {
-  return GetDigit(bytes, i) - kZeroDigit;
-}
-
-template <typename Traits>
-int Ordinal<Traits>::AddDigitValue(std::string* bytes,
-                                   size_t i,
-                                   int digit_value) {
-  DCHECK_LT(i, bytes->length());
-
-  for (int j = static_cast<int>(i); j >= 0 && digit_value > 0; --j) {
-    int byte_j_value = GetDigitValue(*bytes, j) + digit_value;
-    digit_value = byte_j_value / kRadix;
-    DCHECK_LE(digit_value, 1);
-    byte_j_value %= kRadix;
-    (*bytes)[j] = static_cast<char>(kZeroDigit + byte_j_value);
-  }
-  return digit_value;
-}
-
-template <typename Traits>
-size_t Ordinal<Traits>::GetProperLength(const std::string& lower_bound,
-                                        const std::string& bytes) {
-  CHECK_GT(bytes, lower_bound);
-
-  size_t drop_length =
-      GetLengthWithoutTrailingZeroDigits(bytes, bytes.length());
-  // See if the |ordinal| can be truncated after its last non-zero
-  // digit without affecting the ordering.
-  if (drop_length > kMinLength) {
-    size_t truncated_length =
-        GetLengthWithoutTrailingZeroDigits(bytes, drop_length - 1);
-
-    if (truncated_length > 0 &&
-        bytes.compare(0, truncated_length, lower_bound) > 0)
-      drop_length = truncated_length;
-  }
-  return std::max(drop_length, kMinLength);
-}
-
-template <typename Traits>
-std::string Ordinal<Traits>::ComputeMidpoint(const std::string& start,
-                                             const std::string& end) {
-  size_t max_size = std::max(start.length(), end.length()) + 1;
-  std::string midpoint(max_size, kZeroDigit);
-
-  // Perform the operation (start + end) / 2 left-to-right by
-  // maintaining a "forward carry" which is either 0 or
-  // kMidDigitValue.  AddDigitValue() is in general O(n), but this
-  // operation is still O(n) despite that; calls to AddDigitValue()
-  // will overflow at most to the last position where AddDigitValue()
-  // last overflowed.
-  int forward_carry = 0;
-  for (size_t i = 0; i < max_size; ++i) {
-    const int sum_value = GetDigitValue(start, i) + GetDigitValue(end, i);
-    const int digit_value = sum_value / 2 + forward_carry;
-    // AddDigitValue returning a non-zero carry would imply that
-    // midpoint[0] >= kMaxDigit, which one can show is impossible.
-    CHECK_EQ(AddDigitValue(&midpoint, i, digit_value), 0);
-    forward_carry = (sum_value % 2 == 1) ? kMidDigitValue : 0;
-  }
-  DCHECK_EQ(forward_carry, 0);
-
-  return midpoint;
-}
-
-template <typename Traits>
-Ordinal<Traits> Ordinal<Traits>::CreateOrdinalBetween(
-    const Ordinal<Traits>& start,
-    const Ordinal<Traits>& end) {
-  CHECK(start.IsValid());
-  CHECK(end.IsValid());
-  CHECK(start.LessThan(end));
-  const std::string& start_bytes = start.ToInternalValue();
-  const std::string& end_bytes = end.ToInternalValue();
-  DCHECK_LT(start_bytes, end_bytes);
-
-  std::string midpoint = ComputeMidpoint(start_bytes, end_bytes);
-  const size_t proper_length = GetProperLength(start_bytes, midpoint);
-  midpoint.resize(proper_length, kZeroDigit);
-
-  DCHECK_GT(midpoint, start_bytes);
-  DCHECK_LT(midpoint, end_bytes);
-
-  Ordinal<Traits> midpoint_ordinal(midpoint);
-  DCHECK(midpoint_ordinal.IsValid());
-  return midpoint_ordinal;
-}
-
-}  // namespace syncer
-
-#endif  // COMPONENTS_SYNC_BASE_ORDINAL_H_
diff --git a/components/sync/model/BUILD.gn b/components/sync/model/BUILD.gn
index 7f0d2fb..64134bc 100644
--- a/components/sync/model/BUILD.gn
+++ b/components/sync/model/BUILD.gn
@@ -52,6 +52,7 @@
     "processor_entity_tracker.h",
     "proxy_model_type_controller_delegate.cc",
     "proxy_model_type_controller_delegate.h",
+    "string_ordinal.cc",
     "string_ordinal.h",
     "sync_change.cc",
     "sync_change.h",
diff --git a/components/sync/model/string_ordinal.cc b/components/sync/model/string_ordinal.cc
new file mode 100644
index 0000000..6c34584
--- /dev/null
+++ b/components/sync/model/string_ordinal.cc
@@ -0,0 +1,278 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/sync/model/string_ordinal.h"
+
+#include <algorithm>
+
+#include "base/check.h"
+#include "base/check_op.h"
+#include "base/json/string_escape.h"
+
+namespace syncer {
+
+const uint8_t StringOrdinal::kZeroDigit;
+const uint8_t StringOrdinal::kMaxDigit;
+const size_t StringOrdinal::kMinLength;
+const uint8_t StringOrdinal::kOneDigit;
+const uint8_t StringOrdinal::kMidDigit;
+const unsigned int StringOrdinal::kMidDigitValue;
+const unsigned int StringOrdinal::kMaxDigitValue;
+const unsigned int StringOrdinal::kRadix;
+
+StringOrdinal::LessThanFn::LessThanFn() = default;
+
+bool StringOrdinal::LessThanFn::operator()(const StringOrdinal& lhs,
+                                           const StringOrdinal& rhs) const {
+  return lhs.LessThan(rhs);
+}
+
+StringOrdinal::EqualsFn::EqualsFn() = default;
+
+bool StringOrdinal::EqualsFn::operator()(const StringOrdinal& lhs,
+                                         const StringOrdinal& rhs) const {
+  return lhs.Equals(rhs);
+}
+
+bool operator==(const StringOrdinal& lhs, const StringOrdinal& rhs) {
+  return lhs.EqualsOrBothInvalid(rhs);
+}
+
+bool operator!=(const StringOrdinal& lhs, const StringOrdinal& rhs) {
+  return !(lhs == rhs);
+}
+
+StringOrdinal::StringOrdinal(const std::string& bytes)
+    : bytes_(bytes), is_valid_(IsValidOrdinalBytes(bytes_)) {}
+
+StringOrdinal::StringOrdinal() : is_valid_(false) {}
+
+StringOrdinal StringOrdinal::CreateInitialOrdinal() {
+  std::string bytes(kMinLength, kZeroDigit);
+  bytes[0] = kMidDigit;
+  return StringOrdinal(bytes);
+}
+
+bool StringOrdinal::IsValid() const {
+  DCHECK_EQ(IsValidOrdinalBytes(bytes_), is_valid_);
+  return is_valid_;
+}
+
+bool StringOrdinal::EqualsOrBothInvalid(const StringOrdinal& other) const {
+  if (!IsValid() && !other.IsValid())
+    return true;
+
+  if (!IsValid() || !other.IsValid())
+    return false;
+
+  return Equals(other);
+}
+
+std::string StringOrdinal::ToDebugString() const {
+  std::string debug_string =
+      base::EscapeBytesAsInvalidJSONString(bytes_, false /* put_in_quotes */);
+  if (!is_valid_) {
+    debug_string = "INVALID[" + debug_string + "]";
+  }
+  return debug_string;
+}
+
+bool StringOrdinal::LessThan(const StringOrdinal& other) const {
+  CHECK(IsValid());
+  CHECK(other.IsValid());
+  return bytes_ < other.bytes_;
+}
+
+bool StringOrdinal::GreaterThan(const StringOrdinal& other) const {
+  CHECK(IsValid());
+  CHECK(other.IsValid());
+  return bytes_ > other.bytes_;
+}
+
+bool StringOrdinal::Equals(const StringOrdinal& other) const {
+  CHECK(IsValid());
+  CHECK(other.IsValid());
+  return bytes_ == other.bytes_;
+}
+
+StringOrdinal StringOrdinal::CreateBetween(const StringOrdinal& other) const {
+  CHECK(IsValid());
+  CHECK(other.IsValid());
+  CHECK(!Equals(other));
+
+  if (LessThan(other)) {
+    return CreateOrdinalBetween(*this, other);
+  } else {
+    return CreateOrdinalBetween(other, *this);
+  }
+}
+
+StringOrdinal StringOrdinal::CreateBefore() const {
+  CHECK(IsValid());
+  // Create the smallest valid StringOrdinal of the appropriate length
+  // to be the minimum boundary.
+  const size_t length = bytes_.length();
+  std::string start(length, kZeroDigit);
+  start[length - 1] = kOneDigit;
+  if (start == bytes_) {
+    start[length - 1] = kZeroDigit;
+    start += kOneDigit;
+  }
+
+  // Even though |start| is already a valid StringOrdinal that is less
+  // than |*this|, we don't return it because we wouldn't have much space in
+  // front of it to insert potential future values.
+  return CreateBetween(StringOrdinal(start));
+}
+
+StringOrdinal StringOrdinal::CreateAfter() const {
+  CHECK(IsValid());
+  // Create the largest valid StringOrdinal of the appropriate length to be
+  // the maximum boundary.
+  std::string end(bytes_.length(), kMaxDigit);
+  if (end == bytes_)
+    end += kMaxDigit;
+
+  // Even though |end| is already a valid StringOrdinal that is greater than
+  // |*this|, we don't return it because we wouldn't have much space after
+  // it to insert potential future values.
+  return CreateBetween(StringOrdinal(end));
+}
+
+std::string StringOrdinal::ToInternalValue() const {
+  CHECK(IsValid());
+  return bytes_;
+}
+
+bool StringOrdinal::IsValidOrdinalBytes(const std::string& bytes) {
+  const size_t length = bytes.length();
+  if (length < kMinLength)
+    return false;
+
+  bool found_non_zero = false;
+  for (size_t i = 0; i < length; ++i) {
+    const uint8_t byte = bytes[i];
+    if (byte < kZeroDigit || byte > kMaxDigit)
+      return false;
+    if (byte > kZeroDigit)
+      found_non_zero = true;
+  }
+  if (!found_non_zero)
+    return false;
+
+  if (length > kMinLength) {
+    const uint8_t last_byte = bytes[length - 1];
+    if (last_byte == kZeroDigit)
+      return false;
+  }
+
+  return true;
+}
+
+size_t StringOrdinal::GetLengthWithoutTrailingZeroDigits(
+    const std::string& bytes,
+    size_t length) {
+  DCHECK(!bytes.empty());
+  DCHECK_GT(length, 0U);
+
+  size_t end_position =
+      bytes.find_last_not_of(static_cast<char>(kZeroDigit), length - 1);
+
+  // If no non kZeroDigit is found then the string is a string of all zeros
+  // digits so we return 0 as the correct length.
+  if (end_position == std::string::npos)
+    return 0;
+
+  return end_position + 1;
+}
+
+uint8_t StringOrdinal::GetDigit(const std::string& bytes, size_t i) {
+  return (i < bytes.length()) ? bytes[i] : kZeroDigit;
+}
+
+int StringOrdinal::GetDigitValue(const std::string& bytes, size_t i) {
+  return GetDigit(bytes, i) - kZeroDigit;
+}
+
+int StringOrdinal::AddDigitValue(std::string* bytes,
+                                 size_t i,
+                                 int digit_value) {
+  DCHECK_LT(i, bytes->length());
+
+  for (int j = static_cast<int>(i); j >= 0 && digit_value > 0; --j) {
+    int byte_j_value = GetDigitValue(*bytes, j) + digit_value;
+    digit_value = byte_j_value / kRadix;
+    DCHECK_LE(digit_value, 1);
+    byte_j_value %= kRadix;
+    (*bytes)[j] = static_cast<char>(kZeroDigit + byte_j_value);
+  }
+  return digit_value;
+}
+
+size_t StringOrdinal::GetProperLength(const std::string& lower_bound,
+                                      const std::string& bytes) {
+  CHECK_GT(bytes, lower_bound);
+
+  size_t drop_length =
+      GetLengthWithoutTrailingZeroDigits(bytes, bytes.length());
+  // See if the |ordinal| can be truncated after its last non-zero
+  // digit without affecting the ordering.
+  if (drop_length > kMinLength) {
+    size_t truncated_length =
+        GetLengthWithoutTrailingZeroDigits(bytes, drop_length - 1);
+
+    if (truncated_length > 0 &&
+        bytes.compare(0, truncated_length, lower_bound) > 0)
+      drop_length = truncated_length;
+  }
+  return std::max(drop_length, kMinLength);
+}
+
+std::string StringOrdinal::ComputeMidpoint(const std::string& start,
+                                           const std::string& end) {
+  size_t max_size = std::max(start.length(), end.length()) + 1;
+  std::string midpoint(max_size, kZeroDigit);
+
+  // Perform the operation (start + end) / 2 left-to-right by
+  // maintaining a "forward carry" which is either 0 or
+  // kMidDigitValue.  AddDigitValue() is in general O(n), but this
+  // operation is still O(n) despite that; calls to AddDigitValue()
+  // will overflow at most to the last position where AddDigitValue()
+  // last overflowed.
+  int forward_carry = 0;
+  for (size_t i = 0; i < max_size; ++i) {
+    const int sum_value = GetDigitValue(start, i) + GetDigitValue(end, i);
+    const int digit_value = sum_value / 2 + forward_carry;
+    // AddDigitValue returning a non-zero carry would imply that
+    // midpoint[0] >= kMaxDigit, which one can show is impossible.
+    CHECK_EQ(AddDigitValue(&midpoint, i, digit_value), 0);
+    forward_carry = (sum_value % 2 == 1) ? kMidDigitValue : 0;
+  }
+  DCHECK_EQ(forward_carry, 0);
+
+  return midpoint;
+}
+
+StringOrdinal StringOrdinal::CreateOrdinalBetween(const StringOrdinal& start,
+                                                  const StringOrdinal& end) {
+  CHECK(start.IsValid());
+  CHECK(end.IsValid());
+  CHECK(start.LessThan(end));
+  const std::string& start_bytes = start.ToInternalValue();
+  const std::string& end_bytes = end.ToInternalValue();
+  DCHECK_LT(start_bytes, end_bytes);
+
+  std::string midpoint = ComputeMidpoint(start_bytes, end_bytes);
+  const size_t proper_length = GetProperLength(start_bytes, midpoint);
+  midpoint.resize(proper_length, kZeroDigit);
+
+  DCHECK_GT(midpoint, start_bytes);
+  DCHECK_LT(midpoint, end_bytes);
+
+  StringOrdinal midpoint_ordinal(midpoint);
+  DCHECK(midpoint_ordinal.IsValid());
+  return midpoint_ordinal;
+}
+
+}  // namespace syncer
diff --git a/components/sync/model/string_ordinal.h b/components/sync/model/string_ordinal.h
index e375759..8e38e24 100644
--- a/components/sync/model/string_ordinal.h
+++ b/components/sync/model/string_ordinal.h
@@ -5,29 +5,194 @@
 #ifndef COMPONENTS_SYNC_MODEL_STRING_ORDINAL_H_
 #define COMPONENTS_SYNC_MODEL_STRING_ORDINAL_H_
 
-#include <stddef.h>
-#include <stdint.h>
+#include <cstddef>
+#include <cstdint>
+#include <string>
 
-#include "components/sync/base/ordinal.h"
+namespace mojo {
+template <typename DataViewType, typename T>
+struct StructTraits;
+}
 
 namespace syncer {
 
-// A StringOrdinal is an Ordinal with range 'a'-'z' for printability
-// of its internal byte string representation.  (Think of
-// StringOrdinal as being short for PrintableStringOrdinal.)  It
-// should be used for data types that want to maintain one or more
-// orderings for nodes.
+namespace mojom {
+class StringOrdinalDataView;
+}
+
+// WARNING, DO NOT USE! Use UniquePosition instead.
+//
+//
+// A StringOrdinal is an object that can be used for ordering. The
+// StringOrdinal class has an unbounded dense strict total order, which
+// mean for any StringOrdinals a, b and c:
+//
+//  - a < b and b < c implies a < c (transitivity);
+//  - exactly one of a < b, b < a and a = b holds (trichotomy);
+//  - if a < b, there is a StringOrdinal x such that a < x < b (density);
+//  - there are Ordinals<T> x and y such that x < a < y (unboundedness).
+//
+// This means that when StringOrdinal is used for sorting a list, if any
+// item changes its position in the list, only its StringOrdinal value
+// has to change to represent the new order, and all the other values
+// can stay the same.
+//
+// A StringOrdinal is internally represented as an array of bytes, so it
+// can be serialized to and deserialized from disk.
+//
+// A StringOrdinal is valid iff its corresponding string has at least
+// kMinLength characters, does not contain any characters less than
+// kZeroDigit or greater than kMaxDigit, is not all zero digits, and
+// does not have any unnecessary trailing zero digits.
 //
 // Since StringOrdinals contain only printable characters, it is safe
 // to store as a string in a protobuf.
+class StringOrdinal {
+ public:
+  // Functors for use with STL algorithms and containers.
+  class LessThanFn {
+   public:
+    LessThanFn();
 
-struct StringOrdinalTraits {
+    bool operator()(const StringOrdinal& lhs, const StringOrdinal& rhs) const;
+  };
+
+  class EqualsFn {
+   public:
+    EqualsFn();
+
+    bool operator()(const StringOrdinal& lhs, const StringOrdinal& rhs) const;
+  };
+
+  // Creates an StringOrdinal from the given string of bytes. The StringOrdinal
+  // may be valid or invalid.
+  explicit StringOrdinal(const std::string& bytes);
+
+  // Creates an invalid StringOrdinal.
+  StringOrdinal();
+
+  // Creates a valid initial StringOrdinal. This is called to create the first
+  // element of StringOrdinal list (i.e. before we have any other values we can
+  // generate from).
+  static StringOrdinal CreateInitialOrdinal();
+
+  // Returns true iff this StringOrdinal is valid.  This takes constant
+  // time.
+  bool IsValid() const;
+
+  // Returns true iff |*this| == |other| or |*this| and |other|
+  // are both invalid.
+  bool EqualsOrBothInvalid(const StringOrdinal& other) const;
+
+  // Returns a printable string representation of the StringOrdinal suitable
+  // for logging.
+  std::string ToDebugString() const;
+
+  // All remaining functions can only be called if IsValid() holds.
+  // It is an error to call them if IsValid() is false.
+
+  // Order-related functions.
+
+  // Returns true iff |*this| < |other|.
+  bool LessThan(const StringOrdinal& other) const;
+
+  // Returns true iff |*this| > |other|.
+  bool GreaterThan(const StringOrdinal& other) const;
+
+  // Returns true iff |*this| == |other| (i.e. |*this| < |other| and
+  // |other| < |*this| are both false).
+  bool Equals(const StringOrdinal& other) const;
+
+  // Given |*this| != |other|, returns a StringOrdinal x such that
+  // min(|*this|, |other|) < x < max(|*this|, |other|). It is an error
+  // to call this function when |*this| == |other|.
+  StringOrdinal CreateBetween(const StringOrdinal& other) const;
+
+  // Returns a StringOrdinal |x| such that |x| < |*this|.
+  StringOrdinal CreateBefore() const;
+
+  // Returns a StringOrdinal |x| such that |*this| < |x|.
+  StringOrdinal CreateAfter() const;
+
+  // Returns the string of bytes representing the StringOrdinal.  It is
+  // guaranteed that an StringOrdinal constructed from the returned string
+  // will be valid.
+  std::string ToInternalValue() const;
+
+  // Use of copy constructor and default assignment for this class is allowed.
+
+  // Constants for StringOrdinal digits.
   static const uint8_t kZeroDigit = 'a';
   static const uint8_t kMaxDigit = 'z';
   static const size_t kMinLength = 1;
+  static const uint8_t kOneDigit = kZeroDigit + 1;
+  static const uint8_t kMidDigit = kOneDigit + (kMaxDigit - kOneDigit) / 2;
+  static const unsigned int kMidDigitValue = kMidDigit - kZeroDigit;
+  static const unsigned int kMaxDigitValue = kMaxDigit - kZeroDigit;
+  static const unsigned int kRadix = kMaxDigitValue + 1;
+
+  static_assert(kOneDigit > kZeroDigit, "incorrect StringOrdinal one digit");
+  static_assert(kMidDigit > kOneDigit, "incorrect StringOrdinal mid digit");
+  static_assert(kMaxDigit > kMidDigit, "incorrect StringOrdinal max digit");
+  static_assert(kMinLength > 0, "incorrect StringOrdinal min length");
+  static_assert(kMidDigitValue > 1, "incorrect StringOrdinal mid digit");
+  static_assert(kMaxDigitValue > kMidDigitValue,
+                "incorrect StringOrdinal max digit");
+  static_assert(kRadix == kMaxDigitValue + 1, "incorrect StringOrdinal radix");
+
+ private:
+  friend struct mojo::StructTraits<syncer::mojom::StringOrdinalDataView,
+                                   StringOrdinal>;
+
+  // Returns true iff the given byte string satisfies the criteria for
+  // a valid StringOrdinal.
+  static bool IsValidOrdinalBytes(const std::string& bytes);
+
+  // Returns the length that bytes.substr(0, length) would be with
+  // trailing zero digits removed.
+  static size_t GetLengthWithoutTrailingZeroDigits(const std::string& bytes,
+                                                   size_t length);
+
+  // Returns the digit at position i, padding with zero digits if
+  // required.
+  static uint8_t GetDigit(const std::string& bytes, size_t i);
+
+  // Returns the digit value at position i, padding with 0 if required.
+  static int GetDigitValue(const std::string& bytes, size_t i);
+
+  // Adds the given value to |bytes| at position i, carrying when
+  // necessary.  Returns the left-most carry.
+  static int AddDigitValue(std::string* bytes, size_t i, int digit_value);
+
+  // Returns the proper length |bytes| should be resized to, i.e. the
+  // smallest length such that |bytes| is still greater than
+  // |lower_bound| and is still valid.  |bytes| should be greater than
+  // |lower_bound|.
+  static size_t GetProperLength(const std::string& lower_bound,
+                                const std::string& bytes);
+
+  // Compute the midpoint StringOrdinal byte string that is between |start|
+  // and |end|.
+  static std::string ComputeMidpoint(const std::string& start,
+                                     const std::string& end);
+
+  // Create a StringOrdinal that is lexigraphically greater than |start| and
+  // lexigraphically less than |end|. The returned StringOrdinal will be roughly
+  // between |start| and |end|.
+  static StringOrdinal CreateOrdinalBetween(const StringOrdinal& start,
+                                            const StringOrdinal& end);
+
+  // The internal byte string representation of the StringOrdinal.  Never
+  // changes after construction except for assignment.
+  std::string bytes_;
+
+  // A cache of the result of IsValidOrdinalBytes(bytes_).
+  bool is_valid_;
 };
 
-using StringOrdinal = Ordinal<StringOrdinalTraits>;
+bool operator==(const StringOrdinal& lhs, const StringOrdinal& rhs);
+
+bool operator!=(const StringOrdinal& lhs, const StringOrdinal& rhs);
 
 static_assert(StringOrdinal::kZeroDigit == 'a',
               "StringOrdinal has incorrect zero digit");
diff --git a/components/sync/base/ordinal_unittest.cc b/components/sync/model/string_ordinal_unittest.cc
similarity index 97%
rename from components/sync/base/ordinal_unittest.cc
rename to components/sync/model/string_ordinal_unittest.cc
index 5e782fcb..c8ab190 100644
--- a/components/sync/base/ordinal_unittest.cc
+++ b/components/sync/model/string_ordinal_unittest.cc
@@ -2,16 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// TODO(crbug.com/1300525): Move this file to model/ and remove this include
-// (it's kept now to avoid a presubmit error). None of that is done now to allow
-// for a friendlier diff.
-#include "components/sync/base/ordinal.h"
+#include "components/sync/model/string_ordinal.h"
 
+#include <algorithm>
 #include <cctype>
 #include <vector>
 
 #include "base/rand_util.h"
-#include "components/sync/model/string_ordinal.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace syncer {
diff --git a/content/DEPS b/content/DEPS
index ec3de28..f567a63 100644
--- a/content/DEPS
+++ b/content/DEPS
@@ -25,6 +25,7 @@
   # as autofill or extensions, and chrome implementation details such as
   # settings, packaging details, installation or crash reporting.
 
+  "+components/browsing_topics/common",
   "+components/memory_pressure",
   "+components/power_scheduler",
   "+components/services/filesystem",
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index d71fa98d..60c1258c 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -62,6 +62,7 @@
     "//cc/mojo_embedder",
     "//cc/paint",
     "//components/back_forward_cache:enum",
+    "//components/browsing_topics/common:common",
     "//components/cbor",
     "//components/discardable_memory/common",
     "//components/discardable_memory/service",
@@ -401,6 +402,7 @@
     "attribution_reporting/attribution_manager.h",
     "attribution_reporting/attribution_manager_impl.cc",
     "attribution_reporting/attribution_manager_impl.h",
+    "attribution_reporting/attribution_observer.h",
     "attribution_reporting/attribution_page_metrics.cc",
     "attribution_reporting/attribution_page_metrics.h",
     "attribution_reporting/attribution_policy.cc",
@@ -599,6 +601,8 @@
     "browsing_data/storage_partition_code_cache_data_remover.h",
     "browsing_instance.cc",
     "browsing_instance.h",
+    "browsing_topics/browsing_topics_site_data_storage.cc",
+    "browsing_topics/browsing_topics_site_data_storage.h",
     "buckets/bucket_context.cc",
     "buckets/bucket_context.h",
     "buckets/bucket_host.cc",
diff --git a/content/browser/attribution_reporting/attribution_internals_handler_impl.h b/content/browser/attribution_reporting/attribution_internals_handler_impl.h
index a7809e1..e964b72df 100644
--- a/content/browser/attribution_reporting/attribution_internals_handler_impl.h
+++ b/content/browser/attribution_reporting/attribution_internals_handler_impl.h
@@ -9,6 +9,7 @@
 #include "base/scoped_observation.h"
 #include "content/browser/attribution_reporting/attribution_internals.mojom.h"
 #include "content/browser/attribution_reporting/attribution_manager.h"
+#include "content/browser/attribution_reporting/attribution_observer.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_storage.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -26,7 +27,7 @@
 // `AttributionInternalsUI`.
 class AttributionInternalsHandlerImpl
     : public mojom::AttributionInternalsHandler,
-      public AttributionManager::Observer {
+      public AttributionObserver {
  public:
   AttributionInternalsHandlerImpl(
       WebUI* web_ui,
@@ -65,7 +66,7 @@
       std::unique_ptr<AttributionManager::Provider> manager_provider);
 
  private:
-  // AttributionManager::Observer:
+  // AttributionObserver:
   void OnSourcesChanged() override;
   void OnReportsChanged() override;
   void OnSourceDeactivated(
@@ -84,7 +85,7 @@
 
   mojo::RemoteSet<mojom::AttributionInternalsObserver> observers_;
 
-  base::ScopedObservation<AttributionManager, AttributionManager::Observer>
+  base::ScopedObservation<AttributionManager, AttributionObserver>
       manager_observation_{this};
 };
 
diff --git a/content/browser/attribution_reporting/attribution_manager.h b/content/browser/attribution_reporting/attribution_manager.h
index fac50c4..49fa1e1 100644
--- a/content/browser/attribution_reporting/attribution_manager.h
+++ b/content/browser/attribution_reporting/attribution_manager.h
@@ -8,9 +8,7 @@
 #include <vector>
 
 #include "base/callback_forward.h"
-#include "base/observer_list_types.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
-#include "content/browser/attribution_reporting/attribution_storage.h"
 
 namespace base {
 class Time;
@@ -24,12 +22,11 @@
 
 class AttributionTrigger;
 class AttributionDataHostManager;
+class AttributionObserver;
 class StorableSource;
 class StoredSource;
 class WebContents;
 
-struct SendResult;
-
 // Interface that mediates data flow between the network, storage layer, and
 // blink.
 class AttributionManager {
@@ -48,32 +45,11 @@
     virtual AttributionManager* GetManager(WebContents* web_contents) const = 0;
   };
 
-  class Observer : public base::CheckedObserver {
-   public:
-    ~Observer() override = default;
-
-    virtual void OnSourcesChanged() {}
-
-    virtual void OnReportsChanged() {}
-
-    virtual void OnSourceHandled(const StorableSource& source,
-                                 StorableSource::Result result) {}
-
-    virtual void OnSourceDeactivated(
-        const AttributionStorage::DeactivatedSource& source) {}
-
-    virtual void OnReportSent(const AttributionReport& report,
-                              const SendResult& info) {}
-
-    virtual void OnTriggerHandled(
-        const AttributionStorage::CreateReportResult& result) {}
-  };
-
   virtual ~AttributionManager() = default;
 
-  virtual void AddObserver(Observer* observer) = 0;
+  virtual void AddObserver(AttributionObserver* observer) = 0;
 
-  virtual void RemoveObserver(Observer* observer) = 0;
+  virtual void RemoveObserver(AttributionObserver* observer) = 0;
 
   // Gets manager responsible for tracking pending data hosts targeting `this`.
   virtual AttributionDataHostManager* GetDataHostManager() = 0;
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc
index 4ce26ef..c33f6143 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl.cc
@@ -19,6 +19,7 @@
 #include "content/browser/attribution_reporting/attribution_cookie_checker_impl.h"
 #include "content/browser/attribution_reporting/attribution_data_host_manager_impl.h"
 #include "content/browser/attribution_reporting/attribution_info.h"
+#include "content/browser/attribution_reporting/attribution_observer.h"
 #include "content/browser/attribution_reporting/attribution_policy.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_report_network_sender.h"
@@ -241,11 +242,11 @@
                 std::move(session_only_origin_predicate));
 }
 
-void AttributionManagerImpl::AddObserver(Observer* observer) {
+void AttributionManagerImpl::AddObserver(AttributionObserver* observer) {
   observers_.AddObserver(observer);
 }
 
-void AttributionManagerImpl::RemoveObserver(Observer* observer) {
+void AttributionManagerImpl::RemoveObserver(AttributionObserver* observer) {
   observers_.RemoveObserver(observer);
 }
 
@@ -278,7 +279,7 @@
             if (!manager)
               return;
 
-            for (Observer& observer : manager->observers_)
+            for (auto& observer : manager->observers_)
               observer.OnSourceHandled(source, result.status);
 
             manager->scheduler_.ScheduleSend(result.min_fake_report_time);
@@ -414,7 +415,7 @@
     NotifySourceDeactivated(*source);
   }
 
-  for (Observer& observer : observers_)
+  for (auto& observer : observers_)
     observer.OnTriggerHandled(result);
 }
 
@@ -634,23 +635,23 @@
     return;
   }
 
-  for (Observer& observer : observers_)
+  for (auto& observer : observers_)
     observer.OnReportSent(report, info);
 }
 
 void AttributionManagerImpl::NotifySourcesChanged() {
-  for (Observer& observer : observers_)
+  for (auto& observer : observers_)
     observer.OnSourcesChanged();
 }
 
 void AttributionManagerImpl::NotifyReportsChanged() {
-  for (Observer& observer : observers_)
+  for (auto& observer : observers_)
     observer.OnReportsChanged();
 }
 
 void AttributionManagerImpl::NotifySourceDeactivated(
     const AttributionStorage::DeactivatedSource& source) {
-  for (Observer& observer : observers_)
+  for (auto& observer : observers_)
     observer.OnSourceDeactivated(source);
 }
 
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.h b/content/browser/attribution_reporting/attribution_manager_impl.h
index 0caf15b..0d54ce14 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.h
+++ b/content/browser/attribution_reporting/attribution_manager_impl.h
@@ -98,8 +98,8 @@
   ~AttributionManagerImpl() override;
 
   // AttributionManager:
-  void AddObserver(Observer* observer) override;
-  void RemoveObserver(Observer* observer) override;
+  void AddObserver(AttributionObserver* observer) override;
+  void RemoveObserver(AttributionObserver* observer) override;
   AttributionDataHostManager* GetDataHostManager() override;
   void HandleSource(StorableSource source) override;
   void HandleTrigger(AttributionTrigger trigger) override;
@@ -179,7 +179,7 @@
   // Holds pending sources and triggers in the order they were received by the
   // browser. For the time being, they must be processed in this order in order
   // to ensure that behavioral requirements are met and to ensure that
-  // `AttributionManager::Observer`s are notified in the correct order, which
+  // `AttributionObserver`s are notified in the correct order, which
   // the simulator currently depends on. We may be able to loosen this
   // requirement in the future so that there are conceptually separate queues
   // per <source origin, destination origin, reporting origin>.
@@ -203,7 +203,7 @@
   // is expected to be small, so a `flat_set` is used.
   base::flat_set<AttributionReport::EventLevelData::Id> reports_being_sent_;
 
-  base::ObserverList<Observer> observers_;
+  base::ObserverList<AttributionObserver> observers_;
 
   base::WeakPtrFactory<AttributionManagerImpl> weak_factory_;
 };
diff --git a/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc b/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
index a5c2fcc..1cea033 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
@@ -28,6 +28,7 @@
 #include "base/values.h"
 #include "build/build_config.h"
 #include "content/browser/attribution_reporting/attribution_cookie_checker.h"
+#include "content/browser/attribution_reporting/attribution_observer.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_report_sender.h"
 #include "content/browser/attribution_reporting/attribution_storage.h"
@@ -72,7 +73,7 @@
 
 using Checkpoint = ::testing::MockFunction<void(int step)>;
 
-class MockAttributionManagerObserver : public AttributionManager::Observer {
+class MockAttributionObserver : public AttributionObserver {
  public:
   MOCK_METHOD(void, OnSourcesChanged, (), (override));
 
@@ -517,9 +518,9 @@
   attribution_manager_->HandleTrigger(DefaultTrigger());
   EXPECT_THAT(StoredReports(), SizeIs(1));
 
-  MockAttributionManagerObserver observer;
-  base::ScopedObservation<AttributionManager, AttributionManager::Observer>
-      observation(&observer);
+  MockAttributionObserver observer;
+  base::ScopedObservation<AttributionManager, AttributionObserver> observation(
+      &observer);
   observation.Observe(attribution_manager_.get());
 
   // Ensure that observers are notified after the report is deleted.
@@ -610,9 +611,9 @@
 TEST_F(AttributionManagerImplTest, QueuedReportSent_ObserversNotified) {
   base::HistogramTester histograms;
 
-  MockAttributionManagerObserver observer;
-  base::ScopedObservation<AttributionManager, AttributionManager::Observer>
-      observation(&observer);
+  MockAttributionObserver observer;
+  base::ScopedObservation<AttributionManager, AttributionObserver> observation(
+      &observer);
   observation.Observe(attribution_manager_.get());
 
   EXPECT_CALL(observer, OnReportSent(ReportSourceIs(SourceEventIdIs(1u)), _));
@@ -655,9 +656,9 @@
 }
 
 TEST_F(AttributionManagerImplTest, TriggerHandled_ObserversNotified) {
-  MockAttributionManagerObserver observer;
-  base::ScopedObservation<AttributionManager, AttributionManager::Observer>
-      observation(&observer);
+  MockAttributionObserver observer;
+  base::ScopedObservation<AttributionManager, AttributionObserver> observation(
+      &observer);
   observation.Observe(attribution_manager_.get());
 
   Checkpoint checkpoint;
@@ -960,9 +961,9 @@
   attribution_manager_->HandleTrigger(DefaultTrigger());
   EXPECT_THAT(StoredReports(), SizeIs(1));
 
-  MockAttributionManagerObserver observer;
-  base::ScopedObservation<AttributionManager, AttributionManager::Observer>
-      observation(&observer);
+  MockAttributionObserver observer;
+  base::ScopedObservation<AttributionManager, AttributionObserver> observation(
+      &observer);
   observation.Observe(attribution_manager_.get());
 
   // Ensure that deleting a report notifies observers.
@@ -976,9 +977,9 @@
 }
 
 TEST_F(AttributionManagerImplTest, HandleSource_NotifiesObservers) {
-  MockAttributionManagerObserver observer;
-  base::ScopedObservation<AttributionManager, AttributionManager::Observer>
-      observation(&observer);
+  MockAttributionObserver observer;
+  base::ScopedObservation<AttributionManager, AttributionObserver> observation(
+      &observer);
   observation.Observe(attribution_manager_.get());
 
   SourceBuilder builder;
@@ -1022,9 +1023,9 @@
 }
 
 TEST_F(AttributionManagerImplTest, HandleTrigger_NotifiesObservers) {
-  MockAttributionManagerObserver observer;
-  base::ScopedObservation<AttributionManager, AttributionManager::Observer>
-      observation(&observer);
+  MockAttributionObserver observer;
+  base::ScopedObservation<AttributionManager, AttributionObserver> observation(
+      &observer);
   observation.Observe(attribution_manager_.get());
 
   SourceBuilder builder;
@@ -1089,9 +1090,9 @@
 }
 
 TEST_F(AttributionManagerImplTest, ClearData_NotifiesObservers) {
-  MockAttributionManagerObserver observer;
-  base::ScopedObservation<AttributionManager, AttributionManager::Observer>
-      observation(&observer);
+  MockAttributionObserver observer;
+  base::ScopedObservation<AttributionManager, AttributionObserver> observation(
+      &observer);
   observation.Observe(attribution_manager_.get());
 
   EXPECT_CALL(observer, OnSourcesChanged);
@@ -1124,9 +1125,9 @@
   attribution_manager_->HandleTrigger(DefaultTrigger());
   EXPECT_THAT(StoredReports(), SizeIs(1));
 
-  MockAttributionManagerObserver observer;
-  base::ScopedObservation<AttributionManager, AttributionManager::Observer>
-      observation(&observer);
+  MockAttributionObserver observer;
+  base::ScopedObservation<AttributionManager, AttributionObserver> observation(
+      &observer);
   observation.Observe(attribution_manager_.get());
 
   EXPECT_CALL(observer, OnReportSent(_, Field(&SendResult::status,
@@ -1411,9 +1412,9 @@
 
 TEST_F(AttributionManagerImplTest,
        HandleSource_NotifiesObservers_SourceHandled) {
-  MockAttributionManagerObserver observer;
-  base::ScopedObservation<AttributionManager, AttributionManager::Observer>
-      observation(&observer);
+  MockAttributionObserver observer;
+  base::ScopedObservation<AttributionManager, AttributionObserver> observation(
+      &observer);
   observation.Observe(attribution_manager_.get());
 
   const StorableSource source = SourceBuilder().Build();
diff --git a/content/browser/attribution_reporting/attribution_observer.h b/content/browser/attribution_reporting/attribution_observer.h
new file mode 100644
index 0000000..e14d3295
--- /dev/null
+++ b/content/browser/attribution_reporting/attribution_observer.h
@@ -0,0 +1,50 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_OBSERVER_H_
+#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_OBSERVER_H_
+
+#include "base/observer_list_types.h"
+#include "content/browser/attribution_reporting/attribution_storage.h"
+#include "content/browser/attribution_reporting/storable_source.h"
+
+namespace content {
+
+class AttributionReport;
+
+struct SendResult;
+
+// Observes events in the Attribution Reporting API. Observers are registered on
+// `AttributionManager`.
+class AttributionObserver : public base::CheckedObserver {
+ public:
+  ~AttributionObserver() override = default;
+
+  // Called when sources in storage change.
+  virtual void OnSourcesChanged() {}
+
+  // Called when reports in storage change.
+  virtual void OnReportsChanged() {}
+
+  // Called when a source is registered, regardless of success.
+  virtual void OnSourceHandled(const StorableSource& source,
+                               StorableSource::Result result) {}
+
+  // Called when a source is deactivated.
+  virtual void OnSourceDeactivated(
+      const AttributionStorage::DeactivatedSource& source) {}
+
+  // Called when a report is sent, regardless of success, but not for attempts
+  // that will be retried.
+  virtual void OnReportSent(const AttributionReport& report,
+                            const SendResult& info) {}
+
+  // Called when a trigger is registered, regardless of success.
+  virtual void OnTriggerHandled(
+      const AttributionStorage::CreateReportResult& result) {}
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_OBSERVER_H_
diff --git a/content/browser/attribution_reporting/attribution_test_utils.cc b/content/browser/attribution_reporting/attribution_test_utils.cc
index 9c56b04..9b11a0d 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.cc
+++ b/content/browser/attribution_reporting/attribution_test_utils.cc
@@ -18,6 +18,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/task/task_runner_util.h"
 #include "base/test/bind.h"
+#include "content/browser/attribution_reporting/attribution_observer.h"
 #include "content/browser/attribution_reporting/rate_limit_result.h"
 #include "url/gurl.h"
 
@@ -170,11 +171,11 @@
 
 MockAttributionManager::~MockAttributionManager() = default;
 
-void MockAttributionManager::AddObserver(Observer* observer) {
+void MockAttributionManager::AddObserver(AttributionObserver* observer) {
   observers_.AddObserver(observer);
 }
 
-void MockAttributionManager::RemoveObserver(Observer* observer) {
+void MockAttributionManager::RemoveObserver(AttributionObserver* observer) {
   observers_.RemoveObserver(observer);
 }
 
@@ -183,37 +184,37 @@
 }
 
 void MockAttributionManager::NotifySourcesChanged() {
-  for (Observer& observer : observers_)
+  for (auto& observer : observers_)
     observer.OnSourcesChanged();
 }
 
 void MockAttributionManager::NotifyReportsChanged() {
-  for (Observer& observer : observers_)
+  for (auto& observer : observers_)
     observer.OnReportsChanged();
 }
 
 void MockAttributionManager::NotifySourceDeactivated(
     const DeactivatedSource& source) {
-  for (Observer& observer : observers_)
+  for (auto& observer : observers_)
     observer.OnSourceDeactivated(source);
 }
 
 void MockAttributionManager::NotifySourceHandled(
     const StorableSource& source,
     StorableSource::Result result) {
-  for (Observer& observer : observers_)
+  for (auto& observer : observers_)
     observer.OnSourceHandled(source, result);
 }
 
 void MockAttributionManager::NotifyReportSent(const AttributionReport& report,
                                               const SendResult& info) {
-  for (Observer& observer : observers_)
+  for (auto& observer : observers_)
     observer.OnReportSent(report, info);
 }
 
 void MockAttributionManager::NotifyTriggerHandled(
     const AttributionStorage::CreateReportResult& result) {
-  for (Observer& observer : observers_)
+  for (auto& observer : observers_)
     observer.OnTriggerHandled(result);
 }
 
diff --git a/content/browser/attribution_reporting/attribution_test_utils.h b/content/browser/attribution_reporting/attribution_test_utils.h
index 0caff1cd..84de4407 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.h
+++ b/content/browser/attribution_reporting/attribution_test_utils.h
@@ -43,6 +43,7 @@
 namespace content {
 
 class AggregatableHistogramContribution;
+class AttributionObserver;
 class AttributionTrigger;
 
 struct AggregatableAttribution;
@@ -289,8 +290,8 @@
                base::OnceClosure done),
               (override));
 
-  void AddObserver(Observer* observer) override;
-  void RemoveObserver(Observer* observer) override;
+  void AddObserver(AttributionObserver* observer) override;
+  void RemoveObserver(AttributionObserver* observer) override;
   AttributionDataHostManager* GetDataHostManager() override;
 
   void NotifySourcesChanged();
@@ -308,7 +309,7 @@
 
  private:
   std::unique_ptr<AttributionDataHostManager> data_host_manager_;
-  base::ObserverList<Observer, /*check_empty=*/true> observers_;
+  base::ObserverList<AttributionObserver, /*check_empty=*/true> observers_;
 };
 
 // Helper class to construct a StorableSource for tests using default data.
diff --git a/content/browser/browsing_topics/OWNERS b/content/browser/browsing_topics/OWNERS
new file mode 100644
index 0000000..bb1b03b
--- /dev/null
+++ b/content/browser/browsing_topics/OWNERS
@@ -0,0 +1 @@
+file://components/browsing_topics/OWNERS
diff --git a/content/browser/browsing_topics/browsing_topics_site_data_storage.cc b/content/browser/browsing_topics/browsing_topics_site_data_storage.cc
new file mode 100644
index 0000000..c6ef8ea
--- /dev/null
+++ b/content/browser/browsing_topics/browsing_topics_site_data_storage.cc
@@ -0,0 +1,269 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/browsing_topics/browsing_topics_site_data_storage.h"
+
+#include "base/files/file_util.h"
+#include "base/metrics/histogram_functions.h"
+#include "sql/database.h"
+#include "sql/recovery.h"
+#include "sql/statement.h"
+#include "sql/transaction.h"
+#include "third_party/blink/public/common/features.h"
+
+namespace content {
+
+namespace {
+
+// Version number of the database.
+const int kCurrentVersionNumber = 1;
+
+void RecordInitializationStatus(bool successful) {
+  base::UmaHistogramBoolean("BrowsingTopics.SiteDataStorage.InitStatus",
+                            successful);
+}
+
+}  // namespace
+
+BrowsingTopicsSiteDataStorage::BrowsingTopicsSiteDataStorage(
+    const base::FilePath& path_to_database)
+    : path_to_database_(path_to_database) {
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+BrowsingTopicsSiteDataStorage::~BrowsingTopicsSiteDataStorage() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void BrowsingTopicsSiteDataStorage::ExpireDataBefore(base::Time end_time) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!LazyInit())
+    return;
+
+  static constexpr char kDeleteApiUsageSql[] =
+      // clang-format off
+      "DELETE FROM browsing_topics_api_usages "
+          "WHERE last_usage_time < ?";
+  // clang-format on
+
+  sql::Statement delete_api_usage_statement(
+      db_->GetCachedStatement(SQL_FROM_HERE, kDeleteApiUsageSql));
+  delete_api_usage_statement.BindTime(0, end_time);
+
+  delete_api_usage_statement.Run();
+}
+
+browsing_topics::ApiUsageContextQueryResult
+BrowsingTopicsSiteDataStorage::GetBrowsingTopicsApiUsage(base::Time begin_time,
+                                                         base::Time end_time) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!LazyInit())
+    return {};
+
+  // Expire data before `begin_time`, as they are no longer needed.
+  ExpireDataBefore(begin_time);
+
+  static constexpr char kGetApiUsageSql[] =
+      // clang-format off
+      "SELECT hashed_context_domain,hashed_top_host,last_usage_time "
+          "FROM browsing_topics_api_usages "
+          "WHERE last_usage_time>=? AND last_usage_time<? "
+          "ORDER BY last_usage_time DESC "
+          "LIMIT ?";
+  // clang-format on
+
+  sql::Statement statement(
+      db_->GetCachedStatement(SQL_FROM_HERE, kGetApiUsageSql));
+
+  statement.BindTime(0, begin_time);
+  statement.BindTime(1, end_time);
+  statement.BindInt(
+      2,
+      blink::features::
+          kBrowsingTopicsMaxNumberOfApiUsageContextEntriesToLoadPerEpoch.Get());
+
+  std::vector<browsing_topics::ApiUsageContext> contexts;
+  while (statement.Step()) {
+    browsing_topics::ApiUsageContext usage_context;
+    usage_context.hashed_context_domain =
+        browsing_topics::HashedDomain(statement.ColumnInt64(0));
+    usage_context.hashed_top_host =
+        browsing_topics::HashedHost(statement.ColumnInt64(1));
+    usage_context.time = statement.ColumnTime(2);
+
+    contexts.push_back(std::move(usage_context));
+  }
+
+  if (!statement.Succeeded())
+    return {};
+
+  return browsing_topics::ApiUsageContextQueryResult(std::move(contexts));
+}
+
+void BrowsingTopicsSiteDataStorage::OnBrowsingTopicsApiUsed(
+    const browsing_topics::HashedHost& hashed_top_host,
+    const base::flat_set<browsing_topics::HashedDomain>&
+        hashed_context_domains) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!LazyInit())
+    return;
+
+  sql::Transaction transaction(db_.get());
+  if (!transaction.Begin())
+    return;
+
+  base::Time current_time = base::Time::Now();
+
+  for (const browsing_topics::HashedDomain& hashed_context_domain :
+       hashed_context_domains) {
+    static constexpr char kInsertApiUsageSql[] =
+        // clang-format off
+        "INSERT OR REPLACE INTO browsing_topics_api_usages "
+            "(hashed_context_domain,hashed_top_host,last_usage_time) "
+            "VALUES (?,?,?)";
+    // clang-format on
+
+    sql::Statement insert_api_usage_statement(
+        db_->GetCachedStatement(SQL_FROM_HERE, kInsertApiUsageSql));
+    insert_api_usage_statement.BindInt64(0, hashed_context_domain.value());
+    insert_api_usage_statement.BindInt64(1, hashed_top_host.value());
+    insert_api_usage_statement.BindTime(2, current_time);
+
+    if (!insert_api_usage_statement.Run())
+      return;
+  }
+
+  transaction.Commit();
+}
+
+bool BrowsingTopicsSiteDataStorage::LazyInit() {
+  if (db_init_status_ != InitStatus::kUnattempted)
+    return db_init_status_ == InitStatus::kSuccess;
+
+  db_ = std::make_unique<sql::Database>(sql::DatabaseOptions{
+      .exclusive_locking = true, .page_size = 4096, .cache_size = 32});
+  db_->set_histogram_tag("BrowsingTopics");
+
+  // base::Unretained is safe here because this BrowsingTopicsSiteDataStorage
+  // owns the sql::Database instance that stores and uses the callback. So,
+  // `this` is guaranteed to outlive the callback.
+  db_->set_error_callback(
+      base::BindRepeating(&BrowsingTopicsSiteDataStorage::DatabaseErrorCallback,
+                          base::Unretained(this)));
+
+  if (!db_->Open(path_to_database_)) {
+    HandleInitializationFailure();
+    return false;
+  }
+
+  // TODO(yaoxia): measure metrics for the DB file size to have some idea if it
+  // gets too big.
+
+  if (!InitializeTables()) {
+    HandleInitializationFailure();
+    return false;
+  }
+
+  db_init_status_ = InitStatus::kSuccess;
+  RecordInitializationStatus(true);
+  return true;
+}
+
+bool BrowsingTopicsSiteDataStorage::InitializeTables() {
+  sql::Transaction transaction(db_.get());
+  if (!transaction.Begin())
+    return false;
+
+  if (!meta_table_.Init(db_.get(), kCurrentVersionNumber,
+                        kCurrentVersionNumber)) {
+    return false;
+  }
+
+  if (!CreateSchema())
+    return false;
+
+  // This is the first code version. No database version is expected to be
+  // smaller. Fail when this happens.
+  if (meta_table_.GetVersionNumber() < kCurrentVersionNumber)
+    return false;
+
+  if (!transaction.Commit())
+    return false;
+
+  // This is possible with code reverts. The DB will never work until Chrome
+  // is re-upgraded. Assume the user will continue using this Chrome version
+  // and raze the DB to get the feature working.
+  if (meta_table_.GetVersionNumber() > kCurrentVersionNumber) {
+    db_->Raze();
+    meta_table_.Reset();
+    return InitializeTables();
+  }
+
+  return true;
+}
+
+bool BrowsingTopicsSiteDataStorage::CreateSchema() {
+  static constexpr char kBrowsingTopicsApiUsagesTableSql[] =
+      // clang-format off
+      "CREATE TABLE IF NOT EXISTS browsing_topics_api_usages("
+          "hashed_context_domain INTEGER NOT NULL,"
+          "hashed_top_host INTEGER NOT NULL,"
+          "last_usage_time INTEGER NOT NULL,"
+          "PRIMARY KEY (hashed_context_domain,hashed_top_host))";
+  // clang-format on
+  if (!db_->Execute(kBrowsingTopicsApiUsagesTableSql))
+    return false;
+
+  static constexpr char kLastUsageTimeIndexSql[] =
+      // clang-format off
+      "CREATE INDEX IF NOT EXISTS last_usage_time_idx "
+          "ON browsing_topics_api_usages(last_usage_time)";
+  // clang-format on
+  if (!db_->Execute(kLastUsageTimeIndexSql))
+    return false;
+
+  return true;
+}
+
+void BrowsingTopicsSiteDataStorage::HandleInitializationFailure() {
+  db_.reset();
+  db_init_status_ = InitStatus::kFailure;
+  RecordInitializationStatus(false);
+}
+
+void BrowsingTopicsSiteDataStorage::DatabaseErrorCallback(
+    int extended_error,
+    sql::Statement* stmt) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Attempt to recover a corrupt database.
+  if (sql::Recovery::ShouldRecover(extended_error)) {
+    // Prevent reentrant calls.
+    db_->reset_error_callback();
+
+    // After this call, the |db_| handle is poisoned so that future calls will
+    // return errors until the handle is re-opened.
+    sql::Recovery::RecoverDatabaseWithMetaVersion(db_.get(), path_to_database_);
+
+    // The DLOG(FATAL) below is intended to draw immediate attention to errors
+    // in newly-written code. Database corruption is generally a result of OS or
+    // hardware issues, not coding errors at the client level, so displaying the
+    // error would probably lead to confusion. The ignored call signals the
+    // test-expectation framework that the error was handled.
+    std::ignore = sql::Database::IsExpectedSqliteError(extended_error);
+    return;
+  }
+
+  // The default handling is to assert on debug and to ignore on release.
+  if (!sql::Database::IsExpectedSqliteError(extended_error))
+    DLOG(FATAL) << db_->GetErrorMessage();
+
+  // Consider the database closed if we did not attempt to recover so we did not
+  // produce further errors.
+  db_init_status_ = InitStatus::kFailure;
+}
+
+}  // namespace content
diff --git a/content/browser/browsing_topics/browsing_topics_site_data_storage.h b/content/browser/browsing_topics/browsing_topics_site_data_storage.h
new file mode 100644
index 0000000..5e0815c3
--- /dev/null
+++ b/content/browser/browsing_topics/browsing_topics_site_data_storage.h
@@ -0,0 +1,103 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_BROWSING_TOPICS_BROWSING_TOPICS_SITE_DATA_STORAGE_H_
+#define CONTENT_BROWSER_BROWSING_TOPICS_BROWSING_TOPICS_SITE_DATA_STORAGE_H_
+
+#include <string>
+
+#include "base/containers/flat_set.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/thread_annotations.h"
+#include "components/browsing_topics/common/common_types.h"
+#include "content/common/content_export.h"
+#include "sql/meta_table.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace sql {
+class Database;
+class Statement;
+}  // namespace sql
+
+namespace content {
+
+class CONTENT_EXPORT BrowsingTopicsSiteDataStorage {
+ public:
+  explicit BrowsingTopicsSiteDataStorage(
+      const base::FilePath& path_to_database);
+
+  BrowsingTopicsSiteDataStorage(const BrowsingTopicsSiteDataStorage&) = delete;
+  BrowsingTopicsSiteDataStorage& operator=(
+      const BrowsingTopicsSiteDataStorage&) = delete;
+  BrowsingTopicsSiteDataStorage(BrowsingTopicsSiteDataStorage&&) = delete;
+  BrowsingTopicsSiteDataStorage& operator=(BrowsingTopicsSiteDataStorage&&) =
+      delete;
+
+  ~BrowsingTopicsSiteDataStorage();
+
+  // Expire all data before the given time.
+  void ExpireDataBefore(base::Time time);
+
+  // Get all browsing topics `ApiUsageContext` with its `last_usage_time` within
+  // [`begin_time`, `end_time`). Note that it's possible for a usage to occur
+  // within the specified time range, and a more recent usage has renewed its
+  // `last_usage_time`, so that the corresponding context is not retrieved in
+  // this query. In practice, this method will be called with
+  // `end_time` being very close to the current time, so the amount of missed
+  // data should be negligible. This query also deletes all data with
+  // last_usage_time (non-inclusive) less than `begin_time`.
+  browsing_topics::ApiUsageContextQueryResult GetBrowsingTopicsApiUsage(
+      base::Time begin_time,
+      base::Time end_time);
+
+  // Persist the browsing topics api usage context to storage. Called when the
+  // usage is detected in a context on a page.
+  void OnBrowsingTopicsApiUsed(
+      const browsing_topics::HashedHost& hashed_top_host,
+      const base::flat_set<browsing_topics::HashedDomain>&
+          hashed_context_domains);
+
+ private:
+  enum class InitStatus {
+    kUnattempted = 0,  // `LazyInit()` has not yet been called.
+    kSuccess = 1,      // `LazyInit()` succeeded.
+    kFailure = 2,      // `LazyInit()` failed.
+  };
+
+  // Initializes the database if necessary, and returns whether the database is
+  // open.
+  bool LazyInit() VALID_CONTEXT_REQUIRED(sequence_checker_);
+
+  bool InitializeTables() VALID_CONTEXT_REQUIRED(sequence_checker_);
+  bool CreateSchema() VALID_CONTEXT_REQUIRED(sequence_checker_);
+
+  void HandleInitializationFailure() VALID_CONTEXT_REQUIRED(sequence_checker_);
+
+  void DatabaseErrorCallback(int extended_error, sql::Statement* stmt);
+
+  const base::FilePath path_to_database_;
+
+  // Current status of the database initialization. Tracks what stage |this| is
+  // at for lazy initialization, and used as a signal for if the database is
+  // closed. This is initialized in the first call to LazyInit() to avoid doing
+  // additional work in the constructor.
+  InitStatus db_init_status_ GUARDED_BY_CONTEXT(sequence_checker_){
+      InitStatus::kUnattempted};
+
+  // May be null if the database:
+  //  - could not be opened
+  //  - table/index initialization failed
+  std::unique_ptr<sql::Database> db_ GUARDED_BY_CONTEXT(sequence_checker_);
+
+  sql::MetaTable meta_table_ GUARDED_BY_CONTEXT(sequence_checker_);
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  base::WeakPtrFactory<BrowsingTopicsSiteDataStorage> weak_factory_{this};
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_BROWSING_TOPICS_BROWSING_TOPICS_SITE_DATA_STORAGE_H_
diff --git a/content/browser/browsing_topics/browsing_topics_site_data_storage_unittest.cc b/content/browser/browsing_topics/browsing_topics_site_data_storage_unittest.cc
new file mode 100644
index 0000000..76e8d204
--- /dev/null
+++ b/content/browser/browsing_topics/browsing_topics_site_data_storage_unittest.cc
@@ -0,0 +1,481 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/browsing_topics/browsing_topics_site_data_storage.h"
+
+#include <functional>
+#include <memory>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/raw_ptr.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "sql/database.h"
+#include "sql/statement.h"
+#include "sql/test/scoped_error_expecter.h"
+#include "sql/test/test_helpers.h"
+#include "sql/transaction.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+int VersionFromMetaTable(sql::Database& db) {
+  // Get version.
+  sql::Statement s(
+      db.GetUniqueStatement("SELECT value FROM meta WHERE key='version'"));
+  if (!s.Step())
+    return 0;
+  return s.ColumnInt(0);
+}
+
+}  // namespace
+
+class BrowsingTopicsSiteDataStorageTest : public testing::Test {
+ public:
+  BrowsingTopicsSiteDataStorageTest()
+      : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+
+  void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+
+  void TearDown() override { EXPECT_TRUE(temp_dir_.Delete()); }
+
+  base::FilePath DbPath() const {
+    return temp_dir_.GetPath().AppendASCII("TestBrowsingTopicsSiteData.db");
+  }
+
+  base::FilePath GetSqlFilePath(base::StringPiece sql_filename) {
+    base::FilePath file_path;
+    base::PathService::Get(base::DIR_SOURCE_ROOT, &file_path);
+    file_path = file_path.AppendASCII("content/test/data/browsing_topics/");
+    file_path = file_path.AppendASCII(sql_filename);
+    EXPECT_TRUE(base::PathExists(file_path));
+    return file_path;
+  }
+
+  size_t CountApiUsagesEntries(sql::Database& db) {
+    static const char kCountSQL[] =
+        "SELECT COUNT(*) FROM browsing_topics_api_usages";
+    sql::Statement s(db.GetUniqueStatement(kCountSQL));
+    EXPECT_TRUE(s.Step());
+    return s.ColumnInt(0);
+  }
+
+  void OpenDatabase() {
+    topics_storage_.reset();
+    topics_storage_ = std::make_unique<BrowsingTopicsSiteDataStorage>(DbPath());
+  }
+
+  void CloseDatabase() { topics_storage_.reset(); }
+
+  BrowsingTopicsSiteDataStorage* topics_storage() {
+    return topics_storage_.get();
+  }
+
+ protected:
+  base::ScopedTempDir temp_dir_;
+  std::unique_ptr<BrowsingTopicsSiteDataStorage> topics_storage_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
+};
+
+TEST_F(BrowsingTopicsSiteDataStorageTest,
+       DatabaseInitialized_TablesAndIndexesLazilyInitialized) {
+  base::HistogramTester histograms;
+
+  OpenDatabase();
+  CloseDatabase();
+
+  // An unused BrowsingTopicsSiteDataStorage instance should not create the
+  // database.
+  EXPECT_FALSE(base::PathExists(DbPath()));
+
+  // DB init UMA should not be recorded.
+  histograms.ExpectTotalCount("BrowsingTopics.SiteDataStorage.InitStatus", 0);
+
+  OpenDatabase();
+  // Trigger the lazy-initialization.
+  topics_storage()->GetBrowsingTopicsApiUsage(
+      /*begin_time=*/base::Time(), /*end_time=*/base::Time());
+  CloseDatabase();
+
+  EXPECT_TRUE(base::PathExists(DbPath()));
+
+  sql::Database db;
+  EXPECT_TRUE(db.Open(DbPath()));
+
+  // [browsing_topics_api_usages], [meta].
+  EXPECT_EQ(2u, sql::test::CountSQLTables(&db));
+
+  EXPECT_EQ(1, VersionFromMetaTable(db));
+
+  // [sqlite_autoindex_browsing_topics_api_usages_1], [last_usage_time_idx],
+  // and [sqlite_autoindex_meta_1].
+  EXPECT_EQ(3u, sql::test::CountSQLIndices(&db));
+
+  // `hashed_context_domain`, `hashed_top_host`, and `last_usage_time`.
+  EXPECT_EQ(3u,
+            sql::test::CountTableColumns(&db, "browsing_topics_api_usages"));
+
+  EXPECT_EQ(0u, CountApiUsagesEntries(db));
+
+  histograms.ExpectUniqueSample("BrowsingTopics.SiteDataStorage.InitStatus",
+                                true, /*expected_bucket_count=*/1);
+}
+
+TEST_F(BrowsingTopicsSiteDataStorageTest, LoadFromFile_CurrentVersion_Success) {
+  base::HistogramTester histograms;
+
+  ASSERT_TRUE(
+      sql::test::CreateDatabaseFromSQL(DbPath(), GetSqlFilePath("v1.sql")));
+
+  OpenDatabase();
+  // Trigger the lazy-initialization.
+  topics_storage()->GetBrowsingTopicsApiUsage(
+      /*begin_time=*/base::Time(), /*end_time=*/base::Time());
+  CloseDatabase();
+
+  sql::Database db;
+  EXPECT_TRUE(db.Open(DbPath()));
+  EXPECT_EQ(2u, sql::test::CountSQLTables(&db));
+  EXPECT_EQ(1, VersionFromMetaTable(db));
+  EXPECT_EQ(1u, CountApiUsagesEntries(db));
+
+  histograms.ExpectUniqueSample("BrowsingTopics.SiteDataStorage.InitStatus",
+                                true, /*expected_bucket_count=*/1);
+}
+
+TEST_F(BrowsingTopicsSiteDataStorageTest, LoadFromFile_VersionTooOld_Failure) {
+  base::HistogramTester histograms;
+
+  ASSERT_TRUE(sql::test::CreateDatabaseFromSQL(
+      DbPath(), GetSqlFilePath("v0.init_too_old.sql")));
+
+  OpenDatabase();
+  // Trigger the lazy-initialization.
+  topics_storage()->GetBrowsingTopicsApiUsage(
+      /*begin_time=*/base::Time(), /*end_time=*/base::Time());
+  CloseDatabase();
+
+  // Expect that the initialization was unsuccessful. The original database was
+  // unaffected.
+  sql::Database db;
+  EXPECT_TRUE(db.Open(DbPath()));
+  EXPECT_EQ(2u, sql::test::CountSQLTables(&db));
+  EXPECT_EQ(0, VersionFromMetaTable(db));
+  EXPECT_EQ(1u, CountApiUsagesEntries(db));
+
+  histograms.ExpectUniqueSample("BrowsingTopics.SiteDataStorage.InitStatus",
+                                false, /*expected_bucket_count=*/1);
+}
+
+TEST_F(BrowsingTopicsSiteDataStorageTest, LoadFromFile_VersionTooNew_Failure) {
+  base::HistogramTester histograms;
+
+  ASSERT_TRUE(sql::test::CreateDatabaseFromSQL(
+      DbPath(), GetSqlFilePath("v1.init_too_new.sql")));
+
+  OpenDatabase();
+  // Trigger the lazy-initialization.
+  topics_storage()->GetBrowsingTopicsApiUsage(
+      /*begin_time=*/base::Time(), /*end_time=*/base::Time());
+  CloseDatabase();
+
+  // Expect that the initialization was successful. The original database was
+  // razed and re-initialized.
+  sql::Database db;
+  EXPECT_TRUE(db.Open(DbPath()));
+  EXPECT_EQ(2u, sql::test::CountSQLTables(&db));
+  EXPECT_EQ(1, VersionFromMetaTable(db));
+  EXPECT_EQ(0u, CountApiUsagesEntries(db));
+
+  histograms.ExpectUniqueSample("BrowsingTopics.SiteDataStorage.InitStatus",
+                                true, /*expected_bucket_count=*/1);
+}
+
+TEST_F(BrowsingTopicsSiteDataStorageTest, OnBrowsingTopicsApiUsed_SingleEntry) {
+  OpenDatabase();
+  topics_storage()->OnBrowsingTopicsApiUsed(
+      /*hashed_top_host=*/browsing_topics::HashedHost(123),
+      /*hashed_context_domains=*/{browsing_topics::HashedDomain(456)});
+  CloseDatabase();
+
+  sql::Database db;
+  EXPECT_TRUE(db.Open(DbPath()));
+  EXPECT_EQ(1u, CountApiUsagesEntries(db));
+
+  const char kGetAllEntriesSql[] =
+      "SELECT hashed_context_domain, hashed_top_host, last_usage_time FROM "
+      "browsing_topics_api_usages";
+  sql::Statement s(db.GetUniqueStatement(kGetAllEntriesSql));
+  EXPECT_TRUE(s.Step());
+
+  int64_t hashed_context_domain = s.ColumnInt64(0);
+  int64_t hashed_top_host = s.ColumnInt64(1);
+  base::Time time = s.ColumnTime(2);
+
+  EXPECT_EQ(hashed_context_domain, 456);
+  EXPECT_EQ(hashed_top_host, 123);
+  EXPECT_EQ(time, base::Time::Now());
+
+  EXPECT_FALSE(s.Step());
+}
+
+TEST_F(BrowsingTopicsSiteDataStorageTest,
+       OnBrowsingTopicsApiUsed_MultipleEntries) {
+  OpenDatabase();
+  topics_storage()->OnBrowsingTopicsApiUsed(
+      /*hashed_top_host=*/browsing_topics::HashedHost(123),
+      /*hashed_context_domains=*/{browsing_topics::HashedDomain(123)});
+
+  task_environment_.FastForwardBy(base::Seconds(1));
+
+  topics_storage()->OnBrowsingTopicsApiUsed(
+      /*hashed_top_host=*/browsing_topics::HashedHost(123),
+      /*hashed_context_domains=*/{browsing_topics::HashedDomain(456),
+                                  browsing_topics::HashedDomain(789)});
+  topics_storage()->OnBrowsingTopicsApiUsed(
+      /*hashed_top_host=*/browsing_topics::HashedHost(456),
+      /*hashed_context_domains=*/{browsing_topics::HashedDomain(789)});
+  CloseDatabase();
+
+  sql::Database db;
+  EXPECT_TRUE(db.Open(DbPath()));
+  EXPECT_EQ(4u, CountApiUsagesEntries(db));
+
+  const char kGetAllEntriesSql[] =
+      "SELECT hashed_context_domain, hashed_top_host, last_usage_time FROM "
+      "browsing_topics_api_usages "
+      "ORDER BY last_usage_time, hashed_top_host, hashed_context_domain";
+
+  sql::Statement s(db.GetUniqueStatement(kGetAllEntriesSql));
+
+  {
+    EXPECT_TRUE(s.Step());
+
+    int64_t hashed_context_domain = s.ColumnInt64(0);
+    int64_t hashed_top_host = s.ColumnInt64(1);
+    base::Time time = s.ColumnTime(2);
+
+    EXPECT_EQ(hashed_context_domain, 123);
+    EXPECT_EQ(hashed_top_host, 123);
+    EXPECT_EQ(time, base::Time::Now() - base::Seconds(1));
+  }
+
+  {
+    EXPECT_TRUE(s.Step());
+
+    int64_t hashed_context_domain = s.ColumnInt64(0);
+    int64_t hashed_top_host = s.ColumnInt64(1);
+    base::Time time = s.ColumnTime(2);
+
+    EXPECT_EQ(hashed_context_domain, 456);
+    EXPECT_EQ(hashed_top_host, 123);
+    EXPECT_EQ(time, base::Time::Now());
+  }
+
+  {
+    EXPECT_TRUE(s.Step());
+
+    int64_t hashed_context_domain = s.ColumnInt64(0);
+    int64_t hashed_top_host = s.ColumnInt64(1);
+    base::Time time = s.ColumnTime(2);
+
+    EXPECT_EQ(hashed_context_domain, 789u);
+    EXPECT_EQ(hashed_top_host, 123);
+    EXPECT_EQ(time, base::Time::Now());
+  }
+
+  {
+    EXPECT_TRUE(s.Step());
+
+    int64_t hashed_context_domain = s.ColumnInt64(0);
+    int64_t hashed_top_host = s.ColumnInt64(1);
+    base::Time time = s.ColumnTime(2);
+
+    EXPECT_EQ(hashed_context_domain, 789u);
+    EXPECT_EQ(hashed_top_host, 456);
+    EXPECT_EQ(time, base::Time::Now());
+  }
+
+  EXPECT_FALSE(s.Step());
+}
+
+TEST_F(BrowsingTopicsSiteDataStorageTest, GetBrowsingTopicsApiUsage) {
+  OpenDatabase();
+
+  topics_storage()->OnBrowsingTopicsApiUsed(
+      /*hashed_top_host=*/browsing_topics::HashedHost(123),
+      /*hashed_context_domains=*/{browsing_topics::HashedDomain(123)});
+
+  task_environment_.FastForwardBy(base::Seconds(1));
+
+  topics_storage()->OnBrowsingTopicsApiUsed(
+      /*hashed_top_host=*/browsing_topics::HashedHost(123),
+      /*hashed_context_domains=*/{browsing_topics::HashedDomain(456)});
+
+  task_environment_.FastForwardBy(base::Seconds(1));
+
+  browsing_topics::ApiUsageContextQueryResult result =
+      topics_storage()->GetBrowsingTopicsApiUsage(
+          /*begin_time=*/base::Time::Now() - base::Seconds(2),
+          /*end_time=*/base::Time::Now());
+  CloseDatabase();
+
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(result.api_usage_contexts.size(), 2u);
+
+  EXPECT_EQ(result.api_usage_contexts[0].hashed_top_host,
+            browsing_topics::HashedHost(123));
+  EXPECT_EQ(result.api_usage_contexts[0].hashed_context_domain,
+            browsing_topics::HashedDomain(456));
+  EXPECT_EQ(result.api_usage_contexts[0].time,
+            base::Time::Now() - base::Seconds(1));
+
+  EXPECT_EQ(result.api_usage_contexts[1].hashed_top_host,
+            browsing_topics::HashedHost(123));
+  EXPECT_EQ(result.api_usage_contexts[1].hashed_context_domain,
+            browsing_topics::HashedDomain(123));
+  EXPECT_EQ(result.api_usage_contexts[1].time,
+            base::Time::Now() - base::Seconds(2));
+}
+
+TEST_F(BrowsingTopicsSiteDataStorageTest,
+       GetBrowsingTopicsApiUsage_AutoExpireDataBeforeBeginTime) {
+  OpenDatabase();
+
+  topics_storage()->OnBrowsingTopicsApiUsed(
+      /*hashed_top_host=*/browsing_topics::HashedHost(123),
+      /*hashed_context_domains=*/{browsing_topics::HashedDomain(123)});
+
+  task_environment_.FastForwardBy(base::Seconds(1));
+
+  topics_storage()->OnBrowsingTopicsApiUsed(
+      /*hashed_top_host=*/browsing_topics::HashedHost(123),
+      /*hashed_context_domains=*/{browsing_topics::HashedDomain(456)});
+
+  task_environment_.FastForwardBy(base::Seconds(1));
+
+  browsing_topics::ApiUsageContextQueryResult result =
+      topics_storage()->GetBrowsingTopicsApiUsage(
+          /*begin_time=*/base::Time::Now() - base::Seconds(1),
+          /*end_time=*/base::Time::Now());
+  CloseDatabase();
+
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(result.api_usage_contexts.size(), 1u);
+  EXPECT_EQ(result.api_usage_contexts[0].hashed_top_host,
+            browsing_topics::HashedHost(123));
+  EXPECT_EQ(result.api_usage_contexts[0].hashed_context_domain,
+            browsing_topics::HashedDomain(456));
+  EXPECT_EQ(result.api_usage_contexts[0].time,
+            base::Time::Now() - base::Seconds(1));
+
+  // The `GetBrowsingTopicsApiUsage()` should have deleted the first inserted
+  // entry.
+  sql::Database db;
+  EXPECT_TRUE(db.Open(DbPath()));
+  EXPECT_EQ(1u, CountApiUsagesEntries(db));
+}
+
+TEST_F(BrowsingTopicsSiteDataStorageTest, ExpireDataBefore) {
+  OpenDatabase();
+
+  topics_storage()->OnBrowsingTopicsApiUsed(
+      /*hashed_top_host=*/browsing_topics::HashedHost(123),
+      /*hashed_context_domains=*/{browsing_topics::HashedDomain(123)});
+
+  task_environment_.FastForwardBy(base::Seconds(1));
+
+  topics_storage()->OnBrowsingTopicsApiUsed(
+      /*hashed_top_host=*/browsing_topics::HashedHost(123),
+      /*hashed_context_domains=*/{browsing_topics::HashedDomain(456)});
+
+  task_environment_.FastForwardBy(base::Seconds(1));
+
+  topics_storage()->ExpireDataBefore(base::Time::Now() - base::Seconds(1));
+  CloseDatabase();
+
+  sql::Database db;
+  EXPECT_TRUE(db.Open(DbPath()));
+  EXPECT_EQ(1u, CountApiUsagesEntries(db));
+
+  // The `ExpireDataBefore()` should have deleted the first inserted entry.
+  const char kGetAllEntriesSql[] =
+      "SELECT hashed_context_domain, hashed_top_host, last_usage_time FROM "
+      "browsing_topics_api_usages";
+  sql::Statement s(db.GetUniqueStatement(kGetAllEntriesSql));
+  EXPECT_TRUE(s.Step());
+
+  int64_t hashed_context_domain = s.ColumnInt64(0);
+  int64_t hashed_top_host = s.ColumnInt64(1);
+  base::Time time = s.ColumnTime(2);
+
+  EXPECT_EQ(hashed_context_domain, 456);
+  EXPECT_EQ(hashed_top_host, 123);
+  EXPECT_EQ(time, base::Time::Now() - base::Seconds(1));
+
+  EXPECT_FALSE(s.Step());
+}
+
+class BrowsingTopicsSiteDataStorageMaxEntriesToLoadTest
+    : public BrowsingTopicsSiteDataStorageTest {
+ public:
+  BrowsingTopicsSiteDataStorageMaxEntriesToLoadTest() {
+    feature_list_.InitAndEnableFeatureWithParameters(
+        blink::features::kBrowsingTopics,
+        {{"max_number_of_api_usage_context_entries_to_load_per_epoch", "1"}});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(BrowsingTopicsSiteDataStorageMaxEntriesToLoadTest, MaxEntriesToLoad) {
+  OpenDatabase();
+
+  topics_storage()->OnBrowsingTopicsApiUsed(
+      /*hashed_top_host=*/browsing_topics::HashedHost(123),
+      /*hashed_context_domains=*/{browsing_topics::HashedDomain(123)});
+
+  task_environment_.FastForwardBy(base::Seconds(1));
+
+  topics_storage()->OnBrowsingTopicsApiUsed(
+      /*hashed_top_host=*/browsing_topics::HashedHost(123),
+      /*hashed_context_domains=*/{browsing_topics::HashedDomain(456)});
+
+  task_environment_.FastForwardBy(base::Seconds(1));
+
+  browsing_topics::ApiUsageContextQueryResult result =
+      topics_storage()->GetBrowsingTopicsApiUsage(
+          /*begin_time=*/base::Time::Now() - base::Seconds(2),
+          /*end_time=*/base::Time::Now());
+  CloseDatabase();
+
+  // Only the latest entry is loaded; the storage should still have two entries.
+  EXPECT_TRUE(result.success);
+  EXPECT_EQ(result.api_usage_contexts.size(), 1u);
+
+  EXPECT_EQ(result.api_usage_contexts[0].hashed_top_host,
+            browsing_topics::HashedHost(123));
+  EXPECT_EQ(result.api_usage_contexts[0].hashed_context_domain,
+            browsing_topics::HashedDomain(456));
+  EXPECT_EQ(result.api_usage_contexts[0].time,
+            base::Time::Now() - base::Seconds(1));
+
+  sql::Database db;
+  EXPECT_TRUE(db.Open(DbPath()));
+  EXPECT_EQ(2u, CountApiUsagesEntries(db));
+}
+
+}  // namespace content
diff --git a/content/browser/file_system_access/safe_move_helper.cc b/content/browser/file_system_access/safe_move_helper.cc
index bea27b8..414e39f 100644
--- a/content/browser/file_system_access/safe_move_helper.cc
+++ b/content/browser/file_system_access/safe_move_helper.cc
@@ -16,6 +16,7 @@
 #include "components/services/quarantine/quarantine.h"
 #include "content/browser/file_system_access/file_system_access_error.h"
 #include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/global_routing_id.h"
 #include "content/public/common/content_client.h"
 #include "crypto/secure_hash.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
@@ -197,12 +198,18 @@
     return;
   }
 
+  content::GlobalRenderFrameHostId outermost_main_frame_id;
+  auto* rfh = content::RenderFrameHost::FromID(context_.frame_id);
+  if (rfh)
+    outermost_main_frame_id = rfh->GetOutermostMainFrame()->GetGlobalId();
+
   auto item = std::make_unique<FileSystemAccessWriteItem>();
   item->target_file_path = dest_url().path();
   item->full_path = source_url().path();
   item->sha256_hash = hash;
   item->size = size;
   item->frame_url = context_.url;
+  item->outermost_main_frame_id = outermost_main_frame_id;
   item->has_user_gesture = has_transient_user_activation_;
   manager_->permission_context()->PerformAfterWriteChecks(
       std::move(item), context_.frame_id,
diff --git a/content/public/browser/file_system_access_write_item.h b/content/public/browser/file_system_access_write_item.h
index 5d8591d..cd6a4fe 100644
--- a/content/public/browser/file_system_access_write_item.h
+++ b/content/public/browser/file_system_access_write_item.h
@@ -11,6 +11,7 @@
 #include "base/files/file_path.h"
 #include "base/memory/raw_ptr.h"
 #include "content/common/content_export.h"
+#include "content/public/browser/global_routing_id.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -40,6 +41,10 @@
 
   // URL of the frame in which the write operation took place.
   GURL frame_url;
+
+  // id of the outermost main frame in which the write operation took place.
+  GlobalRenderFrameHostId outermost_main_frame_id;
+
   // True iff the frame had a transient user activation when the writer was
   // created.
   bool has_user_gesture = false;
diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc
index bda9909..b86bdbc 100644
--- a/content/public/test/render_view_test.cc
+++ b/content/public/test/render_view_test.cc
@@ -850,7 +850,7 @@
       ->DidFinishSameDocumentNavigation(
           is_new_navigation ? blink::kWebStandardCommit
                             : blink::kWebHistoryInertCommit,
-          false /* is_synchronously_committed */,
+          true /* is_synchronously_committed */,
           blink::mojom::SameDocumentNavigationType::kFragment,
           false /* is_client_redirect */);
 }
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index f20d944..717ec6b3 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -74,8 +74,6 @@
     "gpu_benchmarking_extension.h",
     "in_process_renderer_thread.cc",
     "in_process_renderer_thread.h",
-    "internal_document_state_data.cc",
-    "internal_document_state_data.h",
     "media/audio_decoder.cc",
     "media/audio_decoder.h",
     "media/batching_media_log.cc",
diff --git a/content/renderer/document_state.cc b/content/renderer/document_state.cc
index 7e46571..961fcf2 100644
--- a/content/renderer/document_state.cc
+++ b/content/renderer/document_state.cc
@@ -4,18 +4,17 @@
 
 #include "content/renderer/document_state.h"
 
+#include "content/renderer/navigation_state.h"
+
 namespace content {
 
-DocumentState::DocumentState() : was_load_data_with_base_url_request_(false) {}
+DocumentState::DocumentState() {}
 
 DocumentState::~DocumentState() {}
 
-std::unique_ptr<DocumentState> DocumentState::Clone() {
-  std::unique_ptr<DocumentState> new_document_state(new DocumentState());
-  new_document_state->set_was_load_data_with_base_url_request(
-      was_load_data_with_base_url_request_);
-  new_document_state->set_data_url(data_url_);
-  return new_document_state;
+void DocumentState::set_navigation_state(
+    std::unique_ptr<NavigationState> navigation_state) {
+  navigation_state_ = std::move(navigation_state);
 }
 
 }  // namespace content
diff --git a/content/renderer/document_state.h b/content/renderer/document_state.h
index ea522fb..3a2d552 100644
--- a/content/renderer/document_state.h
+++ b/content/renderer/document_state.h
@@ -15,10 +15,12 @@
 
 namespace content {
 
-// The RenderView stores an instance of this class in the "extra data" of each
-// WebDocumentLoader (see RenderView::DidCreateDataSource).
-class CONTENT_EXPORT DocumentState : public blink::WebDocumentLoader::ExtraData,
-                                     public base::SupportsUserData {
+class NavigationState;
+
+// RenderFrameImpl stores an instance of this class in the "extra data" of each
+// WebDocumentLoader.
+class CONTENT_EXPORT DocumentState
+    : public blink::WebDocumentLoader::ExtraData {
  public:
   DocumentState();
   ~DocumentState() override;
@@ -28,10 +30,6 @@
     return static_cast<DocumentState*>(document_loader->GetExtraData());
   }
 
-  // Returns a copy of the DocumentState. This is a shallow copy,
-  // user data is not copied.
-  std::unique_ptr<DocumentState> Clone();
-
   // For LoadDataWithBaseURL navigations, |was_load_data_with_base_url_request_|
   // is set to true and |data_url_| is set to the data URL of the navigation.
   // Otherwise, |was_load_data_with_base_url_request_| is false and |data_url_|
@@ -51,9 +49,39 @@
   const GURL& data_url() const { return data_url_; }
   void set_data_url(const GURL& data_url) { data_url_ = data_url; }
 
+  // True if the user agent was overridden for this page.
+  bool is_overriding_user_agent() const { return is_overriding_user_agent_; }
+  void set_is_overriding_user_agent(bool state) {
+    is_overriding_user_agent_ = state;
+  }
+
+  // True if we have to reset the scroll and scale state of the page
+  // after the provisional load has been committed.
+  bool must_reset_scroll_and_scale_state() const {
+    return must_reset_scroll_and_scale_state_;
+  }
+  void set_must_reset_scroll_and_scale_state(bool state) {
+    must_reset_scroll_and_scale_state_ = state;
+  }
+
+  // This is a fake navigation request id, which we send to the browser process
+  // together with metrics. Note that renderer does not actually issue a request
+  // for navigation (browser does it instead), but still reports metrics for it.
+  // See content::mojom::ResourceLoadInfo.
+  int request_id() const { return request_id_; }
+  void set_request_id(int request_id) { request_id_ = request_id; }
+
+  NavigationState* navigation_state() { return navigation_state_.get(); }
+  void set_navigation_state(std::unique_ptr<NavigationState> navigation_state);
+  void clear_navigation_state() { navigation_state_.reset(); }
+
  private:
-  bool was_load_data_with_base_url_request_;
+  bool was_load_data_with_base_url_request_ = false;
   GURL data_url_;
+  bool is_overriding_user_agent_ = false;
+  bool must_reset_scroll_and_scale_state_ = false;
+  int request_id_ = -1;
+  std::unique_ptr<NavigationState> navigation_state_;
 };
 
 }  // namespace content
diff --git a/content/renderer/internal_document_state_data.cc b/content/renderer/internal_document_state_data.cc
deleted file mode 100644
index 906d61ab..0000000
--- a/content/renderer/internal_document_state_data.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/renderer/internal_document_state_data.h"
-
-#include "base/memory/ptr_util.h"
-#include "content/renderer/document_state.h"
-#include "content/renderer/navigation_state.h"
-#include "third_party/blink/public/web/web_document_loader.h"
-
-namespace content {
-
-namespace {
-
-// Key InternalDocumentStateData is stored under in DocumentState.
-const char kUserDataKey[] = "InternalDocumentStateData";
-
-}
-
-InternalDocumentStateData::InternalDocumentStateData()
-    : is_overriding_user_agent_(false),
-      must_reset_scroll_and_scale_state_(false) {}
-
-// static
-InternalDocumentStateData* InternalDocumentStateData::FromDocumentLoader(
-    blink::WebDocumentLoader* document_loader) {
-  return FromDocumentState(
-      static_cast<DocumentState*>(document_loader->GetExtraData()));
-}
-
-// static
-InternalDocumentStateData* InternalDocumentStateData::FromDocumentState(
-    DocumentState* ds) {
-  if (!ds)
-    return nullptr;
-  InternalDocumentStateData* data = static_cast<InternalDocumentStateData*>(
-      ds->GetUserData(&kUserDataKey));
-  if (!data) {
-    data = new InternalDocumentStateData;
-    ds->SetUserData(&kUserDataKey, base::WrapUnique(data));
-  }
-  return data;
-}
-
-InternalDocumentStateData::~InternalDocumentStateData() {
-}
-
-void InternalDocumentStateData::CopyFrom(InternalDocumentStateData* other) {
-  is_overriding_user_agent_ = other->is_overriding_user_agent_;
-  must_reset_scroll_and_scale_state_ =
-      other->must_reset_scroll_and_scale_state_;
-  effective_connection_type_ = other->effective_connection_type_;
-  request_id_ = other->request_id_;
-}
-
-void InternalDocumentStateData::set_navigation_state(
-    std::unique_ptr<NavigationState> navigation_state) {
-  navigation_state_ = std::move(navigation_state);
-}
-
-}  // namespace content
diff --git a/content/renderer/internal_document_state_data.h b/content/renderer/internal_document_state_data.h
deleted file mode 100644
index 515e000..0000000
--- a/content/renderer/internal_document_state_data.h
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_RENDERER_INTERNAL_DOCUMENT_STATE_DATA_H_
-#define CONTENT_RENDERER_INTERNAL_DOCUMENT_STATE_DATA_H_
-
-#include <memory>
-
-#include "base/supports_user_data.h"
-#include "net/nqe/effective_connection_type.h"
-#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
-#include "url/gurl.h"
-
-namespace blink {
-class WebDocumentLoader;
-}
-
-namespace content {
-
-class DocumentState;
-class NavigationState;
-
-// Stores internal state per WebDocumentLoader.
-class InternalDocumentStateData : public base::SupportsUserData::Data {
- public:
-  InternalDocumentStateData();
-
-  InternalDocumentStateData(const InternalDocumentStateData&) = delete;
-  InternalDocumentStateData& operator=(const InternalDocumentStateData&) =
-      delete;
-
-  ~InternalDocumentStateData() override;
-
-  static InternalDocumentStateData* FromDocumentLoader(
-      blink::WebDocumentLoader* document_loader);
-  static InternalDocumentStateData* FromDocumentState(DocumentState* ds);
-
-  void CopyFrom(InternalDocumentStateData* other);
-
-  // True if the user agent was overridden for this page.
-  bool is_overriding_user_agent() const { return is_overriding_user_agent_; }
-  void set_is_overriding_user_agent(bool state) {
-    is_overriding_user_agent_ = state;
-  }
-
-  // True if we have to reset the scroll and scale state of the page
-  // after the provisional load has been committed.
-  bool must_reset_scroll_and_scale_state() const {
-    return must_reset_scroll_and_scale_state_;
-  }
-  void set_must_reset_scroll_and_scale_state(bool state) {
-    must_reset_scroll_and_scale_state_ = state;
-  }
-
-  net::EffectiveConnectionType effective_connection_type() const {
-    return effective_connection_type_;
-  }
-  void set_effective_connection_type(
-      net::EffectiveConnectionType effective_connection_type) {
-    effective_connection_type_ = effective_connection_type;
-  }
-
-  // This is a fake navigation request id, which we send to the browser process
-  // together with metrics. Note that renderer does not actually issue a request
-  // for navigation (browser does it instead), but still reports metrics for it.
-  // See content::mojom::ResourceLoadInfo.
-  int request_id() const { return request_id_; }
-  void set_request_id(int request_id) { request_id_ = request_id; }
-
-  NavigationState* navigation_state() { return navigation_state_.get(); }
-  void set_navigation_state(std::unique_ptr<NavigationState> navigation_state);
-
- private:
-  bool is_overriding_user_agent_;
-  bool must_reset_scroll_and_scale_state_;
-  net::EffectiveConnectionType effective_connection_type_ =
-      net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
-  int request_id_ = -1;
-  std::unique_ptr<NavigationState> navigation_state_;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_RENDERER_INTERNAL_DOCUMENT_STATE_DATA_H_
diff --git a/content/renderer/navigation_state.cc b/content/renderer/navigation_state.cc
index fc7ddc7..d459902 100644
--- a/content/renderer/navigation_state.cc
+++ b/content/renderer/navigation_state.cc
@@ -9,7 +9,6 @@
 
 #include "base/memory/ptr_util.h"
 #include "content/common/frame_messages.mojom.h"
-#include "content/renderer/internal_document_state_data.h"
 #include "third_party/blink/public/common/navigation/navigation_params.h"
 #include "third_party/blink/public/mojom/commit_result/commit_result.mojom.h"
 
@@ -43,13 +42,6 @@
       /*was_initiated_in_this_frame=*/true));
 }
 
-// static
-NavigationState* NavigationState::FromDocumentLoader(
-    blink::WebDocumentLoader* document_loader) {
-  return InternalDocumentStateData::FromDocumentLoader(document_loader)
-      ->navigation_state();
-}
-
 bool NavigationState::WasWithinSameDocument() {
   return was_within_same_document_;
 }
diff --git a/content/renderer/navigation_state.h b/content/renderer/navigation_state.h
index f1661d94..cba58a9f 100644
--- a/content/renderer/navigation_state.h
+++ b/content/renderer/navigation_state.h
@@ -13,12 +13,10 @@
 #include "third_party/blink/public/mojom/navigation/navigation_params.mojom.h"
 
 namespace blink {
-class WebDocumentLoader;
-
 namespace mojom {
 enum class CommitResult;
 }
-}
+}  // namespace blink
 
 namespace content {
 
@@ -39,9 +37,6 @@
 
   static std::unique_ptr<NavigationState> CreateForSynchronousCommit();
 
-  static NavigationState* FromDocumentLoader(
-      blink::WebDocumentLoader* document_loader);
-
   // True iff the frame's navigation was within the same document.
   bool WasWithinSameDocument();
 
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index f45c6d7ad..345f6880 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -86,7 +86,6 @@
 #include "content/renderer/effective_connection_type_helper.h"
 #include "content/renderer/frame_owner_properties_converter.h"
 #include "content/renderer/gpu_benchmarking_extension.h"
-#include "content/renderer/internal_document_state_data.h"
 #include "content/renderer/media/media_permission_dispatcher.h"
 #include "content/renderer/mhtml_handle_writer.h"
 #include "content/renderer/mojo/blink_interface_registry_impl.h"
@@ -370,7 +369,7 @@
                                      bool is_main_frame,
                                      bool is_in_fenced_frame_tree) {
   NavigationState* navigation_state =
-      NavigationState::FromDocumentLoader(document_loader);
+      DocumentState::FromDocumentLoader(document_loader)->navigation_state();
   ui::PageTransition default_transition =
       navigation_state->IsForSynchronousCommit()
           ? ui::PAGE_TRANSITION_LINK
@@ -877,8 +876,8 @@
 std::unique_ptr<DocumentState> BuildDocumentState() {
   std::unique_ptr<DocumentState> document_state =
       std::make_unique<DocumentState>();
-  InternalDocumentStateData::FromDocumentState(document_state.get())
-      ->set_navigation_state(NavigationState::CreateForSynchronousCommit());
+  document_state->set_navigation_state(
+      NavigationState::CreateForSynchronousCommit());
   return document_state;
 }
 
@@ -893,18 +892,16 @@
     bool was_initiated_in_this_frame,
     bool is_main_frame) {
   std::unique_ptr<DocumentState> document_state(new DocumentState());
-  InternalDocumentStateData* internal_data =
-      InternalDocumentStateData::FromDocumentState(document_state.get());
 
   DCHECK(!common_params.navigation_start.is_null());
   DCHECK(!common_params.url.SchemeIs(url::kJavaScriptScheme));
 
-  internal_data->set_is_overriding_user_agent(
+  document_state->set_is_overriding_user_agent(
       commit_params.is_overriding_user_agent);
-  internal_data->set_must_reset_scroll_and_scale_state(
+  document_state->set_must_reset_scroll_and_scale_state(
       common_params.navigation_type ==
       blink::mojom::NavigationType::RELOAD_ORIGINAL_REQUEST_URL);
-  internal_data->set_request_id(request_id);
+  document_state->set_request_id(request_id);
 
   // If this is a loadDataWithBaseURL request, save the commit URL so that we
   // can send a DidCommit message with the URL that was originally sent by the
@@ -915,11 +912,9 @@
   if (load_data)
     document_state->set_data_url(common_params.url);
 
-  InternalDocumentStateData::FromDocumentState(document_state.get())
-      ->set_navigation_state(NavigationState::Create(
-          common_params.Clone(), commit_params.Clone(),
-          std::move(commit_callback), std::move(navigation_client),
-          was_initiated_in_this_frame));
+  document_state->set_navigation_state(NavigationState::Create(
+      common_params.Clone(), commit_params.Clone(), std::move(commit_callback),
+      std::move(navigation_client), was_initiated_in_this_frame));
   return document_state;
 }
 
@@ -3156,19 +3151,13 @@
     if (common_params->initiator_origin)
       initiator_origin = common_params->initiator_origin.value();
 
-    DocumentState* original_document_state =
+    DocumentState* document_state =
         DocumentState::FromDocumentLoader(frame_->GetDocumentLoader());
-    std::unique_ptr<DocumentState> document_state =
-        original_document_state->Clone();
-    InternalDocumentStateData* internal_data =
-        InternalDocumentStateData::FromDocumentState(document_state.get());
-    internal_data->CopyFrom(
-        InternalDocumentStateData::FromDocumentState(original_document_state));
     // This is a same-document navigation coming from the browser process (as
     // opposed to a fragment link click, which would have been handled
     // synchronously in the renderer process), therefore
     // |was_initiated_in_this_frame| must be false.
-    internal_data->set_navigation_state(NavigationState::Create(
+    document_state->set_navigation_state(NavigationState::Create(
         std::move(common_params), std::move(commit_params),
         mojom::NavigationClient::CommitNavigationCallback(), nullptr,
         false /* was_initiated_in_this_frame */));
@@ -3177,7 +3166,7 @@
     commit_status = frame_->CommitSameDocumentNavigation(
         url, load_type, item_for_history_navigation, is_client_redirect,
         started_with_transient_activation, initiator_origin,
-        is_browser_initiated, std::move(document_state));
+        is_browser_initiated);
 
     // The load of the URL can result in this frame being removed. Use a
     // WeakPtr as an easy way to detect whether this has occured. If so, this
@@ -3752,9 +3741,9 @@
   navigation_commit_state_ = NavigationCommitState::kDidCommit;
 
   WebDocumentLoader* document_loader = frame_->GetDocumentLoader();
-  InternalDocumentStateData* internal_data =
-      InternalDocumentStateData::FromDocumentLoader(document_loader);
-  NavigationState* navigation_state = internal_data->navigation_state();
+  DocumentState* document_state =
+      DocumentState::FromDocumentLoader(document_loader);
+  NavigationState* navigation_state = document_state->navigation_state();
   DCHECK(!navigation_state->WasWithinSameDocument());
 
   TRACE_EVENT2("navigation,benchmark,rail",
@@ -3905,10 +3894,14 @@
   UpdateEncoding(frame_, frame_->View()->PageEncoding().Utf8());
 
   NotifyObserversOfNavigationCommit(transition);
+
+  document_state->clear_navigation_state();
 }
 
 void RenderFrameImpl::DidCommitDocumentReplacementNavigation(
     blink::WebDocumentLoader* document_loader) {
+  DocumentState::FromDocumentLoader(document_loader)
+      ->set_navigation_state(NavigationState::CreateForSynchronousCommit());
   // TODO(https://crbug.com/855189): figure out which of the following observer
   // calls are necessary, if any.
   for (auto& observer : observers_)
@@ -4049,11 +4042,13 @@
                "RenderFrameImpl::didFinishSameDocumentNavigation", "id",
                routing_id_);
   WebDocumentLoader* document_loader = frame_->GetDocumentLoader();
-  InternalDocumentStateData* data =
-      InternalDocumentStateData::FromDocumentLoader(document_loader);
-  if (is_synchronously_committed)
-    data->set_navigation_state(NavigationState::CreateForSynchronousCommit());
-  data->navigation_state()->set_was_within_same_document(true);
+  DocumentState* document_state =
+      DocumentState::FromDocumentLoader(document_loader);
+  if (is_synchronously_committed) {
+    document_state->set_navigation_state(
+        NavigationState::CreateForSynchronousCommit());
+  }
+  document_state->navigation_state()->set_was_within_same_document(true);
 
   ui::PageTransition transition = GetTransitionType(
       document_loader, IsMainFrame(), GetWebView()->IsFencedFrameRoot());
@@ -4075,10 +4070,13 @@
 
   // If we end up reusing this WebRequest (for example, due to a #ref click),
   // we don't want the transition type to persist.  Just clear it.
-  data->navigation_state()->set_transition_type(ui::PAGE_TRANSITION_LINK);
+  document_state->navigation_state()->set_transition_type(
+      ui::PAGE_TRANSITION_LINK);
 
   for (auto& observer : observers_)
     observer.DidFinishSameDocumentNavigation();
+
+  document_state->clear_navigation_state();
 }
 
 void RenderFrameImpl::WillFreezePage() {
@@ -4516,11 +4514,10 @@
   const WebLocalFrame* main_frame = web_view->MainFrame()->ToWebLocalFrame();
 
   WebDocumentLoader* document_loader = main_frame->GetDocumentLoader();
-  InternalDocumentStateData* internal_data =
-      document_loader
-          ? InternalDocumentStateData::FromDocumentLoader(document_loader)
-          : nullptr;
-  return internal_data && internal_data->is_overriding_user_agent();
+  DocumentState* document_state =
+      document_loader ? DocumentState::FromDocumentLoader(document_loader)
+                      : nullptr;
+  return document_state && document_state->is_overriding_user_agent();
 }
 
 blink::WebString RenderFrameImpl::DoNotTrackValue() {
@@ -4632,10 +4629,9 @@
   WebDocumentLoader* document_loader = frame_->GetDocumentLoader();
   const WebURLResponse& response = document_loader->GetResponse();
 
-  InternalDocumentStateData* internal_data =
-      InternalDocumentStateData::FromDocumentLoader(
-          frame_->GetDocumentLoader());
-  NavigationState* navigation_state = internal_data->navigation_state();
+  DocumentState* document_state =
+      DocumentState::FromDocumentLoader(frame_->GetDocumentLoader());
+  NavigationState* navigation_state = document_state->navigation_state();
 
   auto params = mojom::DidCommitProvisionalLoadParams::New();
   params->http_status_code = response.HttpStatusCode();
@@ -4753,7 +4749,7 @@
 
     // Send the user agent override back.
     params->is_overriding_user_agent =
-        internal_data->is_overriding_user_agent();
+        document_state->is_overriding_user_agent();
 
     params->history_list_was_cleared =
         navigation_state->commit_params().should_clear_history_list;
@@ -4799,7 +4795,7 @@
           requires_universal_access);
     }
   }
-  params->request_id = internal_data->request_id();
+  params->request_id = document_state->request_id();
 
   params->unload_start = GetWebFrame()->Performance().UnloadStart();
   params->unload_end = GetWebFrame()->Performance().UnloadEnd();
@@ -4812,7 +4808,8 @@
 void RenderFrameImpl::UpdateNavigationHistory(
     blink::WebHistoryCommitType commit_type) {
   NavigationState* navigation_state =
-      NavigationState::FromDocumentLoader(frame_->GetDocumentLoader());
+      DocumentState::FromDocumentLoader(frame_->GetDocumentLoader())
+          ->navigation_state();
   const blink::mojom::CommitNavigationParams& commit_params =
       navigation_state->commit_params();
 
@@ -4847,10 +4844,9 @@
 void RenderFrameImpl::UpdateStateForCommit(
     blink::WebHistoryCommitType commit_type,
     ui::PageTransition transition) {
-  InternalDocumentStateData* internal_data =
-      InternalDocumentStateData::FromDocumentLoader(
-          frame_->GetDocumentLoader());
-  NavigationState* navigation_state = internal_data->navigation_state();
+  DocumentState* document_state =
+      DocumentState::FromDocumentLoader(frame_->GetDocumentLoader());
+  NavigationState* navigation_state = document_state->navigation_state();
 
   // We need to update the last committed session history entry with state for
   // the previous page. Do this before updating the current history item.
@@ -4858,9 +4854,9 @@
 
   UpdateNavigationHistory(commit_type);
 
-  if (internal_data->must_reset_scroll_and_scale_state()) {
+  if (document_state->must_reset_scroll_and_scale_state()) {
     GetWebView()->ResetScrollAndScaleState();
-    internal_data->set_must_reset_scroll_and_scale_state(false);
+    document_state->set_must_reset_scroll_and_scale_state(false);
   }
   if (!frame_->Parent()) {  // Only for top frames.
     RenderThreadImpl* render_thread_impl = RenderThreadImpl::current();
@@ -4933,7 +4929,8 @@
         std::move(params), std::move(same_document_params));
   } else {
     NavigationState* navigation_state =
-        NavigationState::FromDocumentLoader(frame_->GetDocumentLoader());
+        DocumentState::FromDocumentLoader(frame_->GetDocumentLoader())
+            ->navigation_state();
     if (navigation_state->has_navigation_client()) {
       navigation_state->RunCommitNavigationCallback(
           std::move(params), std::move(interface_params));
diff --git a/content/renderer/render_view_browsertest.cc b/content/renderer/render_view_browsertest.cc
index 7e4d769..b915910 100644
--- a/content/renderer/render_view_browsertest.cc
+++ b/content/renderer/render_view_browsertest.cc
@@ -50,6 +50,7 @@
 #include "content/renderer/accessibility/render_accessibility_impl.h"
 #include "content/renderer/accessibility/render_accessibility_manager.h"
 #include "content/renderer/agent_scheduling_group.h"
+#include "content/renderer/document_state.h"
 #include "content/renderer/navigation_state.h"
 #include "content/renderer/render_frame_proxy.h"
 #include "content/renderer/render_process.h"
@@ -278,6 +279,32 @@
   return interfaces;
 }
 
+// Helper that collects the CommonNavigationParams off of WebDocumentLoader's
+// NavigationState during commit. The NavigationState is cleared when commit
+// notifications are done, so any assertions about the CommonNavigationParams
+// post-commit require the CommonNavigationParams to be stored manually.
+class CommonParamsFrameLoadWaiter : public FrameLoadWaiter {
+ public:
+  explicit CommonParamsFrameLoadWaiter(RenderFrameImpl* frame)
+      : FrameLoadWaiter(frame), frame_(frame) {}
+
+  const blink::mojom::CommonNavigationParamsPtr& common_params() {
+    return common_params_;
+  }
+
+ private:
+  void DidCommitProvisionalLoad(ui::PageTransition transition) override {
+    NavigationState* navigation_state =
+        DocumentState::FromDocumentLoader(
+            frame_->GetWebFrame()->GetDocumentLoader())
+            ->navigation_state();
+    common_params_ = navigation_state->common_params().Clone();
+  }
+
+  blink::mojom::CommonNavigationParamsPtr common_params_;
+  const RenderFrameImpl* frame_;
+};
+
 }  // namespace
 
 class RenderViewImplTest : public RenderViewTest {
@@ -333,7 +360,7 @@
     web_view_->EnableDeviceEmulation(params);
   }
 
-  void GoToOffsetWithParams(
+  blink::mojom::CommonNavigationParamsPtr GoToOffsetWithParams(
       int offset,
       const blink::PageState& state,
       blink::mojom::CommonNavigationParamsPtr common_params,
@@ -342,6 +369,10 @@
     blink::WebView* webview = web_view_;
     int pending_offset = offset + webview->HistoryBackListCount();
 
+    // The load actually happens asynchronously, so we pump messages to process
+    // the pending continuation.
+    CommonParamsFrameLoadWaiter waiter(frame());
+
     commit_params->page_state = state.ToEncodedData();
     commit_params->nav_entry_id = pending_offset + 1;
     commit_params->pending_history_list_offset = pending_offset;
@@ -352,9 +383,8 @@
         1;
     frame()->Navigate(std::move(common_params), std::move(commit_params));
 
-    // The load actually happens asynchronously, so we pump messages to process
-    // the pending continuation.
-    FrameLoadWaiter(frame()).Wait();
+    waiter.Wait();
+    return waiter.common_params()->Clone();
   }
 
   template <class T>
@@ -2632,16 +2662,14 @@
 // recorded at an appropriate time and is passed in the corresponding message.
 TEST_F(RenderViewImplTest, RendererNavigationStartTransmittedToBrowser) {
   base::TimeTicks lower_bound_navigation_start(base::TimeTicks::Now());
-  FrameLoadWaiter waiter(frame());
+  CommonParamsFrameLoadWaiter waiter(frame());
   frame()->LoadHTMLStringForTesting("hello world", GURL("data:text/html,"),
                                     "UTF-8", GURL(),
                                     false /* replace_current_item */);
   waiter.Wait();
-  NavigationState* navigation_state = NavigationState::FromDocumentLoader(
-      frame()->GetWebFrame()->GetDocumentLoader());
-  EXPECT_FALSE(navigation_state->common_params().navigation_start.is_null());
+  EXPECT_FALSE(waiter.common_params()->navigation_start.is_null());
   EXPECT_LE(lower_bound_navigation_start,
-            navigation_state->common_params().navigation_start);
+            waiter.common_params()->navigation_start);
 }
 
 // Checks that a browser-initiated navigation in an initial document that was
@@ -2651,13 +2679,11 @@
 TEST_F(RenderViewImplTest, BrowserNavigationStart) {
   auto common_params = MakeCommonNavigationParams(-base::Seconds(1));
 
-  FrameLoadWaiter waiter(frame());
+  CommonParamsFrameLoadWaiter waiter(frame());
   frame()->Navigate(common_params.Clone(), DummyCommitNavigationParams());
   waiter.Wait();
-  NavigationState* navigation_state = NavigationState::FromDocumentLoader(
-      frame()->GetWebFrame()->GetDocumentLoader());
   EXPECT_EQ(common_params->navigation_start,
-            navigation_state->common_params().navigation_start);
+            waiter.common_params()->navigation_start);
 }
 
 // Sanity check for the Navigation Timing API |navigationStart| override. We
@@ -2688,13 +2714,11 @@
   ExecuteJavaScriptForTests("document.title = 'Hi!';");
 
   auto common_params = MakeCommonNavigationParams(-base::Seconds(1));
-  FrameLoadWaiter waiter(frame());
+  CommonParamsFrameLoadWaiter waiter(frame());
   frame()->Navigate(common_params.Clone(), DummyCommitNavigationParams());
   waiter.Wait();
-  NavigationState* navigation_state = NavigationState::FromDocumentLoader(
-      frame()->GetWebFrame()->GetDocumentLoader());
   EXPECT_EQ(common_params->navigation_start,
-            navigation_state->common_params().navigation_start);
+            waiter.common_params()->navigation_start);
 }
 
 TEST_F(RenderViewImplTest, NavigationStartForReload) {
@@ -2712,15 +2736,13 @@
 
   // The browser navigation_start should not be used because beforeunload will
   // be fired during Navigate.
-  FrameLoadWaiter waiter(frame());
+  CommonParamsFrameLoadWaiter waiter(frame());
   frame()->Navigate(common_params.Clone(), DummyCommitNavigationParams());
   waiter.Wait();
 
   // The browser navigation_start is always used.
-  NavigationState* navigation_state = NavigationState::FromDocumentLoader(
-      frame()->GetWebFrame()->GetDocumentLoader());
   EXPECT_EQ(common_params->navigation_start,
-            navigation_state->common_params().navigation_start);
+            waiter.common_params()->navigation_start);
 }
 
 TEST_F(RenderViewImplTest, NavigationStartForSameProcessHistoryNavigation) {
@@ -2739,14 +2761,13 @@
   common_params_back->transition = ui::PAGE_TRANSITION_FORWARD_BACK;
   common_params_back->navigation_type =
       blink::mojom::NavigationType::HISTORY_DIFFERENT_DOCUMENT;
-  GoToOffsetWithParams(-1, back_state, common_params_back.Clone(),
-                       DummyCommitNavigationParams());
-  NavigationState* navigation_state = NavigationState::FromDocumentLoader(
-      frame()->GetWebFrame()->GetDocumentLoader());
+  auto final_common_params =
+      GoToOffsetWithParams(-1, back_state, common_params_back.Clone(),
+                           DummyCommitNavigationParams());
 
   // The browser navigation_start is always used.
   EXPECT_EQ(common_params_back->navigation_start,
-            navigation_state->common_params().navigation_start);
+            final_common_params->navigation_start);
 
   // Go forward.
   auto common_params_forward = blink::CreateCommonNavigationParams();
@@ -2755,12 +2776,11 @@
   common_params_forward->transition = ui::PAGE_TRANSITION_FORWARD_BACK;
   common_params_forward->navigation_type =
       blink::mojom::NavigationType::HISTORY_DIFFERENT_DOCUMENT;
-  GoToOffsetWithParams(1, forward_state, common_params_forward.Clone(),
-                       DummyCommitNavigationParams());
-  navigation_state = NavigationState::FromDocumentLoader(
-      frame()->GetWebFrame()->GetDocumentLoader());
+  auto final_common_params2 =
+      GoToOffsetWithParams(-1, back_state, common_params_back.Clone(),
+                           DummyCommitNavigationParams());
   EXPECT_EQ(common_params_forward->navigation_start,
-            navigation_state->common_params().navigation_start);
+            final_common_params2->navigation_start);
 }
 
 TEST_F(RenderViewImplTest, NavigationStartForCrossProcessHistoryNavigation) {
@@ -2777,14 +2797,12 @@
   commit_params->pending_history_list_offset = 1;
   commit_params->current_history_list_offset = 0;
   commit_params->current_history_list_length = 1;
-  FrameLoadWaiter waiter(frame());
+  CommonParamsFrameLoadWaiter waiter(frame());
   frame()->Navigate(common_params.Clone(), std::move(commit_params));
   waiter.Wait();
 
-  NavigationState* navigation_state = NavigationState::FromDocumentLoader(
-      frame()->GetWebFrame()->GetDocumentLoader());
   EXPECT_EQ(common_params->navigation_start,
-            navigation_state->common_params().navigation_start);
+            waiter.common_params()->navigation_start);
 }
 
 TEST_F(RenderViewImplTest, PreferredSizeZoomed) {
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 3f9bed0..678fa48b 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -446,6 +446,7 @@
     "//base/third_party/dynamic_annotations",
     "//build:chromeos_buildflags",
     "//cc:test_support",
+    "//components/browsing_topics/common:common",
     "//components/network_session_configurator/common:common",
     "//components/services/storage",
     "//components/startup_metric_utils/browser",
@@ -1995,6 +1996,7 @@
     "../browser/browsing_data/browsing_data_remover_impl_unittest.cc",
     "../browser/browsing_data/clear_site_data_handler_unittest.cc",
     "../browser/browsing_data/same_site_data_remover_impl_unittest.cc",
+    "../browser/browsing_topics/browsing_topics_site_data_storage_unittest.cc",
     "../browser/buckets/bucket_manager_host_unittest.cc",
     "../browser/byte_stream_unittest.cc",
     "../browser/cache_storage/cache_storage_blob_to_disk_cache_unittest.cc",
@@ -2446,6 +2448,7 @@
       "data/",
       "//media/test/data/",
       "//content/test/data/attribution_reporting/databases/",
+      "//content/test/data/browsing_topics/",
     ]
   }
 
diff --git a/content/test/attribution_simulator_impl.cc b/content/test/attribution_simulator_impl.cc
index 19fccc5..f73df3ec 100644
--- a/content/test/attribution_simulator_impl.cc
+++ b/content/test/attribution_simulator_impl.cc
@@ -22,8 +22,8 @@
 #include "content/browser/attribution_reporting/attribution_cookie_checker.h"
 #include "content/browser/attribution_reporting/attribution_default_random_generator.h"
 #include "content/browser/attribution_reporting/attribution_insecure_random_generator.h"
-#include "content/browser/attribution_reporting/attribution_manager.h"
 #include "content/browser/attribution_reporting/attribution_manager_impl.h"
+#include "content/browser/attribution_reporting/attribution_observer.h"
 #include "content/browser/attribution_reporting/attribution_random_generator.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_report_sender.h"
@@ -142,7 +142,7 @@
 
 // Registers sources and triggers in the `AttributionManagerImpl` and records
 // rejected sources in a JSON list.
-class AttributionEventHandler : public AttributionManager::Observer {
+class AttributionEventHandler : public AttributionObserver {
  public:
   AttributionEventHandler(AttributionManagerImpl* manager,
                           base::Value::ListStorage& rejected_sources,
@@ -175,7 +175,7 @@
   }
 
  private:
-  // AttributionManager::Observer:
+  // AttributionObserver:
 
   void OnSourceHandled(const StorableSource& source,
                        StorableSource::Result result) override {
@@ -234,7 +234,7 @@
     rejected_triggers_.push_back(std::move(dict));
   }
 
-  base::ScopedObservation<AttributionManager, AttributionManager::Observer>
+  base::ScopedObservation<AttributionManagerImpl, AttributionObserver>
       observation_{this};
 
   base::raw_ptr<AttributionManagerImpl> manager_;
diff --git a/content/test/data/browsing_topics/v0.init_too_old.sql b/content/test/data/browsing_topics/v0.init_too_old.sql
new file mode 100644
index 0000000..6229824
--- /dev/null
+++ b/content/test/data/browsing_topics/v0.init_too_old.sql
@@ -0,0 +1,20 @@
+PRAGMA foreign_keys=OFF;
+
+BEGIN TRANSACTION;
+
+CREATE TABLE browsing_topics_api_usages (
+hashed_context_domain INTEGER NOT NULL,
+hashed_top_host INTEGER NOT NULL,
+last_usage_time INTEGER NOT NULL,
+PRIMARY KEY (hashed_context_domain, hashed_top_host));
+
+CREATE INDEX last_usage_time_idx ON browsing_topics_api_usages(last_usage_time);
+
+CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
+
+INSERT INTO meta VALUES('version','0');
+INSERT INTO meta VALUES('last_compatible_version','1');
+
+INSERT INTO browsing_topics_api_usages VALUES (111, 222, 333);
+
+COMMIT;
diff --git a/content/test/data/browsing_topics/v1.init_too_new.sql b/content/test/data/browsing_topics/v1.init_too_new.sql
new file mode 100644
index 0000000..825043b
--- /dev/null
+++ b/content/test/data/browsing_topics/v1.init_too_new.sql
@@ -0,0 +1,20 @@
+PRAGMA foreign_keys=OFF;
+
+BEGIN TRANSACTION;
+
+CREATE TABLE browsing_topics_api_usages (
+hashed_context_domain INTEGER NOT NULL,
+hashed_top_host INTEGER NOT NULL,
+last_usage_time INTEGER NOT NULL,
+PRIMARY KEY (hashed_context_domain, hashed_top_host));
+
+CREATE INDEX last_usage_time_idx ON browsing_topics_api_usages(last_usage_time);
+
+CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
+
+INSERT INTO meta VALUES('version','2');
+INSERT INTO meta VALUES('last_compatible_version','1');
+
+INSERT INTO browsing_topics_api_usages VALUES (111, 222, 333);
+
+COMMIT;
diff --git a/content/test/data/browsing_topics/v1.sql b/content/test/data/browsing_topics/v1.sql
new file mode 100644
index 0000000..10d6373
--- /dev/null
+++ b/content/test/data/browsing_topics/v1.sql
@@ -0,0 +1,20 @@
+PRAGMA foreign_keys=OFF;
+
+BEGIN TRANSACTION;
+
+CREATE TABLE browsing_topics_api_usages (
+hashed_context_domain INTEGER NOT NULL,
+hashed_top_host INTEGER NOT NULL,
+last_usage_time INTEGER NOT NULL,
+PRIMARY KEY (hashed_context_domain, hashed_top_host));
+
+CREATE INDEX last_usage_time_idx ON browsing_topics_api_usages(last_usage_time);
+
+CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
+
+INSERT INTO meta VALUES('version','1');
+INSERT INTO meta VALUES('last_compatible_version','1');
+
+INSERT INTO browsing_topics_api_usages VALUES (111, 222, 333);
+
+COMMIT;
diff --git a/content/test/data/gpu/pixel_webgpu_import_video_frame_offscreen_canvas.html b/content/test/data/gpu/pixel_webgpu_import_video_frame_offscreen_canvas.html
new file mode 100644
index 0000000..3197c77e
--- /dev/null
+++ b/content/test/data/gpu/pixel_webgpu_import_video_frame_offscreen_canvas.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>WebGPU importExternalTexture videoFrame test</title>
+  <style type="text/css">
+  .nomargin {
+    margin: 0px auto;
+  }
+  </style>
+  <script type="text/javascript" src="pixel_webgpu_util.js"></script>
+  <script type="text/javascript" src="webcodecs/webcodecs_common.js"></script>
+  <script type="text/javascript">
+    var g_swapsBeforeAck = 15;
+
+    async function main() {
+      let cnv = document.getElementById('placeholder');
+      let offscreen = cnv.transferControlToOffscreen();
+      let ctx = offscreen.getContext('2d');
+      const gpuCanvas = document.getElementById('canvas_webgpu');
+      const [gpuDevice, gpuContext] = await webGpuUtils.init(gpuCanvas);
+
+      if (!gpuDevice || !gpuContext) {
+        console.error("Failed to initialize WebGPU - skipping test");
+        domAutomationController.send("FAILURE");
+        return;
+      }
+
+      let source = await createFrameSource("hw_decoder", cnv.width, cnv.height);
+      if (!source) {
+        console.error("Cannot get valid video frame - skipping test");
+        domAutomationController.send("FAILURE");
+        return;
+      }
+
+      let frame = await source.getNextFrame();
+      ctx.drawImage(frame, 0, 0, cnv.width, cnv.height);
+
+      const renderCallback = function() {
+        webGpuUtils.importExternalTextureTest(gpuDevice, gpuContext, frame);
+        frame.close();
+        waitForFinish();
+      };
+
+      window.requestAnimationFrame(renderCallback);
+    }
+
+    function waitForFinish() {
+      if (g_swapsBeforeAck == 0) {
+        domAutomationController.send("SUCCESS");
+      } else {
+        g_swapsBeforeAck--;
+        window.requestAnimationFrame(waitForFinish);
+      }
+    }
+  </script>
+</head>
+<body onload="main()">
+  <canvas id="placeholder" width="200" height="200" class="nomargin"></canvas>
+  <canvas id="canvas_webgpu" width="200" height="200" class="nomargin"></canvas>
+</body>
+</html>
diff --git a/content/test/gpu/gpu_tests/pixel_test_pages.py b/content/test/gpu/gpu_tests/pixel_test_pages.py
index 48e7283..64bbd5e 100644
--- a/content/test/gpu/gpu_tests/pixel_test_pages.py
+++ b/content/test/gpu/gpu_tests/pixel_test_pages.py
@@ -364,6 +364,20 @@
                       base_name + '_WebGPUImportVideoFrame',
                       test_rect=[0, 0, 400, 200],
                       browser_args=webgpu_args),
+        PixelTestPage('pixel_webgpu_import_video_frame.html',
+                      base_name + '_WebGPUImportVideoFrameUnaccelerated',
+                      test_rect=[0, 0, 400, 200],
+                      browser_args=webgpu_args +
+                      [cba.DISABLE_ACCELERATED_2D_CANVAS]),
+        PixelTestPage('pixel_webgpu_import_video_frame_offscreen_canvas.html',
+                      base_name + '_WebGPUImportVideoFrameOffscreenCanvas',
+                      test_rect=[0, 0, 400, 200],
+                      browser_args=webgpu_args),
+        PixelTestPage(
+            'pixel_webgpu_import_video_frame_offscreen_canvas.html',
+            base_name + '_WebGPUImportVideoFrameUnacceleratedOffscreenCanvas',
+            test_rect=[0, 0, 400, 200],
+            browser_args=webgpu_args + [cba.DISABLE_ACCELERATED_2D_CANVAS]),
         PixelTestPage('pixel_webgpu_webgl_teximage2d.html',
                       base_name + '_WebGPUWebGLTexImage2D',
                       test_rect=[0, 0, 400, 200],
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index cea9dee..c2ecf78 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -267,6 +267,9 @@
 # always run on Windows RS3 or above.
 crbug.com/1066979 [ win ] Pixel_Canvas2DRedBoxHdr10 [ Failure ]
 
+# Failure on win10 capturing a VideoFrame from unaccelerated OffscreenCanvas clears the source canvas
+crbug.com/1300550 [ win ] Pixel_WebGPUImportVideoFrameUnacceleratedOffscreenCanvas [ Failure ]
+
 # Flakes on gpu-fyi-try-chromeos-kevin and Fuchsia x64, produces notably different image.
 crbug.com/1086687 [ chromeos chromeos-board-kevin ] Pixel_PrecisionRoundedCorner [ Failure ]
 
@@ -328,6 +331,10 @@
 # Pixel_WebGPUImportVideoFrame hangs on Windows FYI bots
 crbug.com/1274796 [ win skia-renderer-gl ] Pixel_WebGPUImportVideoFrame [ Failure ]
 
+# Crashes on gpu-fyi-win10-nvidia-rel-32
+crbug.com/1300670 [ win nvidia-0x2184 passthrough ] Pixel_WebGPUImportVideoFrameUnaccelerated [ Failure ]
+crbug.com/1300670 [ win nvidia-0x2184 passthrough ] Pixel_WebGPUImportVideoFrameOffscreenCanvas [ Failure ]
+
 # Pixel_Video_Media_Stream_Incompatible_Stride flakes with SkiaRenderer GL
 crbug.com/1213542 [ skia-renderer-gl linux nvidia-0x1cb3 ] Pixel_Video_Media_Stream_Incompatible_Stride [ RetryOnFailure ]
 crbug.com/1213542 [ skia-renderer-gl linux nvidia-0x2184 passthrough ] Pixel_Video_Media_Stream_Incompatible_Stride [ RetryOnFailure ]
diff --git a/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
index a1209d2..192387d 100644
--- a/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
@@ -136,6 +136,11 @@
 crbug.com/852089 [ fuchsia ] TraceTest_WebGPU* [ Skip ]
 crbug.com/852089 [ win7 ] TraceTest_WebGPU* [ Skip ]
 
+# Tests that crash on gpu-fyi-win10-nvidia-rel-32
+crbug.com/1300670 [ win nvidia-0x2184 ] TraceTest_WebGPUImportVideoFrameUnacceleratedOffscreenCanvas [ Failure ]
+crbug.com/1300670 [ win nvidia-0x2184 ] TraceTest_WebGPUImportVideoFrameOffscreenCanvas [ Failure ]
+crbug.com/1300670 [ win nvidia-0x2184 ] TraceTest_WebGPUImportVideoFrameUnaccelerated [ Failure ]
+
 ###############################
 # Temporary Skip Expectations #
 ###############################
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index 1e961de..213e5da 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -745,6 +745,7 @@
 crbug.com/1152588 [ linux amd-0x7340 ] conformance2/rendering/multisampling-fragment-evaluation.html [ Failure ]
 crbug.com/1283514 [ linux amd-0x7340 angle-opengl ] conformance2/textures/misc/angle-stuck-depth-textures.html [ Failure ]
 crbug.com/1283514 [ linux amd-0x7340 angle-opengl ] deqp/functional/gles3/fboinvalidate/whole.html [ Failure ]
+crbug.com/1300683 [ linux amd-0x7340 angle-opengl passthrough ] deqp/functional/gles3/fborender/recreate_depth_stencil.html [ RetryOnFailure ]
 
 ####################
 # Android failures #
diff --git a/extensions/browser/app_window/app_window.h b/extensions/browser/app_window/app_window.h
index 43343a5..676bf55 100644
--- a/extensions/browser/app_window/app_window.h
+++ b/extensions/browser/app_window/app_window.h
@@ -248,7 +248,10 @@
   const GURL& initial_url() const { return initial_url_; }
   bool is_hidden() const { return is_hidden_; }
 
+  // Calls to this should always be guarded by a nullptr check as this can
+  // return nullptr if the extension is no longer installed.
   const Extension* GetExtension() const;
+
   NativeAppWindow* GetBaseWindow();
   gfx::NativeWindow GetNativeWindow();
 
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc
index e73186c..e2885365 100644
--- a/gpu/config/gpu_finch_features.cc
+++ b/gpu/config/gpu_finch_features.cc
@@ -324,8 +324,10 @@
     return false;
 
   // DrDc is supported on android MediaPlayer and MCVD path only when
-  // AImageReader is enabled.
-  if (!IsAImageReaderEnabled())
+  // AImageReader is enabled. Also DrDc requires AImageReader max size to be
+  // at least 2 for each gpu thread. Hence DrDc is disabled on devices which has
+  // only 1 image.
+  if (!IsAImageReaderEnabled() || LimitAImageReaderMaxSizeToOne())
     return false;
 
   // Check block list against build info.
@@ -341,8 +343,10 @@
 
 bool IsUsingThreadSafeMediaForWebView() {
 #if BUILDFLAG(IS_ANDROID)
-  // SurfaceTexture can't be thread-safe.
-  if (!IsAImageReaderEnabled())
+  // SurfaceTexture can't be thread-safe. Also thread safe media code currently
+  // requires AImageReader max size to be at least 2 since one image could be
+  // accessed by each gpu thread in webview.
+  if (!IsAImageReaderEnabled() || LimitAImageReaderMaxSizeToOne())
     return false;
 
   // Not yet compatible with Vulkan.
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md
index 7aacc44..e769ab2 100644
--- a/infra/config/generated/cq-builders.md
+++ b/infra/config/generated/cq-builders.md
@@ -391,6 +391,7 @@
 
   Path regular expressions:
   * [`//tools/clang/scripts/update.py`](https://cs.chromium.org/search?q=+file:tools/clang/scripts/update.py)
+  * [`//DEPS`](https://cs.chromium.org/chromium/src/DEPS)
 
 * [win-updater-try-builder-dbg](https://ci.chromium.org/p/chromium/builders/try/win-updater-try-builder-dbg) ([definition](https://cs.chromium.org/search?q=+file:/try.star$+""win-updater-try-builder-dbg"")) ([matching builders](https://cs.chromium.org/search?q=+file:trybots.py+""win-updater-try-builder-dbg""))
 
diff --git a/infra/config/generated/cq-usage/full.cfg b/infra/config/generated/cq-usage/full.cfg
index 1f45f8e..d887804a 100644
--- a/infra/config/generated/cq-usage/full.cfg
+++ b/infra/config/generated/cq-usage/full.cfg
@@ -510,6 +510,7 @@
         name: "chromium/try/reclient-config-deployment-verifier"
         disable_reuse: true
         location_regexp: ".+/[+]/tools/clang/scripts/update.py"
+        location_regexp: ".+/[+]/DEPS"
       }
       builders {
         name: "chromium/try/win-libfuzzer-asan-rel"
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index 9210b3c..581be9d2 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -1762,6 +1762,7 @@
         name: "chromium/try/reclient-config-deployment-verifier"
         disable_reuse: true
         location_regexp: ".+/[+]/tools/clang/scripts/update.py"
+        location_regexp: ".+/[+]/DEPS"
       }
       builders {
         name: "chromium/try/tricium-metrics-analysis"
diff --git a/infra/config/subprojects/chromium/try/presubmit.star b/infra/config/subprojects/chromium/try/presubmit.star
index 1153f312..5fe3eb4 100644
--- a/infra/config/subprojects/chromium/try/presubmit.star
+++ b/infra/config/subprojects/chromium/try/presubmit.star
@@ -105,7 +105,10 @@
         ],
     },
     tryjob = try_.job(
-        location_regexp = [r".+/[+]/tools/clang/scripts/update.py"],
+        location_regexp = [
+            r".+/[+]/tools/clang/scripts/update.py",
+            r".+/[+]/DEPS",
+        ],
     ),
 )
 
diff --git a/ios/chrome/browser/safe_browsing/safe_browsing_egtest.mm b/ios/chrome/browser/safe_browsing/safe_browsing_egtest.mm
index adc37065..731f6d1 100644
--- a/ios/chrome/browser/safe_browsing/safe_browsing_egtest.mm
+++ b/ios/chrome/browser/safe_browsing/safe_browsing_egtest.mm
@@ -12,7 +12,6 @@
 #include "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_earl_grey.h"
 #import "ios/chrome/browser/ui/bookmarks/bookmark_earl_grey_ui.h"
-#import "ios/chrome/browser/ui/tab_switcher/tab_grid/features.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h"
 #import "ios/chrome/test/earl_grey/chrome_matchers.h"
diff --git a/ios/chrome/browser/ui/popup_menu/BUILD.gn b/ios/chrome/browser/ui/popup_menu/BUILD.gn
index a77fda1..916ea98 100644
--- a/ios/chrome/browser/ui/popup_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/BUILD.gn
@@ -27,6 +27,7 @@
     "resources:popup_menu_new_incognito_tab",
     "resources:popup_menu_new_tab",
     "resources:popup_menu_new_window",
+    "resources:popup_menu_open_tabs",
     "resources:popup_menu_paste_and_go",
     "resources:popup_menu_qr_scanner",
     "resources:popup_menu_read_later",
@@ -43,6 +44,7 @@
     "resources:popup_menu_text_zoom",
     "resources:popup_menu_translate",
     "resources:popup_menu_voice_search",
+    "resources:popup_menu_web",
     "//base",
     "//components/bookmarks/browser",
     "//components/bookmarks/common",
diff --git a/ios/chrome/browser/ui/popup_menu/request_desktop_mobile_site_egtest.mm b/ios/chrome/browser/ui/popup_menu/request_desktop_mobile_site_egtest.mm
index 1590337..0b562b9 100644
--- a/ios/chrome/browser/ui/popup_menu/request_desktop_mobile_site_egtest.mm
+++ b/ios/chrome/browser/ui/popup_menu/request_desktop_mobile_site_egtest.mm
@@ -8,7 +8,6 @@
 #include "components/version_info/version_info.h"
 #import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h"
 #import "ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h"
-#import "ios/chrome/browser/ui/tab_switcher/tab_grid/features.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
diff --git a/ios/chrome/browser/ui/popup_menu/resources/BUILD.gn b/ios/chrome/browser/ui/popup_menu/resources/BUILD.gn
index cdd05e9..bba625b 100644
--- a/ios/chrome/browser/ui/popup_menu/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/resources/BUILD.gn
@@ -248,3 +248,19 @@
     "popup_menu_enterprise_icon.imageset/popup_menu_enterprise_icon@3x.png",
   ]
 }
+
+imageset("popup_menu_open_tabs") {
+  sources = [
+    "popup_menu_open_tabs.imageset/Contents.json",
+    "popup_menu_open_tabs.imageset/popup_menu_open_tabs@2x.png",
+    "popup_menu_open_tabs.imageset/popup_menu_open_tabs@3x.png",
+  ]
+}
+
+imageset("popup_menu_web") {
+  sources = [
+    "popup_menu_web.imageset/Contents.json",
+    "popup_menu_web.imageset/popup_menu_web@2x.png",
+    "popup_menu_web.imageset/popup_menu_web@3x.png",
+  ]
+}
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_open_tabs.imageset/Contents.json b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_open_tabs.imageset/Contents.json
new file mode 100644
index 0000000..67e70d3
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_open_tabs.imageset/Contents.json
@@ -0,0 +1,18 @@
+{
+    "images": [
+        {
+            "idiom": "universal",
+            "scale": "2x",
+            "filename": "popup_menu_open_tabs@2x.png"
+        },
+        {
+            "idiom": "universal",
+            "scale": "3x",
+            "filename": "popup_menu_open_tabs@3x.png"
+        }
+    ],
+    "info": {
+        "version": 1,
+        "author": "xcode"
+    }
+}
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_open_tabs.imageset/popup_menu_open_tabs@2x.png b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_open_tabs.imageset/popup_menu_open_tabs@2x.png
new file mode 100644
index 0000000..51981e4a
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_open_tabs.imageset/popup_menu_open_tabs@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_open_tabs.imageset/popup_menu_open_tabs@3x.png b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_open_tabs.imageset/popup_menu_open_tabs@3x.png
new file mode 100644
index 0000000..de9bfe2
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_open_tabs.imageset/popup_menu_open_tabs@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_web.imageset/Contents.json b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_web.imageset/Contents.json
new file mode 100644
index 0000000..2d856b9
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_web.imageset/Contents.json
@@ -0,0 +1,18 @@
+{
+    "images": [
+        {
+            "idiom": "universal",
+            "scale": "2x",
+            "filename": "popup_menu_web@2x.png"
+        },
+        {
+            "idiom": "universal",
+            "scale": "3x",
+            "filename": "popup_menu_web@3x.png"
+        }
+    ],
+    "info": {
+        "version": 1,
+        "author": "xcode"
+    }
+}
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_web.imageset/popup_menu_web@2x.png b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_web.imageset/popup_menu_web@2x.png
new file mode 100644
index 0000000..8b6b03a
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_web.imageset/popup_menu_web@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_web.imageset/popup_menu_web@3x.png b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_web.imageset/popup_menu_web@3x.png
new file mode 100644
index 0000000..a66ef44f
--- /dev/null
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_web.imageset/popup_menu_web@3x.png
Binary files differ
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 c2ef9f24..64ccd71 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
@@ -764,7 +764,7 @@
       initWithType:ItemTypeSuggestedActionSearchWeb];
   searchWebItem.title =
       l10n_util::GetNSString(IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_WEB);
-  searchWebItem.image = [[UIImage imageNamed:@"popup_menu_search"]
+  searchWebItem.image = [[UIImage imageNamed:@"popup_menu_web"]
       imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
   [model addItem:searchWebItem
       toSectionWithIdentifier:SectionIdentifierSuggestedActions];
@@ -775,7 +775,7 @@
         initWithType:ItemTypeSuggestedActionSearchOpenTabs];
     searchOpenTabsItem.title = l10n_util::GetNSString(
         IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_OPEN_TABS);
-    searchOpenTabsItem.image = [[UIImage imageNamed:@"popup_menu_search"]
+    searchOpenTabsItem.image = [[UIImage imageNamed:@"popup_menu_open_tabs"]
         imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
     [model addItem:searchOpenTabsItem
         toSectionWithIdentifier:SectionIdentifierSuggestedActions];
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
index 480407a..0e1ad26 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
@@ -247,8 +247,7 @@
                          IDS_IOS_GOOGLE_SERVICES_SETTINGS_ALLOW_SIGNIN_TEXT
                    detailStringID:
                        IDS_IOS_GOOGLE_SERVICES_SETTINGS_ALLOW_SIGNIN_DETAIL
-                           status:GetStatusForSigninPolicy()
-                     controllable:IsSigninControllableByUser()];
+                           status:GetStatusForSigninPolicy()];
 }
 
 #pragma mark - Load non personalized section
@@ -362,8 +361,7 @@
                              IDS_IOS_GOOGLE_SERVICES_SETTINGS_AUTOCOMPLETE_SEARCHES_AND_URLS_TEXT
                        detailStringID:
                            IDS_IOS_GOOGLE_SERVICES_SETTINGS_AUTOCOMPLETE_SEARCHES_AND_URLS_DETAIL
-                               status:self.autocompleteSearchPreference.value
-                         controllable:self.autocompleteSearchPreference.value];
+                               status:self.autocompleteSearchPreference.value];
       [items addObject:autocompleteItem];
     } else {
       SyncSwitchItem* autocompleteItem = [self
@@ -382,8 +380,7 @@
                              IDS_IOS_GOOGLE_SERVICES_SETTINGS_SAFE_BROWSING_TEXT
                        detailStringID:
                            IDS_IOS_GOOGLE_SERVICES_SETTINGS_SAFE_BROWSING_DETAIL
-                               status:self.safeBrowsingPreference.value
-                         controllable:self.safeBrowsingPreference.value];
+                               status:self.safeBrowsingPreference.value];
       [items addObject:safeBrowsingManagedItem];
     } else {
       SyncSwitchItem* safeBrowsingItem = [self
@@ -405,8 +402,7 @@
                              IDS_IOS_GOOGLE_SERVICES_SETTINGS_IMPROVE_CHROME_TEXT
                        detailStringID:
                            IDS_IOS_GOOGLE_SERVICES_SETTINGS_IMPROVE_CHROME_DETAIL
-                               status:self.sendDataUsagePreference
-                         controllable:self.sendDataUsagePreference];
+                               status:self.sendDataUsagePreference];
       [items addObject:improveChromeItem];
     } else {
       SyncSwitchItem* improveChromeItem = [self
@@ -425,8 +421,7 @@
                              IDS_IOS_GOOGLE_SERVICES_SETTINGS_BETTER_SEARCH_AND_BROWSING_TEXT
                        detailStringID:
                            IDS_IOS_GOOGLE_SERVICES_SETTINGS_BETTER_SEARCH_AND_BROWSING_DETAIL
-                               status:self.anonymizedDataCollectionPreference
-                         controllable:self.anonymizedDataCollectionPreference];
+                               status:self.anonymizedDataCollectionPreference];
       betterSearchAndBrowsingItem.accessibilityIdentifier =
           kBetterSearchAndBrowsingItemAccessibilityID;
       [items addObject:betterSearchAndBrowsingItem];
@@ -448,8 +443,7 @@
             tableViewInfoButtonItemType:TrackPricesOnTabsItemType
                            textStringID:IDS_IOS_TRACK_PRICES_ON_TABS
                          detailStringID:IDS_IOS_TRACK_PRICES_ON_TABS_DESCRIPTION
-                                 status:self.trackPricesOnTabsPreference
-                           controllable:self.trackPricesOnTabsPreference];
+                                 status:self.trackPricesOnTabsPreference];
         trackPricesOnTabsItem.accessibilityIdentifier =
             kTrackPricesOnTabsItemAccessibilityID;
         [items addObject:trackPricesOnTabsItem];
@@ -502,8 +496,7 @@
 - (TableViewInfoButtonItem*)tableViewInfoButtonItemType:(NSInteger)itemType
                                            textStringID:(int)textStringID
                                          detailStringID:(int)detailStringID
-                                                 status:(BOOL)status
-                                           controllable:(BOOL)controllable {
+                                                 status:(BOOL)status {
   TableViewInfoButtonItem* managedItem =
       [[TableViewInfoButtonItem alloc] initWithType:itemType];
   managedItem.text = GetNSString(textStringID);
@@ -514,14 +507,11 @@
     managedItem.tintColor = [UIColor colorNamed:kGrey300Color];
   }
 
-  // When there is no knob (not controllable), then set the color opacity to
-  // 40%.
-  if (!controllable) {
-    managedItem.textColor =
-        [[UIColor colorNamed:kTextPrimaryColor] colorWithAlphaComponent:0.4f];
-    managedItem.detailTextColor =
-        [[UIColor colorNamed:kTextSecondaryColor] colorWithAlphaComponent:0.4f];
-  }
+  // This item is not controllable, then set the color opacity to 40%.
+  managedItem.textColor =
+      [[UIColor colorNamed:kTextPrimaryColor] colorWithAlphaComponent:0.4f];
+  managedItem.detailTextColor =
+      [[UIColor colorNamed:kTextSecondaryColor] colorWithAlphaComponent:0.4f];
   managedItem.accessibilityHint =
       l10n_util::GetNSString(IDS_IOS_TOGGLE_SETTING_MANAGED_ACCESSIBILITY_HINT);
   return managedItem;
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.mm
index 7192b4b..757623b 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.mm
@@ -11,7 +11,6 @@
 #include "base/notreached.h"
 #import "ios/chrome/browser/commerce/price_alert_util.h"
 #import "ios/chrome/browser/ui/elements/top_aligned_image_view.h"
-#import "ios/chrome/browser/ui/tab_switcher/tab_grid/features.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_context_menu_helper.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_context_menu_helper.mm
index d07096f..1bed922f 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_context_menu_helper.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_context_menu_helper.mm
@@ -13,7 +13,6 @@
 #import "ios/chrome/browser/ui/menu/action_factory.h"
 #import "ios/chrome/browser/ui/menu/tab_context_menu_delegate.h"
 #import "ios/chrome/browser/ui/ntp/ntp_util.h"
-#import "ios/chrome/browser/ui/tab_switcher/tab_grid/features.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_cell.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_item.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_menu_actions_data_source.h"
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
index 9660038..9efbac13 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
@@ -888,7 +888,8 @@
 
 - (void)didSelectSearchRecentTabsInSuggestedActionsViewController:
     (SuggestedActionsViewController*)viewController {
-  // TODO(crbug.com/1297859): Log the user action.
+  base::RecordAction(
+      base::UserMetricsAction("TabsSearch.SuggestedActions.RecentTabs"));
   [self.suggestedActionsDelegate searchRecentTabsForText:self.searchText];
 }
 
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.mm
index da94278..882fdac 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/suggested_actions/suggested_actions_view_controller.mm
@@ -105,7 +105,7 @@
       initWithType:ItemTypeSuggestedActionSearchWeb];
   searchWebItem.title =
       l10n_util::GetNSString(IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_WEB);
-  searchWebItem.image = [[UIImage imageNamed:@"popup_menu_search"]
+  searchWebItem.image = [[UIImage imageNamed:@"popup_menu_web"]
       imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
   [model addItem:searchWebItem
       toSectionWithIdentifier:kSectionIdentifierSuggestedActions];
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm
index 2052733b..867144b 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.mm
@@ -5,7 +5,6 @@
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_bottom_toolbar.h"
 
 #include "base/strings/sys_string_conversions.h"
-#import "ios/chrome/browser/ui/tab_switcher/tab_grid/features.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_new_tab_button.h"
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 4970926..25fded0 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
@@ -8,7 +8,6 @@
 #import "base/test/ios/wait_util.h"
 #import "components/bookmarks/common/bookmark_pref_names.h"
 #import "ios/chrome/browser/ui/start_surface/start_surface_features.h"
-#import "ios/chrome/browser/ui/tab_switcher/tab_grid/features.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_constants.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_constants.h"
 #include "ios/chrome/grit/ios_strings.h"
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_transition_egtest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_transition_egtest.mm
index fa7a5ad..6afa7e3 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_transition_egtest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_transition_egtest.mm
@@ -7,7 +7,6 @@
 
 #include "base/bind.h"
 #include "base/strings/sys_string_conversions.h"
-#import "ios/chrome/browser/ui/tab_switcher/tab_grid/features.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
diff --git a/ios/chrome/browser/web/error_page_egtest.mm b/ios/chrome/browser/web/error_page_egtest.mm
index 39e40106..186e1c2a 100644
--- a/ios/chrome/browser/web/error_page_egtest.mm
+++ b/ios/chrome/browser/web/error_page_egtest.mm
@@ -9,7 +9,6 @@
 
 #include "base/bind.h"
 #import "base/test/ios/wait_util.h"
-#import "ios/chrome/browser/ui/tab_switcher/tab_grid/features.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_matchers.h"
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
diff --git a/ios/web/public/webui/web_ui_ios.h b/ios/web/public/webui/web_ui_ios.h
index 8eba45f..e7671cd 100644
--- a/ios/web/public/webui/web_ui_ios.h
+++ b/ios/web/public/webui/web_ui_ios.h
@@ -42,18 +42,23 @@
   virtual void AddMessageHandler(
       std::unique_ptr<WebUIIOSMessageHandler> handler) = 0;
 
+  // TODO(crbug.com/1300095): new version of DeprecatedMessageCallback2 that
+  // takes base::Value::List as a parameter needs to be introduced. Afterwards
+  // existing callers of RegisterDeprecatedMessageCallback() should be migrated
+  // to the new RegisterMessageCallback() (not the one below) version.
+  //
   // Used by WebUIIOSMessageHandlers. If the given message is already
   // registered, the call has no effect.
-  using MessageCallback =
+  using DeprecatedMessageCallback2 =
       base::RepeatingCallback<void(base::Value::ConstListView)>;
   virtual void RegisterMessageCallback(const std::string& message,
-                                       MessageCallback callback) = 0;
+                                       DeprecatedMessageCallback2 callback) = 0;
 
-  // Always use RegisterMessageCallback() above in new code.
-  //
-  // TODO(crbug.com/1243386): Existing callers of
-  // RegisterDeprecatedMessageCallback() should be migrated to
-  // RegisterMessageCallback() if possible.
+  // TODO(crbug.com/1300095): new version of DeprecatedMessageCallback that
+  // takes base::Value::List as a parameter needs to be introduced. Afterwards
+  // existing callers of RegisterDeprecatedMessageCallback() should be migrated
+  // to the new RegisterMessageCallback() (not the one above) version if
+  // possible.
   //
   // Used by WebUIIOSMessageHandlers. If the given message is already
   // registered, the call has no effect.
diff --git a/ios/web/webui/web_ui_ios_impl.h b/ios/web/webui/web_ui_ios_impl.h
index 27aab67..ed2dabf 100644
--- a/ios/web/webui/web_ui_ios_impl.h
+++ b/ios/web/webui/web_ui_ios_impl.h
@@ -37,12 +37,8 @@
   void SetController(std::unique_ptr<WebUIIOSController> controller) override;
   void AddMessageHandler(
       std::unique_ptr<WebUIIOSMessageHandler> handler) override;
-  using MessageCallback =
-      base::RepeatingCallback<void(base::Value::ConstListView)>;
   void RegisterMessageCallback(const std::string& message,
-                               MessageCallback callback) override;
-  using DeprecatedMessageCallback =
-      base::RepeatingCallback<void(const base::ListValue*)>;
+                               DeprecatedMessageCallback2 callback) override;
   void RegisterDeprecatedMessageCallback(
       const std::string& message,
       const DeprecatedMessageCallback& callback) override;
@@ -69,8 +65,9 @@
   void ExecuteJavascript(const std::u16string& javascript);
 
   // A map of message name -> message handling callback.
-  using MessageCallbackMap = std::map<std::string, MessageCallback>;
-  MessageCallbackMap message_callbacks_;
+  using DeprecatedMessageCallback2Map =
+      std::map<std::string, DeprecatedMessageCallback2>;
+  DeprecatedMessageCallback2Map deprecated_message_callbacks_2_;
 
   // A map of message name -> message handling callback.
   using DeprecatedMessageCallbackMap =
diff --git a/ios/web/webui/web_ui_ios_impl.mm b/ios/web/webui/web_ui_ios_impl.mm
index e051dc3..edf4161 100644
--- a/ios/web/webui/web_ui_ios_impl.mm
+++ b/ios/web/webui/web_ui_ios_impl.mm
@@ -108,9 +108,10 @@
       GetJavascriptCall("cr.webUIListenerCallback", modified_args));
 }
 
-void WebUIIOSImpl::RegisterMessageCallback(const std::string& message,
-                                           MessageCallback callback) {
-  message_callbacks_.emplace(message, std::move(callback));
+void WebUIIOSImpl::RegisterMessageCallback(
+    const std::string& message,
+    DeprecatedMessageCallback2 callback) {
+  deprecated_message_callbacks_2_.emplace(message, std::move(callback));
 }
 
 void WebUIIOSImpl::RegisterDeprecatedMessageCallback(
@@ -153,19 +154,20 @@
     return;
 
   // Look up the callback for this message.
-  MessageCallbackMap::const_iterator callback =
-      message_callbacks_.find(message);
-  if (callback != message_callbacks_.end()) {
+  auto deprecated_message_callback_2_it =
+      deprecated_message_callbacks_2_.find(message);
+  if (deprecated_message_callback_2_it !=
+      deprecated_message_callbacks_2_.end()) {
     // Forward this message and content on.
-    callback->second.Run(args.GetListDeprecated());
+    deprecated_message_callback_2_it->second.Run(args.GetListDeprecated());
+    return;
   }
 
   // Look up the deprecated callback for this message.
-  DeprecatedMessageCallbackMap::const_iterator deprecated_callback =
-      deprecated_message_callbacks_.find(message);
-  if (deprecated_callback != deprecated_message_callbacks_.end()) {
+  auto deprecated_callback_it = deprecated_message_callbacks_.find(message);
+  if (deprecated_callback_it != deprecated_message_callbacks_.end()) {
     // Forward this message and content on.
-    deprecated_callback->second.Run(&base::Value::AsListValue(args));
+    deprecated_callback_it->second.Run(&base::Value::AsListValue(args));
   }
 }
 
diff --git a/media/audio/audio_debug_recording_helper.cc b/media/audio/audio_debug_recording_helper.cc
index b7d5328..24d55f9c 100644
--- a/media/audio/audio_debug_recording_helper.cc
+++ b/media/audio/audio_debug_recording_helper.cc
@@ -13,6 +13,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/task/single_thread_task_runner.h"
 #include "media/audio/audio_debug_file_writer.h"
+#include "media/base/audio_bus.h"
 
 namespace media {
 
diff --git a/media/base/audio_parameters.cc b/media/base/audio_parameters.cc
index 401d8034..f60b3a2 100644
--- a/media/base/audio_parameters.cc
+++ b/media/base/audio_parameters.cc
@@ -6,10 +6,22 @@
 
 #include <sstream>
 
+#include "media/base/audio_bus.h"
 #include "media/base/limits.h"
 
 namespace media {
 
+static_assert(AudioBus::kChannelAlignment == kParametersAlignment,
+              "Audio buffer parameters struct alignment not same as AudioBus");
+static_assert(sizeof(AudioInputBufferParameters) %
+                      AudioBus::kChannelAlignment ==
+                  0,
+              "AudioInputBufferParameters not aligned");
+static_assert(sizeof(AudioOutputBufferParameters) %
+                      AudioBus::kChannelAlignment ==
+                  0,
+              "AudioOutputBufferParameters not aligned");
+
 const char* FormatToString(AudioParameters::Format format) {
   switch (format) {
     case AudioParameters::AUDIO_PCM_LINEAR:
diff --git a/media/base/audio_parameters.h b/media/base/audio_parameters.h
index 256bc29..2b8c5f8f 100644
--- a/media/base/audio_parameters.h
+++ b/media/base/audio_parameters.h
@@ -13,7 +13,6 @@
 #include "base/numerics/checked_math.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "media/base/audio_bus.h"
 #include "media/base/audio_latency.h"
 #include "media/base/audio_point.h"
 #include "media/base/channel_layout.h"
@@ -27,19 +26,18 @@
 // size as sizeof(Audio{Input,Output}BufferParameters) + #(bytes in audio
 // buffer) without using packing. Also align Audio{Input,Output}BufferParameters
 // instead of in Audio{Input,Output}Buffer to be able to calculate size like so.
-// Use a macro for the alignment value that's the same as
+// Use a constexpr for the alignment value that's the same as
 // AudioBus::kChannelAlignment, since MSVC doesn't accept the latter to be used.
 #if BUILDFLAG(IS_WIN)
 #pragma warning(push)
 #pragma warning(disable : 4324)  // Disable warning for added padding.
 #endif
-#define PARAMETERS_ALIGNMENT 16
-static_assert(AudioBus::kChannelAlignment == PARAMETERS_ALIGNMENT,
-              "Audio buffer parameters struct alignment not same as AudioBus");
+constexpr int kParametersAlignment = 16;
+
 // ****WARNING****: Do not change the field types or ordering of these fields
 // without checking that alignment is correct. The structs may be concurrently
 // accessed by both 32bit and 64bit process in shmem. http://crbug.com/781095.
-struct MEDIA_SHMEM_EXPORT ALIGNAS(PARAMETERS_ALIGNMENT)
+struct MEDIA_SHMEM_EXPORT ALIGNAS(kParametersAlignment)
     AudioInputBufferParameters {
   double volume;
   int64_t capture_time_us;  // base::TimeTicks in microseconds.
@@ -47,7 +45,7 @@
   uint32_t id;
   bool key_pressed;
 };
-struct MEDIA_SHMEM_EXPORT ALIGNAS(PARAMETERS_ALIGNMENT)
+struct MEDIA_SHMEM_EXPORT ALIGNAS(kParametersAlignment)
     AudioOutputBufferParameters {
   int64_t delay_us;            // base::TimeDelta in microseconds.
   int64_t delay_timestamp_us;  // base::TimeTicks in microseconds.
@@ -55,20 +53,10 @@
   uint32_t bitstream_data_size;
   uint32_t bitstream_frames;
 };
-#undef PARAMETERS_ALIGNMENT
 #if BUILDFLAG(IS_WIN)
 #pragma warning(pop)
 #endif
 
-static_assert(sizeof(AudioInputBufferParameters) %
-                      AudioBus::kChannelAlignment ==
-                  0,
-              "AudioInputBufferParameters not aligned");
-static_assert(sizeof(AudioOutputBufferParameters) %
-                      AudioBus::kChannelAlignment ==
-                  0,
-              "AudioOutputBufferParameters not aligned");
-
 struct MEDIA_SHMEM_EXPORT AudioInputBuffer {
   AudioInputBufferParameters params;
   int8_t audio[1];
@@ -94,6 +82,8 @@
   base::TimeDelta starting_capacity_for_encrypted;
 };
 
+class AudioParameters;
+
 // These convenience function safely computes the size required for
 // |shared_memory_count| AudioInputBuffers, with enough memory for AudioBus
 // data, using |paremeters| (or alternatively |channels| and |frames|). The
diff --git a/media/gpu/windows/d3d11_h265_accelerator.cc b/media/gpu/windows/d3d11_h265_accelerator.cc
index 4398c0f..d1a2f1fe 100644
--- a/media/gpu/windows/d3d11_h265_accelerator.cc
+++ b/media/gpu/windows/d3d11_h265_accelerator.cc
@@ -154,6 +154,8 @@
 
   int i = 0;
   for (auto& it : ref_pic_list) {
+    if (!it)
+      continue;
     D3D11H265Picture* our_ref_pic = it->AsD3D11H265Picture();
     if (!our_ref_pic)
       continue;
@@ -415,6 +417,8 @@
   } while (0)
 
   for (auto& it : ref_pic_list0) {
+    if (!it)
+      continue;
     auto poc = it->pic_order_cnt_val_;
     auto idx = poc_index_into_ref_pic_list_[poc];
     if (idx < 0) {
@@ -438,6 +442,8 @@
     }
   }
   for (auto& it : ref_pic_list1) {
+    if (!it)
+      continue;
     auto poc = it->pic_order_cnt_val_;
     auto idx = poc_index_into_ref_pic_list_[poc];
     if (idx < 0) {
diff --git a/media/mojo/services/gpu_mojo_media_client.cc b/media/mojo/services/gpu_mojo_media_client.cc
index 8f83a4d..40cdaff8 100644
--- a/media/mojo/services/gpu_mojo_media_client.cc
+++ b/media/mojo/services/gpu_mojo_media_client.cc
@@ -13,7 +13,6 @@
 #include "build/chromeos_buildflags.h"
 #include "gpu/ipc/service/gpu_channel.h"
 #include "media/audio/audio_features.h"
-#include "media/audio/audio_opus_encoder.h"
 #include "media/base/audio_decoder.h"
 #include "media/base/cdm_factory.h"
 #include "media/base/media_switches.h"
@@ -119,14 +118,7 @@
     scoped_refptr<base::SequencedTaskRunner> task_runner) {
   if (!base::FeatureList::IsEnabled(features::kPlatformAudioEncoder))
     return nullptr;
-  // TODO(crbug.com/1259883) Right now Opus encoder is all we have, later on
-  // we'll create a real platform encoder here.
-  auto opus_encoder = std::make_unique<AudioOpusEncoder>();
-  auto encoding_runner = base::ThreadPool::CreateSequencedTaskRunner(
-      {base::TaskPriority::USER_BLOCKING});
-  return std::make_unique<OffloadingAudioEncoder>(std::move(opus_encoder),
-                                                  std::move(encoding_runner),
-                                                  std::move(task_runner));
+  return nullptr;
 }
 
 VideoDecoderType GpuMojoMediaClient::GetDecoderImplementationType() {
diff --git a/media/webrtc/audio_processor.h b/media/webrtc/audio_processor.h
index ec08d2ce..1349be27 100644
--- a/media/webrtc/audio_processor.h
+++ b/media/webrtc/audio_processor.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/callback.h"
 #include "base/component_export.h"
 #include "base/files/file.h"
 #include "base/memory/scoped_refptr.h"
diff --git a/net/BUILD.gn b/net/BUILD.gn
index bc26f50..203d3a9 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -2209,7 +2209,16 @@
 
   allow_circular_includes_from = [ "//net/dns:test_support" ]
 
-  data = [ "data/" ]
+  # Data dependencies shared with suites other than net_unittests.
+  data = [
+    "data/cert_net_fetcher_impl_unittest/",
+    "data/dns/",
+    "data/ov_name_constraints/",
+    "data/parse_certificate_unittest/",
+    "data/ssl/",
+    "data/url_request_unittest/",
+    "data/websocket/",
+  ]
 
   if (is_mac) {
     frameworks = [ "Security.framework" ]
@@ -4563,7 +4572,30 @@
     ]
   }
 
-  data = []
+  data = [
+    "data/cache_tests/",
+    "data/certificate_policies_unittest/",
+    "data/cert_issuer_source_aia_unittest/",
+    "data/cert_issuer_source_static_unittest/",
+    "data/crl_unittest/",
+    "data/embedded_test_server/",
+    "data/file_stream_unittest/",
+    "data/filter_unittests/",
+    "data/gencerts/",
+    "data/name_constraints_unittest/",
+    "data/ocsp_unittest/",
+    "data/pac_file_fetcher_unittest/",
+    "data/path_builder_unittest/",
+    "data/quic_http_response_cache_data/",
+    "data/quic_http_response_cache_data_with_push/",
+    "data/spdy_tests/",
+    "data/test.html",
+    "data/trial_comparison_cert_verifier_unittest/",
+    "data/url_fetcher_impl_unittest/",
+    "data/verify_certificate_chain_unittest/",
+    "data/verify_name_match_unittest/",
+    "data/verify_signed_data_unittest/",
+  ]
   data_deps = [
     "third_party/nist-pkits/",
     "//testing/buildbot/filters:net_unittests_filters",
@@ -4891,6 +4923,10 @@
       "//base:i18n",
       "//net",
     ]
+    data = [
+      "fuzzer_data",
+      "fuzzer_dictionaries",
+    ]
     allow_circular_includes_from = [ "//net/dns:fuzzer_test_support" ]
   }
 }
diff --git a/net/tools/quic/quic_simple_client_bin.cc b/net/tools/quic/quic_simple_client_bin.cc
index 9233b80..d55bd2ab0 100644
--- a/net/tools/quic/quic_simple_client_bin.cc
+++ b/net/tools/quic/quic_simple_client_bin.cc
@@ -34,6 +34,8 @@
 //   quic_client http://www.example.com
 
 #include "base/logging.h"
+#include "base/ranges/algorithm.h"
+#include "net/base/address_family.h"
 #include "net/base/net_errors.h"
 #include "net/quic/address_utils.h"
 #include "net/third_party/quiche/src/common/platform/api/quiche_command_line_flags.h"
@@ -47,6 +49,8 @@
 #include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
 #include "net/tools/quic/quic_simple_client.h"
 #include "net/tools/quic/synchronous_host_resolver.h"
+#include "url/scheme_host_port.h"
+#include "url/url_constants.h"
 
 using quic::ProofVerifier;
 
@@ -67,14 +71,35 @@
     quic::QuicIpAddress ip_addr;
     if (!ip_addr.FromString(host_for_lookup)) {
       net::AddressList addresses;
-      int rv =
-          net::SynchronousHostResolver::Resolve(host_for_lookup, &addresses);
+      // TODO(https://crbug.com/1300660) Let the caller pass in the scheme
+      // rather than guessing "https"
+      int rv = net::SynchronousHostResolver::Resolve(
+          url::SchemeHostPort(url::kHttpsScheme, host_for_lookup, port),
+          &addresses);
       if (rv != net::OK) {
         LOG(ERROR) << "Unable to resolve '" << host_for_lookup
                    << "' : " << net::ErrorToShortString(rv);
         return nullptr;
       }
-      ip_addr = net::ToQuicIpAddress(addresses[0].address());
+      const auto endpoint = base::ranges::find_if(
+          addresses,
+          [address_family_for_lookup](net::AddressFamily family) {
+            if (address_family_for_lookup == AF_INET)
+              return family == net::AddressFamily::ADDRESS_FAMILY_IPV4;
+            if (address_family_for_lookup == AF_INET6)
+              return family == net::AddressFamily::ADDRESS_FAMILY_IPV6;
+            return address_family_for_lookup == AF_UNSPEC;
+          },
+          &net::IPEndPoint::GetFamily);
+      if (endpoint == addresses.end()) {
+        LOG(ERROR) << "No results for '" << host_for_lookup
+                   << "' with appropriate address family";
+        return nullptr;
+      }
+      // Arbitrarily select the first result with a matching address family,
+      // ignoring any subsequent matches.
+      ip_addr = net::ToQuicIpAddress(endpoint->address());
+      port = endpoint->port();
     }
 
     quic::QuicServerId server_id(host_for_handshake, port, false);
diff --git a/net/tools/quic/synchronous_host_resolver.cc b/net/tools/quic/synchronous_host_resolver.cc
index d28c50f7..a205e3f 100644
--- a/net/tools/quic/synchronous_host_resolver.cc
+++ b/net/tools/quic/synchronous_host_resolver.cc
@@ -24,6 +24,7 @@
 #include "net/log/net_log.h"
 #include "net/log/net_log_with_source.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/scheme_host_port.h"
 
 namespace net {
 
@@ -40,7 +41,7 @@
   ~ResolverThread() override;
 
   // Called on the main thread.
-  int Resolve(const std::string& host, AddressList* addresses);
+  int Resolve(url::SchemeHostPort scheme_host_port, AddressList* addresses);
 
   // SimpleThread methods:
   void Run() override;
@@ -49,12 +50,11 @@
   void OnResolutionComplete(base::OnceClosure on_done, int rv);
 
   AddressList* addresses_;
-  std::string host_;
-  int rv_;
+  url::SchemeHostPort scheme_host_port_;
+  int rv_ = ERR_UNEXPECTED;
 };
 
-ResolverThread::ResolverThread()
-    : SimpleThread("resolver_thread"), rv_(ERR_UNEXPECTED) {}
+ResolverThread::ResolverThread() : SimpleThread("resolver_thread") {}
 
 ResolverThread::~ResolverThread() = default;
 
@@ -67,11 +67,10 @@
   std::unique_ptr<net::HostResolver> resolver =
       net::HostResolver::CreateStandaloneResolver(NetLog::Get(), options);
 
-  HostPortPair host_port_pair(host_, 80);
   // No need to use a NetworkIsolationKey here, since this is an external tool
   // not used by net/ consumers.
   std::unique_ptr<net::HostResolver::ResolveHostRequest> request =
-      resolver->CreateRequest(host_port_pair, NetworkIsolationKey(),
+      resolver->CreateRequest(scheme_host_port_, NetworkIsolationKey(),
                               NetLogWithSource(), absl::nullopt);
 
   base::RunLoop run_loop;
@@ -89,8 +88,9 @@
   }
 }
 
-int ResolverThread::Resolve(const std::string& host, AddressList* addresses) {
-  host_ = host;
+int ResolverThread::Resolve(url::SchemeHostPort scheme_host_port,
+                            AddressList* addresses) {
+  scheme_host_port_ = std::move(scheme_host_port);
   addresses_ = addresses;
   this->Start();
   this->Join();
@@ -105,10 +105,10 @@
 }  // namespace
 
 // static
-int SynchronousHostResolver::Resolve(const std::string& host,
+int SynchronousHostResolver::Resolve(url::SchemeHostPort scheme_host_port,
                                      AddressList* addresses) {
   ResolverThread resolver;
-  return resolver.Resolve(host, addresses);
+  return resolver.Resolve(std::move(scheme_host_port), addresses);
 }
 
 }  // namespace net
diff --git a/net/tools/quic/synchronous_host_resolver.h b/net/tools/quic/synchronous_host_resolver.h
index 89b15a2..8bb511d 100644
--- a/net/tools/quic/synchronous_host_resolver.h
+++ b/net/tools/quic/synchronous_host_resolver.h
@@ -7,16 +7,16 @@
 #ifndef NET_TOOLS_QUIC_SYNCHRONOUS_HOST_RESOLVER_H_
 #define NET_TOOLS_QUIC_SYNCHRONOUS_HOST_RESOLVER_H_
 
-#include <string>
-
 #include "net/base/address_list.h"
 #include "net/dns/host_resolver.h"
+#include "url/scheme_host_port.h"
 
 namespace net {
 
 class SynchronousHostResolver {
  public:
-  static int Resolve(const std::string& host, AddressList* addresses);
+  static int Resolve(url::SchemeHostPort scheme_host_port,
+                     AddressList* addresses);
 };
 
 }  // namespace net
diff --git a/net/tools/root_store_tool/root_store.proto b/net/tools/root_store_tool/root_store.proto
index 030aff95..e4bd09a 100644
--- a/net/tools/root_store_tool/root_store.proto
+++ b/net/tools/root_store_tool/root_store.proto
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Proto definitions supporting the Chrome Root Store.
+// This file should be manually kept in sync with the corresponding google3
+// file.
+
 syntax = "proto3";
 
 package chrome_root_store;
@@ -22,6 +26,12 @@
   repeated string ev_policy_oids = 3;
 }
 
+// Message storing a complete Chrome Root Store.
 message RootStore {
   repeated TrustAnchor trust_anchors = 1;
+
+  // Major version # of the Chrome Root Store. It is assumed that if
+  // root_store_1.version_major > root_store_2.version_major, then root_store_1
+  // is newer and should be preferred over root_store_2.
+  int64 version_major = 2;
 }
diff --git a/pdf/pdfium/fuzzers/BUILD.gn b/pdf/pdfium/fuzzers/BUILD.gn
index 7916e22..bd4e616 100644
--- a/pdf/pdfium/fuzzers/BUILD.gn
+++ b/pdf/pdfium/fuzzers/BUILD.gn
@@ -59,9 +59,9 @@
         ":pdf_cfx_barcode_fuzzer",
         ":pdf_codec_jpeg_fuzzer",
         ":pdf_css_fuzzer",
-        ":pdf_fm2js_fuzzer",
         ":pdf_formcalc_context_fuzzer",
         ":pdf_formcalc_fuzzer",
+        ":pdf_formcalc_translate_fuzzer",
         ":pdfium_xfa_fuzzer",
         ":pdfium_xfa_lpm_fuzzer",
       ]
@@ -195,10 +195,6 @@
       dict = "dicts/pdf_css.dict"
     }
 
-    pdfium_fuzzer_test("pdf_fm2js_fuzzer") {
-      dict = "dicts/pdf_formcalc.dict"
-    }
-
     pdfium_fuzzer_test("pdf_formcalc_context_fuzzer") {
       dict = "dicts/pdf_xfa_js.dict"
     }
@@ -207,6 +203,10 @@
       dict = "dicts/pdf_formcalc.dict"
     }
 
+    pdfium_fuzzer_test("pdf_formcalc_translate_fuzzer") {
+      dict = "dicts/pdf_formcalc.dict"
+    }
+
     if (pdf_enable_xfa_gif) {
       pdfium_fuzzer_test("pdf_lzw_fuzzer") {
       }
diff --git a/remoting/host/basic_desktop_environment.cc b/remoting/host/basic_desktop_environment.cc
index db502cac..9e7f888 100644
--- a/remoting/host/basic_desktop_environment.cc
+++ b/remoting/host/basic_desktop_environment.cc
@@ -101,6 +101,12 @@
   return nullptr;
 }
 
+std::unique_ptr<DesktopDisplayInfoMonitor>
+BasicDesktopEnvironment::CreateDisplayInfoMonitor() {
+  return std::make_unique<DesktopDisplayInfoMonitor>(ui_task_runner_,
+                                                     client_session_control_);
+}
+
 std::unique_ptr<webrtc::MouseCursorMonitor>
 BasicDesktopEnvironment::CreateMouseCursorMonitor() {
   return std::make_unique<MouseCursorMonitorProxy>(video_capture_task_runner_,
@@ -135,25 +141,25 @@
 }
 
 std::unique_ptr<DesktopAndCursorConditionalComposer>
-BasicDesktopEnvironment::CreateComposingVideoCapturer() {
+BasicDesktopEnvironment::CreateComposingVideoCapturer(
+    std::unique_ptr<DesktopDisplayInfoMonitor> monitor) {
 #if BUILDFLAG(IS_APPLE)
   // Mac includes the mouse cursor in the captured image in curtain mode.
   if (options_.enable_curtaining())
     return nullptr;
 #endif
   return std::make_unique<DesktopAndCursorConditionalComposer>(
-      CreateVideoCapturer());
+      CreateVideoCapturer(std::move(monitor)));
 }
 
 std::unique_ptr<webrtc::DesktopCapturer>
-BasicDesktopEnvironment::CreateVideoCapturer() {
+BasicDesktopEnvironment::CreateVideoCapturer(
+    std::unique_ptr<DesktopDisplayInfoMonitor> monitor) {
   DCHECK(caller_task_runner_->BelongsToCurrentThread());
 
   auto result = std::make_unique<DesktopCapturerProxy>(
       video_capture_task_runner_, ui_task_runner_);
-  result->set_desktop_display_info_monitor(
-      std::make_unique<DesktopDisplayInfoMonitor>(ui_task_runner_,
-                                                  client_session_control_));
+  result->set_desktop_display_info_monitor(std::move(monitor));
   result->CreateCapturer(desktop_capture_options());
   return std::move(result);
 }
diff --git a/remoting/host/basic_desktop_environment.h b/remoting/host/basic_desktop_environment.h
index 8f1f249..dd4e439 100644
--- a/remoting/host/basic_desktop_environment.h
+++ b/remoting/host/basic_desktop_environment.h
@@ -39,7 +39,10 @@
   std::unique_ptr<AudioCapturer> CreateAudioCapturer() override;
   std::unique_ptr<InputInjector> CreateInputInjector() override;
   std::unique_ptr<ScreenControls> CreateScreenControls() override;
-  std::unique_ptr<webrtc::DesktopCapturer> CreateVideoCapturer() override;
+  std::unique_ptr<webrtc::DesktopCapturer> CreateVideoCapturer(
+      std::unique_ptr<DesktopDisplayInfoMonitor> monitor) override;
+  std::unique_ptr<DesktopDisplayInfoMonitor> CreateDisplayInfoMonitor()
+      override;
   std::unique_ptr<webrtc::MouseCursorMonitor> CreateMouseCursorMonitor()
       override;
   std::unique_ptr<KeyboardLayoutMonitor> CreateKeyboardLayoutMonitor(
@@ -52,7 +55,8 @@
   void SetCapabilities(const std::string& capabilities) override;
   uint32_t GetDesktopSessionId() const override;
   std::unique_ptr<DesktopAndCursorConditionalComposer>
-  CreateComposingVideoCapturer() override;
+  CreateComposingVideoCapturer(
+      std::unique_ptr<DesktopDisplayInfoMonitor> monitor) override;
 
  protected:
   friend class BasicDesktopEnvironmentFactory;
diff --git a/remoting/host/client_session.cc b/remoting/host/client_session.cc
index ca12864..b0082ab 100644
--- a/remoting/host/client_session.cc
+++ b/remoting/host/client_session.cc
@@ -23,6 +23,7 @@
 #include "remoting/host/audio_capturer.h"
 #include "remoting/host/base/screen_controls.h"
 #include "remoting/host/base/screen_resolution.h"
+#include "remoting/host/desktop_display_info_monitor.h"
 #include "remoting/host/desktop_environment.h"
 #include "remoting/host/file_transfer/file_transfer_message_handler.h"
 #include "remoting/host/file_transfer/rtc_log_file_operations.h"
@@ -456,15 +457,23 @@
 void ClientSession::CreateMediaStreams() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  // Create monitor to be used by the capturer. This is used only by
+  // DesktopCapturerProxy. With multi-process, the IpcDesktopEnvironment
+  // version of this method returns nullptr, and the monitor is created
+  // in the Desktop process instead (where DesktopCapturerProxy is used).
+  auto monitor = desktop_environment_->CreateDisplayInfoMonitor();
+
   // Create a VideoStream to pump frames from the capturer to the client.
-  auto composer = desktop_environment_->CreateComposingVideoCapturer();
+  auto composer =
+      desktop_environment_->CreateComposingVideoCapturer(std::move(monitor));
   if (composer) {
     desktop_and_cursor_composer_ = composer->GetWeakPtr();
     video_stream_ =
         connection_->StartVideoStream(kStreamName, std::move(composer));
   } else {
     video_stream_ = connection_->StartVideoStream(
-        kStreamName, desktop_environment_->CreateVideoCapturer());
+        kStreamName,
+        desktop_environment_->CreateVideoCapturer(std::move(monitor)));
   }
 
   // Create a AudioStream to pump audio from the capturer to the client.
diff --git a/remoting/host/desktop_environment.h b/remoting/host/desktop_environment.h
index 439c796..639b9cbe 100644
--- a/remoting/host/desktop_environment.h
+++ b/remoting/host/desktop_environment.h
@@ -26,6 +26,7 @@
 class AudioCapturer;
 class ClientSessionControl;
 class ClientSessionEvents;
+class DesktopDisplayInfoMonitor;
 class FileOperations;
 class InputInjector;
 class KeyboardLayoutMonitor;
@@ -48,7 +49,22 @@
   virtual std::unique_ptr<AudioCapturer> CreateAudioCapturer() = 0;
   virtual std::unique_ptr<InputInjector> CreateInputInjector() = 0;
   virtual std::unique_ptr<ScreenControls> CreateScreenControls() = 0;
-  virtual std::unique_ptr<webrtc::DesktopCapturer> CreateVideoCapturer() = 0;
+
+  // |monitor| is an optional parameter. If provided, it will be notified on
+  // every captured frame so it can refresh the display-info. Used (by
+  // DesktopCapturerProxy) only for the single-video-stream case, where there is
+  // only one capturer which owns the monitor. For multi-stream, the monitor is
+  // owned and managed independently from DesktopCapturerProxy.
+  // TODO(lambroslambrou): Remove this parameter when the single-stream
+  // implementation is removed. Alternatively, if the Win/Mac implementations
+  // of DesktopDisplayInfoLoader are updated to be event-driven (instead of
+  // polling per captured frame), this parameter could be removed even in
+  // the single-video-stream case.
+  virtual std::unique_ptr<webrtc::DesktopCapturer> CreateVideoCapturer(
+      std::unique_ptr<DesktopDisplayInfoMonitor> monitor) = 0;
+
+  virtual std::unique_ptr<DesktopDisplayInfoMonitor>
+  CreateDisplayInfoMonitor() = 0;
   virtual std::unique_ptr<webrtc::MouseCursorMonitor>
   CreateMouseCursorMonitor() = 0;
   virtual std::unique_ptr<KeyboardLayoutMonitor> CreateKeyboardLayoutMonitor(
@@ -63,7 +79,8 @@
   // If the platform already does this, this method return null, and the caller
   // should use CreateVideoCapturer() instead.
   virtual std::unique_ptr<DesktopAndCursorConditionalComposer>
-  CreateComposingVideoCapturer() = 0;
+  CreateComposingVideoCapturer(
+      std::unique_ptr<DesktopDisplayInfoMonitor> monitor) = 0;
 
   // Returns the set of all capabilities supported by |this|.
   virtual std::string GetCapabilities() const = 0;
diff --git a/remoting/host/desktop_process_unittest.cc b/remoting/host/desktop_process_unittest.cc
index 2514c7cb..c591801 100644
--- a/remoting/host/desktop_process_unittest.cc
+++ b/remoting/host/desktop_process_unittest.cc
@@ -218,7 +218,7 @@
       .WillOnce(Invoke(this, &DesktopProcessTest::CreateInputInjector));
   EXPECT_CALL(*desktop_environment, CreateActionExecutor()).Times(AtMost(1));
   EXPECT_CALL(*desktop_environment, CreateScreenControls()).Times(AtMost(1));
-  EXPECT_CALL(*desktop_environment, CreateVideoCapturer())
+  EXPECT_CALL(*desktop_environment, CreateVideoCapturer(_))
       .Times(AtMost(1))
       .WillOnce(
           Return(ByMove(std::make_unique<protocol::FakeDesktopCapturer>())));
diff --git a/remoting/host/desktop_session_agent.cc b/remoting/host/desktop_session_agent.cc
index 2db5138..697fb05 100644
--- a/remoting/host/desktop_session_agent.cc
+++ b/remoting/host/desktop_session_agent.cc
@@ -32,6 +32,7 @@
 #include "remoting/host/base/screen_resolution.h"
 #include "remoting/host/chromoting_messages.h"
 #include "remoting/host/crash_process.h"
+#include "remoting/host/desktop_display_info_monitor.h"
 #include "remoting/host/desktop_environment.h"
 #include "remoting/host/input_injector.h"
 #include "remoting/host/keyboard_layout_monitor.h"
@@ -431,8 +432,12 @@
   }
 
   // Start the video capturer and mouse cursor monitor.
+  // TODO(lambroslambrou): When supporting multiple streams, this class should
+  // create and own a single DesktopDisplayInfoMonitor and call Start() on it,
+  // instead of passing it to CreateVideoCapturer() here.
   video_capturer_ = std::make_unique<DesktopAndCursorConditionalComposer>(
-      desktop_environment_->CreateVideoCapturer());
+      desktop_environment_->CreateVideoCapturer(
+          desktop_environment_->CreateDisplayInfoMonitor()));
   video_capturer_->Start(this);
   video_capturer_->SetSharedMemoryFactory(
       std::make_unique<SharedMemoryFactoryImpl>(
diff --git a/remoting/host/fake_desktop_environment.cc b/remoting/host/fake_desktop_environment.cc
index 8bb75b9..345020d 100644
--- a/remoting/host/fake_desktop_environment.cc
+++ b/remoting/host/fake_desktop_environment.cc
@@ -10,6 +10,7 @@
 #include "base/memory/weak_ptr.h"
 #include "remoting/host/audio_capturer.h"
 #include "remoting/host/desktop_capturer_proxy.h"
+#include "remoting/host/desktop_display_info_monitor.h"
 #include "remoting/host/fake_keyboard_layout_monitor.h"
 #include "remoting/host/file_transfer/file_operations.h"
 #include "remoting/host/input_injector.h"
@@ -87,7 +88,8 @@
 }
 
 std::unique_ptr<webrtc::DesktopCapturer>
-FakeDesktopEnvironment::CreateVideoCapturer() {
+FakeDesktopEnvironment::CreateVideoCapturer(
+    std::unique_ptr<DesktopDisplayInfoMonitor> monitor) {
   auto fake_capturer = std::make_unique<protocol::FakeDesktopCapturer>();
   if (!frame_generator_.is_null())
     fake_capturer->set_frame_generator(frame_generator_);
@@ -98,6 +100,11 @@
   return std::move(result);
 }
 
+std::unique_ptr<DesktopDisplayInfoMonitor>
+FakeDesktopEnvironment::CreateDisplayInfoMonitor() {
+  return nullptr;
+}
+
 std::unique_ptr<webrtc::MouseCursorMonitor>
 FakeDesktopEnvironment::CreateMouseCursorMonitor() {
   return std::make_unique<FakeMouseCursorMonitor>();
@@ -129,7 +136,8 @@
 }
 
 std::unique_ptr<DesktopAndCursorConditionalComposer>
-FakeDesktopEnvironment::CreateComposingVideoCapturer() {
+FakeDesktopEnvironment::CreateComposingVideoCapturer(
+    std::unique_ptr<DesktopDisplayInfoMonitor> monitor) {
   return nullptr;
 }
 
diff --git a/remoting/host/fake_desktop_environment.h b/remoting/host/fake_desktop_environment.h
index 6821b08..c8afd5a 100644
--- a/remoting/host/fake_desktop_environment.h
+++ b/remoting/host/fake_desktop_environment.h
@@ -106,7 +106,10 @@
   std::unique_ptr<AudioCapturer> CreateAudioCapturer() override;
   std::unique_ptr<InputInjector> CreateInputInjector() override;
   std::unique_ptr<ScreenControls> CreateScreenControls() override;
-  std::unique_ptr<webrtc::DesktopCapturer> CreateVideoCapturer() override;
+  std::unique_ptr<webrtc::DesktopCapturer> CreateVideoCapturer(
+      std::unique_ptr<DesktopDisplayInfoMonitor> monitor) override;
+  std::unique_ptr<DesktopDisplayInfoMonitor> CreateDisplayInfoMonitor()
+      override;
   std::unique_ptr<webrtc::MouseCursorMonitor> CreateMouseCursorMonitor()
       override;
   std::unique_ptr<KeyboardLayoutMonitor> CreateKeyboardLayoutMonitor(
@@ -119,7 +122,8 @@
   void SetCapabilities(const std::string& capabilities) override;
   uint32_t GetDesktopSessionId() const override;
   std::unique_ptr<DesktopAndCursorConditionalComposer>
-  CreateComposingVideoCapturer() override;
+  CreateComposingVideoCapturer(
+      std::unique_ptr<DesktopDisplayInfoMonitor> monitor) override;
 
   base::WeakPtr<FakeInputInjector> last_input_injector() {
     return last_input_injector_;
diff --git a/remoting/host/host_mock_objects.h b/remoting/host/host_mock_objects.h
index 63d2dcda..64f20c1c 100644
--- a/remoting/host/host_mock_objects.h
+++ b/remoting/host/host_mock_objects.h
@@ -20,6 +20,7 @@
 #include "remoting/host/client_session_control.h"
 #include "remoting/host/client_session_details.h"
 #include "remoting/host/client_session_events.h"
+#include "remoting/host/desktop_display_info_monitor.h"
 #include "remoting/host/desktop_environment.h"
 #include "remoting/host/file_transfer/file_operations.h"
 #include "remoting/host/host_status_observer.h"
@@ -64,6 +65,10 @@
               (override));
   MOCK_METHOD(std::unique_ptr<webrtc::DesktopCapturer>,
               CreateVideoCapturer,
+              (std::unique_ptr<DesktopDisplayInfoMonitor>),
+              (override));
+  MOCK_METHOD(std::unique_ptr<DesktopDisplayInfoMonitor>,
+              CreateDisplayInfoMonitor,
               (),
               (override));
   MOCK_METHOD(std::unique_ptr<webrtc::MouseCursorMonitor>,
@@ -84,7 +89,7 @@
               (override));
   MOCK_METHOD(std::unique_ptr<DesktopAndCursorConditionalComposer>,
               CreateComposingVideoCapturer,
-              (),
+              (std::unique_ptr<DesktopDisplayInfoMonitor> monitor),
               (override));
   MOCK_METHOD(std::string, GetCapabilities, (), (const, override));
   MOCK_METHOD(void, SetCapabilities, (const std::string&), (override));
diff --git a/remoting/host/ipc_desktop_environment.cc b/remoting/host/ipc_desktop_environment.cc
index ac1acff1..9c78c232 100644
--- a/remoting/host/ipc_desktop_environment.cc
+++ b/remoting/host/ipc_desktop_environment.cc
@@ -17,6 +17,7 @@
 #include "remoting/host/audio_capturer.h"
 #include "remoting/host/base/screen_controls.h"
 #include "remoting/host/client_session_control.h"
+#include "remoting/host/desktop_display_info_monitor.h"
 #include "remoting/host/desktop_session.h"
 #include "remoting/host/desktop_session_proxy.h"
 #include "remoting/host/file_transfer/file_operations.h"
@@ -65,6 +66,12 @@
   return desktop_session_proxy_->CreateScreenControls();
 }
 
+std::unique_ptr<DesktopDisplayInfoMonitor>
+IpcDesktopEnvironment::CreateDisplayInfoMonitor() {
+  // Not used in the Network process.
+  return nullptr;
+}
+
 std::unique_ptr<webrtc::MouseCursorMonitor>
 IpcDesktopEnvironment::CreateMouseCursorMonitor() {
   return desktop_session_proxy_->CreateMouseCursorMonitor();
@@ -78,7 +85,8 @@
 }
 
 std::unique_ptr<webrtc::DesktopCapturer>
-IpcDesktopEnvironment::CreateVideoCapturer() {
+IpcDesktopEnvironment::CreateVideoCapturer(
+    std::unique_ptr<DesktopDisplayInfoMonitor> monitor) {
   return desktop_session_proxy_->CreateVideoCapturer();
 }
 
@@ -104,7 +112,8 @@
 }
 
 std::unique_ptr<DesktopAndCursorConditionalComposer>
-IpcDesktopEnvironment::CreateComposingVideoCapturer() {
+IpcDesktopEnvironment::CreateComposingVideoCapturer(
+    std::unique_ptr<DesktopDisplayInfoMonitor> monitor) {
   // Cursor compositing is done by the desktop process if necessary.
   return nullptr;
 }
diff --git a/remoting/host/ipc_desktop_environment.h b/remoting/host/ipc_desktop_environment.h
index 8806103f..c95fcf0 100644
--- a/remoting/host/ipc_desktop_environment.h
+++ b/remoting/host/ipc_desktop_environment.h
@@ -58,7 +58,10 @@
   std::unique_ptr<AudioCapturer> CreateAudioCapturer() override;
   std::unique_ptr<InputInjector> CreateInputInjector() override;
   std::unique_ptr<ScreenControls> CreateScreenControls() override;
-  std::unique_ptr<webrtc::DesktopCapturer> CreateVideoCapturer() override;
+  std::unique_ptr<webrtc::DesktopCapturer> CreateVideoCapturer(
+      std::unique_ptr<DesktopDisplayInfoMonitor> monitor) override;
+  std::unique_ptr<DesktopDisplayInfoMonitor> CreateDisplayInfoMonitor()
+      override;
   std::unique_ptr<webrtc::MouseCursorMonitor> CreateMouseCursorMonitor()
       override;
   std::unique_ptr<KeyboardLayoutMonitor> CreateKeyboardLayoutMonitor(
@@ -71,7 +74,8 @@
   void SetCapabilities(const std::string& capabilities) override;
   uint32_t GetDesktopSessionId() const override;
   std::unique_ptr<DesktopAndCursorConditionalComposer>
-  CreateComposingVideoCapturer() override;
+  CreateComposingVideoCapturer(
+      std::unique_ptr<DesktopDisplayInfoMonitor> monitor) override;
 
  private:
   scoped_refptr<DesktopSessionProxy> desktop_session_proxy_;
diff --git a/remoting/host/ipc_desktop_environment_unittest.cc b/remoting/host/ipc_desktop_environment_unittest.cc
index a99bd826..73b6400 100644
--- a/remoting/host/ipc_desktop_environment_unittest.cc
+++ b/remoting/host/ipc_desktop_environment_unittest.cc
@@ -376,8 +376,7 @@
   input_injector_ = desktop_environment_->CreateInputInjector();
 
   // Create the screen capturer.
-  video_capturer_ =
-      desktop_environment_->CreateVideoCapturer();
+  video_capturer_ = desktop_environment_->CreateVideoCapturer(nullptr);
 
   desktop_environment_->SetCapabilities(std::string());
 
@@ -416,7 +415,7 @@
       .Times(AtMost(1))
       .WillOnce(Invoke(this, &IpcDesktopEnvironmentTest::CreateInputInjector));
   EXPECT_CALL(*desktop_environment, CreateScreenControls()).Times(AtMost(1));
-  EXPECT_CALL(*desktop_environment, CreateVideoCapturer())
+  EXPECT_CALL(*desktop_environment, CreateVideoCapturer(_))
       .Times(AtMost(1))
       .WillOnce(
           Return(ByMove(std::make_unique<protocol::FakeDesktopCapturer>())));
diff --git a/remoting/host/linux/linux_me2me_host.py b/remoting/host/linux/linux_me2me_host.py
index da1446b..744d779 100755
--- a/remoting/host/linux/linux_me2me_host.py
+++ b/remoting/host/linux/linux_me2me_host.py
@@ -853,7 +853,7 @@
     socket_num = 1
     full_sock_path = os.path.join(self.runtime_dir, "wayland-%s" % socket_num)
     while ((os.path.exists(full_sock_path)) and
-            socket_num <= self.MAX_WAYLAND_SOCKET_NUM)):
+            socket_num <= self.MAX_WAYLAND_SOCKET_NUM):
       socket_num += 1
       full_sock_path = os.path.join(self.runtime_dir, "wayland-%s" % socket_num)
     if socket_num > self.MAX_WAYLAND_SOCKET_NUM:
diff --git a/services/network/sct_auditing/sct_auditing_cache.cc b/services/network/sct_auditing/sct_auditing_cache.cc
index 758ff04..e3b2997 100644
--- a/services/network/sct_auditing/sct_auditing_cache.cc
+++ b/services/network/sct_auditing/sct_auditing_cache.cc
@@ -4,6 +4,8 @@
 
 #include "services/network/sct_auditing/sct_auditing_cache.h"
 
+#include <algorithm>
+
 #include "base/callback.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/rand_util.h"
@@ -163,6 +165,13 @@
   return report_entry;
 }
 
+bool SCTAuditingCache::IsPopularSCT(base::span<const uint8_t> sct_leaf_hash) {
+  // Copy into a vector to make comparisons easier.
+  std::vector<uint8_t> leaf_hash(sct_leaf_hash.begin(), sct_leaf_hash.end());
+  return std::binary_search(popular_scts_.begin(), popular_scts_.end(),
+                            leaf_hash);
+}
+
 void SCTAuditingCache::ClearCache() {
   // Empty the deduplication cache.
   dedupe_cache_.Clear();
diff --git a/services/network/sct_auditing/sct_auditing_cache.h b/services/network/sct_auditing/sct_auditing_cache.h
index 022f980..50cae28 100644
--- a/services/network/sct_auditing/sct_auditing_cache.h
+++ b/services/network/sct_auditing/sct_auditing_cache.h
@@ -82,6 +82,9 @@
       const net::SignedCertificateTimestampAndStatusList&
           signed_certificate_timestamps);
 
+  // Returns true if |sct_leaf_hash| corresponds to a known popular SCT.
+  bool IsPopularSCT(base::span<const uint8_t> sct_leaf_hash);
+
   void ClearCache();
 
   void set_sampling_rate(double rate) { sampling_rate_ = rate; }
diff --git a/services/network/sct_auditing/sct_auditing_handler.cc b/services/network/sct_auditing/sct_auditing_handler.cc
index d694d2a..5258f8c 100644
--- a/services/network/sct_auditing/sct_auditing_handler.cc
+++ b/services/network/sct_auditing/sct_auditing_handler.cc
@@ -56,6 +56,11 @@
 const char kReportKey[] = "report";
 const char kSCTHashdanceMetadataKey[] = "sct_metadata";
 
+void RecordPopularSCTSkippedMetrics(bool popular_sct_skipped) {
+  base::UmaHistogramBoolean("Security.SCTAuditing.OptOut.PopularSCTSkipped",
+                            popular_sct_skipped);
+}
+
 }  // namespace
 
 SCTAuditingHandler::SCTAuditingHandler(NetworkContext* context,
@@ -133,6 +138,16 @@
     result = net::ct::HashMerkleTreeLeaf(tree_leaf, &sct_metadata->leaf_hash);
     DCHECK(result);
 
+    // Do not report if this is a known popular SCT.
+    if (owner_network_context_->network_service()
+            ->sct_auditing_cache()
+            ->IsPopularSCT(
+                base::as_bytes(base::make_span(sct_metadata->leaf_hash)))) {
+      RecordPopularSCTSkippedMetrics(true);
+      return;
+    }
+    RecordPopularSCTSkippedMetrics(false);
+
     // Find the corresponding log entry metadata.
     const std::vector<mojom::CTLogInfoPtr>& logs =
         owner_network_context_->network_service()->log_list();
diff --git a/services/network/sct_auditing/sct_auditing_handler_unittest.cc b/services/network/sct_auditing/sct_auditing_handler_unittest.cc
index a144fd07..530806e 100644
--- a/services/network/sct_auditing/sct_auditing_handler_unittest.cc
+++ b/services/network/sct_auditing/sct_auditing_handler_unittest.cc
@@ -290,7 +290,8 @@
 
 // If operating on hashdance mode, calculate and store the SCT leaf hash and
 // append log metadata.
-TEST_F(SCTAuditingHandlerTest, PopularSCTMetadataOnHashdanceMode) {
+TEST_F(SCTAuditingHandlerTest, PopulateSCTMetadataOnHashdanceMode) {
+  base::HistogramTester histograms;
   const net::HostPortPair host_port_pair("example.com", 443);
   net::SignedCertificateTimestampAndStatusList sct_list;
   const base::Time issued = base::Time::Now();
@@ -306,7 +307,6 @@
     handler_->MaybeEnqueueReport(host_port_pair, chain_.get(), sct_list);
     auto* pending_reporters = handler_->GetPendingReportersForTesting();
     ASSERT_EQ(pending_reporters->size(), 1u);
-
     for (const auto& reporter : *pending_reporters) {
       if (mode == mojom::SCTAuditingMode::kHashdance) {
         net::ct::MerkleTreeLeaf merkle_tree_leaf;
@@ -327,6 +327,55 @@
         EXPECT_FALSE(reporter.second->sct_hashdance_metadata());
       }
     }
+    histograms.ExpectUniqueSample(
+        "Security.SCTAuditing.OptOut.PopularSCTSkipped", false,
+        mode == mojom::SCTAuditingMode::kHashdance ? 1 : 0);
+    handler_->ClearPendingReports();
+    network_service_->sct_auditing_cache()->ClearCache();
+  }
+}
+
+// If operating on hashdance mode, do not report popular SCTs.
+TEST_F(SCTAuditingHandlerTest, DoNotReportPopularSCT) {
+  const net::HostPortPair host_port_pair("example.com", 443);
+  net::SignedCertificateTimestampAndStatusList sct_list;
+  MakeTestSCTAndStatus(
+      net::ct::SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION, "extensions",
+      "valid_signature", base::Time::Now(), net::ct::SCT_STATUS_OK, &sct_list);
+  net::ct::MerkleTreeLeaf merkle_tree_leaf;
+  std::string leaf_hash_string;
+  ASSERT_TRUE(net::ct::GetMerkleTreeLeaf(chain_.get(), sct_list.at(0).sct.get(),
+                                         &merkle_tree_leaf));
+  ASSERT_TRUE(net::ct::HashMerkleTreeLeaf(merkle_tree_leaf, &leaf_hash_string));
+  std::vector<uint8_t> leaf_hash(leaf_hash_string.begin(),
+                                 leaf_hash_string.end());
+
+  // Create a list of sorted leaf hashes that will contain the SCT's leaf hash.
+  std::vector<std::vector<uint8_t>> leaf_hashes;
+  for (size_t byte = 0; byte < 256; ++byte) {
+    std::vector<uint8_t> new_leaf_hash = leaf_hash;
+    new_leaf_hash[0] = byte;
+    leaf_hashes.emplace_back(std::move(new_leaf_hash));
+  }
+
+  network_service_->sct_auditing_cache()->set_popular_scts({leaf_hash});
+
+  for (mojom::SCTAuditingMode mode :
+       {mojom::SCTAuditingMode::kEnhancedSafeBrowsingReporting,
+        mojom::SCTAuditingMode::kHashdance}) {
+    SCOPED_TRACE(testing::Message() << "Mode: " << static_cast<int>(mode));
+    base::HistogramTester histograms;
+    handler_->SetMode(mode);
+    handler_->MaybeEnqueueReport(host_port_pair, chain_.get(), sct_list);
+    auto* pending_reporters = handler_->GetPendingReportersForTesting();
+    EXPECT_EQ(pending_reporters->size(),
+              mode == mojom::SCTAuditingMode::kHashdance ? 0u : 1u);
+
+    // The hashdance request should record a count for PopularSCTSkipped.
+    histograms.ExpectUniqueSample(
+        "Security.SCTAuditing.OptOut.PopularSCTSkipped", true,
+        mode == mojom::SCTAuditingMode::kHashdance ? 1 : 0);
+
     handler_->ClearPendingReports();
     network_service_->sct_auditing_cache()->ClearCache();
   }
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index bdad468..a974e48 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -200,21 +200,6 @@
             ]
         }
     ],
-    "AndroidMessagesPermissionUpdate": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "MessagesForAndroidPermissionUpdate"
-                    ]
-                }
-            ]
-        }
-    ],
     "AndroidMessagesSaveCard": [
         {
             "platforms": [
@@ -4878,9 +4863,9 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled_20210726",
+                    "name": "Enabled_20220224",
                     "params": {
-                        "compress_bitmaps": "true"
+                        "compress_bitmaps": "false"
                     },
                     "enable_features": [
                         "PaintPreviewShowOnStartup"
diff --git a/third_party/android_deps/buildSrc/src/main/groovy/ChromiumDepGraph.groovy b/third_party/android_deps/buildSrc/src/main/groovy/ChromiumDepGraph.groovy
index f8f3acf..1d64260e 100644
--- a/third_party/android_deps/buildSrc/src/main/groovy/ChromiumDepGraph.groovy
+++ b/third_party/android_deps/buildSrc/src/main/groovy/ChromiumDepGraph.groovy
@@ -181,6 +181,9 @@
         org_robolectric_junit: new PropertyOverride(
             licensePath: 'licenses/Codehaus_License-2009.txt',
             licenseName: 'MIT'),
+        org_robolectric_nativeruntime: new PropertyOverride(
+            licensePath: 'licenses/Codehaus_License-2009.txt',
+            licenseName: 'MIT'),
         org_robolectric_pluginapi: new PropertyOverride(
             licensePath: 'licenses/Codehaus_License-2009.txt',
             licenseName: 'MIT'),
diff --git a/third_party/android_deps/libs/backport_util_concurrent_backport_util_concurrent/3pp/fetch.py b/third_party/android_deps/libs/backport_util_concurrent_backport_util_concurrent/3pp/fetch.py
deleted file mode 100755
index 15d28d8..0000000
--- a/third_party/android_deps/libs/backport_util_concurrent_backport_util_concurrent/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'backport-util-concurrent'
-_MODULE_NAME = 'backport-util-concurrent'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/classworlds_classworlds/3pp/3pp.pb b/third_party/android_deps/libs/classworlds_classworlds/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/classworlds_classworlds/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/classworlds_classworlds/3pp/fetch.py b/third_party/android_deps/libs/classworlds_classworlds/3pp/fetch.py
deleted file mode 100755
index 7e6e4c0..0000000
--- a/third_party/android_deps/libs/classworlds_classworlds/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'classworlds'
-_MODULE_NAME = 'classworlds'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/nekohtml_nekohtml/3pp/3pp.pb b/third_party/android_deps/libs/nekohtml_nekohtml/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/nekohtml_nekohtml/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/nekohtml_nekohtml/3pp/fetch.py b/third_party/android_deps/libs/nekohtml_nekohtml/3pp/fetch.py
deleted file mode 100755
index 8023ad3..0000000
--- a/third_party/android_deps/libs/nekohtml_nekohtml/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'nekohtml'
-_MODULE_NAME = 'nekohtml'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/nekohtml_xercesminimal/3pp/3pp.pb b/third_party/android_deps/libs/nekohtml_xercesminimal/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/nekohtml_xercesminimal/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/nekohtml_xercesminimal/3pp/fetch.py b/third_party/android_deps/libs/nekohtml_xercesminimal/3pp/fetch.py
deleted file mode 100755
index e84243f9..0000000
--- a/third_party/android_deps/libs/nekohtml_xercesminimal/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'nekohtml'
-_MODULE_NAME = 'xercesMinimal'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = '1.9.6.2'
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_ant_ant/3pp/fetch.py b/third_party/android_deps/libs/org_apache_ant_ant/3pp/fetch.py
deleted file mode 100644
index d116427e..0000000
--- a/third_party/android_deps/libs/org_apache_ant_ant/3pp/fetch.py
+++ /dev/null
@@ -1,73 +0,0 @@
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/ant'
-_MODULE_NAME = 'ant'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode(
-        'utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_ant_ant_launcher/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_ant_ant_launcher/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_ant_ant_launcher/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_ant_ant_launcher/3pp/fetch.py b/third_party/android_deps/libs/org_apache_ant_ant_launcher/3pp/fetch.py
deleted file mode 100755
index 2aa40bd..0000000
--- a/third_party/android_deps/libs/org_apache_ant_ant_launcher/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/ant'
-_MODULE_NAME = 'ant-launcher'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_ant_tasks/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_maven_ant_tasks/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_ant_tasks/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_ant_tasks/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_maven_ant_tasks/3pp/fetch.py
deleted file mode 100755
index e8fbac5e..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_ant_tasks/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven'
-_MODULE_NAME = 'maven-ant-tasks'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_artifact/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_maven_artifact/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_artifact/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_artifact/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_maven_artifact/3pp/fetch.py
deleted file mode 100755
index 0832316..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_artifact/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven'
-_MODULE_NAME = 'maven-artifact'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_artifact_manager/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_maven_artifact_manager/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_artifact_manager/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_artifact_manager/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_maven_artifact_manager/3pp/fetch.py
deleted file mode 100755
index cd1bc74..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_artifact_manager/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven'
-_MODULE_NAME = 'maven-artifact-manager'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_error_diagnostics/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_maven_error_diagnostics/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_error_diagnostics/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_error_diagnostics/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_maven_error_diagnostics/3pp/fetch.py
deleted file mode 100755
index c3abf39..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_error_diagnostics/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven'
-_MODULE_NAME = 'maven-error-diagnostics'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_model/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_maven_model/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_model/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_model/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_maven_model/3pp/fetch.py
deleted file mode 100755
index 81766545..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_model/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven'
-_MODULE_NAME = 'maven-model'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_plugin_registry/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_maven_plugin_registry/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_plugin_registry/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_plugin_registry/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_maven_plugin_registry/3pp/fetch.py
deleted file mode 100755
index 0f920386..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_plugin_registry/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven'
-_MODULE_NAME = 'maven-plugin-registry'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_profile/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_maven_profile/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_profile/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_project/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_maven_project/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_project/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_project/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_maven_project/3pp/fetch.py
deleted file mode 100755
index 2e40e7e..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_project/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven'
-_MODULE_NAME = 'maven-project'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_repository_metadata/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_maven_repository_metadata/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_repository_metadata/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_repository_metadata/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_maven_repository_metadata/3pp/fetch.py
deleted file mode 100755
index 5e7613c..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_repository_metadata/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven'
-_MODULE_NAME = 'maven-repository-metadata'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_settings/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_maven_settings/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_settings/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_settings/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_maven_settings/3pp/fetch.py
deleted file mode 100755
index 14bfd809..0000000
--- a/third_party/android_deps/libs/org_apache_maven_maven_settings/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven'
-_MODULE_NAME = 'maven-settings'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_file/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_wagon_wagon_file/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_file/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_file/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_wagon_wagon_file/3pp/fetch.py
deleted file mode 100755
index 5266035..0000000
--- a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_file/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven/wagon'
-_MODULE_NAME = 'wagon-file'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_http_lightweight/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_wagon_wagon_http_lightweight/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_http_lightweight/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_http_lightweight/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_wagon_wagon_http_lightweight/3pp/fetch.py
deleted file mode 100755
index 2e352b2a..0000000
--- a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_http_lightweight/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven/wagon'
-_MODULE_NAME = 'wagon-http-lightweight'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_http_shared/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_wagon_wagon_http_shared/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_http_shared/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_http_shared/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_wagon_wagon_http_shared/3pp/fetch.py
deleted file mode 100755
index 1f94c70..0000000
--- a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_http_shared/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven/wagon'
-_MODULE_NAME = 'wagon-http-shared'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_provider_api/3pp/3pp.pb b/third_party/android_deps/libs/org_apache_maven_wagon_wagon_provider_api/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_provider_api/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_provider_api/3pp/fetch.py b/third_party/android_deps/libs/org_apache_maven_wagon_wagon_provider_api/3pp/fetch.py
deleted file mode 100755
index 5061abc..0000000
--- a/third_party/android_deps/libs/org_apache_maven_wagon_wagon_provider_api/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven/wagon'
-_MODULE_NAME = 'wagon-provider-api'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_codehaus_plexus_plexus_container_default/3pp/3pp.pb b/third_party/android_deps/libs/org_codehaus_plexus_plexus_container_default/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_codehaus_plexus_plexus_container_default/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_codehaus_plexus_plexus_container_default/3pp/fetch.py b/third_party/android_deps/libs/org_codehaus_plexus_plexus_container_default/3pp/fetch.py
deleted file mode 100755
index bb7a442..0000000
--- a/third_party/android_deps/libs/org_codehaus_plexus_plexus_container_default/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/codehaus/plexus'
-_MODULE_NAME = 'plexus-container-default'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_codehaus_plexus_plexus_interpolation/3pp/3pp.pb b/third_party/android_deps/libs/org_codehaus_plexus_plexus_interpolation/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_codehaus_plexus_plexus_interpolation/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_codehaus_plexus_plexus_interpolation/3pp/fetch.py b/third_party/android_deps/libs/org_codehaus_plexus_plexus_interpolation/3pp/fetch.py
deleted file mode 100755
index 92c9eb90..0000000
--- a/third_party/android_deps/libs/org_codehaus_plexus_plexus_interpolation/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/codehaus/plexus'
-_MODULE_NAME = 'plexus-interpolation'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_codehaus_plexus_plexus_utils/3pp/3pp.pb b/third_party/android_deps/libs/org_codehaus_plexus_plexus_utils/3pp/3pp.pb
deleted file mode 100644
index d8137c1..0000000
--- a/third_party/android_deps/libs/org_codehaus_plexus_plexus_utils/3pp/3pp.pb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
-
-create {
-  source {
-    script { name: "fetch.py" }
-  }
-}
-
-upload {
-  pkg_prefix: "chromium/third_party/android_deps/libs"
-  universal: true
-}
diff --git a/third_party/android_deps/libs/org_codehaus_plexus_plexus_utils/3pp/fetch.py b/third_party/android_deps/libs/org_codehaus_plexus_plexus_utils/3pp/fetch.py
deleted file mode 100755
index 2f611f0..0000000
--- a/third_party/android_deps/libs/org_codehaus_plexus_plexus_utils/3pp/fetch.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This is generated, do not edit. Update BuildConfigGenerator.groovy and
-# 3ppFetch.template instead.
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import re
-
-from six.moves import urllib
-
-_REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/codehaus/plexus'
-_MODULE_NAME = 'plexus-utils'
-_FILE_EXT = 'jar'
-_OVERRIDE_LATEST = None
-_PATCH_VERSION = 'cr0'
-
-
-def do_latest():
-    if _OVERRIDE_LATEST is not None:
-        print(_OVERRIDE_LATEST)
-        return
-    maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
-        _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
-    # Do not parse xml with the python included parser since it is susceptible
-    # to maliciously crafted xmls. Only use regular expression parsing to be
-    # safe. RE should be enough to handle what we need to extract.
-    match = re.search('<latest>([^<]+)</latest>', metadata)
-    if match:
-        latest = match.group(1)
-    else:
-        # if no latest info was found just hope the versions are sorted and the
-        # last one is the latest (as is commonly the case).
-        latest = re.findall('<version>([^<]+)</version>', metadata)[-1]
-    print(latest + f'.{_PATCH_VERSION}')
-
-
-def get_download_url(version):
-    # Remove the patch version when getting the download url
-    version_no_patch, patch = version.rsplit('.', 1)
-    if patch.startswith('cr'):
-        version = version_no_patch
-    file_url = '{0}/{1}/{2}/{3}/{2}-{3}.{4}'.format(_REPO_URL, _GROUP_NAME,
-                                                    _MODULE_NAME, version,
-                                                    _FILE_EXT)
-    file_name = file_url.rsplit('/', 1)[-1]
-
-    partial_manifest = {
-        'url': [file_url],
-        'name': [file_name],
-        'ext': '.' + _FILE_EXT,
-    }
-    print(json.dumps(partial_manifest))
-
-
-def main():
-    ap = argparse.ArgumentParser()
-    sub = ap.add_subparsers()
-
-    latest = sub.add_parser("latest")
-    latest.set_defaults(func=lambda _opts: do_latest())
-
-    download = sub.add_parser("get_url")
-    download.set_defaults(
-        func=lambda _opts: get_download_url(os.environ['_3PP_VERSION']))
-
-    opts = ap.parse_args()
-    opts.func(opts)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/third_party/android_deps/libs/org_apache_ant_ant/3pp/3pp.pb b/third_party/android_deps/libs/org_robolectric_nativeruntime/3pp/3pp.pb
similarity index 100%
rename from third_party/android_deps/libs/org_apache_ant_ant/3pp/3pp.pb
rename to third_party/android_deps/libs/org_robolectric_nativeruntime/3pp/3pp.pb
diff --git a/third_party/android_deps/libs/org_apache_maven_maven_profile/3pp/fetch.py b/third_party/android_deps/libs/org_robolectric_nativeruntime/3pp/fetch.py
similarity index 96%
rename from third_party/android_deps/libs/org_apache_maven_maven_profile/3pp/fetch.py
rename to third_party/android_deps/libs/org_robolectric_nativeruntime/3pp/fetch.py
index 737a5a2a..62d39a2 100755
--- a/third_party/android_deps/libs/org_apache_maven_maven_profile/3pp/fetch.py
+++ b/third_party/android_deps/libs/org_robolectric_nativeruntime/3pp/fetch.py
@@ -16,8 +16,8 @@
 from six.moves import urllib
 
 _REPO_URL = 'https://repo.maven.apache.org/maven2'
-_GROUP_NAME = 'org/apache/maven'
-_MODULE_NAME = 'maven-profile'
+_GROUP_NAME = 'org/robolectric'
+_MODULE_NAME = 'nativeruntime'
 _FILE_EXT = 'jar'
 _OVERRIDE_LATEST = None
 _PATCH_VERSION = 'cr0'
@@ -29,7 +29,8 @@
         return
     maven_metadata_url = '{}/{}/{}/maven-metadata.xml'.format(
         _REPO_URL, _GROUP_NAME, _MODULE_NAME)
-    metadata = urllib.request.urlopen(maven_metadata_url).read().decode('utf-8')
+    metadata = urllib.request.urlopen(maven_metadata_url).read().decode(
+        'utf-8')
     # Do not parse xml with the python included parser since it is susceptible
     # to maliciously crafted xmls. Only use regular expression parsing to be
     # safe. RE should be enough to handle what we need to extract.
diff --git a/third_party/android_deps/libs/org_robolectric_nativeruntime/LICENSE b/third_party/android_deps/libs/org_robolectric_nativeruntime/LICENSE
new file mode 100644
index 0000000..370fb55
--- /dev/null
+++ b/third_party/android_deps/libs/org_robolectric_nativeruntime/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2009 codehaus.org.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/android_deps/libs/org_robolectric_nativeruntime/OWNERS b/third_party/android_deps/libs/org_robolectric_nativeruntime/OWNERS
new file mode 100644
index 0000000..aea47a05
--- /dev/null
+++ b/third_party/android_deps/libs/org_robolectric_nativeruntime/OWNERS
@@ -0,0 +1 @@
+file://third_party/android_deps/OWNERS
diff --git a/third_party/android_deps/libs/org_robolectric_nativeruntime/README.chromium b/third_party/android_deps/libs/org_robolectric_nativeruntime/README.chromium
new file mode 100644
index 0000000..6a6e3b16
--- /dev/null
+++ b/third_party/android_deps/libs/org_robolectric_nativeruntime/README.chromium
@@ -0,0 +1,13 @@
+Name: nativeruntime
+Short Name: nativeruntime
+URL: http://robolectric.org
+Version: 4.7.3
+License: MIT
+License File: NOT_SHIPPED
+Security Critical: no
+
+Description:
+An alternative Android testing framework.
+
+Local Modifications:
+No modifications.
diff --git a/third_party/android_deps/libs/org_robolectric_nativeruntime/cipd.yaml b/third_party/android_deps/libs/org_robolectric_nativeruntime/cipd.yaml
new file mode 100644
index 0000000..1dabf767
--- /dev/null
+++ b/third_party/android_deps/libs/org_robolectric_nativeruntime/cipd.yaml
@@ -0,0 +1,10 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# To create CIPD package run the following command.
+# cipd create --pkg-def cipd.yaml -tag version:2@4.7.3.cr0
+package: chromium/third_party/android_deps/libs/org_robolectric_nativeruntime
+description: "nativeruntime"
+data:
+- file: nativeruntime-4.7.3.jar
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 9399d9c8..1193390 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -1023,6 +1023,19 @@
   return base::FeatureList::IsEnabled(blink::features::kAllowURNsInIframes);
 }
 
+// https://github.com/jkarlin/topics
+// Kill switch for the Topics API.
+const base::Feature kBrowsingTopics{"BrowsingTopics",
+                                    base::FEATURE_DISABLED_BY_DEFAULT};
+// The max number of entries allowed to be retrieved from the
+// `BrowsingTopicsSiteDataStorage` database for each query for the API usage
+// contexts. The query will occur once per epoch (week) at topics calculation
+// time. The intent is to cap the peak memory usage.
+const base::FeatureParam<int>
+    kBrowsingTopicsMaxNumberOfApiUsageContextEntriesToLoadPerEpoch{
+        &kBrowsingTopics,
+        "max_number_of_api_usage_context_entries_to_load_per_epoch", 100000};
+
 // Enable the ability to minimize processing in the WebRTC APM when all audio
 // tracks are disabled. If disabled, the APM in WebRTC will ignore attempts to
 // set it in a low-processing mode when all audio tracks are disabled.
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index fcbc595..fec732f 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -463,6 +463,10 @@
 // Returns true when Prerender2 feature is enabled.
 BLINK_COMMON_EXPORT bool IsAllowURNsInIframeEnabled();
 
+BLINK_COMMON_EXPORT extern const base::Feature kBrowsingTopics;
+BLINK_COMMON_EXPORT extern const base::FeatureParam<int>
+    kBrowsingTopicsMaxNumberOfApiUsageContextEntriesToLoadPerEpoch;
+
 // Control switch for minimizing processing in the WebRTC APM when all audio
 // tracks are disabled.
 BLINK_COMMON_EXPORT extern const base::Feature
diff --git a/third_party/blink/public/web/web_navigation_control.h b/third_party/blink/public/web/web_navigation_control.h
index 7d2ce2b..6bc8ade 100644
--- a/third_party/blink/public/web/web_navigation_control.h
+++ b/third_party/blink/public/web/web_navigation_control.h
@@ -54,8 +54,7 @@
       bool is_client_redirect,
       bool has_transient_user_activation,
       const WebSecurityOrigin& initiator_origin,
-      bool is_browser_initiated,
-      std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) = 0;
+      bool is_browser_initiated) = 0;
 
   // Override the normal rules that determine whether the frame is on the
   // initial empty document or not. Used to propagate state when this frame has
diff --git a/third_party/blink/renderer/core/app_history/app_history_test.cc b/third_party/blink/renderer/core/app_history/app_history_test.cc
index 584e9f5..fa9f9ad 100644
--- a/third_party/blink/renderer/core/app_history/app_history_test.cc
+++ b/third_party/blink/renderer/core/app_history/app_history_test.cc
@@ -78,7 +78,7 @@
       false /* has_transient_user_activation */, nullptr /* initiator_origin */,
       false /* is_synchronously_committed */,
       mojom::blink::TriggeringEventInfo::kNotFromEvent,
-      true /* is_browser_initiated */, nullptr);
+      true /* is_browser_initiated */);
 
   EXPECT_EQ(result, mojom::blink::CommitResult::Ok);
 }
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 774cb24..2c7a4acd 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -6451,7 +6451,7 @@
   return MakeGarbageCollected<Attr>(*this, q_name, g_empty_atom);
 }
 
-const SVGDocumentExtensions* Document::SvgExtensions() {
+const SVGDocumentExtensions* Document::SvgExtensions() const {
   return svg_extensions_.Get();
 }
 
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index cab07be..b3838e0 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -1299,7 +1299,7 @@
 
   void RemoveAllEventListeners() final;
 
-  const SVGDocumentExtensions* SvgExtensions();
+  const SVGDocumentExtensions* SvgExtensions() const;
   SVGDocumentExtensions& AccessSVGExtensions();
 
   bool AllowInlineEventHandler(Node*,
diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc
index 0ec17b9..c0ce1bb 100644
--- a/third_party/blink/renderer/core/exported/web_view_test.cc
+++ b/third_party/blink/renderer/core/exported/web_view_test.cc
@@ -2504,21 +2504,21 @@
       false /* has_transient_user_activation */, nullptr /* initiator_origin */,
       false /* is_synchronously_committed */,
       mojom::blink::TriggeringEventInfo::kNotFromEvent,
-      true /* is_browser_initiated */, nullptr);
+      true /* is_browser_initiated */);
   main_frame_local->Loader().GetDocumentLoader()->CommitSameDocumentNavigation(
       item2->Url(), WebFrameLoadType::kBackForward, item2.Get(),
       ClientRedirectPolicy::kNotClientRedirect,
       false /* has_transient_user_activation */, nullptr /* initiator_origin */,
       false /* is_synchronously_committed */,
       mojom::blink::TriggeringEventInfo::kNotFromEvent,
-      true /* is_browser_initiated */, nullptr);
+      true /* is_browser_initiated */);
   main_frame_local->Loader().GetDocumentLoader()->CommitSameDocumentNavigation(
       item1->Url(), WebFrameLoadType::kBackForward, item1.Get(),
       ClientRedirectPolicy::kNotClientRedirect,
       false /* has_transient_user_activation */, nullptr /* initiator_origin */,
       false /* is_synchronously_committed */,
       mojom::blink::TriggeringEventInfo::kNotFromEvent,
-      true /* is_browser_initiated */, nullptr);
+      true /* is_browser_initiated */);
   web_view_impl->MainFrameWidget()->UpdateAllLifecyclePhases(
       DocumentUpdateReason::kTest);
 
@@ -2540,7 +2540,7 @@
       false /* has_transient_user_activation */, nullptr /* initiator_origin */,
       false /* is_synchronously_committed */,
       mojom::blink::TriggeringEventInfo::kNotFromEvent,
-      true /* is_browser_initiated */, nullptr);
+      true /* is_browser_initiated */);
 
   main_frame_local->Loader().GetDocumentLoader()->CommitSameDocumentNavigation(
       item3->Url(), WebFrameLoadType::kBackForward, item3.Get(),
@@ -2548,7 +2548,7 @@
       false /* has_transient_user_activation */, nullptr /* initiator_origin */,
       false /* is_synchronously_committed */,
       mojom::blink::TriggeringEventInfo::kNotFromEvent,
-      true /* is_browser_initiated */, nullptr);
+      true /* is_browser_initiated */);
   // The scroll offset is only applied via invoking the anchor via the main
   // lifecycle, or a forced layout.
   // TODO(chrishtr): At the moment, WebLocalFrameImpl::GetScrollOffset() does
diff --git a/third_party/blink/renderer/core/frame/local_frame_client.h b/third_party/blink/renderer/core/frame/local_frame_client.h
index 32de870..386fb615 100644
--- a/third_party/blink/renderer/core/frame/local_frame_client.h
+++ b/third_party/blink/renderer/core/frame/local_frame_client.h
@@ -234,10 +234,6 @@
       std::unique_ptr<PolicyContainer> policy_container,
       std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) = 0;
 
-  virtual void UpdateDocumentLoader(
-      DocumentLoader* document_loader,
-      std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) = 0;
-
   virtual String UserAgent() = 0;
   virtual String FullUserAgent() = 0;
   virtual String ReducedUserAgent() = 0;
diff --git a/third_party/blink/renderer/core/frame/local_frame_client_impl.cc b/third_party/blink/renderer/core/frame/local_frame_client_impl.cc
index e0a867aa..e193f6c 100644
--- a/third_party/blink/renderer/core/frame/local_frame_client_impl.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_client_impl.cc
@@ -783,13 +783,6 @@
   return document_loader;
 }
 
-void LocalFrameClientImpl::UpdateDocumentLoader(
-    DocumentLoader* document_loader,
-    std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) {
-  static_cast<WebDocumentLoaderImpl*>(document_loader)
-      ->SetExtraData(std::move(extra_data));
-}
-
 String LocalFrameClientImpl::UserAgent() {
   WebString override =
       web_frame_->Client() ? web_frame_->Client()->UserAgentOverride() : "";
diff --git a/third_party/blink/renderer/core/frame/local_frame_client_impl.h b/third_party/blink/renderer/core/frame/local_frame_client_impl.h
index 6eb7606..503b864 100644
--- a/third_party/blink/renderer/core/frame/local_frame_client_impl.h
+++ b/third_party/blink/renderer/core/frame/local_frame_client_impl.h
@@ -166,11 +166,6 @@
       std::unique_ptr<PolicyContainer> policy_container,
       std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) override;
 
-  // Updates the underlying |WebDocumentLoaderImpl| of |DocumentLoader| with
-  // extra_data.
-  void UpdateDocumentLoader(
-      DocumentLoader* document_loader,
-      std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) override;
   WTF::String UserAgent() override;
   WTF::String FullUserAgent() override;
   WTF::String ReducedUserAgent() override;
diff --git a/third_party/blink/renderer/core/frame/web_frame_test.cc b/third_party/blink/renderer/core/frame/web_frame_test.cc
index 94339269..1ae74fb7 100644
--- a/third_party/blink/renderer/core/frame/web_frame_test.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_test.cc
@@ -7992,7 +7992,7 @@
       false /* has_transient_user_activation */, /*initiator_origin=*/nullptr,
       /*is_synchronously_committed=*/false,
       mojom::blink::TriggeringEventInfo::kNotFromEvent,
-      true /* is_browser_initiated */, nullptr /* extra_data */);
+      true /* is_browser_initiated */);
   EXPECT_EQ(kWebBackForwardCommit, client.LastCommitType());
 }
 
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index dd163e2..7bf877e6 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -2416,8 +2416,7 @@
     bool is_client_redirect,
     bool has_transient_user_activation,
     const WebSecurityOrigin& initiator_origin,
-    bool is_browser_initiated,
-    std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) {
+    bool is_browser_initiated) {
   DCHECK(GetFrame());
   DCHECK(!url.ProtocolIs("javascript"));
 
@@ -2428,8 +2427,7 @@
                          : ClientRedirectPolicy::kNotClientRedirect,
       has_transient_user_activation, initiator_origin.Get(),
       /*is_synchronously_committed=*/false,
-      mojom::blink::TriggeringEventInfo::kNotFromEvent, is_browser_initiated,
-      std::move(extra_data));
+      mojom::blink::TriggeringEventInfo::kNotFromEvent, is_browser_initiated);
 }
 
 bool WebLocalFrameImpl::IsLoading() const {
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.h b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
index 2264b68..ef79b28 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.h
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
@@ -355,8 +355,7 @@
       bool is_client_redirect,
       bool has_transient_user_activation,
       const WebSecurityOrigin& initiator_origin,
-      bool is_browser_initiated,
-      std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) override;
+      bool is_browser_initiated) override;
   void SetIsNotOnInitialEmptyDocument() override;
   bool IsOnInitialEmptyDocument() override;
   bool WillStartNavigation(const WebNavigationInfo&) override;
diff --git a/third_party/blink/renderer/core/html/forms/resources/calendar_picker.js b/third_party/blink/renderer/core/html/forms/resources/calendar_picker.js
index 1afcf8206..f19563e18 100644
--- a/third_party/blink/renderer/core/html/forms/resources/calendar_picker.js
+++ b/third_party/blink/renderer/core/html/forms/resources/calendar_picker.js
@@ -2608,143 +2608,136 @@
   };
 }
 
-/**
- * @constructor
- * @extends ListView
- * @param {!Month} minimumMonth
- * @param {!Month} maximumMonth
- */
-function YearListView(minimumMonth, maximumMonth, config) {
-  ListView.call(this);
-  this.element.classList.add('year-list-view');
-
+class YearListView extends ListView {
   /**
-   * @type {?Month}
+   * @param {!Month} minimumMonth
+   * @param {!Month} maximumMonth
    */
-  this._selectedMonth = null;
-  /**
-   * @type {!Month}
-   * @const
-   * @protected
-   */
-  this._minimumMonth = minimumMonth;
-  /**
-   * @type {!Month}
-   * @const
-   * @protected
-   */
-  this._maximumMonth = maximumMonth;
+  constructor(minimumMonth, maximumMonth, config) {
+    super();
+    this.element.classList.add('year-list-view');
 
-  this.scrollView.minimumContentOffset =
-      (this._minimumMonth.year - 1) * YearListCell.GetHeight();
-  this.scrollView.maximumContentOffset =
-      (this._maximumMonth.year - 1) * YearListCell.GetHeight() +
-      YearListCell.GetSelectedHeight();
+    /**
+     * @type {?Month}
+     */
+    this._selectedMonth = null;
+    /**
+     * @type {!Month}
+     * @const
+     * @protected
+     */
+    this._minimumMonth = minimumMonth;
+    /**
+     * @type {!Month}
+     * @const
+     * @protected
+     */
+    this._maximumMonth = maximumMonth;
 
-  /**
-   * @type {!Object}
-   * @const
-   * @protected
-   */
-  this._runningAnimators = {};
-  /**
-   * @type {!Array}
-   * @const
-   * @protected
-   */
-  this._animatingRows = [];
-  /**
-   * @type {!boolean}
-   * @protected
-   */
-  this._ignoreMouseOutUntillNextMouseOver = false;
+    this.scrollView.minimumContentOffset =
+        (this._minimumMonth.year - 1) * YearListCell.GetHeight();
+    this.scrollView.maximumContentOffset =
+        (this._maximumMonth.year - 1) * YearListCell.GetHeight() +
+        YearListCell.GetSelectedHeight();
 
-  /**
-   * @type {!ScrubbyScrollBar}
-   * @const
-   */
-  this.scrubbyScrollBar = new ScrubbyScrollBar(this.scrollView);
-  this.scrubbyScrollBar.attachTo(this);
+    /**
+     * @type {!Object}
+     * @const
+     * @protected
+     */
+    this._runningAnimators = {};
+    /**
+     * @type {!Array}
+     * @const
+     * @protected
+     */
+    this._animatingRows = [];
+    /**
+     * @type {!boolean}
+     * @protected
+     */
+    this._ignoreMouseOutUntillNextMouseOver = false;
 
-  this.element.addEventListener('keydown', this.onKeyDown, false);
+    /**
+     * @type {!ScrubbyScrollBar}
+     * @const
+     */
+    this.scrubbyScrollBar = new ScrubbyScrollBar(this.scrollView);
+    this.scrubbyScrollBar.attachTo(this);
 
-  if (config && config.mode == 'month') {
-    this.type = 'month';
-    this._dateTypeConstructor = Month;
+    this.element.addEventListener('keydown', this.onKeyDown.bind(this));
 
-    this._setValidDateConfig(config);
+    if (config && config.mode == 'month') {
+      this.type = 'month';
+      this._dateTypeConstructor = Month;
 
-    this._hadValidValueWhenOpened = false;
-    var initialSelection = parseDateString(config.currentValue);
-    if (initialSelection) {
-      this._hadValidValueWhenOpened = this.isValid(initialSelection);
-      this._selectedMonth = this.getNearestValidRange(
-          initialSelection, /*lookForwardFirst*/ true);
+      this._setValidDateConfig(config);
+
+      this._hadValidValueWhenOpened = false;
+      var initialSelection = parseDateString(config.currentValue);
+      if (initialSelection) {
+        this._hadValidValueWhenOpened = this.isValid(initialSelection);
+        this._selectedMonth = this.getNearestValidRange(
+            initialSelection, /*lookForwardFirst*/ true);
+      } else {
+        // Ensure that the next month closest to today is selected to start with
+        // so that the user can simply submit the popup to choose it.
+        this._selectedMonth = this.getValidRangeNearestToDay(
+            this._dateTypeConstructor.createFromToday(),
+            /*lookForwardFirst*/ true);
+      }
+
+      this._initialSelectedMonth = this._selectedMonth;
     } else {
-      // Ensure that the next month closest to today is selected to start with so that
-      // the user can simply submit the popup to choose it.
-      this._selectedMonth = this.getValidRangeNearestToDay(
-          this._dateTypeConstructor.createFromToday(),
-          /*lookForwardFirst*/ true);
+      // This is a month switcher menu embedded in another calendar control.
+      // Set up our config so that getNearestValidRangeLookingForward(Backward)
+      // when called on this YearListView will navigate by month.
+      this.config = {};
+      this.config.minimumValue = minimumMonth;
+      this.config.maximumValue = maximumMonth;
+      this.config.step = Month.DefaultStep;
+      this.config.stepBase = Month.DefaultStepBase;
+      this._dateTypeConstructor = Month;
     }
-
-    this._initialSelectedMonth = this._selectedMonth;
-  } else {
-    // This is a month switcher menu embedded in another calendar control.
-    // Set up our config so that getNearestValidRangeLookingForward(Backward)
-    // when called on this YearListView will navigate by month.
-    this.config = {};
-    this.config.minimumValue = minimumMonth;
-    this.config.maximumValue = maximumMonth;
-    this.config.step = Month.DefaultStep;
-    this.config.stepBase = Month.DefaultStepBase;
-    this._dateTypeConstructor = Month;
   }
-}
 
-{
-  YearListView.prototype = Object.create(ListView.prototype);
-  Object.assign(YearListView.prototype, DateRangeManager);
-
-  YearListView._VisibleYears = 4;
-  YearListView._Height = YearListCell._SelectedHeight - 1 +
+  static _VisibleYears = 4;
+  static _Height = YearListCell._SelectedHeight - 1 +
       YearListView._VisibleYears * YearListCell._Height;
-  YearListView.GetHeight = function() {
+  static GetHeight() {
     return YearListView._Height;
   };
-  YearListView.EventTypeYearListViewDidHide = 'yearListViewDidHide';
-  YearListView.EventTypeYearListViewDidSelectMonth =
-      'yearListViewDidSelectMonth';
+  static EventTypeYearListViewDidHide = 'yearListViewDidHide';
+  static EventTypeYearListViewDidSelectMonth = 'yearListViewDidSelectMonth';
 
   /**
    * @param {!number} width Width in pixels.
    * @override
    */
-  YearListView.prototype.setWidth = function(width) {
-    ListView.prototype.setWidth.call(
-        this, width - this.scrubbyScrollBar.element.offsetWidth);
+  setWidth(width) {
+    super.setWidth(width - this.scrubbyScrollBar.element.offsetWidth);
     this.element.style.width = width + 'px';
-  };
+  }
 
   /**
    * @param {!number} height Height in pixels.
    * @override
    */
-  YearListView.prototype.setHeight = function(height) {
-    ListView.prototype.setHeight.call(this, height);
+  setHeight(height) {
+    super.setHeight(height);
     this.scrubbyScrollBar.setHeight(height);
-  };
+  }
 
   /**
    * @enum {number}
    */
-  YearListView.RowAnimationDirection = {Opening: 0, Closing: 1};
+  static RowAnimationDirection = {Opening: 0, Closing: 1};
 
   /**
    * @param {!number} row
    * @param {!YearListView.RowAnimationDirection} direction
    */
-  YearListView.prototype._animateRow = function(row, direction) {
+  _animateRow(row, direction) {
     var fromValue = direction === YearListView.RowAnimationDirection.Closing ?
         YearListCell.GetSelectedHeight() :
         YearListCell.GetHeight();
@@ -2755,7 +2748,7 @@
     }
     var cell = this.cellAtRow(row);
     var animator = new TransitionAnimator();
-    animator.step = this.onCellHeightAnimatorStep;
+    animator.step = this.onCellHeightAnimatorStep.bind(this);
     animator.setFrom(fromValue);
     animator.setTo(
         direction === YearListView.RowAnimationDirection.Opening ?
@@ -2765,38 +2758,39 @@
     animator.duration = 300;
     animator.row = row;
     animator.on(
-        Animator.EventTypeDidAnimationStop, this.onCellHeightAnimatorDidStop);
+        Animator.EventTypeDidAnimationStop,
+        this.onCellHeightAnimatorDidStop.bind(this));
     this._runningAnimators[row] = animator;
     this._animatingRows.push(row);
     this._animatingRows.sort();
     animator.start();
-  };
+  }
 
   /**
    * @param {?Animator} animator
    */
-  YearListView.prototype.onCellHeightAnimatorDidStop = function(animator) {
+  onCellHeightAnimatorDidStop(animator) {
     delete this._runningAnimators[animator.row];
     var index = this._animatingRows.indexOf(animator.row);
     this._animatingRows.splice(index, 1);
-  };
+  }
 
   /**
    * @param {!Animator} animator
    */
-  YearListView.prototype.onCellHeightAnimatorStep = function(animator) {
+  onCellHeightAnimatorStep(animator) {
     var cell = this.cellAtRow(animator.row);
     if (cell)
       cell.setHeight(animator.currentValue);
     this.updateCells();
-  };
+  }
 
   /**
    * @param {?Event} event
    */
-  YearListView.prototype.onClick = function(event) {
+  onClick(event) {
     var oldSelectedRow = this.selectedRow;
-    ListView.prototype.onClick.call(this, event);
+    super.onClick(event);
     var year = this.selectedRow + 1;
     if (this.selectedRow !== oldSelectedRow) {
       // Always start with first month when changing the year.
@@ -2813,14 +2807,14 @@
           YearListView.EventTypeYearListViewDidSelectMonth, this,
           new Month(year, month));
     }
-  };
+  }
 
   /**
    * @param {!number} scrollOffset
    * @return {!number}
    * @override
    */
-  YearListView.prototype.rowAtScrollOffset = function(scrollOffset) {
+  rowAtScrollOffset(scrollOffset) {
     var remainingOffset = scrollOffset;
     var lastAnimatingRow = 0;
     var rowsWithIrregularHeight = this._animatingRows.slice();
@@ -2846,14 +2840,14 @@
     }
     return lastAnimatingRow +
         Math.floor(remainingOffset / YearListCell.GetHeight());
-  };
+  }
 
   /**
    * @param {!number} row
    * @return {!number}
    * @override
    */
-  YearListView.prototype.scrollOffsetForRow = function(row) {
+  scrollOffsetForRow(row) {
     var scrollOffset = row * YearListCell.GetHeight();
     for (var i = 0; i < this._animatingRows.length; ++i) {
       var animatingRow = this._animatingRows[i];
@@ -2868,14 +2862,14 @@
           YearListCell.GetSelectedHeight() - YearListCell.GetHeight();
     }
     return scrollOffset;
-  };
+  }
 
   /**
    * @param {!number} row
    * @return {!YearListCell}
    * @override
    */
-  YearListView.prototype.prepareNewCell = function(row) {
+  prepareNewCell(row) {
     var cell = YearListCell._recycleBin.pop() ||
         new YearListCell(global.params.shortMonthLabels);
     cell.reset(row);
@@ -2915,12 +2909,12 @@
     else
       cell.setHeight(YearListCell.GetHeight());
     return cell;
-  };
+  }
 
   /**
    * @override
    */
-  YearListView.prototype.updateCells = function() {
+  updateCells() {
     var firstVisibleRow = this.firstVisibleRow();
     var lastVisibleRow = this.lastVisibleRow();
     console.assert(firstVisibleRow <= lastVisibleRow);
@@ -2938,12 +2932,12 @@
         this.addCellIfNecessary(i);
     }
     this.setNeedsUpdateCells(false);
-  };
+  }
 
   /**
    * @override
    */
-  YearListView.prototype.deselect = function() {
+  deselect() {
     if (this.selectedRow === ListView.NoSelection)
       return;
     var selectedCell = this._cells[this.selectedRow];
@@ -2953,9 +2947,9 @@
         this.selectedRow, YearListView.RowAnimationDirection.Closing);
     this.selectedRow = ListView.NoSelection;
     this.setNeedsUpdateCells(true);
-  };
+  }
 
-  YearListView.prototype.deselectWithoutAnimating = function() {
+  deselectWithoutAnimating() {
     if (this.selectedRow === ListView.NoSelection)
       return;
     var selectedCell = this._cells[this.selectedRow];
@@ -2965,13 +2959,13 @@
     }
     this.selectedRow = ListView.NoSelection;
     this.setNeedsUpdateCells(true);
-  };
+  }
 
   /**
    * @param {!number} row
    * @override
    */
-  YearListView.prototype.select = function(row) {
+  select(row) {
     if (this.selectedRow === row)
       return;
     this.deselect();
@@ -2986,12 +2980,12 @@
         selectedCell.setSelected(true);
     }
     this.setNeedsUpdateCells(true);
-  };
+  }
 
   /**
    * @param {!number} row
    */
-  YearListView.prototype.selectWithoutAnimating = function(row) {
+  selectWithoutAnimating(row) {
     if (this.selectedRow === row)
       return;
     this.deselectWithoutAnimating();
@@ -3006,13 +3000,13 @@
       }
     }
     this.setNeedsUpdateCells(true);
-  };
+  }
 
   /**
    * @param {!Month} month
    * @return {?HTMLDivElement}
    */
-  YearListView.prototype.buttonForMonth = function(month) {
+  buttonForMonth(month) {
     if (!month)
       return null;
     var row = month.year - 1;
@@ -3020,9 +3014,9 @@
     if (!cell)
       return null;
     return cell.monthButtons[month.month];
-  };
+  }
 
-  YearListView.prototype.dehighlightMonth = function() {
+  dehighlightMonth() {
     if (!this.highlightedMonth)
       return;
     var monthButton = this.buttonForMonth(this.highlightedMonth);
@@ -3031,12 +3025,12 @@
     }
     this.highlightedMonth = null;
     this.element.removeAttribute('aria-activedescendant');
-  };
+  }
 
   /**
    * @param {!Month} month
    */
-  YearListView.prototype.highlightMonth = function(month) {
+  highlightMonth(month) {
     if (this.highlightedMonth && this.highlightedMonth.equals(month))
       return;
     this.dehighlightMonth();
@@ -3048,9 +3042,9 @@
       monthButton.classList.add(YearListCell.ClassNameHighlighted);
       this.element.setAttribute('aria-activedescendant', monthButton.id);
     }
-  };
+  }
 
-  YearListView.prototype.setSelectedMonth = function(month) {
+  setSelectedMonth(month) {
     var oldMonthButton = this.buttonForMonth(this._selectedMonth);
     if (oldMonthButton) {
       oldMonthButton.classList.remove(YearListCell.ClassNameSelected);
@@ -3065,52 +3059,52 @@
       this.element.setAttribute('aria-activedescendant', newMonthButton.id);
       newMonthButton.setAttribute('aria-selected', true);
     }
-  };
+  }
 
-  YearListView.prototype.setSelectedMonthAndUpdateView = function(month) {
+  setSelectedMonthAndUpdateView(month) {
     this.setSelectedMonth(month);
 
     this.select(this._selectedMonth.year - 1);
 
     this.scrollView.scrollTo(this.selectedRow * YearListCell.GetHeight(), true);
-  };
+  }
 
-  YearListView.prototype.showSelectedMonth = function() {
+  showSelectedMonth() {
     var monthButton = this.buttonForMonth(this._selectedMonth);
     if (monthButton) {
       monthButton.classList.add(YearListCell.ClassNameSelected);
     }
-  };
+  }
 
   /**
    * @param {!Month} month
    */
-  YearListView.prototype.show = function(month) {
+  show(month) {
     this._ignoreMouseOutUntillNextMouseOver = true;
 
     this.scrollToRow(month.year - 1, false);
     this.selectWithoutAnimating(month.year - 1);
     this.showSelectedMonth();
-  };
+  }
 
-  YearListView.prototype.hide = function() {
+  hide() {
     this.dispatchEvent(YearListView.EventTypeYearListViewDidHide, this);
-  };
+  }
 
   /**
    * @param {!Month} month
    */
-  YearListView.prototype._moveHighlightTo = function(month) {
+  _moveHighlightTo(month) {
     this.highlightMonth(month);
     this.select(this.highlightedMonth.year - 1);
     this.scrollView.scrollTo(this.selectedRow * YearListCell.GetHeight(), true);
     return true;
-  };
+  }
 
   /**
    * @param {?Event} event
    */
-  YearListView.prototype.onKeyDown = function(event) {
+  onKeyDown(event) {
     var key = event.key;
     var eventHandled = false;
     if (this._selectedMonth) {
@@ -3198,9 +3192,11 @@
       event.stopPropagation();
       event.preventDefault();
     }
-  };
+  }
 }
 
+Object.assign(YearListView.prototype, DateRangeManager);
+
 /**
  * @constructor
  * @extends View
diff --git a/third_party/blink/renderer/core/layout/layout_shift_tracker_test.cc b/third_party/blink/renderer/core/layout/layout_shift_tracker_test.cc
index c23fbfd..9815f92 100644
--- a/third_party/blink/renderer/core/layout/layout_shift_tracker_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_shift_tracker_test.cc
@@ -322,8 +322,7 @@
       ClientRedirectPolicy::kNotClientRedirect,
       false /* has_transient_user_activation */, nullptr /* initiator_origin */,
       false /* is_synchronously_committed */,
-      mojom::blink::TriggeringEventInfo::kNotFromEvent, is_browser_initiated,
-      nullptr);
+      mojom::blink::TriggeringEventInfo::kNotFromEvent, is_browser_initiated);
 
   Compositor().BeginFrame();
   test::RunPendingTasks();
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
index 8086c62e..d84cf8bf 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
@@ -1346,7 +1346,7 @@
           bool row_container_separation = has_processed_first_line_;
           NGBreakStatus row_break_status = BreakBeforeRowIfNeeded(
               line_output, (*row_break_between_outputs)[flex_line_idx],
-              flex_line_idx, flex_item->ng_input_node, *layout_result,
+              flex_line_idx, flex_item->ng_input_node,
               row_container_separation);
           if (row_break_status == NGBreakStatus::kBrokeBefore) {
             ConsumeRemainingFragmentainerSpace();
@@ -1678,7 +1678,6 @@
     EBreakBetween row_break_between,
     wtf_size_t row_index,
     NGLayoutInputNode child,
-    const NGLayoutResult& layout_result,
     bool has_container_separation) {
   DCHECK(is_horizontal_flow_);
   DCHECK(involved_in_block_fragmentation_);
@@ -1691,9 +1690,10 @@
   NGBreakAppeal appeal_before = kBreakAppealPerfect;
   if (has_container_separation) {
     if (IsForcedBreakValue(ConstraintSpace(), row_break_between)) {
-      BreakBeforeChild(ConstraintSpace(), child, layout_result,
+      BreakBeforeChild(ConstraintSpace(), child, /* layout_result */ nullptr,
                        fragmentainer_block_offset, kBreakAppealPerfect,
-                       /* is_forced_break */ true, &container_builder_);
+                       /* is_forced_break */ true, &container_builder_,
+                       row.line_cross_size);
       return NGBreakStatus::kBrokeBefore;
     }
   } else {
@@ -1716,8 +1716,9 @@
   // We're out of space. Figure out where to insert a soft break. It will either
   // be before this row, or before an earlier sibling, if there's a more
   // appealing breakpoint there.
-  if (!AttemptRowSoftBreak(child, layout_result, appeal_before,
-                           fragmentainer_block_offset, row.line_cross_size))
+  if (!AttemptSoftBreak(ConstraintSpace(), child, /* layout_result */ nullptr,
+                        fragmentainer_block_offset, appeal_before,
+                        &container_builder_, row.line_cross_size))
     return NGBreakStatus::kNeedsEarlierBreak;
 
   return NGBreakStatus::kBrokeBefore;
@@ -1770,30 +1771,6 @@
   return true;
 }
 
-bool NGFlexLayoutAlgorithm::AttemptRowSoftBreak(
-    NGLayoutInputNode child,
-    const NGLayoutResult& layout_result,
-    NGBreakAppeal appeal_before,
-    LayoutUnit fragmentainer_block_offset,
-    LayoutUnit row_block_size) {
-  if (container_builder_.HasEarlyBreak() &&
-      container_builder_.EarlyBreak().BreakAppeal() > appeal_before) {
-    PropagateSpaceShortage(ConstraintSpace(), /* layout_result */ nullptr,
-                           fragmentainer_block_offset, &container_builder_,
-                           row_block_size);
-    return false;
-  }
-
-  // Break before the row. Note that there may be a better break further up
-  // with higher appeal (but it's too early to tell), in which case this
-  // breakpoint will be replaced.
-  // TODO(almaher): PropagateSpaceShortage() will be different for rows.
-  BreakBeforeChild(ConstraintSpace(), child, layout_result,
-                   fragmentainer_block_offset, appeal_before,
-                   /* is_forced_break */ false, &container_builder_);
-  return true;
-}
-
 #if DCHECK_IS_ON()
 void NGFlexLayoutAlgorithm::CheckFlexLines(
     const Vector<NGFlexLine>& flex_line_outputs) const {
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h
index 301c272..4be6fff4 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h
@@ -122,7 +122,6 @@
                                        EBreakBetween row_break_between,
                                        wtf_size_t row_index,
                                        NGLayoutInputNode child,
-                                       const NGLayoutResult& layout_result,
                                        bool has_container_separation);
 
   // Move past the breakpoint before the row, if possible, and return true. Also
@@ -135,19 +134,6 @@
                              LayoutUnit row_block_size,
                              wtf_size_t row_index);
 
-  // Attempt to insert a soft break before the row, and return true if we did.
-  // If false is returned, it means that the desired breakpoint is earlier in
-  // the container, and that we need to abort and re-layout to that breakpoint.
-  // |child| and |layout_result| should be those associated with the first child
-  // in the row. |appeal_before|, |fragmentainer_block_offset| and
-  // |row_block_size| are specific to the row itself. See
-  // |::blink::AttemptSoftBreak()| for more documentation.
-  bool AttemptRowSoftBreak(NGLayoutInputNode child,
-                           const NGLayoutResult& layout_result,
-                           NGBreakAppeal appeal_before,
-                           LayoutUnit fragmentainer_block_offset,
-                           LayoutUnit row_block_size);
-
 #if DCHECK_IS_ON()
   void CheckFlexLines(const Vector<NGFlexLine>& flex_line_outputs) const;
 #endif
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
index 876daa40..67edd66 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
@@ -1177,7 +1177,7 @@
     LayoutUnit fragmentainer_block_offset =
         ConstraintSpace().FragmentainerOffsetAtBfc() +
         positioned_float.bfc_offset.block_offset;
-    BreakBeforeChild(ConstraintSpace(), child, *positioned_float.layout_result,
+    BreakBeforeChild(ConstraintSpace(), child, positioned_float.layout_result,
                      fragmentainer_block_offset,
                      /* appeal */ absl::nullopt,
                      /* is_forced_break */ false, &container_builder_);
@@ -2394,7 +2394,7 @@
     EBreakBetween break_between =
         CalculateBreakBetweenValue(child, layout_result, container_builder_);
     if (IsForcedBreakValue(ConstraintSpace(), break_between)) {
-      BreakBeforeChild(ConstraintSpace(), child, layout_result,
+      BreakBeforeChild(ConstraintSpace(), child, &layout_result,
                        fragmentainer_block_offset, kBreakAppealPerfect,
                        /* is_forced_break */ true, &container_builder_);
       ConsumeRemainingFragmentainerSpace(previous_inflow_position);
@@ -2501,7 +2501,7 @@
     }
   }
 
-  if (!AttemptSoftBreak(ConstraintSpace(), child, layout_result,
+  if (!AttemptSoftBreak(ConstraintSpace(), child, &layout_result,
                         fragmentainer_block_offset, appeal_before,
                         &container_builder_))
     return NGBreakStatus::kNeedsEarlierBreak;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc
index fac18fd..fbcf74fc 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc
@@ -596,7 +596,7 @@
     EBreakBetween break_between =
         CalculateBreakBetweenValue(child, layout_result, *builder);
     if (IsForcedBreakValue(space, break_between)) {
-      BreakBeforeChild(space, child, layout_result, fragmentainer_block_offset,
+      BreakBeforeChild(space, child, &layout_result, fragmentainer_block_offset,
                        kBreakAppealPerfect, /* is_forced_break */ true,
                        builder);
       return NGBreakStatus::kBrokeBefore;
@@ -615,8 +615,8 @@
   // Breaking inside the child isn't appealing, and we're out of space. Figure
   // out where to insert a soft break. It will either be before this child, or
   // before an earlier sibling, if there's a more appealing breakpoint there.
-  if (!AttemptSoftBreak(space, child, layout_result, fragmentainer_block_offset,
-                        appeal_before, builder))
+  if (!AttemptSoftBreak(space, child, &layout_result,
+                        fragmentainer_block_offset, appeal_before, builder))
     return NGBreakStatus::kNeedsEarlierBreak;
 
   return NGBreakStatus::kBrokeBefore;
@@ -624,16 +624,18 @@
 
 void BreakBeforeChild(const NGConstraintSpace& space,
                       NGLayoutInputNode child,
-                      const NGLayoutResult& layout_result,
+                      const NGLayoutResult* layout_result,
                       LayoutUnit fragmentainer_block_offset,
                       absl::optional<NGBreakAppeal> appeal,
                       bool is_forced_break,
-                      NGBoxFragmentBuilder* builder) {
+                      NGBoxFragmentBuilder* builder,
+                      absl::optional<LayoutUnit> block_size_override) {
 #if DCHECK_IS_ON()
-  if (layout_result.Status() == NGLayoutResult::kSuccess) {
+  DCHECK(layout_result || block_size_override);
+  if (layout_result && layout_result->Status() == NGLayoutResult::kSuccess) {
     // In order to successfully break before a node, this has to be its first
     // fragment.
-    const auto& physical_fragment = layout_result.PhysicalFragment();
+    const auto& physical_fragment = layout_result->PhysicalFragment();
     DCHECK(!physical_fragment.IsBox() ||
            To<NGPhysicalBoxFragment>(physical_fragment).IsFirstForNode());
   }
@@ -643,16 +645,17 @@
   // (only blocks), because line boxes need handle it in their own way (due to
   // how we implement widows).
   if (child.IsBlock() && space.HasKnownFragmentainerBlockSize()) {
-    PropagateSpaceShortage(space, &layout_result, fragmentainer_block_offset,
-                           builder);
+    PropagateSpaceShortage(space, layout_result, fragmentainer_block_offset,
+                           builder, block_size_override);
   }
 
   // If the fragmentainer block-size is unknown, we have no reason to insert
   // soft breaks.
   DCHECK(is_forced_break || space.HasKnownFragmentainerBlockSize());
 
-  if (space.ShouldPropagateChildBreakValues() && !is_forced_break)
-    builder->PropagateChildBreakValues(layout_result);
+  if (layout_result && space.ShouldPropagateChildBreakValues() &&
+      !is_forced_break)
+    builder->PropagateChildBreakValues(*layout_result);
 
   // We'll drop the fragment (if any) on the floor and retry at the start of the
   // next fragmentainer.
@@ -859,18 +862,20 @@
 
 bool AttemptSoftBreak(const NGConstraintSpace& space,
                       NGLayoutInputNode child,
-                      const NGLayoutResult& layout_result,
+                      const NGLayoutResult* layout_result,
                       LayoutUnit fragmentainer_block_offset,
                       NGBreakAppeal appeal_before,
-                      NGBoxFragmentBuilder* builder) {
-  // if there's a breakpoint with higher appeal among earlier siblings, we need
+                      NGBoxFragmentBuilder* builder,
+                      absl::optional<LayoutUnit> block_size_override) {
+  DCHECK(layout_result || block_size_override);
+  // If there's a breakpoint with higher appeal among earlier siblings, we need
   // to abort and re-layout to that breakpoint.
   if (builder->HasEarlyBreak() &&
       builder->EarlyBreak().BreakAppeal() > appeal_before) {
     // Found a better place to break. Before aborting, calculate and report
     // space shortage from where we'd actually break.
-    PropagateSpaceShortage(space, &layout_result, fragmentainer_block_offset,
-                           builder);
+    PropagateSpaceShortage(space, layout_result, fragmentainer_block_offset,
+                           builder, block_size_override);
     return false;
   }
 
@@ -878,7 +883,8 @@
   // with higher appeal (but it's too early to tell), in which case this
   // breakpoint will be replaced.
   BreakBeforeChild(space, child, layout_result, fragmentainer_block_offset,
-                   appeal_before, /* is_forced_break */ false, builder);
+                   appeal_before, /* is_forced_break */ false, builder,
+                   block_size_override);
   return true;
 }
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h b/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h
index 0e280ff..0dad67fd 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h
@@ -243,13 +243,17 @@
                                        NGBoxFragmentBuilder*);
 
 // Insert a break before the child, and propagate space shortage if needed.
-void BreakBeforeChild(const NGConstraintSpace&,
-                      NGLayoutInputNode child,
-                      const NGLayoutResult&,
-                      LayoutUnit fragmentainer_block_offset,
-                      absl::optional<NGBreakAppeal> appeal,
-                      bool is_forced_break,
-                      NGBoxFragmentBuilder*);
+// |block_size_override| should only be supplied when you wish to propagate a
+// different block-size than that of the provided layout result.
+void BreakBeforeChild(
+    const NGConstraintSpace&,
+    NGLayoutInputNode child,
+    const NGLayoutResult*,
+    LayoutUnit fragmentainer_block_offset,
+    absl::optional<NGBreakAppeal> appeal,
+    bool is_forced_break,
+    NGBoxFragmentBuilder*,
+    absl::optional<LayoutUnit> block_size_override = absl::nullopt);
 
 // Propagate the block-size of unbreakable content. This is used to inflate the
 // initial minimal column block-size when balancing columns, before we calculate
@@ -303,12 +307,16 @@
 // Attempt to insert a soft break before the child, and return true if we did.
 // If false is returned, it means that the desired breakpoint is earlier in the
 // container, and that we need to abort and re-layout to that breakpoint.
-bool AttemptSoftBreak(const NGConstraintSpace&,
-                      NGLayoutInputNode child,
-                      const NGLayoutResult&,
-                      LayoutUnit fragmentainer_block_offset,
-                      NGBreakAppeal appeal_before,
-                      NGBoxFragmentBuilder*);
+// |block_size_override| should only be supplied when you wish to propagate a
+// different block-size than that of the provided layout result.
+bool AttemptSoftBreak(
+    const NGConstraintSpace&,
+    NGLayoutInputNode child,
+    const NGLayoutResult*,
+    LayoutUnit fragmentainer_block_offset,
+    NGBreakAppeal appeal_before,
+    NGBoxFragmentBuilder*,
+    absl::optional<LayoutUnit> block_size_override = absl::nullopt);
 
 // If we have an previously found break point, and we're entering an ancestor of
 // the node we're going to break before, return the early break inside. This can
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 940c29b..8f849e5 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -1256,8 +1256,7 @@
     const SecurityOrigin* initiator_origin,
     bool is_synchronously_committed,
     mojom::blink::TriggeringEventInfo triggering_event_info,
-    bool is_browser_initiated,
-    std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) {
+    bool is_browser_initiated) {
   DCHECK(!IsReloadLoadType(frame_load_type));
   DCHECK(frame_->GetDocument());
   DCHECK(!is_browser_initiated || !is_synchronously_committed);
@@ -1346,8 +1345,7 @@
                       same_document_navigation_type, client_redirect_policy,
                       has_transient_user_activation,
                       WTF::RetainedRef(initiator_origin), is_browser_initiated,
-                      is_synchronously_committed, triggering_event_info,
-                      std::move(extra_data)));
+                      is_synchronously_committed, triggering_event_info));
   } else {
     // Treat a navigation to the same url as replacing only if it did not
     // originate from a cross-origin iframe. If |is_synchronously_committed| is
@@ -1359,8 +1357,8 @@
     CommitSameDocumentNavigationInternal(
         url, frame_load_type, history_item, same_document_navigation_type,
         client_redirect_policy, has_transient_user_activation, initiator_origin,
-        is_browser_initiated, is_synchronously_committed, triggering_event_info,
-        std::move(extra_data));
+        is_browser_initiated, is_synchronously_committed,
+        triggering_event_info);
   }
   return mojom::CommitResult::Ok;
 }
@@ -1375,8 +1373,7 @@
     const SecurityOrigin* initiator_origin,
     bool is_browser_initiated,
     bool is_synchronously_committed,
-    mojom::blink::TriggeringEventInfo triggering_event_info,
-    std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) {
+    mojom::blink::TriggeringEventInfo triggering_event_info) {
   // If this function was scheduled to run asynchronously, this DocumentLoader
   // might have been detached before the task ran.
   if (!frame_)
@@ -1422,8 +1419,6 @@
       history_item_->ItemSequenceNumber() == history_item->ItemSequenceNumber();
   if (history_item)
     history_item_ = history_item;
-  if (extra_data)
-    GetLocalFrameClient().UpdateDocumentLoader(this, std::move(extra_data));
   UpdateForSameDocumentNavigation(
       url, same_document_navigation_type, nullptr,
       mojom::blink::ScrollRestorationType::kAuto, frame_load_type,
diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h
index bbfe47a..30cac0e 100644
--- a/third_party/blink/renderer/core/loader/document_loader.h
+++ b/third_party/blink/renderer/core/loader/document_loader.h
@@ -237,8 +237,7 @@
       const SecurityOrigin* initiator_origin,
       bool is_synchronously_committed,
       mojom::blink::TriggeringEventInfo,
-      bool is_browser_initiated,
-      std::unique_ptr<WebDocumentLoader::ExtraData>);
+      bool is_browser_initiated);
 
   void SetDefersLoading(LoaderFreezeMode);
 
@@ -415,8 +414,7 @@
       const SecurityOrigin* initiator_origin,
       bool is_browser_initiated,
       bool is_synchronously_committed,
-      mojom::blink::TriggeringEventInfo,
-      std::unique_ptr<WebDocumentLoader::ExtraData>);
+      mojom::blink::TriggeringEventInfo);
 
   // Use these method only where it's guaranteed that |m_frame| hasn't been
   // cleared.
diff --git a/third_party/blink/renderer/core/loader/empty_clients.h b/third_party/blink/renderer/core/loader/empty_clients.h
index 4b639069..d91e67a5 100644
--- a/third_party/blink/renderer/core/loader/empty_clients.h
+++ b/third_party/blink/renderer/core/loader/empty_clients.h
@@ -304,9 +304,6 @@
       std::unique_ptr<WebNavigationParams> navigation_params,
       std::unique_ptr<PolicyContainer> policy_container,
       std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) override;
-  void UpdateDocumentLoader(
-      DocumentLoader* document_loader,
-      std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) override {}
 
   String UserAgent() override { return ""; }
   String FullUserAgent() override { return ""; }
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index 22ff5a06..8ab32bb9 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -694,7 +694,7 @@
                                       frame_load_type),
         resource_request.HasUserGesture(), origin_window->GetSecurityOrigin(),
         /*is_synchronously_committed=*/true, request.GetTriggeringEventInfo(),
-        false /* is_browser_initiated */, nullptr /* extra_data */);
+        false /* is_browser_initiated */);
     return;
   }
 
diff --git a/third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.cc b/third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.cc
index f4ff6cc..f954d40a 100644
--- a/third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.cc
@@ -28,6 +28,8 @@
   compositor_animation_->SetAnimationDelegate(this);
 }
 
+// TODO(dbaron): This should probably DCHECK(element_detached_), but too
+// many unittests would fail such a DCHECK().
 ScrollAnimatorCompositorCoordinator::~ScrollAnimatorCompositorCoordinator() =
     default;
 
@@ -36,6 +38,13 @@
   compositor_animation_.reset();
 }
 
+void ScrollAnimatorCompositorCoordinator::DetachElement() {
+  DCHECK(!element_detached_);
+  element_detached_ = true;
+  ReattachCompositorAnimationIfNeeded(
+      GetScrollableArea()->GetCompositorAnimationTimeline());
+}
+
 void ScrollAnimatorCompositorCoordinator::ResetAnimationState() {
   run_state_ = RunState::kIdle;
   RemoveAnimation();
@@ -184,7 +193,10 @@
 bool ScrollAnimatorCompositorCoordinator::ReattachCompositorAnimationIfNeeded(
     CompositorAnimationTimeline* timeline) {
   bool reattached = false;
-  CompositorElementId element_id = GetScrollElementId();
+  CompositorElementId element_id;
+  if (!element_detached_) {
+    element_id = GetScrollElementId();
+  }
   if (element_id != element_id_) {
     if (compositor_animation_ && timeline) {
       // Detach from old layer (if any).
diff --git a/third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.h b/third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.h
index 03bbc18..5d31fbf 100644
--- a/third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.h
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.h
@@ -92,6 +92,8 @@
   void Dispose();
   String RunStateAsText() const;
 
+  void DetachElement();
+
   virtual bool HasRunningAnimation() const { return false; }
 
   virtual void ResetAnimationState();
@@ -191,6 +193,8 @@
   bool impl_only_animation_takeover_;
 
  private:
+  bool element_detached_ = false;
+
   CompositorElementId GetScrollElementId() const;
   bool HasImplOnlyAnimationUpdate() const;
   void UpdateImplOnlyCompositorAnimations();
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.cc b/third_party/blink/renderer/core/scroll/scrollable_area.cc
index 82f87d8..df17fe54 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.cc
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.cc
@@ -127,8 +127,14 @@
 void ScrollableArea::ClearScrollableArea() {
   if (mac_scrollbar_animator_)
     mac_scrollbar_animator_->Dispose();
-  scroll_animator_.Clear();
-  programmatic_scroll_animator_.Clear();
+  if (scroll_animator_) {
+    scroll_animator_->DetachElement();
+    scroll_animator_.Clear();
+  }
+  if (programmatic_scroll_animator_) {
+    programmatic_scroll_animator_->DetachElement();
+    programmatic_scroll_animator_.Clear();
+  }
   if (fade_overlay_scrollbars_timer_)
     fade_overlay_scrollbars_timer_->Value().Stop();
 }
diff --git a/third_party/blink/renderer/core/svg/graphics/svg_image.cc b/third_party/blink/renderer/core/svg/graphics/svg_image.cc
index ffd5e5b..8d5ac4e 100644
--- a/third_party/blink/renderer/core/svg/graphics/svg_image.cc
+++ b/third_party/blink/renderer/core/svg/graphics/svg_image.cc
@@ -59,6 +59,7 @@
 #include "third_party/blink/renderer/core/svg/animation/smil_time_container.h"
 #include "third_party/blink/renderer/core/svg/graphics/svg_image_chrome_client.h"
 #include "third_party/blink/renderer/core/svg/svg_animated_preserve_aspect_ratio.h"
+#include "third_party/blink/renderer/core/svg/svg_document_extensions.h"
 #include "third_party/blink/renderer/core/svg/svg_fe_image_element.h"
 #include "third_party/blink/renderer/core/svg/svg_image_element.h"
 #include "third_party/blink/renderer/core/svg/svg_svg_element.h"
@@ -151,6 +152,11 @@
   }
 };
 
+bool HasSmilAnimations(const Document& document) {
+  const SVGDocumentExtensions* extensions = document.SvgExtensions();
+  return extensions && extensions->HasSmilAnimations();
+}
+
 }  // namespace
 
 // SVGImageLocalFrameClient is used to wait until SVG document's load event
@@ -667,8 +673,8 @@
   SVGSVGElement* root_element = RootElement();
   if (!root_element)
     return false;
-  return root_element->TimeContainer()->HasAnimations() ||
-         root_element->GetDocument().Timeline().HasPendingUpdates();
+  const Document& document = root_element->GetDocument();
+  return HasSmilAnimations(document) || document.Timeline().HasPendingUpdates();
 }
 
 void SVGImage::ServiceAnimations(
@@ -741,7 +747,7 @@
 
 void SVGImage::UpdateUseCounters(const Document& document) const {
   if (SVGSVGElement* root_element = RootElement()) {
-    if (root_element->TimeContainer()->HasAnimations()) {
+    if (HasSmilAnimations(root_element->GetDocument())) {
       document.CountUse(WebFeature::kSVGSMILAnimationInImageRegardlessOfCache);
     }
   }
diff --git a/third_party/blink/renderer/core/svg/graphics/svg_image_test.cc b/third_party/blink/renderer/core/svg/graphics/svg_image_test.cc
index a5ba577..5d8573f3 100644
--- a/third_party/blink/renderer/core/svg/graphics/svg_image_test.cc
+++ b/third_party/blink/renderer/core/svg/graphics/svg_image_test.cc
@@ -259,6 +259,36 @@
   EXPECT_EQ(bitmap.getColor(90, 90), SK_ColorBLUE);
 }
 
+TEST_F(SVGImageTest, SVGWithSmilAnimationIsAnimated) {
+  const bool kShouldPause = true;
+  Load(R"SVG(
+         <svg xmlns="http://www.w3.org/2000/svg">
+           <rect width="10" height="10"/>
+           <animateTransform attributeName="transform" type="rotate"
+                             from="0 5 5" to="360 5 5" dur="1s"
+                             repeatCount="indefinite"/>
+         </svg>)SVG",
+       kShouldPause);
+
+  EXPECT_TRUE(GetImage().MaybeAnimated());
+}
+
+TEST_F(SVGImageTest, NestedSVGWithSmilAnimationIsAnimated) {
+  const bool kShouldPause = true;
+  Load(R"SVG(
+         <svg xmlns="http://www.w3.org/2000/svg">
+           <svg>
+             <rect width="10" height="10"/>
+             <animateTransform attributeName="transform" type="rotate"
+                               from="0 5 5" to="360 5 5" dur="1s"
+                               repeatCount="indefinite"/>
+           </svg>
+         </svg>)SVG",
+       kShouldPause);
+
+  EXPECT_TRUE(GetImage().MaybeAnimated());
+}
+
 class SVGImageSimTest : public SimTest, private ScopedMockOverlayScrollbars {};
 
 TEST_F(SVGImageSimTest, PageVisibilityHiddenToVisible) {
diff --git a/third_party/blink/renderer/core/svg/svg_document_extensions.cc b/third_party/blink/renderer/core/svg/svg_document_extensions.cc
index f583f91..5359911d 100644
--- a/third_party/blink/renderer/core/svg/svg_document_extensions.cc
+++ b/third_party/blink/renderer/core/svg/svg_document_extensions.cc
@@ -105,6 +105,14 @@
     element->pauseAnimations();
 }
 
+bool SVGDocumentExtensions::HasSmilAnimations() const {
+  for (SVGSVGElement* element : time_containers_) {
+    if (element->TimeContainer()->HasAnimations())
+      return true;
+  }
+  return false;
+}
+
 void SVGDocumentExtensions::DispatchSVGLoadEventToOutermostSVGElements() {
   HeapVector<Member<SVGSVGElement>> time_containers;
   CopyToVector(time_containers_, time_containers);
diff --git a/third_party/blink/renderer/core/svg/svg_document_extensions.h b/third_party/blink/renderer/core/svg/svg_document_extensions.h
index fed22a8..60d1eecb 100644
--- a/third_party/blink/renderer/core/svg/svg_document_extensions.h
+++ b/third_party/blink/renderer/core/svg/svg_document_extensions.h
@@ -58,6 +58,7 @@
 
   void StartAnimations();
   void PauseAnimations();
+  bool HasSmilAnimations() const;
   // True if a SMIL animation frame is successfully scheduled.
   bool ServiceSmilAnimations();
   void ServiceWebAnimations();
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index 74ad3ad..fe0f977 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -4585,9 +4585,26 @@
   // now layout is clean.
   SetNeedsToUpdateChildren();
 
-  SANITIZER_CHECK(LastKnownIsIncludedInTreeValue())
-      << "Should only fire children changed on included nodes: "
-      << ToString(true, true);
+  // The caller, AXObjectCacheImpl::ChildrenChangedWithCleanLayout(), is only
+  // Between the time that AXObjectCacheImpl::ChildrenChanged() determines
+  // which included parent to use and now, it's possible that the parent will
+  // no longer be ignored. This is rare, but is covered by this test:
+  // external/wpt/accessibility/crashtests/delayed-ignored-change.html/
+  //
+  // If this object is no longer included in the tree, then our parent needs to
+  // recompute its included-in-the-tree children vector. (And if our parent
+  // isn't included in the tree either, it will recursively update its parent
+  // and so on.)
+  //
+  // The first ancestor that's included in the tree will
+  // be the one that actually fires the ChildrenChanged
+  // event notification.
+  if (!LastKnownIsIncludedInTreeValue()) {
+    if (AXObject* ax_parent = CachedParentObject()) {
+      ax_parent->ChildrenChangedWithCleanLayout();
+      return;
+    }
+  }
 
   // TODO(accessibility) Move this up.
   if (!CanHaveChildren())
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_deliverer.h b/third_party/blink/renderer/platform/mediastream/media_stream_audio_deliverer.h
index 8a535bbd0..71b64e8 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_deliverer.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_deliverer.h
@@ -11,6 +11,7 @@
 #include "base/synchronization/lock.h"
 #include "base/threading/thread_checker.h"
 #include "base/trace_event/trace_event.h"
+#include "media/base/audio_bus.h"
 #include "media/base/audio_parameters.h"
 #include "third_party/blink/public/platform/modules/webrtc/webrtc_logging.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 4be2387..f8e74a8 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1632,6 +1632,7 @@
 virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-034.html [ Pass ]
 virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-035.html [ Pass ]
 virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-037.html [ Pass ]
+virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-038.html [ Pass ]
 virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/single-line-column-flex-fragmentation-012.html [ Pass ]
 virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/single-line-column-flex-fragmentation-014.html [ Pass ]
 virtual/layout_ng_flex_frag/external/wpt/css/css-break/flexbox/single-line-column-flex-fragmentation-015.html [ Pass ]
@@ -4306,6 +4307,7 @@
 crbug.com/660611 external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-034.html [ Failure ]
 crbug.com/660611 external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-035.html [ Failure ]
 crbug.com/660611 external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-037.html [ Failure ]
+crbug.com/660611 external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-038.html [ Failure ]
 crbug.com/660611 external/wpt/css/css-break/flexbox/single-line-column-flex-fragmentation-012.html [ Failure ]
 crbug.com/660611 external/wpt/css/css-break/flexbox/single-line-column-flex-fragmentation-014.html [ Failure ]
 crbug.com/660611 external/wpt/css/css-break/flexbox/single-line-column-flex-fragmentation-015.html [ Failure ]
@@ -7367,7 +7369,6 @@
 crbug.com/1278570 [ Linux ] virtual/gpu-rasterization/images/directly-composited-image-orientation.html [ Failure Pass ]
 
 # Sheriff 2021-12-21
-crbug.com/1281780 [ Mac ] media/video-poster-after-playing.html [ Failure Pass ]
 crbug.com/1281782 [ Mac ] virtual/no-alloc-direct-call/external/wpt/html/canvas/element/compositing/2d.composite.canvas.source-out.html [ Crash Pass ]
 crbug.com/1281782 [ Mac ] virtual/no-alloc-direct-call/external/wpt/html/canvas/element/compositing/2d.composite.clip.xor.html [ Crash Pass ]
 crbug.com/1281782 [ Mac ] virtual/no-alloc-direct-call/external/wpt/html/canvas/element/compositing/2d.composite.operation.darker.html [ Crash Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/accessibility/crashtests/delayed-ignored-change.html b/third_party/blink/web_tests/external/wpt/accessibility/crashtests/delayed-ignored-change.html
new file mode 100644
index 0000000..1fc98a1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/accessibility/crashtests/delayed-ignored-change.html
@@ -0,0 +1,17 @@
+<html class="test-wait">
+<style>
+  body { font-size: x-large; }
+  .hidden { visibility: hidden; }
+</style>
+<rb class="hidden">
+  <textarea></textarea>
+</rb>
+<script>
+document.addEventListener('load', () => {
+  window.requestIdleCallback(() => {
+    document.querySelector('style').remove();
+    document.documentElement.className = '';
+  });
+}, true);
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-038.html b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-038.html
new file mode 100644
index 0000000..e745987
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-038.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>
+  Multi-line row flex fragmentation: break-before:avoid and column balancing.
+</title>
+<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#pagination">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<style>
+  #flex {
+    display: flex;
+    flex-wrap: wrap;
+  }
+  #flex > div {
+    width: 50px;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:100px; height:100px; background:red;">
+  <div style="width: 100px; columns: 2; column-gap: 0;position: relative; background: green;">
+    <div id="flex">
+      <div style="height: 25px;"></div>
+      <div style="height: 50px; break-before:avoid;"></div>
+      <div style="height: 5px; width: 25px;"></div>
+      <div style="height: 25px; width: 25px; break-before: avoid;"></div>
+      <div style="height: 50px;"></div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/fast/forms/text/input-readonly-focus.html b/third_party/blink/web_tests/fast/forms/text/input-readonly-focus.html
index 1e866c9..7cdd9fd4 100644
--- a/third_party/blink/web_tests/fast/forms/text/input-readonly-focus.html
+++ b/third_party/blink/web_tests/fast/forms/text/input-readonly-focus.html
@@ -1,28 +1,19 @@
-<html>
-<head>
-<script>
-function runTest() {
-  testRunner.waitUntilDone();
-  testRunner.dumpAsText();
+<!DOCTYPE html>
 
-  var input = document.getElementById('i');
-  eventSender.mouseMoveTo(input.offsetLeft + input.offsetWidth / 2, input.offsetTop + input.offsetHeight / 2);
-  eventSender.mouseDown();
-  eventSender.mouseUp();
-}
-window.onload = function() {
-  var input = document.getElementById('i');
-  input.onfocus = function(e) {
+<input readonly>
+
+<script>
+window.onload = () => {
+  if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+  }
+  const input = document.querySelector('input');
+  input.onfocus = (e) => {
     document.body.innerHTML = 'PASS';
     if (window.testRunner)
       window.testRunner.notifyDone();
   }
-  if (window.testRunner)
-    runTest();
+  input.focus();
 }
 </script>
-</head>
-<body>
-<input id="i" readonly>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/media/video-poster-after-playing.html b/third_party/blink/web_tests/media/video-poster-after-playing.html
index ed3d894a..5eaf9c467 100644
--- a/third_party/blink/web_tests/media/video-poster-after-playing.html
+++ b/third_party/blink/web_tests/media/video-poster-after-playing.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 <title>Tests setting the poster attribute after a video is playing.</title>
+<video></video>
 <script>
 if (window.testRunner)
   testRunner.waitUntilDone();
@@ -9,14 +10,14 @@
   video.addEventListener("playing", function () {
     video.poster = "content/abe.png";
   });
-  video.addEventListener("timeupdate", function () {
-    if (video.currentTime > 1 && window.testRunner)
+  video.requestVideoFrameCallback(function () {
+    if (window.testRunner)
       testRunner.notifyDone();
   });
 
+  video.src = "resources/test-positive-start-time.webm";
   video.play();
 }
 
 window.addEventListener('load', startTest, false);
 </script>
-<video src="resources/test-positive-start-time.webm"></video>
diff --git a/tools/aggregation_service/aggregation_service_tool_main.cc b/tools/aggregation_service/aggregation_service_tool_main.cc
index 96331196..97557a6 100644
--- a/tools/aggregation_service/aggregation_service_tool_main.cc
+++ b/tools/aggregation_service/aggregation_service_tool_main.cc
@@ -189,9 +189,8 @@
   std::vector<GURL> processing_urls;
   static constexpr char kDefaultEndpointPath[] = "keys.json";
   for (auto& kv : kv_pairs) {
-    url::Replacements<char> replacements;
-    replacements.SetPath(kDefaultEndpointPath,
-                         url::Component(0, strlen(kDefaultEndpointPath)));
+    GURL::Replacements replacements;
+    replacements.SetPathStr(kDefaultEndpointPath);
     GURL url = url::Origin::Create(GURL(kv.first))
                    .GetURL()
                    .ReplaceComponents(replacements);
@@ -273,4 +272,4 @@
     return 1;
 
   return 0;
-}
\ No newline at end of file
+}
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 3f4116b..7a95e0d 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -2850,7 +2850,7 @@
       'official', 'goma', 'minimal_symbols', 'fuchsia', 'fuchsia_include_sd_images', 'arm64', 'ffmpeg_branding_chrome', 'proprietary_codecs', 'test_isolate_no_emulator'
     ],
     'official_goma_fuchsia_x64_perf': [
-      'official', 'goma', 'minimal_symbols', 'fuchsia', 'fuchsia_include_workstation_image', 'x64', 'ffmpeg_branding_chrome', 'proprietary_codecs', 'test_isolate_no_emulator'
+      'official', 'goma', 'minimal_symbols', 'fuchsia', 'fuchsia_include_chromebook_image', 'x64', 'ffmpeg_branding_chrome', 'proprietary_codecs', 'test_isolate_no_emulator'
     ],
 
     'official_goma_linux_pgo': [
@@ -3621,12 +3621,16 @@
       'gn_args': 'fuchsia_code_coverage=true',
     },
 
+    'fuchsia_include_chromebook_image': {
+      'gn_args': 'fuchsia_additional_boot_images=["//third_party/fuchsia-sdk/images-internal/chromebook-x64-release/"]',
+    },
+
     'fuchsia_include_sd_images': {
       'gn_args': 'fuchsia_additional_boot_images=["//third_party/fuchsia-sdk/images-internal/astro-release/","//third_party/fuchsia-sdk/images-internal/sherlock-release/"]',
     },
 
     'fuchsia_include_workstation_image': {
-      'gn_args': 'fuchsia_additional_boot_images=["//third_party/fuchsia-sdk/images/qemu-x64-release/","//third_party/fuchsia-sdk/images-internal/chromebook-x64-release/"]',
+      'gn_args': 'fuchsia_additional_boot_images=["//third_party/fuchsia-sdk/images/qemu-x64-release/"]',
     },
 
     'full_symbols': {
diff --git a/tools/mb/mb_config_expectations/chromium.fyi.json b/tools/mb/mb_config_expectations/chromium.fyi.json
index a28ffd25..664213e 100644
--- a/tools/mb/mb_config_expectations/chromium.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.fyi.json
@@ -573,8 +573,7 @@
     "gn_args": {
       "dcheck_always_on": false,
       "fuchsia_additional_boot_images": [
-        "//third_party/fuchsia-sdk/images/qemu-x64-release/",
-        "//third_party/fuchsia-sdk/images-internal/chromebook-x64-release/"
+        "//third_party/fuchsia-sdk/images/qemu-x64-release/"
       ],
       "fuchsia_browser_type": "chrome",
       "is_component_build": false,
diff --git a/tools/mb/mb_config_expectations/chromium.perf.fyi.json b/tools/mb/mb_config_expectations/chromium.perf.fyi.json
index ac1c412..ea89105b 100644
--- a/tools/mb/mb_config_expectations/chromium.perf.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.perf.fyi.json
@@ -61,7 +61,6 @@
     "gn_args": {
       "ffmpeg_branding": "Chrome",
       "fuchsia_additional_boot_images": [
-        "//third_party/fuchsia-sdk/images/qemu-x64-release/",
         "//third_party/fuchsia-sdk/images-internal/chromebook-x64-release/"
       ],
       "is_chrome_branded": true,
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.perf.json b/tools/mb/mb_config_expectations/tryserver.chromium.perf.json
index 1dc4a83..38edc0b 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.perf.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.perf.json
@@ -96,7 +96,6 @@
     "gn_args": {
       "ffmpeg_branding": "Chrome",
       "fuchsia_additional_boot_images": [
-        "//third_party/fuchsia-sdk/images/qemu-x64-release/",
         "//third_party/fuchsia-sdk/images-internal/chromebook-x64-release/"
       ],
       "is_chrome_branded": true,
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index b7539eb..cc5bd026 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -31095,6 +31095,15 @@
   </description>
 </action>
 
+<action name="TabsSearch.SuggestedActions.RecentTabs">
+  <owner>michaeldo@chromium.org</owner>
+  <owner>mrefaat@chromium.org</owner>
+  <description>
+    The user tapped the suggested search action to search recent tabs for the
+    entered search term.
+  </description>
+</action>
+
 <action name="TabsSearch.SuggestedActions.SearchHistory">
   <owner>michaeldo@chromium.org</owner>
   <owner>mrefaat@chromium.org</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index c403deb0..4b23549 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -2844,6 +2844,9 @@
       label="SERVICE_WORKER_SETTINGS_GET_REQUESTED_WITH_HEADER_MODE">
     ServiceWorkerWebSettingsCompat#getRequestedWithHeaderMode()
   </int>
+  <int value="60" label="GET_VARIATIONS_HEADER">
+    WebViewCompat#getVariationsHeader()
+  </int>
 </enum>
 
 <enum name="ANGLEProgramCacheResult">
@@ -96271,6 +96274,20 @@
   <int value="5" label="CycleRightSnapInTablet"/>
 </enum>
 
+<enum name="WindowSnapActionSource">
+  <int value="0" label="Drag window to screen edge to snap"/>
+  <int value="1" label="Use window caption button to snap"/>
+  <int value="2" label="Use keyboard shortcut to snap"/>
+  <int value="3" label="Drag or select overview window to snap"/>
+  <int value="4" label="Long press overview button to snap"/>
+  <int value="5" label="Drag up from shelf to snap"/>
+  <int value="6" label="Drag down from top to snap"/>
+  <int value="7" label="Drag a tab to snap"/>
+  <int value="8" label="Auto snapped by splitview"/>
+  <int value="9" label="Window is snapped from window state restore"/>
+  <int value="10" label="Other ways to snap a window"/>
+</enum>
+
 <enum name="WindowsNotificationActivationStatus">
   <int value="0" label="SUCCESS"/>
   <int value="1" label="GET_PROFILE_ID_INVALID_LAUNCH_ID (deprecated)"/>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 5d74954fe..7b4e324 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -3966,6 +3966,16 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.Wm.WindowSnapActionSource" enum="WindowSnapActionSource"
+    expires_after="2023-02-24">
+  <owner>xdai@chromium.org</owner>
+  <owner>nupurjain@chromium.org</owner>
+  <summary>
+    Emitted when a window is to be snapped. Records different ways for a user to
+    snap a window.
+  </summary>
+</histogram>
+
 </histograms>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index 1f59a6d..30012693 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -1386,7 +1386,7 @@
 </histogram>
 
 <histogram name="ChromeOS.SecurityAnomaly" enum="SecurityAnomaly"
-    expires_after="2022-04-03">
+    expires_after="2022-10-03">
   <owner>jorgelo@chromium.org</owner>
   <owner>chromeos-security-core@google.com</owner>
   <summary>
@@ -1399,7 +1399,7 @@
 </histogram>
 
 <histogram name="ChromeOS.SecurityAnomalyUploadSuccess" enum="Boolean"
-    expires_after="2022-04-03">
+    expires_after="2022-10-03">
   <owner>jorgelo@chromium.org</owner>
   <owner>chromeos-security-core@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/input/histograms.xml b/tools/metrics/histograms/metadata/input/histograms.xml
index d4cc78c..226a8ee0 100644
--- a/tools/metrics/histograms/metadata/input/histograms.xml
+++ b/tools/metrics/histograms/metadata/input/histograms.xml
@@ -529,7 +529,7 @@
 </histogram>
 
 <histogram name="InputMethod.ID2" enum="InputMethodID2"
-    expires_after="2022-04-03">
+    expires_after="2023-02-22">
   <owner>tranbaoduy@chromium.org</owner>
   <owner>shuchen@chromium.org</owner>
   <owner>essential-inputs-team@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index a3eea518..b50475c 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -1425,6 +1425,17 @@
   <summary>Maximal amount of memory allocated by decoder.</summary>
 </histogram>
 
+<histogram name="BrowsingTopics.SiteDataStorage.InitStatus"
+    enum="BooleanSuccess" expires_after="2023-02-10">
+  <owner>yaoxia@chromium.org</owner>
+  <owner>jkarlin@chromium.org</owner>
+  <summary>
+    Records initialization status of BrowsingTopics SiteDataStorage database.
+    Recored when the database is lazily initialised when the first operation is
+    encountered.
+  </summary>
+</histogram>
+
 <histogram name="Canvas.TextMetrics.SetFont" units="microseconds"
     expires_after="2021-07-14">
   <obsolete>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index 657d27d..c6869cf 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -1259,17 +1259,25 @@
 </histogram>
 
 <histogram
-    name="SafeBrowsing.NavigationObserver.IdentifyReferrerChainByWebContentsTime"
+    name="SafeBrowsing.NavigationObserver.IdentifyReferrerChainByRenderFrameHostTime"
     units="ms" expires_after="2022-07-03">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
-    Logs the time it takes to identify referrer chain by web contents. Logged
-    each time the function is called.
+    Logs the time it takes to identify referrer chain by render frame host.
+    Logged each time the function is called.
   </summary>
 </histogram>
 
 <histogram
+    name="SafeBrowsing.NavigationObserver.MissingInitiatorRenderFrameHostPortal"
+    units="BooleanExists" expires_after="2022-07-23">
+  <owner>vollick@chromium.org</owner>
+  <owner>chrome-safebrowsing-alerts@google.com</owner>
+  <summary>Logs the number of times we have a missing initiator RFH.</summary>
+</histogram>
+
+<histogram
     name="SafeBrowsing.NavigationObserver.NavigationEventsRecordedLength"
     units="count" expires_after="2022-08-10">
   <owner>drubery@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/security/histograms.xml b/tools/metrics/histograms/metadata/security/histograms.xml
index 33e4dec..bf2c562 100644
--- a/tools/metrics/histograms/metadata/security/histograms.xml
+++ b/tools/metrics/histograms/metadata/security/histograms.xml
@@ -576,6 +576,18 @@
   </summary>
 </histogram>
 
+<histogram name="Security.SCTAuditing.OptOut.PopularSCTSkipped" enum="Boolean"
+    expires_after="M104">
+  <owner>cthomp@chromium.org</owner>
+  <owner>nsatragno@chromium.org</owner>
+  <owner>trusty-transport@chromium.org</owner>
+  <summary>
+    Records whether an SCT selected for a hashdance lookup query was skipped
+    because it was found on the Popular SCTs list. Recorded once when creating a
+    new auditing report, but before the report is sampled or deduplicated.
+  </summary>
+</histogram>
+
 <histogram name="Security.SecurityLevel.DownloadStarted" enum="SecurityLevel"
     expires_after="2022-09-01">
   <owner>cthomp@chromium.org</owner>
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 00ca4454..b5e04ae 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -41,6 +41,7 @@
 
 # Benchmark: blink_perf.css
 crbug.com/891878 [ android-nexus-5x android-webview ] blink_perf.css/CustomPropertiesVarAlias.html [ Skip ]
+crbug.com/1300672 blink_perf.css/HasSiblingDescendantInvalidation.html [ Skip ]
 
 # Benchmark: blink_perf.events
 crbug.com/963945 [ android-nexus-5x android-webview ] blink_perf.events/EventsDispatchingInDeeplyNestedV1ShadowTrees.html [ Skip ]
@@ -62,6 +63,7 @@
 crbug.com/859979 [ android-webview ] blink_perf.paint/paint-offset-changes.html [ Skip ]
 crbug.com/901493 [ android-nexus-6 android-webview ] blink_perf.paint/* [ Skip ]
 crbug.com/1000460 [ android-pixel-2 ] blink_perf.paint/color-changes.html [ Skip ]
+crbug.com/1300693 [ android-pixel-2 android-not-webview ] blink_perf.paint/custom-highlights.html [ Skip ]
 
 # Benchmark: blink_perf.parser
 crbug.com/966913 [ android-nexus-5x android-webview ] blink_perf.parser/query-selector-all-class-deep.html [ Skip ]
@@ -96,6 +98,9 @@
 crbug.com/1236631 [ mac ] desktop_ui/side_search:* [ Skip ]
 crbug.com/1236631 [ linux ] desktop_ui/side_search:* [ Skip ]
 
+# Benchmark: blink_perf.webcodecs
+crbug.com/1300680 [ android ] blink_perf.webcodecs/software-video-encoding.html [ Skip ]
+
 # Benchmark: dromaeo
 crbug.com/1050065 [ android-pixel-2 ] dromaeo/http://dromaeo.com?dom-modify [ Skip ]
 
diff --git a/ui/base/data_transfer_policy/data_transfer_endpoint_serializer_unittest.cc b/ui/base/data_transfer_policy/data_transfer_endpoint_serializer_unittest.cc
index 144ecdb..609d15e3 100644
--- a/ui/base/data_transfer_policy/data_transfer_endpoint_serializer_unittest.cc
+++ b/ui/base/data_transfer_policy/data_transfer_endpoint_serializer_unittest.cc
@@ -4,7 +4,7 @@
 
 #include "ui/base/data_transfer_policy/data_transfer_endpoint_serializer.h"
 
-#include "build/chromeos_buildflags.h"
+#include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
 #include "url/gurl.h"
@@ -18,11 +18,9 @@
     R"({"endpoint_type":"url","url":"https://www.google.com/","url_origin":"https://www.google.com"})";
 constexpr char kExampleJsonUrlTypeNoUrl[] = R"({"endpoint_type":"url"})";
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-// TODO(crbug.com/1280545): Enable test when VM DataTransferEndpoint endpoint
-// types are built in Lacros.
+#if BUILDFLAG(IS_CHROMEOS)
 constexpr char kExampleJsonNonUrlType[] = R"({"endpoint_type":"crostini"})";
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 }  // namespace
 
@@ -52,9 +50,7 @@
   EXPECT_EQ(nullptr, actual);
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-// TODO(crbug.com/1280545): Enable test when VM DataTransferEndpoint endpoint
-// types are built in Lacros.
+#if BUILDFLAG(IS_CHROMEOS)
 TEST(DataTransferEndpointSerializerTest, DataTransferEndpointToJsonNonUrl) {
   const DataTransferEndpoint example(EndpointType::kCrostini,
                                      /*notify_if_restricted=*/true);
@@ -71,6 +67,6 @@
   EXPECT_EQ(EndpointType::kCrostini, actual->type());
   EXPECT_EQ(nullptr, actual->GetURL());
 }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 }  // namespace ui
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
index 0072f13d..bc544e8 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
@@ -421,7 +421,7 @@
   // activation change event. This is needed since the activation client may
   // check whether this widget can receive activation when deciding which window
   // should receive activation next.
-  base::AutoReset<bool> resetter(&should_activate_, active);
+  base::AutoReset<absl::optional<bool>> resetter(&should_activate_, active);
 
   if (active) {
     // TODO(nektar): We need to harmonize the firing of accessibility
@@ -1268,7 +1268,8 @@
 // DesktopNativeWidgetAura, wm::ActivationDelegate implementation:
 
 bool DesktopNativeWidgetAura::ShouldActivate() const {
-  return should_activate_ && native_widget_delegate_->CanActivate();
+  return (!should_activate_.has_value() || should_activate_.value()) &&
+         native_widget_delegate_->CanActivate();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1297,17 +1298,23 @@
   // content window are active. When the window tree host gains and looses
   // activation, the window tree host calls into HandleActivationChanged() which
   // notifies delegates of the activation change.
-  // However the widget's content window can still be deactivated while the
-  // window tree host remains active. This is the case if a chid widget is
-  // spawned (e.g. a bubble) and the child's content window is activated. This
-  // is a valid state since the window tree host should remain active while
-  // the child is active.
-  // In this case this widget would no longer be considered active but since the
-  // window tree host has not deactivated delegates are not notified in
-  // HandleActivationChanged(). To ensure activation updates are propagated
-  // correctly we must notify delegates here.
+  // However the widget's content window can still be deactivated and activated
+  // while the window tree host remains active. For e.g. if a child widget is
+  // spawned (e.g. a bubble) the child's content window is activated and the
+  // root content window is deactivated. This is a valid state since the window
+  // tree host should remain active while the child is active. The child bubble
+  // can then be closed and activation returns to the root content window - all
+  // without the window tree host's activation state changing.
+  // As the activation state of the content window changes we must ensure that
+  // we notify this widget's delegate. Do this here for cases where we are not
+  // handling an activation change in HandleActivationChanged() since delegates
+  // will be notified of the change there directly.
   const bool content_window_activated = content_window_ == gained_active;
-  if (desktop_window_tree_host_->IsActive() && !content_window_activated) {
+  const bool tree_host_active = desktop_window_tree_host_->IsActive();
+  // TODO(crbug.com/1300567): Update focus rules to avoid focusing the desktop
+  // widget's content window if its window tree host is not active.
+  if (!should_activate_.has_value() &&
+      (tree_host_active || !content_window_activated)) {
     native_widget_delegate_->OnNativeWidgetActivationChanged(
         content_window_activated);
   }
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.h b/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
index 9ea7e63a..362bf7b 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
@@ -322,7 +322,7 @@
   // change event in `HandleActivationChanged()`.This is needed as the widget
   // may not have propagated its new activation state to its delegate before the
   // activation client decides which window to activate next.
-  bool should_activate_ = true;
+  absl::optional<bool> should_activate_;
 
   gfx::NativeCursor cursor_;
   // We must manually reference count the number of users of |cursor_manager_|
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura_interactive_uitest.cc b/ui/views/widget/desktop_aura/desktop_native_widget_aura_interactive_uitest.cc
index 8833870..da74e658 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura_interactive_uitest.cc
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura_interactive_uitest.cc
@@ -96,4 +96,64 @@
   widget2->CloseNow();
 }
 
+// Tests to make sure that a widget that shows an active child has activation
+// correctly propagate to the child's content window. This also tests to make
+// sure that when this child window is closed, and the desktop widget's window
+// tree host remains active, the widget's content window has its activation
+// state restored. This tests against a regression where the desktop widget
+// would not receive activation when it's child bubbles were closed (see
+// crbug.com/1294404).
+TEST_F(DesktopNativeWidgetAuraTest,
+       DesktopWidgetsRegainFocusWhenChildWidgetClosed) {
+  auto widget = std::make_unique<Widget>();
+  Widget::InitParams params(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+  params.context = GetContext();
+  params.native_widget = new DesktopNativeWidgetAura(widget.get());
+  params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+  widget->Init(std::move(params));
+
+  auto widget_child = std::make_unique<Widget>();
+  Widget::InitParams params_child(Widget::InitParams::TYPE_BUBBLE);
+  params_child.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+  params_child.parent = widget->GetNativeView();
+  params_child.native_widget =
+      CreatePlatformNativeWidgetImpl(widget_child.get(), kDefault, nullptr);
+  widget_child->Init(std::move(params_child));
+  widget_child->widget_delegate()->SetCanActivate(true);
+
+  auto* activation_client =
+      wm::GetActivationClient(widget->GetNativeView()->GetRootWindow());
+  auto* activation_client_child =
+      wm::GetActivationClient(widget_child->GetNativeView()->GetRootWindow());
+
+  // All widgets belonging to the same tree host should share an activation
+  // client.
+  ASSERT_EQ(activation_client, activation_client_child);
+
+  widget->Show();
+  views::test::WidgetActivationWaiter(widget.get(), true).Wait();
+  views::test::WidgetActivationWaiter(widget_child.get(), false).Wait();
+  EXPECT_TRUE(widget->IsActive());
+  EXPECT_FALSE(widget_child->IsActive());
+  EXPECT_EQ(activation_client->GetActiveWindow(), widget->GetNativeView());
+
+  widget_child->Show();
+  views::test::WidgetActivationWaiter(widget.get(), false).Wait();
+  views::test::WidgetActivationWaiter(widget_child.get(), true).Wait();
+  EXPECT_FALSE(widget->IsActive());
+  EXPECT_TRUE(widget_child->IsActive());
+  EXPECT_EQ(activation_client->GetActiveWindow(),
+            widget_child->GetNativeView());
+
+  widget_child->Close();
+  views::test::WidgetActivationWaiter(widget.get(), true).Wait();
+  views::test::WidgetActivationWaiter(widget_child.get(), false).Wait();
+  EXPECT_TRUE(widget->IsActive());
+  EXPECT_FALSE(widget_child->IsActive());
+  EXPECT_EQ(activation_client->GetActiveWindow(), widget->GetNativeView());
+
+  widget_child->CloseNow();
+  widget->CloseNow();
+}
+
 }  // namespace views::test
diff --git a/ui/webui/resources/cr_components/chromeos/network/BUILD.gn b/ui/webui/resources/cr_components/chromeos/network/BUILD.gn
index 61ff21c..c731d058 100644
--- a/ui/webui/resources/cr_components/chromeos/network/BUILD.gn
+++ b/ui/webui/resources/cr_components/chromeos/network/BUILD.gn
@@ -384,7 +384,7 @@
     ":network_listener_behavior.m",
     ":network_password_input.m",
     ":network_shared_css.m",
-    "//chrome/browser/ui/webui/settings/chromeos/search:mojo_bindings_js_library_for_compile",
+    "//chrome/browser/ui/webui/settings/ash/search:mojo_bindings_js_library_for_compile",
     "//third_party/polymer/v3_0/components-chromium/iron-flex-layout:iron-flex-layout",
     "//third_party/polymer/v3_0/components-chromium/iron-icon:iron-icon",
     "//third_party/polymer/v3_0/components-chromium/paper-spinner:paper-spinner-lite",
diff --git a/weblayer/browser/safe_browsing/weblayer_client_side_detection_host_delegate.cc b/weblayer/browser/safe_browsing/weblayer_client_side_detection_host_delegate.cc
index 1000b4e..abb4b8a 100644
--- a/weblayer/browser/safe_browsing/weblayer_client_side_detection_host_delegate.cc
+++ b/weblayer/browser/safe_browsing/weblayer_client_side_detection_host_delegate.cc
@@ -7,6 +7,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/android/remote_database_manager.h"
 #include "components/safe_browsing/content/browser/client_side_detection_service.h"
+#include "content/public/browser/global_routing_id.h"
 #include "weblayer/browser/browser_context_impl.h"
 #include "weblayer/browser/browser_process.h"
 #include "weblayer/browser/safe_browsing/client_side_detection_service_factory.h"
@@ -55,6 +56,7 @@
 
 void WebLayerClientSideDetectionHostDelegate::AddReferrerChain(
     safe_browsing::ClientPhishingRequest* verdict,
-    GURL current_url) {}
+    GURL current_url,
+    const content::GlobalRenderFrameHostId& current_outermost_main_frame_id) {}
 
 }  // namespace weblayer
diff --git a/weblayer/browser/safe_browsing/weblayer_client_side_detection_host_delegate.h b/weblayer/browser/safe_browsing/weblayer_client_side_detection_host_delegate.h
index da5fa22..9bcb2db0 100644
--- a/weblayer/browser/safe_browsing/weblayer_client_side_detection_host_delegate.h
+++ b/weblayer/browser/safe_browsing/weblayer_client_side_detection_host_delegate.h
@@ -10,6 +10,10 @@
 #include "components/safe_browsing/core/common/proto/csd.pb.h"
 #include "url/gurl.h"
 
+namespace content {
+struct GlobalRenderFrameHostId;
+}  // namespace content
+
 namespace weblayer {
 
 class WebLayerClientSideDetectionHostDelegate
@@ -35,7 +39,9 @@
   safe_browsing::ClientSideDetectionService* GetClientSideDetectionService()
       override;
   void AddReferrerChain(safe_browsing::ClientPhishingRequest* verdict,
-                        GURL current_url) override;
+                        GURL current_url,
+                        const content::GlobalRenderFrameHostId&
+                            current_outermost_main_frame_id) override;
 
  private:
   raw_ptr<content::WebContents> web_contents_;