diff --git a/DEPS b/DEPS
index 46abb180..9471d76 100644
--- a/DEPS
+++ b/DEPS
@@ -312,15 +312,15 @@
   # 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': 'cdede8e2e18b68d4412ac09d7d583034d22f3162',
+  'skia_revision': '9e23cc57008740ab890bd9add274af8a2bdd7882',
   # 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': 'f1f9e588bb78fa34cb3f94e5a5a258011311c1d9',
+  'v8_revision': '08a022d7017fd62a01493e29d7d4a54651ef1237',
   # 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': '94179bed1c110a6904b2c3794048214b4af9ea86',
+  'angle_revision': 'c74d2634e6e094865e2ad1c43e37f00910c119be',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -383,7 +383,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '221ecf70737a2ee01ca0f9aae6133b1a05dc5f09',
+  'catapult_revision': '48216439d9883995da2edbf29ffa82740666c5c3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling chromium_variations
   # and whatever else without interference from each other.
@@ -403,7 +403,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '19d8873c294f5859d5dfcf6a4a57966761f48694',
+  'devtools_frontend_revision': 'e6256afd3f8f181453984b407fe63c50de9559f5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -427,7 +427,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': '48de84dfe6458204fe463d1249e336680302ff71',
+  'dawn_revision': '48ca92d67dde7192a4871aa4f17a73af7c204e94',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -520,6 +520,9 @@
   # 'magic' variable to tell depot_tools that git submodules should be accepted
   # but parity with DEPS file is expected.
   'SUBMODULE_MIGRATION': 'True',
+
+  # condition to allowlist deps for non-git-source processing.
+  'non_git_source': 'True',
 }
 
 # Only these hosts are allowed for dependencies in this DEPS file.
@@ -831,7 +834,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '0fb8a71ee651916fff95a11702236ff8d559206f',
+    '5adefb939901b88ee5b674e74bc2223694aa45b1',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1202,7 +1205,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '5f2fd897ad3e517576bef2eab8e9250d43aaf800',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '1a8fe4df13a1bdd4845fb4845f412214a76e56aa',
     'condition': 'checkout_src_internal',
   },
 
@@ -1858,7 +1861,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'bf1843b38f586e55fd1fa6f6a084f3a5f92f3047',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'fffd489d2e5be9f48d3d46ab753baa9b020f3652',
+    Var('webrtc_git') + '/src.git' + '@' + '0a8703b5c117ea8a6bd5eb7fa6d350681126cb20',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -3942,7 +3945,7 @@
 
   'src/chrome/browser/platform_experience/win': {
       'url': Var('chrome_git') + '/chrome/browser/platform_experience/win.git' + '@' +
-        'd33c073148ceea6b4fe7ec64fb8379519bfea545',
+        'c92c8d2a9a4b96aa46b63395584bdd654cd551ac',
       'condition': 'checkout_src_internal',
   },
 
@@ -4078,7 +4081,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        'b82843b98be70e1c4f8af58b22f418142f341954',
+        '54c1c4d2bfa106bef94a8026a4562243ac89dfbd',
       'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/NavigationListenerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/NavigationListenerTest.java
new file mode 100644
index 0000000..f5cda850
--- /dev/null
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/NavigationListenerTest.java
@@ -0,0 +1,1054 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview.test;
+
+import android.os.Bundle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
+
+import org.json.JSONObject;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import org.chromium.android_webview.AwContents;
+import org.chromium.android_webview.JsReplyProxy;
+import org.chromium.android_webview.WebMessageListener;
+import org.chromium.android_webview.test.TestAwContentsClient.OnReceivedTitleHelper;
+import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.Feature;
+import org.chromium.components.embedder_support.util.WebResourceResponseInfo;
+import org.chromium.content_public.browser.test.util.HistoryUtils;
+import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer.OnPageStartedHelper;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.EmbeddedTestServerRule;
+import org.chromium.net.test.util.TestWebServer;
+
+import java.io.ByteArrayInputStream;
+import java.net.URLEncoder;
+
+/** Test suite for the special navigation listener that will be notified of navigation messages */
+@RunWith(Parameterized.class)
+@UseParametersRunnerFactory(AwJUnit4ClassRunnerWithParameters.Factory.class)
+@Batch(Batch.PER_CLASS)
+public class NavigationListenerTest extends AwParameterizedTest {
+    @Rule public AwActivityTestRule mActivityTestRule;
+
+    @ClassRule public static EmbeddedTestServerRule sTestServerRule = new EmbeddedTestServerRule();
+
+    private static final String RESOURCE_PATH = "/android_webview/test/data";
+    private static final String PAGE_A = RESOURCE_PATH + "/hello_world.html";
+    private static final String PAGE_B = RESOURCE_PATH + "/safe.html";
+    private static final String PAGE_WITH_IFRAME = RESOURCE_PATH + "/iframe_access.html";
+    private static final String NAVIGATION_LISTENER_NAME = "experimentalWebViewNavigationListener";
+    private static final String ENCODING = "UTF-8";
+
+    private EmbeddedTestServer mTestServer;
+    private TestAwContentsClient mContentsClient;
+    private AwContents mAwContents;
+    private TestWebMessageListener mListener;
+
+    public NavigationListenerTest(AwSettingsMutation param) {
+        this.mActivityTestRule = new AwActivityTestRule(param.getMutation());
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContentsClient = new TestAwContentsClient();
+        final AwTestContainerView testContainerView =
+                mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
+        mAwContents = testContainerView.getAwContents();
+        mListener = new TestWebMessageListener();
+        mActivityTestRule.getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true);
+        mTestServer = sTestServerRule.getServer();
+    }
+
+    // Test that adding the special navigationListener will result in receiving
+    // navigation messages for a variety of navigation cases:
+    // 1) Regular navigation
+    // 2) Reload
+    // 3) Same-document navigation
+    // 4) Same-document history navigation
+    // 5) Failed navigation resulting in an error page.
+    // TODO: Add tests for SSL error?
+    @Test
+    @LargeTest
+    @Feature({"AndroidWebView", "NavigationListener"})
+    @CommandLineFlags.Add({"enable-features=EnableNavigationListener"})
+    public void testNavigationVariousCases() throws Throwable {
+        // Add the special listener object which will receive navigation messages.
+        addWebMessageListenerOnUiThread(
+                mAwContents, NAVIGATION_LISTENER_NAME, new String[] {"*"}, mListener);
+        // The first message received should be the NAVIGATION_MESSAGE_OPTED_IN message.
+        TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "NAVIGATION_MESSAGE_OPTED_IN");
+        Assert.assertTrue(data.mIsMainFrame);
+        Assert.assertEquals(0, data.mPorts.length);
+        // The JavaScriptReplyProxy should be 1:1 with Page. Save the current proxy (which is the
+        // for the initial empty document), so that we can check if the proxy changes after
+        // navigations.
+        JsReplyProxy page1ReplyProxy = data.mReplyProxy;
+
+        // No actual navigationListener object is created on the JS side.
+        Assert.assertFalse(
+                hasJavaScriptObject(
+                        NAVIGATION_LISTENER_NAME, mActivityTestRule, mAwContents, mContentsClient));
+
+        // Navigation #1: Navigate to `url` to trigger navigation messages.
+        final String url = loadUrlFromPath(PAGE_A);
+
+        // Since this navigation creates a new Page, the previous Page gets
+        // deleted.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page1ReplyProxy, data.mReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page2ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page1ReplyProxy, page2ReplyProxy);
+
+        // After the new page finishes loading, we get a PAGE_LOAD_END message.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        // Navigation #2: Do a same-document navigation to `url2`.
+        final String url2 = loadUrlFromPath(PAGE_A + "#foo");
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url2,
+                /* isSameDocument */ true,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        // Since the Page stays the same, the JavaScriptReplyProxy also stays
+        // the same.
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        // No PAGE_LOAD_END for same-document navigations.
+
+        // Navigation #3: Do a renderer-initiated reload.
+        mActivityTestRule.executeJavaScriptAndWaitForResult(
+                mAwContents, mContentsClient, "location.reload()");
+        // Since this navigation creates a new Page, the previous Page gets
+        // deleted.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        // The notification should indicate that the navigation is a reload, and
+        // uses a new JavaScriptReplyProxy.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url2,
+                /* isSameDocument */ false,
+                /* isReload */ true,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ true,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page3ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page2ReplyProxy, page3ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page3ReplyProxy, data.mReplyProxy);
+
+        // Navigation #4: Do a same-document history navigation.
+        mActivityTestRule.executeJavaScriptAndWaitForResult(
+                mAwContents, mContentsClient, "history.go(-1)");
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url,
+                /* isSameDocument */ true,
+                /* isReload */ false,
+                /* isHistory */ true,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ true,
+                /* committed */ true,
+                /* statusCode */ 200);
+        // The JsReplyProxy is reused after a same-document navigation.
+        Assert.assertEquals(page3ReplyProxy, data.mReplyProxy);
+
+        // No PAGE_LOAD_END for same-document navigations.
+
+        // Navigation #5: Do a navigation to a non-existent page, resulting in a 404 error.
+        mActivityTestRule.executeJavaScriptAndWaitForResult(
+                mAwContents, mContentsClient, "location.href = '404.html'; ");
+
+        // Since this navigation creates a new Page (an error page), the
+        // previous Page gets deleted.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page3ReplyProxy, data.mReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                mAwContents.getUrl().getSpec(),
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ true,
+                /* isPageInitiated */ true,
+                /* committed */ true,
+                /* statusCode */ 404);
+
+        // A new JavaScriptReplyProxy should be used for the new Page.
+        JsReplyProxy page4ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page2ReplyProxy, page4ReplyProxy);
+        Assert.assertNotEquals(page3ReplyProxy, page4ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page4ReplyProxy, data.mReplyProxy);
+
+        Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+    }
+
+    // Test navigation messages when navigation to a URL that results in 204 No Content.
+    @Test
+    @MediumTest
+    @Feature({"AndroidWebView", "NavigationListener"})
+    @CommandLineFlags.Add({"enable-features=EnableNavigationListener"})
+    public void testNavigation204() throws Throwable {
+        // Add the special listener object which will receive navigation messages, and get the
+        // JsReplyProxy for the initial empty document.
+        JsReplyProxy page1ReplyProxy = setUpAndGetInitialProxy();
+
+        TestWebServer webServer = TestWebServer.start();
+        try {
+            // Navigate to a URL that results in 204 No Content response. This navigation won't
+            // commit.
+            final String url204 = webServer.setResponseWithNoContentStatus("/page204.html");
+            mActivityTestRule.loadUrlAsync(mAwContents, url204);
+
+            // The navigation didn't commit but still dispatched a navigation message, which will
+            // reuse the initial empty document's JsReplyProxy (since this navigation didn't create
+            // a new page).
+            TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+            assertNavigationData(
+                    data,
+                    url204,
+                    /* isSameDocument */ false,
+                    /* isReload */ false,
+                    /* isHistory */ false,
+                    /* isErrorPage */ false,
+                    /* isPageInitiated */ false,
+                    /* committed */ false,
+                    /* statusCode */ 204);
+            Assert.assertEquals(page1ReplyProxy, data.mReplyProxy);
+
+            // No more messages as the navigation didn't commit.
+            Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+        } finally {
+            webServer.shutdown();
+        }
+    }
+
+    // Test navigation messages when navigating to a page with an iframe.
+    @Test
+    @MediumTest
+    @Feature({"AndroidWebView", "NavigationListener"})
+    @CommandLineFlags.Add({"enable-features=EnableNavigationListener"})
+    public void testNavigationPageWithIframe() throws Throwable {
+        // Add the special listener object which will receive navigation messages, and get the
+        // JsReplyProxy for the initial empty document.
+        JsReplyProxy page1ReplyProxy = setUpAndGetInitialProxy();
+
+        // Navigate to a page that has an iframe. When the iframe is loaded, the title of the main
+        // document will be set to the iframe's URL.
+        final OnReceivedTitleHelper onReceivedTitleHelper =
+                mContentsClient.getOnReceivedTitleHelper();
+        final int titleCallCount = onReceivedTitleHelper.getCallCount();
+        final String pageWithIframeURL = mTestServer.getURL(PAGE_WITH_IFRAME);
+        final String iframeURL = mTestServer.getURL(PAGE_A);
+        mActivityTestRule.loadUrlSync(
+                mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithIframeURL);
+
+        // Since this navigation creates a new Page, the previous Page gets
+        // deleted.
+        TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page1ReplyProxy, data.mReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                pageWithIframeURL,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page2ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page1ReplyProxy, page2ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        // Check that the main document's title has been updated to the iframe's URL, indicating
+        // that the iframe had finished loading.
+        onReceivedTitleHelper.waitForCallback(titleCallCount);
+        Assert.assertEquals(iframeURL, onReceivedTitleHelper.getTitle());
+
+        // Navigation #2: Navigate to `url2`, to ensure that we don't receive any navigation message
+        // for the iframe loaded by the previous page..
+        final String url2 = loadUrlFromPath(PAGE_B);
+
+        // Since this navigation creates a new Page, the previous Page gets
+        // deleted.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        // The notification should indicate that the navigation is
+        // cross-document and uses a new JavaScriptReplyProxy.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url2,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page3ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page2ReplyProxy, page3ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page3ReplyProxy, data.mReplyProxy);
+
+        Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+    }
+
+    // Test navigation messages when navigating to a URL that redirects.
+    @Test
+    @MediumTest
+    @Feature({"AndroidWebView", "NavigationListener"})
+    @CommandLineFlags.Add({"enable-features=EnableNavigationListener"})
+    public void testNavigationRedirect() throws Throwable {
+        // Add the special listener object which will receive navigation messages, and get the
+        // JsReplyProxy for the initial empty document.
+        JsReplyProxy page1ReplyProxy = setUpAndGetInitialProxy();
+
+        // Navigate to `redirectingURL`, which redirects to `redirectTargetURL`.
+        final String redirectTargetURL = mTestServer.getURL(PAGE_A);
+        final String redirectingURL =
+                mTestServer.getURL(
+                        "/server-redirect?" + URLEncoder.encode(redirectTargetURL, ENCODING));
+        mActivityTestRule.loadUrlSync(
+                mAwContents, mContentsClient.getOnPageFinishedHelper(), redirectingURL);
+
+        // Since this navigation creates a new Page, the previous Page gets
+        // deleted.
+        TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page1ReplyProxy, data.mReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                redirectTargetURL,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page2ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page1ReplyProxy, page2ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+    }
+
+    // Test navigation messages when navigating to about:blank.
+    @Test
+    @MediumTest
+    @Feature({"AndroidWebView", "NavigationListener"})
+    @CommandLineFlags.Add({"enable-features=EnableNavigationListener"})
+    public void testNavigationAboutBlank() throws Throwable {
+        // Add the special listener object which will receive navigation messages, and get the
+        // JsReplyProxy for the initial empty document.
+        JsReplyProxy page1ReplyProxy = setUpAndGetInitialProxy();
+
+        // Navigate to about:blank.
+        mActivityTestRule.loadUrlSync(
+                mAwContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
+
+        // Since this navigation creates a new Page, the previous Page gets
+        // deleted.
+        TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page1ReplyProxy, data.mReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                "about:blank",
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page2ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page1ReplyProxy, page2ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        // Navigate same-document to about:blank#foo.
+        mActivityTestRule.executeJavaScriptAndWaitForResult(
+                mAwContents, mContentsClient, "location.href = 'about:blank#foo';");
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                "about:blank#foo",
+                /* isSameDocument */ true,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ true,
+                /* committed */ true,
+                /* statusCode */ 200);
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+    }
+
+    // Test navigation messages when navigating with restoreState().
+    @Test
+    @MediumTest
+    @Feature({"AndroidWebView", "NavigationListener"})
+    @CommandLineFlags.Add({"enable-features=EnableNavigationListener"})
+    public void testNavigationRestoreState() throws Throwable {
+        // Navigation #1: Set up the listener and navigate to `url`. This will create a new page and
+        // an associated JsReplyProxy.
+        final String url = mTestServer.getURL(PAGE_A);
+        JsReplyProxy page2ReplyProxy = setUpAndNavigateToNewPage(url);
+
+        // Navigation #2: Save and restore to a new AwContents, which should trigger a load to
+        // `url` again.
+        TestAwContentsClient newContentsClient = new TestAwContentsClient();
+        AwTestContainerView newView =
+                mActivityTestRule.createAwTestContainerViewOnMainSync(newContentsClient);
+        AwContents newContents = newView.getAwContents();
+        AwActivityTestRule.enableJavaScriptOnUiThread(newContents);
+        TestWebMessageListener newListener = new TestWebMessageListener();
+        addWebMessageListenerOnUiThread(
+                newContents, NAVIGATION_LISTENER_NAME, new String[] {"*"}, newListener);
+
+        InstrumentationRegistry.getInstrumentation()
+                .runOnMainSync(
+                        () -> {
+                            Bundle bundle = new Bundle();
+                            boolean result = mAwContents.saveState(bundle);
+                            Assert.assertTrue(result);
+                            result = newContents.restoreState(bundle);
+                            Assert.assertTrue(result);
+                        });
+        // Since the navigation uses a new AwContents, we get a new opt-in message.
+        TestWebMessageListener.Data data = newListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "NAVIGATION_MESSAGE_OPTED_IN");
+        JsReplyProxy page3ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page2ReplyProxy, page3ReplyProxy);
+
+        // The restore navigation should trigger navigation messages, and page deletion notification
+        // for the initial empty document of the new AwContents.
+        data = newListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page3ReplyProxy, data.mReplyProxy);
+
+        data = newListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ true,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page4ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page3ReplyProxy, page4ReplyProxy);
+
+        data = newListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page4ReplyProxy, data.mReplyProxy);
+
+        Assert.assertTrue(newListener.hasNoMoreOnPostMessage());
+        Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+    }
+
+    // Test navigation messages when navigating with loadDataWithBaseURL().
+    @Test
+    @MediumTest
+    @Feature({"AndroidWebView", "NavigationListener"})
+    @CommandLineFlags.Add({"enable-features=EnableNavigationListener"})
+    public void testNavigationLoadDataWithBaseURL() throws Throwable {
+        // Add the special listener object which will receive navigation messages, and get the
+        // JsReplyProxy for the initial empty document.
+        JsReplyProxy page1ReplyProxy = setUpAndGetInitialProxy();
+
+        // Navigate with loadDataWithBaseURL.
+        final String html = "<html><body>foo</body></html>";
+        final String baseUrl = "http://www.google.com";
+        mActivityTestRule.loadDataWithBaseUrlSync(
+                mAwContents,
+                mContentsClient.getOnPageFinishedHelper(),
+                html,
+                "text/html",
+                false,
+                baseUrl,
+                null);
+
+        // Since this navigation creates a new Page, the previous Page gets
+        // deleted.
+        TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page1ReplyProxy, data.mReplyProxy);
+
+        // Assert that the navigation messages correspond to this navigation.
+
+        // Note that the URL in the navigation message will only contain the data: URL prefix
+        // instead of the whole `html` content.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                "data:text/html;charset=utf-8;base64,",
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page2ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page1ReplyProxy, page2ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+    }
+
+    // Test navigation messages for navigations that get intercepted.
+    @Test
+    @MediumTest
+    @Feature({"AndroidWebView", "NavigationListener"})
+    @CommandLineFlags.Add({"enable-features=EnableNavigationListener"})
+    public void testNavigationIntercepted() throws Throwable {
+        // Add the special listener object which will receive navigation messages, and get the
+        // JsReplyProxy for the initial empty document.
+        JsReplyProxy page1ReplyProxy = setUpAndGetInitialProxy();
+
+        TestAwContentsClient.ShouldInterceptRequestHelper shouldInterceptRequestHelper =
+                mContentsClient.getShouldInterceptRequestHelper();
+        shouldInterceptRequestHelper.setReturnValue(
+                new WebResourceResponseInfo(
+                        "text/html",
+                        ENCODING,
+                        new ByteArrayInputStream("foo".getBytes(ENCODING)),
+                        200,
+                        "OK",
+                        /* responseHeaders= */ null));
+
+        // Navigation #1: Navigate to `url` which will be intercepted to contain "foo".
+        final String url = loadUrlFromPath(PAGE_A);
+
+        // Since this navigation creates a new Page, the previous Page gets
+        // deleted.
+        TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page1ReplyProxy, data.mReplyProxy);
+
+        // The navigation message for the navigation to `url` should be received
+        // after the opt-in message.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        JsReplyProxy page2ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page1ReplyProxy, page2ReplyProxy);
+
+        // Navigation #2: Navigate to `url2`, which will be intercepted to result in an error page.
+        shouldInterceptRequestHelper.setReturnValue(
+                new WebResourceResponseInfo(
+                        "text/html",
+                        ENCODING,
+                        new ByteArrayInputStream("".getBytes(ENCODING)),
+                        500,
+                        "Internal Server Error",
+                        /* responseHeaders= */ null));
+        final String url2 = loadUrlFromPath(PAGE_B);
+
+        // Since this navigation creates a new Page, the previous Page gets
+        // deleted.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        // The notification should indicate that the navigation is
+        // cross-document and uses a new JavaScriptReplyProxy.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url2,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ true,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 500);
+        JsReplyProxy page3ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page2ReplyProxy, page3ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page3ReplyProxy, data.mReplyProxy);
+        Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+    }
+
+    // Test the navigation messages for navigations that get overridden.
+    @Test
+    @MediumTest
+    @Feature({"AndroidWebView", "NavigationListener"})
+    @CommandLineFlags.Add({"enable-features=EnableNavigationListener"})
+    public void testNavigationOverridden() throws Throwable {
+        // Add the special listener object which will receive navigation messages, and get the
+        // JsReplyProxy for the initial empty document.
+        JsReplyProxy page1ReplyProxy = setUpAndGetInitialProxy();
+
+        final TestAwContentsClient.ShouldOverrideUrlLoadingHelper shouldOverrideUrlLoadingHelper =
+                mContentsClient.getShouldOverrideUrlLoadingHelper();
+        try {
+            // Set up the helper to override navigations to `url2` (and only `url2`).
+            final String url2 = mTestServer.getURL(PAGE_B);
+            shouldOverrideUrlLoadingHelper.setUrlToOverride(url2);
+            shouldOverrideUrlLoadingHelper.setShouldOverrideUrlLoadingReturnValue(true);
+
+            // Navigation #2: Trigger a renderer-initiated navigation to `url2`, which should get
+            // overridden.
+            int currentCallCount = shouldOverrideUrlLoadingHelper.getCallCount();
+            mActivityTestRule.executeJavaScriptAndWaitForResult(
+                    mAwContents, mContentsClient, "window.location.href = '" + url2 + "';");
+            shouldOverrideUrlLoadingHelper.waitForCallback(currentCallCount);
+            Assert.assertEquals(
+                    shouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl(), url2);
+
+            // No navigation messages will be received as the navigations above got overridden.
+            Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+
+            // Navigation #3: Navigate to `url3` which is not initially overridden but will
+            // redirect to `url2`, at which point it will get overridden.
+            final String url3 =
+                    mTestServer.getURL("/server-redirect?" + URLEncoder.encode(url2, ENCODING));
+            currentCallCount += 1;
+            mActivityTestRule.executeJavaScriptAndWaitForResult(
+                    mAwContents, mContentsClient, "window.location.href = '" + url3 + "';");
+            shouldOverrideUrlLoadingHelper.waitForCallback(currentCallCount, 2);
+            Assert.assertEquals(
+                    shouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl(), url2);
+
+            // Different from the previous navigation, we'll get a NAVIGATION_COMPLETED message
+            // indicating that this navigation didn't commit and was redirected to `url2`. This is
+            // because this navigation got overridden during redirect (after the navigation
+            // starts) instead of at the very beginning before the navigation officially starts.
+            TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+            assertNavigationData(
+                    data,
+                    url2,
+                    /* isSameDocument */ false,
+                    /* isReload */ false,
+                    /* isHistory */ false,
+                    /* isErrorPage */ false,
+                    /* isPageInitiated */ true,
+                    /* committed */ false,
+                    /* statusCode */ 301);
+            Assert.assertEquals(page1ReplyProxy, data.mReplyProxy);
+
+            Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+
+        } finally {
+            shouldOverrideUrlLoadingHelper.setShouldOverrideUrlLoadingReturnValue(false);
+            shouldOverrideUrlLoadingHelper.setUrlToOverride(null);
+        }
+    }
+
+    // Test navigation messages on history navigations with BFCache disabled.
+    @Test
+    @LargeTest
+    @Feature({"AndroidWebView", "NavigationListener"})
+    @CommandLineFlags.Add({
+        "enable-features=EnableNavigationListener",
+        "disable-features=WebViewBackForwardCache"
+    })
+    public void testNavigationHistoryNavigationBFCacheDisabled() throws Throwable {
+        // Navigation #1: Set up the listener and navigate to `url`. This will create a new page and
+        // an associated JsReplyProxy.
+        final String url = mTestServer.getURL(PAGE_A);
+        JsReplyProxy page2ReplyProxy = setUpAndNavigateToNewPage(url);
+
+        // Navigation #2: Navigate to `url2`.
+        final String url2 = loadUrlFromPath(PAGE_B);
+
+        // Since this navigation creates a new Page, the previous Page gets
+        // deleted.
+        TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        // The notification should indicate that the navigation is
+        // cross-document and uses a new JavaScriptReplyProxy.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url2,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page3ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page2ReplyProxy, page3ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page3ReplyProxy, data.mReplyProxy);
+
+        // Navigation #3: Do a back navigation to the `url` Page.
+        OnPageStartedHelper onPageStartedHelper = mContentsClient.getOnPageStartedHelper();
+        HistoryUtils.goBackSync(
+                InstrumentationRegistry.getInstrumentation(),
+                mAwContents.getWebContents(),
+                onPageStartedHelper);
+
+        // Since this navigation creates a new Page, the `url2` Page gets
+        // deleted.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page3ReplyProxy, data.mReplyProxy);
+
+        // The history navigation creates a new Page and uses a new
+        // JsReplyProxy.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ true,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page4ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page2ReplyProxy, page4ReplyProxy);
+        Assert.assertNotEquals(page3ReplyProxy, page4ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page4ReplyProxy, data.mReplyProxy);
+
+        Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+    }
+
+    // Test the navigation messages on history navigations with BFCache enabled.
+    @Test
+    @LargeTest
+    @Feature({"AndroidWebView", "NavigationListener"})
+    @CommandLineFlags.Add({"enable-features=EnableNavigationListener,WebViewBackForwardCache"})
+    public void testNavigationHistoryNavigationBFCacheEnabled() throws Throwable {
+        // Navigation #1: Set up the listener and navigate to `url`. This will create a new page and
+        // an associated JsReplyProxy.
+        final String url = mTestServer.getURL(PAGE_A);
+        JsReplyProxy page2ReplyProxy = setUpAndNavigateToNewPage(url);
+
+        // Navigation #2: Navigate to `url2`.
+        final String url2 = loadUrlFromPath(PAGE_B);
+
+        // The notification should indicate that the navigation is
+        // cross-document and uses a new JavaScriptReplyProxy. Since the
+        // previous page gets BFCached, we don't get a PAGE_DELETED for the
+        // previous page.
+        TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url2,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page3ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page2ReplyProxy, page3ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page3ReplyProxy, data.mReplyProxy);
+
+        // Navigation #3: Do a back navigation to the `url` Page.
+        OnPageStartedHelper onPageStartedHelper = mContentsClient.getOnPageStartedHelper();
+        HistoryUtils.goBackSync(
+                InstrumentationRegistry.getInstrumentation(),
+                mAwContents.getWebContents(),
+                onPageStartedHelper);
+
+        // The history navigation restores the `url` page from BFCache, so it
+        // reuses the `page2ReplyProxy`.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ true,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        // No PAGE_LOAD_END for BFCache restores, as the page content didn't get
+        // re-loaded.
+
+        Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+    }
+
+    // Test the navigation messages on history navigations with BFCache enabled, but the page was
+    // evicted from BFCache.
+    @Test
+    @LargeTest
+    @Feature({"AndroidWebView", "NavigationListener"})
+    @CommandLineFlags.Add({"enable-features=EnableNavigationListener,WebViewBackForwardCache"})
+    public void testNavigationHistoryNavigationToEvictedPageBFCacheEnabled() throws Throwable {
+        // Navigation #1: Set up the listener and navigate to `url`. This will create a new page and
+        // an associated JsReplyProxy.
+        final String url = mTestServer.getURL(PAGE_A);
+        JsReplyProxy page2ReplyProxy = setUpAndNavigateToNewPage(url);
+
+        // Navigation #2: Navigate to `url2`.
+        final String url2 = loadUrlFromPath(PAGE_B);
+
+        // The notification should indicate that the navigation is
+        // cross-document and uses a new JavaScriptReplyProxy. Since the
+        // previous page gets BFCached, we don't get a PAGE_DELETED for the
+        // previous page.
+        TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url2,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page3ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page2ReplyProxy, page3ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page3ReplyProxy, data.mReplyProxy);
+
+        // Add another WebMessageListener, which will evict all BFCached pages.
+        addWebMessageListenerOnUiThread(
+                mAwContents, "foo", new String[] {"*"}, new TestWebMessageListener());
+
+        // The `url` Page gets deleted as it's evicted from BFCache.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+
+        // Navigation #3: Do a back navigation to the `url` Page.
+        OnPageStartedHelper onPageStartedHelper = mContentsClient.getOnPageStartedHelper();
+        HistoryUtils.goBackSync(
+                InstrumentationRegistry.getInstrumentation(),
+                mAwContents.getWebContents(),
+                onPageStartedHelper);
+
+        // No PAGE_DELETED for `url2`, as that page is BFCached.
+
+        // Since the `url` page is already evicted, the history navigation didn't do a BFCache
+        // restore, and instead creates a new Page and uses a new JsReplyProxy.
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ true,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page4ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page2ReplyProxy, page4ReplyProxy);
+        Assert.assertNotEquals(page3ReplyProxy, page4ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page4ReplyProxy, data.mReplyProxy);
+
+        Assert.assertTrue(mListener.hasNoMoreOnPostMessage());
+    }
+
+    private String loadUrlFromPath(String path) throws Exception {
+        final String url = mTestServer.getURL(path);
+        mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
+        return url;
+    }
+
+    private static void addWebMessageListenerOnUiThread(
+            final AwContents awContents,
+            final String jsObjectName,
+            final String[] allowedOriginRules,
+            final WebMessageListener listener)
+            throws Exception {
+        TestWebMessageListener.addWebMessageListenerOnUiThread(
+                awContents, jsObjectName, allowedOriginRules, listener);
+    }
+
+    private static boolean hasJavaScriptObject(
+            final String jsObjectName,
+            final AwActivityTestRule rule,
+            final AwContents awContents,
+            final TestAwContentsClient contentsClient)
+            throws Throwable {
+        final String result =
+                rule.executeJavaScriptAndWaitForResult(
+                        awContents, contentsClient, "typeof " + jsObjectName + " !== 'undefined'");
+        return result.equals("true");
+    }
+
+    private void assertNavigationMessageType(TestWebMessageListener.Data data, String type)
+            throws Throwable {
+        Assert.assertEquals(type, new JSONObject(data.getAsString()).getString("type"));
+    }
+
+    private void assertNavigationData(
+            TestWebMessageListener.Data data,
+            String url,
+            boolean isSameDocument,
+            boolean isReload,
+            boolean isHistory,
+            boolean isErrorPage,
+            boolean isPageInitiated,
+            boolean committed,
+            int statusCode)
+            throws Throwable {
+        var dataObj = new JSONObject(data.getAsString());
+        Assert.assertEquals("NAVIGATION_COMPLETED", dataObj.getString("type"));
+        Assert.assertEquals(url, dataObj.getString("url"));
+        Assert.assertEquals(isSameDocument, dataObj.getBoolean("isSameDocument"));
+        Assert.assertEquals(isReload, dataObj.getBoolean("isReload"));
+        Assert.assertEquals(isHistory, dataObj.getBoolean("isHistory"));
+        Assert.assertEquals(isErrorPage, dataObj.getBoolean("isErrorPage"));
+        Assert.assertEquals(isPageInitiated, dataObj.getBoolean("isPageInitiated"));
+        Assert.assertEquals(committed, dataObj.getBoolean("committed"));
+        Assert.assertEquals(statusCode, dataObj.getInt("statusCode"));
+    }
+
+    private JsReplyProxy setUpAndGetInitialProxy() throws Throwable {
+        // Add the special listener object which will receive navigation messages.
+        addWebMessageListenerOnUiThread(
+                mAwContents, NAVIGATION_LISTENER_NAME, new String[] {"*"}, mListener);
+        // The first message received should be the NAVIGATION_MESSAGE_OPTED_IN message. This will
+        // fire with the JSReplyProxy associated with the initial empty document, which we should
+        // return.
+        TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "NAVIGATION_MESSAGE_OPTED_IN");
+        return data.mReplyProxy;
+    }
+
+    private JsReplyProxy setUpAndNavigateToNewPage(String url) throws Throwable {
+        JsReplyProxy page1ReplyProxy = setUpAndGetInitialProxy();
+
+        // Navigate to `url`.
+        mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
+
+        // Since this navigation creates a new Page, the previous Page gets
+        // deleted.
+        TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_DELETED");
+        Assert.assertEquals(page1ReplyProxy, data.mReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationData(
+                data,
+                url,
+                /* isSameDocument */ false,
+                /* isReload */ false,
+                /* isHistory */ false,
+                /* isErrorPage */ false,
+                /* isPageInitiated */ false,
+                /* committed */ true,
+                /* statusCode */ 200);
+        JsReplyProxy page2ReplyProxy = data.mReplyProxy;
+        Assert.assertNotEquals(page1ReplyProxy, page2ReplyProxy);
+
+        data = mListener.waitForOnPostMessage();
+        assertNavigationMessageType(data, "PAGE_LOAD_END");
+        Assert.assertEquals(page2ReplyProxy, data.mReplyProxy);
+        return page2ReplyProxy;
+    }
+}
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java b/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java
index fed96200..d804880 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/TestAwContentsClient.java
@@ -571,6 +571,7 @@
     public static class ShouldOverrideUrlLoadingHelper extends CallbackHelper {
         private String mShouldOverrideUrlLoadingUrl;
         private boolean mShouldOverrideUrlLoadingReturnValue;
+        private String mUrlToOverride;
         private boolean mIsRedirect;
         private boolean mHasUserGesture;
         private boolean mIsOutermostMainFrame;
@@ -584,12 +585,21 @@
             mShouldOverrideUrlLoadingReturnValue = value;
         }
 
+        void setUrlToOverride(String urlToOverride) {
+            mUrlToOverride = urlToOverride;
+        }
+
         public String getShouldOverrideUrlLoadingUrl() {
             assert getCallCount() > 0;
             return mShouldOverrideUrlLoadingUrl;
         }
 
-        public boolean getShouldOverrideUrlLoadingReturnValue() {
+        public boolean getShouldOverrideUrlLoadingReturnValue(AwWebResourceRequest request) {
+            if (mUrlToOverride != null && !request.url.equals(mUrlToOverride)) {
+                // If `mUrlToOverride` is set, only override requests with a matching URL.
+                return false;
+            }
+
             return mShouldOverrideUrlLoadingReturnValue;
         }
 
@@ -629,7 +639,7 @@
         if (TRACE) Log.i(TAG, "shouldOverrideUrlLoading " + request.url);
         super.shouldOverrideUrlLoading(request);
         boolean returnValue =
-                mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingReturnValue();
+                mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingReturnValue(request);
         mShouldOverrideUrlLoadingHelper.notifyCalled(
                 request.url,
                 request.isRedirect,
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index ac309c7..6884081b 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -393,6 +393,7 @@
     "../javatests/src/org/chromium/android_webview/test/MultiProfileTest.java",
     "../javatests/src/org/chromium/android_webview/test/MultiProfileTestRule.java",
     "../javatests/src/org/chromium/android_webview/test/NavigationHistoryTest.java",
+    "../javatests/src/org/chromium/android_webview/test/NavigationListenerTest.java",
     "../javatests/src/org/chromium/android_webview/test/OnDiskFileTest.java",
     "../javatests/src/org/chromium/android_webview/test/PolicyUrlFilteringTest.java",
     "../javatests/src/org/chromium/android_webview/test/PopupWindowTest.java",
diff --git a/ash/app_list/views/folder_header_view.cc b/ash/app_list/views/folder_header_view.cc
index c9fbba78..93df4646 100644
--- a/ash/app_list/views/folder_header_view.cc
+++ b/ash/app_list/views/folder_header_view.cc
@@ -106,7 +106,8 @@
 
   ~FolderNameView() override = default;
 
-  gfx::Size CalculatePreferredSize() const override {
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override {
     return gfx::Size(kMaxFolderHeaderWidth, kFolderHeaderHeight);
   }
 
@@ -287,7 +288,8 @@
 
   ~FolderNameJellyView() override = default;
 
-  gfx::Size CalculatePreferredSize() const override {
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override {
     return gfx::Size(kMaxFolderHeaderWidth, kFolderHeaderHeight);
   }
 
diff --git a/ash/clipboard/views/clipboard_history_item_view.cc b/ash/clipboard/views/clipboard_history_item_view.cc
index 52fb543c..b2d1bec0 100644
--- a/ash/clipboard/views/clipboard_history_item_view.cc
+++ b/ash/clipboard/views/clipboard_history_item_view.cc
@@ -404,10 +404,13 @@
   return GetClipboardHistoryItemImpl(item_id_, clipboard_history_);
 }
 
-gfx::Size ClipboardHistoryItemView::CalculatePreferredSize() const {
+gfx::Size ClipboardHistoryItemView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   const int preferred_width =
       clipboard_history_util::GetPreferredItemViewWidth();
-  return gfx::Size(preferred_width, GetHeightForWidth(preferred_width));
+  return gfx::Size(
+      preferred_width,
+      GetLayoutManager()->GetPreferredHeightForWidth(this, preferred_width));
 }
 
 void ClipboardHistoryItemView::GetAccessibleNodeData(ui::AXNodeData* data) {
diff --git a/ash/clipboard/views/clipboard_history_item_view.h b/ash/clipboard/views/clipboard_history_item_view.h
index ac023ce..d0163bf 100644
--- a/ash/clipboard/views/clipboard_history_item_view.h
+++ b/ash/clipboard/views/clipboard_history_item_view.h
@@ -131,7 +131,8 @@
   class DisplayView;
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void GetAccessibleNodeData(ui::AXNodeData* data) override;
 
   // Initializes the menu item after its construction.
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 3c890a30..994519b8 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -2803,13 +2803,6 @@
              "TilingWindowResize",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Forces the time of day wallpaper to change on an automatic sunset-to-sunrise
-// schedule, regardless of what dark/light mode settings are active.
-// Not used if time of day wallpaper is not enabled.
-BASE_FEATURE(kTimeOfDayWallpaperForcedAutoSchedule,
-             "TimeOfDayWallpaperForcedAutoSchedule",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enables retrieving time of day screen saver assets from DLC, rather than from
 // rootfs.
 BASE_FEATURE(kTimeOfDayDlc, "TimeOfDayDlc", base::FEATURE_DISABLED_BY_DEFAULT);
@@ -4458,11 +4451,6 @@
   return base::FeatureList::IsEnabled(kFeatureManagementTimeOfDayWallpaper);
 }
 
-bool IsTimeOfDayWallpaperForcedAutoScheduleEnabled() {
-  return IsTimeOfDayWallpaperEnabled() &&
-         base::FeatureList::IsEnabled(kTimeOfDayWallpaperForcedAutoSchedule);
-}
-
 bool IsTimeOfDayDlcEnabled() {
   return IsTimeOfDayScreenSaverEnabled() &&
          base::FeatureList::IsEnabled(kTimeOfDayDlc);
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 61f6655..046c6aa 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -857,8 +857,6 @@
 BASE_DECLARE_FEATURE(kTetheringExperimentalFunctionality);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kTerminalDev);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kTilingWindowResize);
-COMPONENT_EXPORT(ASH_CONSTANTS)
-BASE_DECLARE_FEATURE(kTimeOfDayWallpaperForcedAutoSchedule);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kTimeOfDayDlc);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kTouchVirtualKeyboardPolicyListenPrefsAtLogin);
@@ -1309,7 +1307,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsTimeOfDayScreenSaverEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsTimeOfDayWallpaperEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
-bool IsTimeOfDayWallpaperForcedAutoScheduleEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsTimeOfDayDlcEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsTabClusterUIEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsTouchpadInDiagnosticsAppEnabled();
diff --git a/ash/glanceables/tasks/glanceables_task_view.cc b/ash/glanceables/tasks/glanceables_task_view.cc
index 7ba1b78e..2db659b 100644
--- a/ash/glanceables/tasks/glanceables_task_view.cc
+++ b/ash/glanceables/tasks/glanceables_task_view.cc
@@ -145,7 +145,8 @@
   ~TaskViewTextField() override = default;
 
   // SystemTextfield:
-  gfx::Size CalculatePreferredSize() const override {
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override {
     return gfx::Size(0, TypographyProvider::Get()->ResolveLineHeight(
                             TypographyToken::kCrosButton2));
   }
diff --git a/ash/login/ui/login_password_view.cc b/ash/login/ui/login_password_view.cc
index e92ae9a..7e0b2ce 100644
--- a/ash/login/ui/login_password_view.cc
+++ b/ash/login/ui/login_password_view.cc
@@ -227,7 +227,8 @@
 
   // This is useful when the display password button is not shown. In such a
   // case, the login text field needs to define its size.
-  gfx::Size CalculatePreferredSize() const override {
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override {
     return gfx::Size(kPasswordTotalWidthDp, kIconSizeDp);
   }
 
diff --git a/ash/picker/metrics/picker_session_metrics.cc b/ash/picker/metrics/picker_session_metrics.cc
index a1004766..b74bac9 100644
--- a/ash/picker/metrics/picker_session_metrics.cc
+++ b/ash/picker/metrics/picker_session_metrics.cc
@@ -4,8 +4,12 @@
 
 #include "ash/picker/metrics/picker_session_metrics.h"
 
+#include "ash/public/cpp/picker/picker_category.h"
+#include "ash/public/cpp/picker/picker_search_result.h"
+#include "base/functional/overloaded.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/notreached.h"
 #include "components/metrics/structured/structured_events.h"
 #include "components/metrics/structured/structured_metrics_client.h"
 #include "ui/base/ime/text_input_client.h"
@@ -69,25 +73,209 @@
   return 0;
 }
 
+cros_events::PickerSessionOutcome ConvertToCrosEventSessionOutcome(
+    PickerSessionMetrics::SessionOutcome outcome) {
+  switch (outcome) {
+    case PickerSessionMetrics::SessionOutcome::kUnknown:
+      return cros_events::PickerSessionOutcome::UNKNOWN;
+    case PickerSessionMetrics::SessionOutcome::kInsertedOrCopied:
+      return cros_events::PickerSessionOutcome::INSERTED_OR_COPIED;
+    case PickerSessionMetrics::SessionOutcome::kAbandoned:
+      return cros_events::PickerSessionOutcome::ABANDONED;
+    case PickerSessionMetrics::SessionOutcome::kRedirected:
+      return cros_events::PickerSessionOutcome::REDIRECTED;
+    case PickerSessionMetrics::SessionOutcome::kFormat:
+      return cros_events::PickerSessionOutcome::FORMAT;
+  }
+}
+
+cros_events::PickerAction ConvertToCrosEventAction(
+    std::optional<PickerCategory> action) {
+  if (!action.has_value()) {
+    return cros_events::PickerAction::UNKNOWN;
+  }
+  switch (*action) {
+    case PickerCategory::kEditorWrite:
+      return cros_events::PickerAction::OPEN_EDITOR_WRITE;
+    case PickerCategory::kEditorRewrite:
+      return cros_events::PickerAction::OPEN_EDITOR_REWRITE;
+    case PickerCategory::kLinks:
+      return cros_events::PickerAction::OPEN_LINKS;
+    case PickerCategory::kExpressions:
+      return cros_events::PickerAction::OPEN_EXPRESSIONS;
+    case PickerCategory::kClipboard:
+      return cros_events::PickerAction::OPEN_CLIPBOARD;
+    case PickerCategory::kDriveFiles:
+      return cros_events::PickerAction::OPEN_DRIVE_FILES;
+    case PickerCategory::kLocalFiles:
+      return cros_events::PickerAction::OPEN_LOCAL_FILES;
+    case PickerCategory::kDatesTimes:
+      return cros_events::PickerAction::OPEN_DATES_TIMES;
+    case PickerCategory::kUnitsMaths:
+      return cros_events::PickerAction::OPEN_UNITS_MATHS;
+    case PickerCategory::kUpperCase:
+      return cros_events::PickerAction::TRANSFORM_UPPER_CASE;
+    case PickerCategory::kLowerCase:
+      return cros_events::PickerAction::TRANSFORM_LOWER_CASE;
+    case PickerCategory::kSentenceCase:
+      return cros_events::PickerAction::TRANSFORM_SENTENCE_CASE;
+    case PickerCategory::kTitleCase:
+      return cros_events::PickerAction::TRANSFORM_TITLE_CASE;
+    case PickerCategory::kCapsOn:
+      return cros_events::PickerAction::CAPS_ON;
+    case PickerCategory::kCapsOff:
+      return cros_events::PickerAction::CAPS_OFF;
+  }
+}
+
+cros_events::PickerResultSource GetResultSource(
+    std::optional<PickerSearchResult> result) {
+  if (!result.has_value()) {
+    return cros_events::PickerResultSource::UNKNOWN;
+  }
+  using ReturnType = cros_events::PickerResultSource;
+  return std::visit(
+      base::Overloaded{
+          [](const PickerSearchResult::TextData& data) {
+            switch (data.source) {
+              case PickerSearchResult::TextData::Source::kUnknown:
+                return cros_events::PickerResultSource::UNKNOWN;
+              case PickerSearchResult::TextData::Source::kDate:
+                return cros_events::PickerResultSource::DATES_TIMES;
+              case PickerSearchResult::TextData::Source::kMath:
+                return cros_events::PickerResultSource::UNITS_MATHS;
+              case PickerSearchResult::TextData::Source::kCaseTransform:
+                return cros_events::PickerResultSource::CASE_TRANSFORM;
+              case PickerSearchResult::TextData::Source::kOmnibox:
+                return cros_events::PickerResultSource::OMNIBOX;
+            }
+          },
+          [](const PickerSearchResult::EmojiData& data) {
+            return cros_events::PickerResultSource::EMOJI;
+          },
+          [](const PickerSearchResult::SymbolData& data) {
+            return cros_events::PickerResultSource::EMOJI;
+          },
+          [](const PickerSearchResult::EmoticonData& data) {
+            return cros_events::PickerResultSource::EMOJI;
+          },
+          [](const PickerSearchResult::ClipboardData& data) {
+            return cros_events::PickerResultSource::CLIPBOARD;
+          },
+          [](const PickerSearchResult::GifData& data) {
+            return cros_events::PickerResultSource::TENOR;
+          },
+          [](const PickerSearchResult::BrowsingHistoryData& data) {
+            return cros_events::PickerResultSource::OMNIBOX;
+          },
+          [](const PickerSearchResult::LocalFileData& data) {
+            return cros_events::PickerResultSource::LOCAL_FILES;
+          },
+          [](const PickerSearchResult::DriveFileData& data) {
+            return cros_events::PickerResultSource::DRIVE_FILES;
+          },
+          [](const PickerSearchResult::CategoryData& data) -> ReturnType {
+            NOTREACHED_NORETURN();
+          },
+          [](const PickerSearchResult::SearchRequestData& data) -> ReturnType {
+            NOTREACHED_NORETURN();
+          },
+          [](const PickerSearchResult::EditorData& data) -> ReturnType {
+            NOTREACHED_NORETURN();
+          },
+      },
+      result->data());
+}
+
+cros_events::PickerResultType GetResultType(
+    std::optional<PickerSearchResult> result) {
+  if (!result.has_value()) {
+    return cros_events::PickerResultType::UNKNOWN;
+  }
+  using ReturnType = cros_events::PickerResultType;
+  return std::visit(
+      base::Overloaded{
+          [](const PickerSearchResult::TextData& data) {
+            return cros_events::PickerResultType::TEXT;
+          },
+          [](const PickerSearchResult::EmojiData& data) {
+            return cros_events::PickerResultType::EMOJI;
+          },
+          [](const PickerSearchResult::SymbolData& data) {
+            return cros_events::PickerResultType::SYMBOL;
+          },
+          [](const PickerSearchResult::EmoticonData& data) {
+            return cros_events::PickerResultType::EMOTICON;
+          },
+          [](const PickerSearchResult::ClipboardData& data) {
+            switch (data.display_format) {
+              case PickerSearchResult::ClipboardData::DisplayFormat::kFile:
+                return cros_events::PickerResultType::CLIPBOARD_FILE;
+              case PickerSearchResult::ClipboardData::DisplayFormat::kText:
+                return cros_events::PickerResultType::CLIPBOARD_TEXT;
+              case PickerSearchResult::ClipboardData::DisplayFormat::kImage:
+                return cros_events::PickerResultType::CLIPBOARD_IMAGE;
+              case PickerSearchResult::ClipboardData::DisplayFormat::kHtml:
+                return cros_events::PickerResultType::CLIPBOARD_HTML;
+            }
+          },
+          [](const PickerSearchResult::GifData& data) {
+            return cros_events::PickerResultType::GIF;
+          },
+          [](const PickerSearchResult::BrowsingHistoryData& data) {
+            return cros_events::PickerResultType::LINK;
+          },
+          [](const PickerSearchResult::LocalFileData& data) {
+            return cros_events::PickerResultType::LOCAL_FILE;
+          },
+          [](const PickerSearchResult::DriveFileData& data) {
+            return cros_events::PickerResultType::DRIVE_FILE;
+          },
+          [](const PickerSearchResult::CategoryData& data) -> ReturnType {
+            NOTREACHED_NORETURN();
+          },
+          [](const PickerSearchResult::SearchRequestData& data) -> ReturnType {
+            NOTREACHED_NORETURN();
+          },
+          [](const PickerSearchResult::EditorData& data) -> ReturnType {
+            NOTREACHED_NORETURN();
+          },
+      },
+      result->data());
+}
+
 }  // namespace
 
 PickerSessionMetrics::PickerSessionMetrics() = default;
 
 PickerSessionMetrics::~PickerSessionMetrics() {
-  if (recorded_outcome_) {
-    return;
-  }
-
-  RecordOutcome(SessionOutcome::kUnknown);
+  OnFinishSession();
 }
 
-void PickerSessionMetrics::RecordOutcome(SessionOutcome outcome) {
-  if (recorded_outcome_) {
-    return;
+void PickerSessionMetrics::SetOutcome(SessionOutcome outcome) {
+  if (outcome_ == SessionOutcome::kUnknown) {
+    outcome_ = outcome;
   }
+}
 
-  base::UmaHistogramEnumeration("Ash.Picker.Session.Outcome", outcome);
-  recorded_outcome_ = true;
+void PickerSessionMetrics::SetAction(PickerCategory action) {
+  if (!action_.has_value()) {
+    action_ = action;
+  }
+}
+
+void PickerSessionMetrics::SetInsertedResult(PickerSearchResult inserted_result,
+                                             int index) {
+  if (!inserted_result_.has_value()) {
+    inserted_result_ = inserted_result;
+    result_index_ = index;
+  }
+}
+
+void PickerSessionMetrics::UpdateSearchQuery(std::u16string_view search_query) {
+  int new_length = static_cast<int>(search_query.length());
+  search_query_total_edits_ += abs(new_length - search_query_length_);
+  search_query_length_ = new_length;
 }
 
 void PickerSessionMetrics::OnStartSession(ui::TextInputClient* client) {
@@ -97,4 +285,17 @@
           .SetSelectionLength(GetSelectionLength(client)));
 }
 
+void PickerSessionMetrics::OnFinishSession() {
+  base::UmaHistogramEnumeration("Ash.Picker.Session.Outcome", outcome_);
+  ms::StructuredMetricsClient::Record(
+      cros_events::Picker_FinishSession()
+          .SetOutcome(ConvertToCrosEventSessionOutcome(outcome_))
+          .SetAction(ConvertToCrosEventAction(action_))
+          .SetResultSource(GetResultSource(std::move(inserted_result_)))
+          .SetResultType(GetResultType(std::move(inserted_result_)))
+          .SetTotalEdits(search_query_total_edits_)
+          .SetFinalQuerySize(search_query_length_)
+          .SetResultIndex(result_index_));
+}
+
 }  // namespace ash
diff --git a/ash/picker/metrics/picker_session_metrics.h b/ash/picker/metrics/picker_session_metrics.h
index 06cdbff..1a9a40d 100644
--- a/ash/picker/metrics/picker_session_metrics.h
+++ b/ash/picker/metrics/picker_session_metrics.h
@@ -6,8 +6,11 @@
 #define ASH_PICKER_METRICS_PICKER_SESSION_METRICS_H_
 
 #include <optional>
+#include <string>
 
 #include "ash/ash_export.h"
+#include "ash/public/cpp/picker/picker_category.h"
+#include "ash/public/cpp/picker/picker_search_result.h"
 
 namespace ui {
 class TextInputClient;
@@ -27,20 +30,50 @@
     kInsertedOrCopied = 1,
     // User abandons the session (e.g. by closing the window without inserting).
     kAbandoned = 2,
-    kMaxValue = kAbandoned,
+    // User selects an action to open another window, e.g. the Emoji picker.
+    kRedirected = 3,
+    // User selects an action related to text format.
+    kFormat = 4,
+    kMaxValue = kFormat,
   };
 
   PickerSessionMetrics();
   ~PickerSessionMetrics();
 
-  void RecordOutcome(SessionOutcome outcome);
+  // Sets session outcome. This is expected to be called exactly once during
+  // a session.
+  void SetOutcome(SessionOutcome outcome);
+
+  // Sets user action. This is expected to be called at most once during a
+  // session.
+  // TODO(b/336402739): replace the argument type with some action enum after
+  // refactor.
+  void SetAction(PickerCategory action);
+
+  // Sets the search result which user inserts. This is expected to be called at
+  // most once during a session.
+  void SetInsertedResult(PickerSearchResult inserted_result, int index);
+
+  // Updates the search query to latest and accumulates total edits.
+  void UpdateSearchQuery(std::u16string_view search_query);
 
   // Records CrOS event metrics when a picker session starts.
   void OnStartSession(ui::TextInputClient* client);
 
  private:
-  // Whether the outcome of this session has been recorded.
-  bool recorded_outcome_ = false;
+  // Records CrOS event metrics when a picker session finishes.
+  void OnFinishSession();
+
+  SessionOutcome outcome_ = SessionOutcome::kUnknown;
+
+  // TODO(b/336402739): replace the type with some action enum after refactor.
+  std::optional<PickerCategory> action_;
+
+  std::optional<PickerSearchResult> inserted_result_;
+  int result_index_ = -1;
+
+  int search_query_total_edits_ = 0;
+  int search_query_length_ = 0;
 };
 
 }  // namespace ash
diff --git a/ash/picker/metrics/picker_session_metrics_unittest.cc b/ash/picker/metrics/picker_session_metrics_unittest.cc
index 08c93b88..f02d228 100644
--- a/ash/picker/metrics/picker_session_metrics_unittest.cc
+++ b/ash/picker/metrics/picker_session_metrics_unittest.cc
@@ -30,23 +30,23 @@
       structured_metrics_recorder_;
 };
 
-TEST_F(PickerSessionMetricsTest, RecordsSessionOutcomeOnce) {
+TEST_F(PickerSessionMetricsTest, RecordsUmaSessionOutcomeOnce) {
   base::HistogramTester histogram;
-  PickerSessionMetrics metrics;
+  {
+    PickerSessionMetrics metrics;
 
-  metrics.RecordOutcome(
-      PickerSessionMetrics::SessionOutcome::kInsertedOrCopied);
-  metrics.RecordOutcome(
-      PickerSessionMetrics::SessionOutcome::kInsertedOrCopied);
-  metrics.RecordOutcome(PickerSessionMetrics::SessionOutcome::kAbandoned);
-  metrics.RecordOutcome(PickerSessionMetrics::SessionOutcome::kUnknown);
+    metrics.SetOutcome(PickerSessionMetrics::SessionOutcome::kInsertedOrCopied);
+    metrics.SetOutcome(PickerSessionMetrics::SessionOutcome::kInsertedOrCopied);
+    metrics.SetOutcome(PickerSessionMetrics::SessionOutcome::kAbandoned);
+    metrics.SetOutcome(PickerSessionMetrics::SessionOutcome::kUnknown);
+  }
 
   histogram.ExpectUniqueSample(
       "Ash.Picker.Session.Outcome",
       PickerSessionMetrics::SessionOutcome::kInsertedOrCopied, 1);
 }
 
-TEST_F(PickerSessionMetricsTest, RecordsUnknownOutcomeOnDestruction) {
+TEST_F(PickerSessionMetricsTest, RecordsUmaUnknownOutcomeOnDestruction) {
   base::HistogramTester histogram;
   { PickerSessionMetrics metrics; }
 
@@ -55,19 +55,6 @@
                                1);
 }
 
-TEST_F(PickerSessionMetricsTest,
-       DoesNotRecordUnknownOutcomeOnDestructionIfOutcomeWasRecorded) {
-  base::HistogramTester histogram;
-  {
-    PickerSessionMetrics metrics;
-    metrics.RecordOutcome(PickerSessionMetrics::SessionOutcome::kAbandoned);
-  }
-
-  histogram.ExpectUniqueSample("Ash.Picker.Session.Outcome",
-                               PickerSessionMetrics::SessionOutcome::kAbandoned,
-                               1);
-}
-
 auto ContainsEvent(const metrics::structured::Event& event) {
   return Contains(AllOf(
       Property("event name", &metrics::structured::Event::event_name,
@@ -118,5 +105,70 @@
               ContainsEvent(expected_event));
 }
 
+TEST_F(PickerSessionMetricsTest, RecordsDefaultFinishSessionEvent) {
+  { PickerSessionMetrics metrics; }
+
+  cros_events::Picker_FinishSession expected_event;
+  expected_event.SetOutcome(cros_events::PickerSessionOutcome::UNKNOWN)
+      .SetAction(cros_events::PickerAction::UNKNOWN)
+      .SetResultSource(cros_events::PickerResultSource::UNKNOWN)
+      .SetResultType(cros_events::PickerResultType::UNKNOWN)
+      .SetTotalEdits(0)
+      .SetFinalQuerySize(0)
+      .SetResultIndex(-1);
+  EXPECT_THAT(structured_metrics_recorder_.GetEvents(),
+              ContainsEvent(expected_event));
+}
+
+TEST_F(PickerSessionMetricsTest, RecordsFinishSessionEventForInsert) {
+  {
+    PickerSessionMetrics metrics;
+    metrics.SetAction(PickerCategory::kDatesTimes);
+    metrics.UpdateSearchQuery(u"abc");
+    metrics.UpdateSearchQuery(u"abcdef");
+    metrics.UpdateSearchQuery(u"abcde");
+    metrics.SetInsertedResult(
+        PickerSearchResult::Text(u"primary",
+                                 PickerSearchResult::TextData::Source::kDate),
+        3);
+    metrics.SetOutcome(PickerSessionMetrics::SessionOutcome::kInsertedOrCopied);
+  }
+
+  cros_events::Picker_FinishSession expected_event;
+  expected_event
+      .SetOutcome(cros_events::PickerSessionOutcome::INSERTED_OR_COPIED)
+      .SetAction(cros_events::PickerAction::OPEN_DATES_TIMES)
+      .SetResultSource(cros_events::PickerResultSource::DATES_TIMES)
+      .SetResultType(cros_events::PickerResultType::TEXT)
+      .SetTotalEdits(7)
+      .SetFinalQuerySize(5)
+      .SetResultIndex(3);
+  EXPECT_THAT(structured_metrics_recorder_.GetEvents(),
+              ContainsEvent(expected_event));
+}
+
+TEST_F(PickerSessionMetricsTest, RecordsFinishSessionEventForCaseTransform) {
+  {
+    PickerSessionMetrics metrics;
+    metrics.SetAction(PickerCategory::kUpperCase);
+    metrics.SetInsertedResult(
+        PickerSearchResult::Text(
+            u"primary", PickerSearchResult::TextData::Source::kCaseTransform),
+        0);
+    metrics.SetOutcome(PickerSessionMetrics::SessionOutcome::kFormat);
+  }
+
+  cros_events::Picker_FinishSession expected_event;
+  expected_event.SetOutcome(cros_events::PickerSessionOutcome::FORMAT)
+      .SetAction(cros_events::PickerAction::TRANSFORM_UPPER_CASE)
+      .SetResultSource(cros_events::PickerResultSource::CASE_TRANSFORM)
+      .SetResultType(cros_events::PickerResultType::TEXT)
+      .SetTotalEdits(0)
+      .SetFinalQuerySize(0)
+      .SetResultIndex(0);
+  EXPECT_THAT(structured_metrics_recorder_.GetEvents(),
+              ContainsEvent(expected_event));
+}
+
 }  // namespace
 }  // namespace ash
diff --git a/ash/picker/picker_controller.cc b/ash/picker/picker_controller.cc
index 1cc2bf84..ef43fca 100644
--- a/ash/picker/picker_controller.cc
+++ b/ash/picker/picker_controller.cc
@@ -308,7 +308,7 @@
   CHECK(client_);
 
   if (widget_) {
-    session_metrics_->RecordOutcome(
+    session_metrics_->SetOutcome(
         PickerSessionMetrics::SessionOutcome::kAbandoned);
     widget_->Close();
     model_.reset();
@@ -452,7 +452,7 @@
       },
       GetInsertionContentForResult(result));
 
-  session_metrics_->RecordOutcome(
+  session_metrics_->SetOutcome(
       PickerSessionMetrics::SessionOutcome::kInsertedOrCopied);
 }
 
diff --git a/ash/picker/views/picker_list_item_view.cc b/ash/picker/views/picker_list_item_view.cc
index b3f97aa..12bfd7a3 100644
--- a/ash/picker/views/picker_list_item_view.cc
+++ b/ash/picker/views/picker_list_item_view.cc
@@ -32,7 +32,7 @@
 namespace ash {
 namespace {
 
-constexpr auto kPickerListItemBorderInsets = gfx::Insets::TLBR(8, 16, 8, 8);
+constexpr auto kPickerListItemBorderInsets = gfx::Insets::TLBR(8, 16, 8, 16);
 
 constexpr gfx::Size kLeadingIconSizeDip(20, 20);
 constexpr int kImageDisplayHeight = 72;
@@ -44,12 +44,17 @@
     : PickerItemView(std::move(select_item_callback),
                      FocusIndicatorStyle::kFocusBar) {
   SetLayoutManager(std::make_unique<views::FlexLayout>());
-  auto* item_contents =
-      AddChildView(views::Builder<views::FlexLayoutView>()
-                       .SetOrientation(views::LayoutOrientation::kHorizontal)
-                       .SetCrossAxisAlignment(views::LayoutAlignment::kStart)
-                       .SetCanProcessEventsWithinSubtree(false)
-                       .Build());
+  auto* item_contents = AddChildView(
+      views::Builder<views::FlexLayoutView>()
+          .SetOrientation(views::LayoutOrientation::kHorizontal)
+          .SetCrossAxisAlignment(views::LayoutAlignment::kStart)
+          .SetProperty(
+              views::kFlexBehaviorKey,
+              views::FlexSpecification(views::LayoutOrientation::kHorizontal,
+                                       views::MinimumFlexSizeRule::kScaleToZero,
+                                       views::MaximumFlexSizeRule::kUnbounded))
+          .SetCanProcessEventsWithinSubtree(false)
+          .Build());
 
   leading_container_ = item_contents->AddChildView(
       views::Builder<views::FlexLayoutView>()
@@ -60,11 +65,18 @@
   auto* main_container = item_contents->AddChildView(
       views::Builder<views::FlexLayoutView>()
           .SetOrientation(views::LayoutOrientation::kVertical)
+          .SetProperty(views::kFlexBehaviorKey,
+                       views::FlexSpecification(
+                           views::LayoutOrientation::kVertical,
+                           views::MinimumFlexSizeRule::kScaleToZero,
+                           views::MaximumFlexSizeRule::kUnbounded,
+                           /*adjust_height_for_width=*/false,
+                           views::MinimumFlexSizeRule::kScaleToZero))
           .Build());
-  primary_container_ =
-      main_container->AddChildView(std::make_unique<views::FlexLayoutView>());
-  secondary_container_ =
-      main_container->AddChildView(std::make_unique<views::FlexLayoutView>());
+  primary_container_ = main_container->AddChildView(
+      views::Builder<views::View>().SetUseDefaultFillLayout(true).Build());
+  secondary_container_ = main_container->AddChildView(
+      views::Builder<views::View>().SetUseDefaultFillLayout(true).Build());
 
   SetBorder(views::CreateEmptyBorder(kPickerListItemBorderInsets));
   SetProperty(views::kElementIdentifierKey,
@@ -75,9 +87,11 @@
 
 void PickerListItemView::SetPrimaryText(const std::u16string& primary_text) {
   primary_container_->RemoveAllChildViews();
-  primary_container_->AddChildView(
+  views::Label* label = primary_container_->AddChildView(
       bubble_utils::CreateLabel(TypographyToken::kCrosBody2, primary_text,
                                 cros_tokens::kCrosSysOnSurface));
+  label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
+  label->SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL);
   SetAccessibleName(primary_text);
 }
 
@@ -116,9 +130,12 @@
   if (secondary_text.empty()) {
     return;
   }
-  secondary_container_->AddChildView(bubble_utils::CreateLabel(
-      TypographyToken::kCrosAnnotation2, secondary_text,
-      cros_tokens::kCrosSysOnSurfaceVariant));
+  views::Label* label =
+      secondary_container_->AddChildView(bubble_utils::CreateLabel(
+          TypographyToken::kCrosAnnotation2, secondary_text,
+          cros_tokens::kCrosSysOnSurfaceVariant));
+  label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
+  label->SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL);
 }
 
 std::u16string PickerListItemView::GetPrimaryTextForTesting() const {
diff --git a/ash/public/cpp/rounded_image_view.cc b/ash/public/cpp/rounded_image_view.cc
index 78f0b427..930a167 100644
--- a/ash/public/cpp/rounded_image_view.cc
+++ b/ash/public/cpp/rounded_image_view.cc
@@ -62,7 +62,8 @@
   SetCornerRadii(corner_radius, corner_radius, corner_radius, corner_radius);
 }
 
-gfx::Size RoundedImageView::CalculatePreferredSize() const {
+gfx::Size RoundedImageView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   return gfx::Size(GetImageSize().width() + GetInsets().width(),
                    GetImageSize().height() + GetInsets().height());
 }
diff --git a/ash/public/cpp/rounded_image_view.h b/ash/public/cpp/rounded_image_view.h
index e436a621..85784f26 100644
--- a/ash/public/cpp/rounded_image_view.h
+++ b/ash/public/cpp/rounded_image_view.h
@@ -53,7 +53,8 @@
   void SetCornerRadius(int corner_radius);
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void OnPaint(gfx::Canvas* canvas) override;
 
   const gfx::ImageSkia& original_image() const { return original_image_; }
diff --git a/ash/resources/vector_icons/picker_calendar.icon b/ash/resources/vector_icons/picker_calendar.icon
index d73a1f8d..57e304e8 100644
--- a/ash/resources/vector_icons/picker_calendar.icon
+++ b/ash/resources/vector_icons/picker_calendar.icon
@@ -3,42 +3,103 @@
 // found in the LICENSE file.
 
 CANVAS_DIMENSIONS, 20,
+FILL_RULE_NONZERO,
 MOVE_TO, 4.56f, 18.17f,
-CUBIC_TO, 4.08f, 18.17f, 3.67f, 18, 3.33f, 17.67f,
-CUBIC_TO, 3, 17.32f, 2.83f, 16.91f, 2.83f, 16.44f,
+R_CUBIC_TO, -0.49f, 0, -0.9f, -0.17f, -1.23f, -0.5f,
+R_ARC_TO, 1.72f, 1.72f, 0, 0, 1, -0.5f, -1.23f,
 V_LINE_TO, 5.56f,
-CUBIC_TO, 2.83f, 5.09f, 3, 4.69f, 3.33f, 4.35f,
-CUBIC_TO, 3.67f, 4.01f, 4.08f, 3.83f, 4.56f, 3.83f,
+R_CUBIC_TO, 0, -0.47f, 0.17f, -0.87f, 0.5f, -1.21f,
+R_CUBIC_TO, 0.33f, -0.35f, 0.74f, -0.52f, 1.23f, -0.52f,
 H_LINE_TO, 6,
-V_LINE_TO, 1.83f,
-H_LINE_TO, 7.63f,
-V_LINE_TO, 3.83f,
-H_LINE_TO, 12.38f,
-V_LINE_TO, 1.83f,
+R_V_LINE_TO, -2,
+R_H_LINE_TO, 1.63f,
+R_V_LINE_TO, 2,
+R_H_LINE_TO, 4.75f,
+R_V_LINE_TO, -2,
 H_LINE_TO, 14,
-V_LINE_TO, 3.83f,
-H_LINE_TO, 15.44f,
-CUBIC_TO, 15.92f, 3.83f, 16.33f, 4.01f, 16.67f, 4.35f,
-CUBIC_TO, 17, 4.69f, 17.17f, 5.09f, 17.17f, 5.56f,
-V_LINE_TO, 16.44f,
-CUBIC_TO, 17.17f, 16.91f, 17, 17.32f, 16.67f, 17.67f,
-CUBIC_TO, 16.33f, 18, 15.92f, 18.17f, 15.44f, 18.17f,
+R_V_LINE_TO, 2,
+R_H_LINE_TO, 1.44f,
+R_CUBIC_TO, 0.49f, 0, 0.9f, 0.17f, 1.23f, 0.52f,
+R_CUBIC_TO, 0.33f, 0.33f, 0.5f, 0.74f, 0.5f, 1.21f,
+R_V_LINE_TO, 10.88f,
+R_CUBIC_TO, 0, 0.47f, -0.17f, 0.88f, -0.5f, 1.23f,
+R_CUBIC_TO, -0.33f, 0.33f, -0.74f, 0.5f, -1.23f, 0.5f,
 H_LINE_TO, 4.56f,
 CLOSE,
-MOVE_TO, 4.56f, 16.44f,
-H_LINE_TO, 15.44f,
+R_MOVE_TO, 0, -1.73f,
+R_H_LINE_TO, 10.88f,
 V_LINE_TO, 9,
 H_LINE_TO, 4.56f,
-V_LINE_TO, 16.44f,
+R_V_LINE_TO, 7.44f,
 CLOSE,
-MOVE_TO, 4.56f, 7.5f,
-H_LINE_TO, 15.44f,
+R_MOVE_TO, 0, -8.94f,
+R_H_LINE_TO, 10.88f,
 V_LINE_TO, 5.56f,
 H_LINE_TO, 4.56f,
 V_LINE_TO, 7.5f,
 CLOSE,
-MOVE_TO, 4.56f, 7.5f,
+R_MOVE_TO, 0, 0,
 V_LINE_TO, 5.56f,
 V_LINE_TO, 7.5f,
 CLOSE,
+MOVE_TO, 10, 12.04f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.56f, -0.23f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.23f, -0.56f,
+R_CUBIC_TO, 0, -0.22f, 0.08f, -0.41f, 0.23f, -0.56f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, 0.56f, -0.23f,
+R_CUBIC_TO, 0.22f, 0, 0.41f, 0.08f, 0.56f, 0.23f,
+R_CUBIC_TO, 0.15f, 0.15f, 0.23f, 0.34f, 0.23f, 0.56f,
+R_CUBIC_TO, 0, 0.22f, -0.08f, 0.41f, -0.23f, 0.56f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.56f, 0.23f,
+CLOSE,
+R_MOVE_TO, -3.25f, 0,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.56f, -0.23f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.23f, -0.56f,
+R_CUBIC_TO, 0, -0.22f, 0.08f, -0.41f, 0.23f, -0.56f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, 0.56f, -0.23f,
+R_CUBIC_TO, 0.22f, 0, 0.41f, 0.08f, 0.56f, 0.23f,
+R_CUBIC_TO, 0.15f, 0.15f, 0.23f, 0.34f, 0.23f, 0.56f,
+R_CUBIC_TO, 0, 0.22f, -0.08f, 0.41f, -0.23f, 0.56f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.56f, 0.23f,
+CLOSE,
+R_MOVE_TO, 6.5f, 0,
+R_ARC_TO, 0.81f, 0.81f, 0, 0, 1, -0.56f, -0.23f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.23f, -0.56f,
+R_CUBIC_TO, 0, -0.22f, 0.08f, -0.41f, 0.23f, -0.56f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, 0.56f, -0.23f,
+R_CUBIC_TO, 0.22f, 0, 0.41f, 0.08f, 0.56f, 0.23f,
+R_CUBIC_TO, 0.15f, 0.15f, 0.23f, 0.34f, 0.23f, 0.56f,
+R_CUBIC_TO, 0, 0.22f, -0.08f, 0.41f, -0.23f, 0.56f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.56f, 0.23f,
+CLOSE,
+R_MOVE_TO, -3.25f, 3,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.56f, -0.23f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.23f, -0.56f,
+R_CUBIC_TO, 0, -0.22f, 0.08f, -0.41f, 0.23f, -0.56f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, 0.56f, -0.23f,
+R_CUBIC_TO, 0.22f, 0, 0.41f, 0.08f, 0.56f, 0.23f,
+R_CUBIC_TO, 0.15f, 0.15f, 0.23f, 0.34f, 0.23f, 0.56f,
+R_ARC_TO, 0.81f, 0.81f, 0, 0, 1, -0.23f, 0.56f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.56f, 0.23f,
+CLOSE,
+R_MOVE_TO, -3.25f, 0,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.56f, -0.23f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.23f, -0.56f,
+R_CUBIC_TO, 0, -0.22f, 0.08f, -0.41f, 0.23f, -0.56f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, 0.56f, -0.23f,
+R_CUBIC_TO, 0.22f, 0, 0.41f, 0.08f, 0.56f, 0.23f,
+R_CUBIC_TO, 0.15f, 0.15f, 0.23f, 0.34f, 0.23f, 0.56f,
+R_ARC_TO, 0.81f, 0.81f, 0, 0, 1, -0.23f, 0.56f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.56f, 0.23f,
+CLOSE,
+R_MOVE_TO, 6.5f, 0,
+R_ARC_TO, 0.81f, 0.81f, 0, 0, 1, -0.56f, -0.23f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.23f, -0.56f,
+R_CUBIC_TO, 0, -0.22f, 0.08f, -0.41f, 0.23f, -0.56f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, 0.56f, -0.23f,
+R_CUBIC_TO, 0.22f, 0, 0.41f, 0.08f, 0.56f, 0.23f,
+R_CUBIC_TO, 0.15f, 0.15f, 0.23f, 0.34f, 0.23f, 0.56f,
+R_ARC_TO, 0.81f, 0.81f, 0, 0, 1, -0.23f, 0.56f,
+R_ARC_TO, 0.77f, 0.77f, 0, 0, 1, -0.56f, 0.23f,
+CLOSE,
 NEW_PATH
diff --git a/ash/search_box/search_box_view_base.cc b/ash/search_box/search_box_view_base.cc
index 54f7efdd..f0af52e 100644
--- a/ash/search_box/search_box_view_base.cc
+++ b/ash/search_box/search_box_view_base.cc
@@ -213,7 +213,8 @@
   ~SearchBoxTextfield() override = default;
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override {
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override {
     // Overridden so the BoxLayoutView 'text_container_' can properly layout
     // the search box and ghost text.
     const std::u16string& text = GetText();
@@ -645,11 +646,16 @@
   return true;
 }
 
-gfx::Size SearchBoxViewBase::CalculatePreferredSize() const {
-  const int iph_height =
-      iph_view_tracker_.view()
-          ? iph_view_tracker_.view()->GetPreferredSize().height()
-          : 0;
+gfx::Size SearchBoxViewBase::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
+  views::SizeBounds content_available_size(available_size);
+  gfx::Insets insets = GetInsets();
+  content_available_size.Enlarge(-insets.width(), -insets.height());
+  const int iph_height = iph_view_tracker_.view()
+                             ? iph_view_tracker_.view()
+                                   ->GetPreferredSize(content_available_size)
+                                   .height()
+                             : 0;
   return gfx::Size(kSearchBoxPreferredWidth,
                    kSearchBoxPreferredHeight + iph_height);
 }
diff --git a/ash/search_box/search_box_view_base.h b/ash/search_box/search_box_view_base.h
index 87e0fc4..d1bb72ad 100644
--- a/ash/search_box/search_box_view_base.h
+++ b/ash/search_box/search_box_view_base.h
@@ -126,7 +126,8 @@
   bool OnTextfieldEvent(ui::EventType type);
 
   // Overridden from views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void OnGestureEvent(ui::GestureEvent* event) override;
   void OnMouseEvent(ui::MouseEvent* event) override;
   void OnThemeChanged() override;
diff --git a/ash/shelf/home_button.cc b/ash/shelf/home_button.cc
index 1994d54..54f5c01 100644
--- a/ash/shelf/home_button.cc
+++ b/ash/shelf/home_button.cc
@@ -391,9 +391,10 @@
   ShelfConfig::Get()->RemoveObserver(this);
 }
 
-gfx::Size HomeButton::CalculatePreferredSize() const {
+gfx::Size HomeButton::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   const gfx::Size control_button_size =
-      ShelfControlButton::CalculatePreferredSize();
+      ShelfControlButton::CalculatePreferredSize(available_size);
 
   // Take the preferred size of the expandable container into consideration when
   // it is visible. Note that the button width is already included in the label
@@ -412,7 +413,7 @@
   LayoutSuperclass<ShelfControlButton>(this);
 
   button_image_view_->SetBoundsRect(
-      gfx::Rect(ShelfControlButton::CalculatePreferredSize()));
+      gfx::Rect(ShelfControlButton::CalculatePreferredSize({})));
 
   if (expandable_container_) {
     if (shelf_->IsHorizontalAlignment()) {
@@ -428,13 +429,13 @@
         expandable_container_->SetBorder(
             views::CreateEmptyBorder(gfx::Insets::TLBR(
                 0,
-                ShelfControlButton::CalculatePreferredSize().width() +
+                ShelfControlButton::CalculatePreferredSize({}).width() +
                     kQuickAppStartMargin,
                 0, 0)));
       } else {
         expandable_container_->SetBorder(
             views::CreateEmptyBorder(gfx::Insets::TLBR(
-                ShelfControlButton::CalculatePreferredSize().height() +
+                ShelfControlButton::CalculatePreferredSize({}).height() +
                     kQuickAppStartMargin,
                 0, 0, 0)));
       }
@@ -675,7 +676,7 @@
 
 void HomeButton::CreateExpandableContainer() {
   const int home_button_width =
-      ShelfControlButton::CalculatePreferredSize().width();
+      ShelfControlButton::CalculatePreferredSize({}).width();
 
   // Add container at 0 index so it's stacked under other views (e.g.
   // `button_image_view_`, and focus ring).
@@ -700,7 +701,7 @@
 
   CreateExpandableContainer();
   expandable_container_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
-      0, ShelfControlButton::CalculatePreferredSize().width(), 0, 16)));
+      0, ShelfControlButton::CalculatePreferredSize({}).width(), 0, 16)));
 
   // Create a view to clip the `nudge_label_` to the area right of the home
   // button during nudge label animation.
@@ -734,14 +735,14 @@
 void HomeButton::CreateQuickAppButton() {
   CreateExpandableContainer();
   if (shelf_->IsHorizontalAlignment()) {
-    expandable_container_->SetBorder(views::CreateEmptyBorder(
-        gfx::Insets::TLBR(0,
-                          ShelfControlButton::CalculatePreferredSize().width() +
-                              kQuickAppStartMargin,
-                          0, 0)));
+    expandable_container_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
+        0,
+        ShelfControlButton::CalculatePreferredSize({}).width() +
+            kQuickAppStartMargin,
+        0, 0)));
   } else {
     expandable_container_->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
-        ShelfControlButton::CalculatePreferredSize().height() +
+        ShelfControlButton::CalculatePreferredSize({}).height() +
             kQuickAppStartMargin,
         0, 0, 0)));
   }
@@ -752,7 +753,8 @@
   quick_app_button_->SetAccessibleName(
       AppListModelProvider::Get()->quick_app_access_model()->GetAppName());
 
-  const int control_size = ShelfControlButton::CalculatePreferredSize().width();
+  const int control_size =
+      ShelfControlButton::CalculatePreferredSize({}).width();
 
   const gfx::Size preferred_size = gfx::Size(control_size, control_size);
 
@@ -787,7 +789,8 @@
   nudge_ripple_layer_.Reset(std::make_unique<ui::Layer>());
   ui::Layer* ripple_layer = nudge_ripple_layer_.layer();
 
-  float ripple_diameter = ShelfControlButton::CalculatePreferredSize().width();
+  float ripple_diameter =
+      ShelfControlButton::CalculatePreferredSize({}).width();
   auto* color_provider = GetColorProvider();
   DCHECK(color_provider);
   ripple_layer_delegate_ = std::make_unique<views::CircleLayerDelegate>(
@@ -1050,7 +1053,8 @@
     return;
   }
 
-  const int control_size = ShelfControlButton::CalculatePreferredSize().width();
+  const int control_size =
+      ShelfControlButton::CalculatePreferredSize({}).width();
   quick_app_button_->SetImageModel(
       views::Button::STATE_NORMAL,
       ui::ImageModel::FromImageSkia(
@@ -1127,7 +1131,7 @@
 
 gfx::Transform HomeButton::GetTransformForContainerChildBehindHomeButton() {
   const int home_button_width =
-      ShelfControlButton::CalculatePreferredSize().width();
+      ShelfControlButton::CalculatePreferredSize({}).width();
 
   const int container_visible_width =
       expandable_container_->width() - home_button_width;
@@ -1146,7 +1150,7 @@
 
 gfx::Rect HomeButton::GetExpandableContainerClipRectToHomeButton() {
   const int home_button_width =
-      ShelfControlButton::CalculatePreferredSize().width();
+      ShelfControlButton::CalculatePreferredSize({}).width();
   const int container_visible_width =
       expandable_container_->width() - home_button_width;
 
diff --git a/ash/shelf/home_button.h b/ash/shelf/home_button.h
index 4039050..e696225 100644
--- a/ash/shelf/home_button.h
+++ b/ash/shelf/home_button.h
@@ -96,7 +96,8 @@
   ~HomeButton() override;
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void Layout(PassKey) override;
 
   // views::Button:
diff --git a/ash/shelf/kiosk_app_instruction_bubble.cc b/ash/shelf/kiosk_app_instruction_bubble.cc
index 32d73bf..4700d6a 100644
--- a/ash/shelf/kiosk_app_instruction_bubble.cc
+++ b/ash/shelf/kiosk_app_instruction_bubble.cc
@@ -98,11 +98,13 @@
   title_->SetEnabledColor(label_color);
 }
 
-gfx::Size KioskAppInstructionBubble::CalculatePreferredSize() const {
+gfx::Size KioskAppInstructionBubble::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   const int bubble_margin = views::LayoutProvider::Get()->GetDistanceMetric(
       views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_CONTROL);
   const int width = kBubblePreferredInternalWidth + 2 * bubble_margin;
-  return gfx::Size(width, GetHeightForWidth(width));
+  return gfx::Size(width,
+                   GetLayoutManager()->GetPreferredHeightForWidth(this, width));
 }
 
 BEGIN_METADATA(KioskAppInstructionBubble)
diff --git a/ash/shelf/kiosk_app_instruction_bubble.h b/ash/shelf/kiosk_app_instruction_bubble.h
index 997e3af..de0b5db 100644
--- a/ash/shelf/kiosk_app_instruction_bubble.h
+++ b/ash/shelf/kiosk_app_instruction_bubble.h
@@ -36,7 +36,8 @@
  private:
   // views::View:
   void OnThemeChanged() override;
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
 
   raw_ptr<views::Label> title_ = nullptr;
 };
diff --git a/ash/shelf/scrollable_shelf_view.cc b/ash/shelf/scrollable_shelf_view.cc
index 9a4260c..04675ba 100644
--- a/ash/shelf/scrollable_shelf_view.cc
+++ b/ash/shelf/scrollable_shelf_view.cc
@@ -223,7 +223,7 @@
   // CalculateIdealSize. Because ShelfView::CalculatePreferredSize relies on the
   // bounds of app icon. Meanwhile, the icon's bounds may be updated by
   // animation.
-  const gfx::Rect ideal_bounds = gfx::Rect(CalculatePreferredSize());
+  const gfx::Rect ideal_bounds = gfx::Rect(CalculatePreferredSize({}));
 
   const gfx::Rect local_bounds = GetLocalBounds();
   gfx::Rect shelf_view_bounds =
@@ -562,7 +562,7 @@
       available_local_bounds.width(), available_local_bounds.height());
 
   int gap = CanFitAllAppsWithoutScrolling(available_local_bounds.size(),
-                                          CalculatePreferredSize())
+                                          CalculatePreferredSize({}))
                 ? available_size_for_app_icons - icons_size
                 : 0;  // overflow
 
@@ -712,8 +712,9 @@
   return shelf_view_->shelf();
 }
 
-gfx::Size ScrollableShelfView::CalculatePreferredSize() const {
-  return shelf_container_view_->GetPreferredSize();
+gfx::Size ScrollableShelfView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
+  return shelf_container_view_->GetPreferredSize(available_size);
 }
 
 void ScrollableShelfView::Layout(PassKey) {
diff --git a/ash/shelf/scrollable_shelf_view.h b/ash/shelf/scrollable_shelf_view.h
index aa6025ffa..8aec710b 100644
--- a/ash/shelf/scrollable_shelf_view.h
+++ b/ash/shelf/scrollable_shelf_view.h
@@ -234,7 +234,8 @@
   const Shelf* GetShelf() const;
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void Layout(PassKey) override;
   void ChildPreferredSizeChanged(views::View* child) override;
   void OnScrollEvent(ui::ScrollEvent* event) override;
diff --git a/ash/shelf/shelf_container_view.cc b/ash/shelf/shelf_container_view.cc
index a83cb40..5161c06 100644
--- a/ash/shelf/shelf_container_view.cc
+++ b/ash/shelf/shelf_container_view.cc
@@ -33,7 +33,8 @@
              : gfx::Size(button_size, button_strip_size);
 }
 
-gfx::Size ShelfContainerView::CalculatePreferredSize() const {
+gfx::Size ShelfContainerView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   return CalculateIdealSize(shelf_view_->GetButtonSize());
 }
 
diff --git a/ash/shelf/shelf_container_view.h b/ash/shelf/shelf_container_view.h
index a7b8cbd..5ef0c63 100644
--- a/ash/shelf/shelf_container_view.h
+++ b/ash/shelf/shelf_container_view.h
@@ -37,7 +37,8 @@
   virtual void TranslateShelfView(const gfx::Vector2dF& offset);
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void ChildPreferredSizeChanged(views::View* child) override;
 
  protected:
diff --git a/ash/shelf/shelf_control_button.cc b/ash/shelf/shelf_control_button.cc
index cc49708..c55e7618 100644
--- a/ash/shelf/shelf_control_button.cc
+++ b/ash/shelf/shelf_control_button.cc
@@ -86,7 +86,8 @@
   return gfx::RectF(GetLocalBounds()).CenterPoint();
 }
 
-gfx::Size ShelfControlButton::CalculatePreferredSize() const {
+gfx::Size ShelfControlButton::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   return gfx::Size(ShelfConfig::Get()->control_size(),
                    ShelfConfig::Get()->control_size());
 }
diff --git a/ash/shelf/shelf_control_button.h b/ash/shelf/shelf_control_button.h
index dfe54214..2418e8f 100644
--- a/ash/shelf/shelf_control_button.h
+++ b/ash/shelf/shelf_control_button.h
@@ -35,7 +35,8 @@
   void set_ideal_bounds(const gfx::Rect& bounds) { ideal_bounds_ = bounds; }
 
   // ShelfButton:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
  private:
diff --git a/ash/shelf/shelf_navigation_widget.cc b/ash/shelf/shelf_navigation_widget.cc
index 1b17c0ca..51f3096 100644
--- a/ash/shelf/shelf_navigation_widget.cc
+++ b/ash/shelf/shelf_navigation_widget.cc
@@ -712,8 +712,8 @@
     controls_space +=
         home_button_shown
             ? (shelf_->IsHorizontalAlignment()
-                   ? GetHomeButton()->CalculatePreferredSize().width()
-                   : GetHomeButton()->CalculatePreferredSize().height())
+                   ? GetHomeButton()->CalculatePreferredSize({}).width()
+                   : GetHomeButton()->CalculatePreferredSize({}).height())
             : 0;
     controls_space += back_button_shown ? control_size : 0;
     controls_space +=
diff --git a/ash/shelf/shelf_tooltip_bubble.cc b/ash/shelf/shelf_tooltip_bubble.cc
index b3707f3..50c6f174 100644
--- a/ash/shelf/shelf_tooltip_bubble.cc
+++ b/ash/shelf/shelf_tooltip_bubble.cc
@@ -81,8 +81,10 @@
   GetBubbleFrameView()->SetBackgroundColor(color());
 }
 
-gfx::Size ShelfTooltipBubble::CalculatePreferredSize() const {
-  const gfx::Size size = BubbleDialogDelegateView::CalculatePreferredSize();
+gfx::Size ShelfTooltipBubble::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
+  const gfx::Size size =
+      BubbleDialogDelegateView::CalculatePreferredSize(available_size);
 
   if (chromeos::features::IsJellyrollEnabled()) {
     return size;
diff --git a/ash/shelf/shelf_tooltip_bubble.h b/ash/shelf/shelf_tooltip_bubble.h
index e28fd51..85365d8 100644
--- a/ash/shelf/shelf_tooltip_bubble.h
+++ b/ash/shelf/shelf_tooltip_bubble.h
@@ -36,7 +36,8 @@
   void OnThemeChanged() override;
 
   // BubbleDialogDelegateView overrides:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
 };
 
 }  // namespace ash
diff --git a/ash/shelf/shelf_view.cc b/ash/shelf/shelf_view.cc
index e25c93b..355f90a 100644
--- a/ash/shelf/shelf_view.cc
+++ b/ash/shelf/shelf_view.cc
@@ -571,7 +571,8 @@
   return gfx::Rect(origin, preferred_size);
 }
 
-gfx::Size ShelfView::CalculatePreferredSize() const {
+gfx::Size ShelfView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   const int hotseat_size = shelf_->hotseat_widget()->GetHotseatSize();
   if (visible_views_indices_.empty()) {
     // There are no visible shelf items.
diff --git a/ash/shelf/shelf_view.h b/ash/shelf/shelf_view.h
index 960940d..e523330 100644
--- a/ash/shelf/shelf_view.h
+++ b/ash/shelf/shelf_view.h
@@ -163,7 +163,8 @@
   gfx::Rect GetVisibleItemsBoundsInScreen();
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   gfx::Rect GetAnchorBoundsInScreen() const override;
   void OnThemeChanged() override;
   void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
diff --git a/ash/shelf/window_preview.cc b/ash/shelf/window_preview.cc
index ff5d64f..01aa40e 100644
--- a/ash/shelf/window_preview.cc
+++ b/ash/shelf/window_preview.cc
@@ -56,7 +56,8 @@
 
 WindowPreview::~WindowPreview() = default;
 
-gfx::Size WindowPreview::CalculatePreferredSize() const {
+gfx::Size WindowPreview::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   // The preview itself will always be strictly contained within its container,
   // so only the container's size matters to calculate the preferred size.
   const gfx::Size container_size = GetPreviewContainerSize();
@@ -69,8 +70,7 @@
 void WindowPreview::Layout(PassKey) {
   gfx::Rect content_rect = GetContentsBounds();
 
-  gfx::Size title_size =
-      title_->CalculatePreferredSize(views::SizeBounds(title_->width(), {}));
+  gfx::Size title_size = title_->CalculatePreferredSize({});
   int title_height_with_padding =
       kTitleLineHeight + kTitleMarginTop + kTitleMarginBottom;
   int title_width =
@@ -85,7 +85,7 @@
                 content_rect.y(), kCloseButtonSize, kCloseButtonSize));
 
   const gfx::Size container_size = GetPreviewContainerSize();
-  gfx::Size mirror_size = preview_view_->CalculatePreferredSize();
+  gfx::Size mirror_size = preview_view_->CalculatePreferredSize({});
   float preview_ratio = static_cast<float>(mirror_size.width()) /
                         static_cast<float>(mirror_size.height());
 
diff --git a/ash/shelf/window_preview.h b/ash/shelf/window_preview.h
index 4145b835..5fb0910c 100644
--- a/ash/shelf/window_preview.h
+++ b/ash/shelf/window_preview.h
@@ -53,7 +53,8 @@
   ~WindowPreview() override;
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void Layout(PassKey) override;
   bool OnMousePressed(const ui::MouseEvent& event) override;
   void OnThemeChanged() override;
diff --git a/ash/style/system_textfield.cc b/ash/style/system_textfield.cc
index f6b0f4af..77f5bb2f 100644
--- a/ash/style/system_textfield.cc
+++ b/ash/style/system_textfield.cc
@@ -274,7 +274,8 @@
       corner_radius_));
 }
 
-gfx::Size SystemTextfield::CalculatePreferredSize() const {
+gfx::Size SystemTextfield::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   // The width of container equals to the content width with horizontal padding.
   // The height of the container dependents on the type.
   const std::u16string& text = GetText();
diff --git a/ash/style/system_textfield.h b/ash/style/system_textfield.h
index afa957b..ce81c59 100644
--- a/ash/style/system_textfield.h
+++ b/ash/style/system_textfield.h
@@ -57,7 +57,8 @@
   void UpdateBackground();
 
   // views::Textfield:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void SetBorder(std::unique_ptr<views::Border> b) override;
   void OnMouseEntered(const ui::MouseEvent& event) override;
   void OnMouseExited(const ui::MouseEvent& event) override;
diff --git a/ash/system/notification_center/views/ash_notification_expand_button.cc b/ash/system/notification_center/views/ash_notification_expand_button.cc
index de1edf7..9df8c78 100644
--- a/ash/system/notification_center/views/ash_notification_expand_button.cc
+++ b/ash/system/notification_center/views/ash_notification_expand_button.cc
@@ -214,8 +214,9 @@
   UpdateBackgroundColor();
 }
 
-gfx::Size AshNotificationExpandButton::CalculatePreferredSize() const {
-  gfx::Size size = Button::CalculatePreferredSize();
+gfx::Size AshNotificationExpandButton::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
+  gfx::Size size = Button::CalculatePreferredSize(available_size);
 
   // When label is fading out, it is still visible but we should not consider
   // its size in our calculation here, so that size change animation can be
diff --git a/ash/system/notification_center/views/ash_notification_expand_button.h b/ash/system/notification_center/views/ash_notification_expand_button.h
index a195dd3130..d6c1e93 100644
--- a/ash/system/notification_center/views/ash_notification_expand_button.h
+++ b/ash/system/notification_center/views/ash_notification_expand_button.h
@@ -52,7 +52,8 @@
 
   // views::Button:
   void OnThemeChanged() override;
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
 
   void SetNotificationTitleForButtonTooltip(
       const std::u16string& notification_title);
diff --git a/ash/system/notification_center/views/ash_notification_view.cc b/ash/system/notification_center/views/ash_notification_view.cc
index 03cd86dc..4a414fc 100644
--- a/ash/system/notification_center/views/ash_notification_view.cc
+++ b/ash/system/notification_center/views/ash_notification_view.cc
@@ -421,15 +421,16 @@
   max_available_width_ = max_available_width;
 }
 
-gfx::Size AshNotificationView::NotificationTitleRow::CalculatePreferredSize()
-    const {
+gfx::Size AshNotificationView::NotificationTitleRow::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   // TODO(crbug.com/1349528): The size constraint is not passed down from the
   // views tree in the first round of layout, so setting a fixed width to bound
   // the view. The layout manager can size the view beyond this width if there
   // is available space. This works similar to applying a max width on the
   // internal labels.
   return gfx::Size(max_available_width_,
-                   GetHeightForWidth(max_available_width_));
+                   GetLayoutManager()->GetPreferredHeightForWidth(
+                       this, max_available_width_));
 }
 
 void AshNotificationView::NotificationTitleRow::OnThemeChanged() {
diff --git a/ash/system/notification_center/views/ash_notification_view.h b/ash/system/notification_center/views/ash_notification_view.h
index 4a9bb98..fe2c94f 100644
--- a/ash/system/notification_center/views/ash_notification_view.h
+++ b/ash/system/notification_center/views/ash_notification_view.h
@@ -208,7 +208,8 @@
     void SetMaxAvailableWidth(int max_available_width);
 
     // views::View:
-    gfx::Size CalculatePreferredSize() const override;
+    gfx::Size CalculatePreferredSize(
+        const views::SizeBounds& available_size) const override;
     void OnThemeChanged() override;
 
     views::Label* title_view() { return title_view_; }
diff --git a/ash/system/notification_center/views/message_view_container.cc b/ash/system/notification_center/views/message_view_container.cc
index b130189..81305108 100644
--- a/ash/system/notification_center/views/message_view_container.cc
+++ b/ash/system/notification_center/views/message_view_container.cc
@@ -120,7 +120,8 @@
   views::View::PreferredSizeChanged();
 }
 
-gfx::Size MessageViewContainer::CalculatePreferredSize() const {
+gfx::Size MessageViewContainer::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   if (list_view_ && list_view_->IsAnimatingExpandOrCollapseContainer(this)) {
     // Width should never change, only height.
     return gfx::Size(kNotificationInMessageCenterWidth,
diff --git a/ash/system/notification_center/views/message_view_container.h b/ash/system/notification_center/views/message_view_container.h
index a640e3d..44e08cd 100644
--- a/ash/system/notification_center/views/message_view_container.h
+++ b/ash/system/notification_center/views/message_view_container.h
@@ -67,7 +67,8 @@
   void TriggerPreferredSizeChangedForAnimation();
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void ChildPreferredSizeChanged(views::View* child) override;
 
   // MessageView::Observer:
diff --git a/ash/system/notification_center/views/notification_list_view.cc b/ash/system/notification_center/views/notification_list_view.cc
index 9983153..d4eea2b3 100644
--- a/ash/system/notification_center/views/notification_list_view.cc
+++ b/ash/system/notification_center/views/notification_list_view.cc
@@ -391,7 +391,8 @@
   return (it == children().cend()) ? gfx::Rect() : (*it)->bounds();
 }
 
-gfx::Size NotificationListView::CalculatePreferredSize() const {
+gfx::Size NotificationListView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   if (state_ == State::IDLE) {
     return gfx::Size(message_view_width_, target_height_);
   }
diff --git a/ash/system/notification_center/views/notification_list_view.h b/ash/system/notification_center/views/notification_list_view.h
index 556cd00..f5913dc 100644
--- a/ash/system/notification_center/views/notification_list_view.h
+++ b/ash/system/notification_center/views/notification_list_view.h
@@ -127,7 +127,8 @@
   void ChildPreferredSizeChanged(views::View* child) override;
   void PreferredSizeChanged() override;
   void Layout(PassKey) override;
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
 
   // message_center::NotificationViewController:
   void AnimateResize() override;
diff --git a/ash/system/notification_center/views/notifier_settings_view.cc b/ash/system/notification_center/views/notifier_settings_view.cc
index 59f3d64..678e922 100644
--- a/ash/system/notification_center/views/notifier_settings_view.cc
+++ b/ash/system/notification_center/views/notifier_settings_view.cc
@@ -128,7 +128,8 @@
 
   // views::View:
   void Layout(PassKey) override;
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   void OnFocus() override;
   bool OnKeyPressed(const ui::KeyEvent& event) override;
@@ -166,7 +167,8 @@
                                            : FocusBehavior::NEVER);
 }
 
-gfx::Size NotifierButtonWrapperView::CalculatePreferredSize() const {
+gfx::Size NotifierButtonWrapperView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   return gfx::Size(kWidth, kNotifierButtonWrapperHeight);
 }
 
@@ -801,7 +803,8 @@
   return size;
 }
 
-gfx::Size NotifierSettingsView::CalculatePreferredSize() const {
+gfx::Size NotifierSettingsView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   gfx::Size header_size = header_view_->GetPreferredSize();
   // |scroller_| and |no_notifiers_view_| do not exist when notifications
   // settings are split out of the notifier_settings_view.
diff --git a/ash/system/notification_center/views/notifier_settings_view.h b/ash/system/notification_center/views/notifier_settings_view.h
index ac2c7a3..dcd8a43 100644
--- a/ash/system/notification_center/views/notifier_settings_view.h
+++ b/ash/system/notification_center/views/notifier_settings_view.h
@@ -104,7 +104,8 @@
   // Overridden from views::View:
   void Layout(PassKey) override;
   gfx::Size GetMinimumSize() const override;
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   bool OnKeyPressed(const ui::KeyEvent& event) override;
   bool OnMouseWheel(const ui::MouseWheelEvent& event) override;
 
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index 0a81284e..12a661b4 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -3159,7 +3159,7 @@
 const ScheduledFeature& WallpaperControllerImpl::GetScheduleForOnlineWallpaper(
     const std::string& collection_id) const {
   if (::ash::IsTimeOfDayWallpaper(collection_id) &&
-      features::IsTimeOfDayWallpaperForcedAutoScheduleEnabled()) {
+      features::IsTimeOfDayWallpaperEnabled()) {
     return *time_of_day_scheduler_;
   } else {
     return *Shell::Get()->dark_light_mode_controller();
diff --git a/ash/wallpaper/wallpaper_controller_unittest.cc b/ash/wallpaper/wallpaper_controller_unittest.cc
index d16af78..9313a9e 100644
--- a/ash/wallpaper/wallpaper_controller_unittest.cc
+++ b/ash/wallpaper/wallpaper_controller_unittest.cc
@@ -5070,9 +5070,6 @@
     return;
   }
 
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(
-      features::kTimeOfDayWallpaperForcedAutoSchedule);
   const auto backdrop_image_data = TimeOfDayImageSet();
   client_.AddCollection(wallpaper_constants::kTimeOfDayWallpaperCollectionId,
                         backdrop_image_data);
diff --git a/ash/webui/camera_app_ui/resources/js/device/stream_manager.ts b/ash/webui/camera_app_ui/resources/js/device/stream_manager.ts
index f775578..b554953 100644
--- a/ash/webui/camera_app_ui/resources/js/device/stream_manager.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/stream_manager.ts
@@ -89,9 +89,14 @@
   private readonly videoConfigFilter: (config: VideoConfig) => boolean;
 
   private constructor() {
-    if (loadTimeData.getBoard() === 'grunt' &&
-        !state.get(state.State.DISABLE_VIDEO_RESOLUTION_FILTER_FOR_TESTING)) {
-      this.videoConfigFilter = ({height}: VideoConfig) => height < 720;
+    if (loadTimeData.getBoard() === 'grunt') {
+      this.videoConfigFilter = ({height}: VideoConfig) => {
+        if (state.get(
+                state.State.DISABLE_VIDEO_RESOLUTION_FILTER_FOR_TESTING)) {
+          return true;
+        }
+        return height < 720;
+      };
     } else {
       this.videoConfigFilter = () => true;
     }
diff --git a/ash/webui/camera_app_ui/resources/js/models/video_saver.ts b/ash/webui/camera_app_ui/resources/js/models/video_saver.ts
index f6f3481d..499cc54 100644
--- a/ash/webui/camera_app_ui/resources/js/models/video_saver.ts
+++ b/ash/webui/camera_app_ui/resources/js/models/video_saver.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {assert, assertInstanceof} from '../assert.js';
+import {assertExists, assertInstanceof} from '../assert.js';
 import {Intent} from '../intent.js';
 import * as Comlink from '../lib/comlink.js';
 import {
@@ -252,10 +252,9 @@
   private readonly encoder: VideoEncoder;
 
   /**
-   * Maps a frame's timestamp with frameNo, only storing frames being
-   * encoded.
+   * Queue containing frameNo of frames being encoded.
    */
-  private readonly frameNoMap = new Map<number, number>();
+  private readonly frameNoQueue: number[] = [];
 
   /**
    * Maps all encoded frames with their frame numbers.
@@ -347,13 +346,11 @@
    * |chunk| to Blob and stores with its frame number.
    */
   onFrameEncoded(chunk: EncodedVideoChunk): void {
-    const frameNo = this.frameNoMap.get(chunk.timestamp);
-    assert(frameNo !== undefined);
+    const frameNo = assertExists(this.frameNoQueue.shift());
     const chunkData = new Uint8Array(chunk.byteLength);
     chunk.copyTo(chunkData);
     this.frames.set(frameNo, new Blob([chunkData]));
     this.maxFrameNo = frameNo;
-    this.frameNoMap.delete(chunk.timestamp);
   }
 
   /**
@@ -363,7 +360,7 @@
     if (frame.timestamp === null || this.ended || this.canceled) {
       return;
     }
-    this.frameNoMap.set(frame.timestamp, frameNo);
+    this.frameNoQueue.push(frameNo);
     // Frames that are only in the initial speed video don't have to be encoded
     // as key frames because they'll be dropped soon.
     const keyFrame = frameNo % (this.initialSpeed * 2) === 0;
diff --git a/ash/webui/common/resources/cellular_setup/button_bar.html b/ash/webui/common/resources/cellular_setup/button_bar.html
index bda3ccb..e4dd650 100644
--- a/ash/webui/common/resources/cellular_setup/button_bar.html
+++ b/ash/webui/common/resources/cellular_setup/button_bar.html
@@ -14,13 +14,6 @@
   }
 
 </style>
-<cr-button id="backward"
-    class="cancel-button"
-    on-click="onBackwardButtonClicked_"
-    disabled="[[isButtonDisabled_(Button.BACKWARD, buttonState.*)]]"
-    hidden$="[[isButtonHidden_(Button.BACKWARD, buttonState.*)]]">
-  [[i18n('back')]]
-</cr-button>
 <div id="flex"></div>
 <cr-button id="cancel"
     class="cancel-button"
diff --git a/ash/webui/common/resources/cellular_setup/button_bar.ts b/ash/webui/common/resources/cellular_setup/button_bar.ts
index 70cb660..fedfde0 100644
--- a/ash/webui/common/resources/cellular_setup/button_bar.ts
+++ b/ash/webui/common/resources/cellular_setup/button_bar.ts
@@ -74,13 +74,6 @@
     }
   }
 
-  private onBackwardButtonClicked_(): void {
-    this.dispatchEvent(new CustomEvent('backward-nav-requested', {
-      bubbles: true,
-      composed: true,
-    }));
-  }
-
   private onCancelButtonClicked_(): void {
     this.dispatchEvent(new CustomEvent('cancel-requested', {
       bubbles: true,
@@ -98,8 +91,6 @@
   private getButtonBarState_(button: Button): ButtonState|undefined {
     assert(this.buttonState);
     switch (button) {
-      case Button.BACKWARD:
-        return this.buttonState.backward;
       case Button.CANCEL:
         return this.buttonState.cancel;
       case Button.FORWARD:
diff --git a/ash/webui/common/resources/cellular_setup/cellular_setup.ts b/ash/webui/common/resources/cellular_setup/cellular_setup.ts
index ef74455..7bd68af 100644
--- a/ash/webui/common/resources/cellular_setup/cellular_setup.ts
+++ b/ash/webui/common/resources/cellular_setup/cellular_setup.ts
@@ -117,8 +117,6 @@
   override ready() {
     super.ready();
 
-    this.addEventListener(
-        'backward-nav-requested', this.onBackwardNavRequested_);
     this.addEventListener('retry-requested', this.onRetryRequested_);
     this.addEventListener('forward-nav-requested', this.onForwardNavRequested_);
     this.addEventListener('cancel-requested', this.onCancelRequested_);
@@ -132,10 +130,6 @@
     }
   }
 
-  private onBackwardNavRequested_(): void {
-    this.currentPage_.navigateBackward();
-  }
-
   private onCancelRequested_(): void {
     this.dispatchEvent(new CustomEvent('exit-cellular-setup', {
       bubbles: true,
diff --git a/ash/webui/common/resources/cellular_setup/cellular_types.ts b/ash/webui/common/resources/cellular_setup/cellular_types.ts
index cf7986d0..39f4c07 100644
--- a/ash/webui/common/resources/cellular_setup/cellular_types.ts
+++ b/ash/webui/common/resources/cellular_setup/cellular_types.ts
@@ -18,13 +18,11 @@
 }
 
 export enum Button {
-  BACKWARD = 1,
-  CANCEL = 2,
-  FORWARD = 3,
+  CANCEL = 1,
+  FORWARD = 2,
 }
 
 export interface ButtonBarState {
-  backward?: ButtonState;
   cancel?: ButtonState;
   forward?: ButtonState;
 }
diff --git a/ash/webui/common/resources/cellular_setup/esim_flow_ui.ts b/ash/webui/common/resources/cellular_setup/esim_flow_ui.ts
index ab19e24..4b975f7e 100644
--- a/ash/webui/common/resources/cellular_setup/esim_flow_ui.ts
+++ b/ash/webui/common/resources/cellular_setup/esim_flow_ui.ts
@@ -462,7 +462,6 @@
       cancelButtonStateIfEnabled: ButtonState): ButtonBarState {
     this.forwardButtonLabel = this.i18n('next');
     return {
-      backward: ButtonState.HIDDEN,
       cancel: cancelButtonStateIfEnabled,
       forward: enableForwardBtn ? ButtonState.ENABLED : ButtonState.DISABLED,
     };
@@ -473,7 +472,6 @@
       cancelButtonStateIfEnabled: ButtonState): ButtonBarState {
     this.forwardButtonLabel = this.i18n('confirm');
     return {
-      backward: ButtonState.HIDDEN,
       cancel: cancelButtonStateIfEnabled,
       forward: enableForwardBtn ? ButtonState.ENABLED : ButtonState.DISABLED,
     };
@@ -491,7 +489,6 @@
       case EsimUiState.PROFILE_SEARCH:
         this.forwardButtonLabel = this.i18n('next');
         buttonState = {
-          backward: ButtonState.HIDDEN,
           cancel: cancelButtonStateIfEnabled,
           forward: ButtonState.DISABLED,
         };
@@ -499,7 +496,6 @@
       case EsimUiState.PROFILE_SEARCH_CONSENT:
         this.forwardButtonLabel = this.i18n('profileDiscoveryConsentScan');
         buttonState = {
-          backward: ButtonState.HIDDEN,
           cancel: ButtonState.ENABLED,
           forward: ButtonState.ENABLED,
         };
@@ -510,9 +506,7 @@
         break;
       case EsimUiState.ACTIVATION_CODE_ENTRY_READY:
         buttonState = this.generateButtonStateForActivationPage_(
-            /*enableForwardBtn*/ true,
-            cancelButtonStateIfEnabled,
-        );
+            /*enableForwardBtn*/ true, cancelButtonStateIfEnabled);
         break;
       case EsimUiState.ACTIVATION_CODE_ENTRY_INSTALLING:
         buttonState = this.generateButtonStateForActivationPage_(
@@ -533,14 +527,12 @@
       case EsimUiState.PROFILE_SELECTION:
         this.updateForwardButtonLabel_();
         buttonState = {
-          backward: ButtonState.HIDDEN,
           cancel: cancelButtonStateIfEnabled,
           forward: ButtonState.ENABLED,
         };
         break;
       case EsimUiState.PROFILE_SELECTION_INSTALLING:
         buttonState = {
-          backward: ButtonState.HIDDEN,
           cancel: cancelButtonStateIfDisabled,
           forward: ButtonState.DISABLED,
         };
@@ -548,7 +540,6 @@
       case EsimUiState.SETUP_FINISH:
         this.forwardButtonLabel = this.i18n('done');
         buttonState = {
-          backward: ButtonState.HIDDEN,
           cancel: ButtonState.HIDDEN,
           forward: ButtonState.ENABLED,
         };
@@ -681,31 +672,6 @@
   }
 
   /** SubflowMixin override */
-  override navigateBackward(): void {
-    if (this.profilesFound_() &&
-        (this.state_ === EsimUiState.ACTIVATION_CODE_ENTRY ||
-         this.state_ === EsimUiState.ACTIVATION_CODE_ENTRY_READY)) {
-      this.state_ = EsimUiState.PROFILE_SELECTION;
-      return;
-    }
-
-    if (this.state_ === EsimUiState.CONFIRMATION_CODE_ENTRY ||
-        this.state_ === EsimUiState.CONFIRMATION_CODE_ENTRY_READY) {
-      if (this.activationCode_) {
-        this.state_ = EsimUiState.ACTIVATION_CODE_ENTRY_READY;
-        return;
-      } else if (this.profilesFound_()) {
-        this.state_ = EsimUiState.PROFILE_SELECTION;
-        return;
-      }
-    }
-    console.error(
-        'Navigate backward faled for : ' + this.state_ +
-        ' this state does not support backward navigation.');
-    assertNotReached();
-  }
-
-  /** SubflowMixin override */
   override maybeFocusPageElement(): boolean {
     switch (this.state_) {
       case EsimUiState.ACTIVATION_CODE_ENTRY:
diff --git a/ash/webui/common/resources/cellular_setup/psim_flow_ui.ts b/ash/webui/common/resources/cellular_setup/psim_flow_ui.ts
index dd52eef..08f9cf44 100644
--- a/ash/webui/common/resources/cellular_setup/psim_flow_ui.ts
+++ b/ash/webui/common/resources/cellular_setup/psim_flow_ui.ts
@@ -368,7 +368,6 @@
       case PsimUiState.WAITING_FOR_USER_PAYMENT:
         this.forwardButtonLabel = this.i18n('next');
         buttonState = {
-          backward: ButtonState.HIDDEN,
           cancel: ButtonState.ENABLED,
           forward: ButtonState.DISABLED,
         };
@@ -376,7 +375,6 @@
       case PsimUiState.TIMEOUT_START_ACTIVATION:
         this.forwardButtonLabel = this.i18n('tryAgain');
         buttonState = {
-          backward: ButtonState.HIDDEN,
           cancel: ButtonState.ENABLED,
           forward: ButtonState.ENABLED,
         };
@@ -384,7 +382,6 @@
       case PsimUiState.ACTIVATION_SUCCESS:
         this.forwardButtonLabel = this.i18n('next');
         buttonState = {
-          backward: ButtonState.HIDDEN,
           cancel: ButtonState.ENABLED,
           forward: ButtonState.ENABLED,
         };
@@ -394,7 +391,6 @@
       case PsimUiState.FINAL_TIMEOUT_START_ACTIVATION:
         this.forwardButtonLabel = this.i18n('done');
         buttonState = {
-          backward: ButtonState.HIDDEN,
           cancel: ButtonState.ENABLED,
           forward: ButtonState.ENABLED,
         };
@@ -403,7 +399,6 @@
       case PsimUiState.TIMEOUT_FINISH_ACTIVATION:
         this.forwardButtonLabel = this.i18n('done');
         buttonState = {
-          backward: ButtonState.HIDDEN,
           cancel: ButtonState.HIDDEN,
           forward: ButtonState.ENABLED,
         };
diff --git a/ash/webui/common/resources/cellular_setup/subflow_mixin.ts b/ash/webui/common/resources/cellular_setup/subflow_mixin.ts
index 3c96fe3a..dd0d960 100644
--- a/ash/webui/common/resources/cellular_setup/subflow_mixin.ts
+++ b/ash/webui/common/resources/cellular_setup/subflow_mixin.ts
@@ -36,10 +36,6 @@
           assertNotReached();
         }
 
-        navigateBackward(): void {
-          assertNotReached();
-        }
-
         maybeFocusPageElement(): boolean {
           return false;
         }
@@ -52,6 +48,5 @@
   buttonState: ButtonBarState;
   initSubflow(): void;
   navigateForward(): void;
-  navigateBackward(): void;
   maybeFocusPageElement(): boolean;
 }
diff --git a/ash/webui/personalization_app/personalization_app_ui.cc b/ash/webui/personalization_app/personalization_app_ui.cc
index f41dd93e..dad1dcc4 100644
--- a/ash/webui/personalization_app/personalization_app_ui.cc
+++ b/ash/webui/personalization_app/personalization_app_ui.cc
@@ -535,9 +535,6 @@
   source->AddBoolean("isTimeOfDayWallpaperEnabled",
                      features::IsTimeOfDayWallpaperEnabled());
 
-  source->AddBoolean("isTimeOfDayWallpaperForcedAutoScheduleEnabled",
-                     features::IsTimeOfDayWallpaperForcedAutoScheduleEnabled());
-
   source->AddBoolean("isCrosPrivacyHubLocationEnabled",
                      features::IsCrosPrivacyHubLocationEnabled());
 
diff --git a/ash/webui/personalization_app/resources/js/load_time_booleans.ts b/ash/webui/personalization_app/resources/js/load_time_booleans.ts
index 93612d536..38cba92 100644
--- a/ash/webui/personalization_app/resources/js/load_time_booleans.ts
+++ b/ash/webui/personalization_app/resources/js/load_time_booleans.ts
@@ -47,11 +47,6 @@
   return loadTimeData.getBoolean('isTimeOfDayWallpaperEnabled');
 }
 
-export function isTimeOfDayWallpaperForcedAutoScheduleEnabled() {
-  return loadTimeData.getBoolean(
-      'isTimeOfDayWallpaperForcedAutoScheduleEnabled');
-}
-
 export function isCrosPrivacyHubLocationEnabled() {
   return loadTimeData.getBoolean('isCrosPrivacyHubLocationEnabled');
 }
diff --git a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_images_element.ts b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_images_element.ts
index 15c52fb..011399f 100644
--- a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_images_element.ts
+++ b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_images_element.ts
@@ -18,7 +18,7 @@
 
 import {CurrentWallpaper, OnlineImageType, WallpaperCollection, WallpaperImage, WallpaperType} from '../../personalization_app.mojom-webui.js';
 import {dismissTimeOfDayBanner} from '../ambient/ambient_controller.js';
-import {isTimeOfDayWallpaperForcedAutoScheduleEnabled} from '../load_time_booleans.js';
+import {isTimeOfDayWallpaperEnabled} from '../load_time_booleans.js';
 import {PersonalizationRouterElement} from '../personalization_router_element.js';
 import {WithPersonalizationStore} from '../personalization_store.js';
 import {setColorModeAutoSchedule} from '../theme/theme_controller.js';
@@ -349,7 +349,7 @@
 
   private async shouldShowTimeOfDayWallpaperDialog_(tile: ImageTile):
       Promise<boolean> {
-    if (isTimeOfDayWallpaperForcedAutoScheduleEnabled()) {
+    if (isTimeOfDayWallpaperEnabled()) {
       await getShouldShowTimeOfDayWallpaperDialog(
           getWallpaperProvider(), this.getStore());
     }
diff --git a/ash/wm/desks/desk_textfield.cc b/ash/wm/desks/desk_textfield.cc
index 15f6e01..de583b5 100644
--- a/ash/wm/desks/desk_textfield.cc
+++ b/ash/wm/desks/desk_textfield.cc
@@ -43,7 +43,8 @@
   focus_manager->SetStoredFocusView(nullptr);
 }
 
-gfx::Size DeskTextfield::CalculatePreferredSize() const {
+gfx::Size DeskTextfield::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   const std::u16string& text = GetText();
   int width = 0;
   int height = 0;
diff --git a/ash/wm/desks/desk_textfield.h b/ash/wm/desks/desk_textfield.h
index e92ead3..ae5f298b 100644
--- a/ash/wm/desks/desk_textfield.h
+++ b/ash/wm/desks/desk_textfield.h
@@ -38,7 +38,8 @@
   }
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   bool SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) override;
   std::u16string GetTooltipText(const gfx::Point& p) const override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
diff --git a/ash/wm/desks/templates/saved_desk_grid_view.cc b/ash/wm/desks/templates/saved_desk_grid_view.cc
index fb3c426..09c6756 100644
--- a/ash/wm/desks/templates/saved_desk_grid_view.cc
+++ b/ash/wm/desks/templates/saved_desk_grid_view.cc
@@ -210,7 +210,8 @@
   return false;
 }
 
-gfx::Size SavedDeskGridView::CalculatePreferredSize() const {
+gfx::Size SavedDeskGridView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   if (grid_items_.empty())
     return gfx::Size();
 
diff --git a/ash/wm/desks/templates/saved_desk_grid_view.h b/ash/wm/desks/templates/saved_desk_grid_view.h
index 4bd086f..392ca46 100644
--- a/ash/wm/desks/templates/saved_desk_grid_view.h
+++ b/ash/wm/desks/templates/saved_desk_grid_view.h
@@ -72,7 +72,8 @@
   SavedDeskItemView* GetItemForUUID(const base::Uuid& uuid);
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void Layout(PassKey) override;
   void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
 
diff --git a/ash/wm/desks/templates/saved_desk_icon_view.cc b/ash/wm/desks/templates/saved_desk_icon_view.cc
index 8898951..aa7a1f7 100644
--- a/ash/wm/desks/templates/saved_desk_icon_view.cc
+++ b/ash/wm/desks/templates/saved_desk_icon_view.cc
@@ -82,20 +82,23 @@
 
 SavedDeskIconView::~SavedDeskIconView() = default;
 
-gfx::Size SavedDeskIconView::CalculatePreferredSize() const {
+gfx::Size SavedDeskIconView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   // The width for the icon. The overflow icon doesn't have an icon so it's
   // zero.
   int width = (IsOverflowIcon() ? 0 : kIconViewSize);
 
-  // Add the label width if the label view exists. The reason for having the max
-  // is to have a minimum width.
-  width += count_label_
-               ? std::max(kIconViewSize,
-                          count_label_
-                              ->CalculatePreferredSize(
-                                  views::SizeBounds(count_label_->width(), {}))
-                              .width())
-               : 0;
+  if (count_label_) {
+    views::SizeBound label_width = std::max<views::SizeBound>(
+        kIconViewSize, available_size.width() - width);
+
+    // Add the label width if the label view exists. The reason for having the
+    // max is to have a minimum width.
+    width += std::max(
+        kIconViewSize,
+        count_label_->CalculatePreferredSize(views::SizeBounds(label_width, {}))
+            .width());
+  }
 
   return gfx::Size(width, kIconViewSize);
 }
@@ -149,7 +152,7 @@
 
 void SavedDeskRegularIconView::Layout(PassKey) {
   DCHECK(icon_view_);
-  gfx::Size icon_preferred_size = icon_view_->CalculatePreferredSize();
+  gfx::Size icon_preferred_size = icon_view_->CalculatePreferredSize({});
   icon_view_->SetBoundsRect(gfx::Rect(
       base::ClampFloor((kIconViewSize - icon_preferred_size.width()) / 2.0),
       base::ClampFloor((kIconViewSize - icon_preferred_size.height()) / 2.0),
diff --git a/ash/wm/desks/templates/saved_desk_icon_view.h b/ash/wm/desks/templates/saved_desk_icon_view.h
index ec82e14..c8695d0 100644
--- a/ash/wm/desks/templates/saved_desk_icon_view.h
+++ b/ash/wm/desks/templates/saved_desk_icon_view.h
@@ -58,7 +58,8 @@
   ~SavedDeskIconView() override;
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
 
   // Sets `count_` to `count` and updates the `count_label_`. Please note,
   // currently it does not support update on regular icon.
diff --git a/ash/wm/window_cycle/window_cycle_item_view.cc b/ash/wm/window_cycle/window_cycle_item_view.cc
index 1b449b5..6867e3fe 100644
--- a/ash/wm/window_cycle/window_cycle_item_view.cc
+++ b/ash/wm/window_cycle/window_cycle_item_view.cc
@@ -163,7 +163,8 @@
                       source_window(), /*include_header_rounding=*/false)));
 }
 
-gfx::Size WindowCycleItemView::CalculatePreferredSize() const {
+gfx::Size WindowCycleItemView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   // Previews can range in width from half to double of
   // |kFixedPreviewHeightDp|. Padding will be added to the
   // sides to achieve this if the preview is too narrow.
diff --git a/ash/wm/window_cycle/window_cycle_item_view.h b/ash/wm/window_cycle/window_cycle_item_view.h
index 2d5f561..660344f6 100644
--- a/ash/wm/window_cycle/window_cycle_item_view.h
+++ b/ash/wm/window_cycle/window_cycle_item_view.h
@@ -50,7 +50,8 @@
   void Layout(PassKey) override;
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   bool HandleAccessibleAction(const ui::AXActionData& action_data) override;
 
   // WindowMiniViewBase:
diff --git a/ash/wm/window_cycle/window_cycle_list.cc b/ash/wm/window_cycle/window_cycle_list.cc
index 9979452f..11d7a9a 100644
--- a/ash/wm/window_cycle/window_cycle_list.cc
+++ b/ash/wm/window_cycle/window_cycle_list.cc
@@ -484,7 +484,7 @@
 
   // The windows should not shift position when selecting when there's enough
   // room to display all windows.
-  if (cycle_view_ && cycle_view_->CalculatePreferredSize().width() ==
+  if (cycle_view_ && cycle_view_->CalculatePreferredSize({}).width() ==
                          cycle_view_->CalculateMaxWidth()) {
     cycle_view_->ScrollToWindow(windows_[current_index_]);
   }
diff --git a/ash/wm/window_cycle/window_cycle_view.cc b/ash/wm/window_cycle/window_cycle_view.cc
index 3bffe45..2a89cd4 100644
--- a/ash/wm/window_cycle/window_cycle_view.cc
+++ b/ash/wm/window_cycle/window_cycle_view.cc
@@ -553,7 +553,8 @@
          2 * kBackgroundHorizontalInsetDp;
 }
 
-gfx::Size WindowCycleView::CalculatePreferredSize() const {
+gfx::Size WindowCycleView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   gfx::Size size = GetContentContainerBounds().size();
   // `mirror_container_` can have window list that overflow out of the
   // screen, but the window cycle view with a bandshield, cropping the
diff --git a/ash/wm/window_cycle/window_cycle_view.h b/ash/wm/window_cycle/window_cycle_view.h
index 918c8c18..0e7018a 100644
--- a/ash/wm/window_cycle/window_cycle_view.h
+++ b/ash/wm/window_cycle/window_cycle_view.h
@@ -131,7 +131,8 @@
   int CalculateMaxWidth() const;
 
   // views::WidgetDelegateView:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void Layout(PassKey) override;
 
   // ui::ImplicitAnimationObserver:
diff --git a/ash/wm/window_mirror_view.cc b/ash/wm/window_mirror_view.cc
index c204032b..da79a72c 100644
--- a/ash/wm/window_mirror_view.cc
+++ b/ash/wm/window_mirror_view.cc
@@ -67,7 +67,8 @@
   }
 }
 
-gfx::Size WindowMirrorView::CalculatePreferredSize() const {
+gfx::Size WindowMirrorView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   return show_non_client_view_ ? source_->bounds().size()
                                : GetClientAreaBounds().size();
 }
diff --git a/ash/wm/window_mirror_view.h b/ash/wm/window_mirror_view.h
index 1d5d3fe..ff03566 100644
--- a/ash/wm/window_mirror_view.h
+++ b/ash/wm/window_mirror_view.h
@@ -50,7 +50,8 @@
   void OnWindowDestroying(aura::Window* window) override;
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void Layout(PassKey) override;
   bool GetNeedsNotificationWhenVisibleBoundsChange() const override;
   void OnVisibleBoundsChanged() override;
diff --git a/ash/wm/window_preview_view.cc b/ash/wm/window_preview_view.cc
index cd2bcbf..e6006be 100644
--- a/ash/wm/window_preview_view.cc
+++ b/ash/wm/window_preview_view.cc
@@ -64,7 +64,8 @@
     entry.second->RecreateMirrorLayers();
 }
 
-gfx::Size WindowPreviewView::CalculatePreferredSize() const {
+gfx::Size WindowPreviewView::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   // The preferred size of this view is the union of all the windows it is made
   // up of with, scaled to match the ratio of the main window to its mirror
   // view's preferred size.
@@ -75,7 +76,7 @@
   gfx::SizeF window_size(1.f, 1.f);
   auto it = mirror_views_.find(root);
   if (it != mirror_views_.end()) {
-    window_size = gfx::SizeF(it->second->CalculatePreferredSize());
+    window_size = gfx::SizeF(it->second->CalculatePreferredSize({}));
     if (window_size.IsEmpty())
       return gfx::Size();  // Avoids divide by zero below.
   }
diff --git a/ash/wm/window_preview_view.h b/ash/wm/window_preview_view.h
index fb1eecc..a5edafb 100644
--- a/ash/wm/window_preview_view.h
+++ b/ash/wm/window_preview_view.h
@@ -39,7 +39,8 @@
   void RecreatePreviews();
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void Layout(PassKey) override;
 
   // aura::client::TransientWindowClientObserver:
diff --git a/ash/wm/workspace/multi_window_resize_controller.cc b/ash/wm/workspace/multi_window_resize_controller.cc
index ac548c1..b20bbb8 100644
--- a/ash/wm/workspace/multi_window_resize_controller.cc
+++ b/ash/wm/workspace/multi_window_resize_controller.cc
@@ -138,7 +138,8 @@
   ~ResizeView() override = default;
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override {
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override {
     const bool vert = direction_ == Direction::kLeftRight;
     return gfx::Size(vert ? kLongSide : kShortSide,
                      vert ? kShortSide : kLongSide);
diff --git a/base/BUILD.gn b/base/BUILD.gn
index a470137f..22cc581 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -4880,6 +4880,7 @@
       "test/android/javatests/src/org/chromium/base/test/util/SkipCheck.java",
       "test/android/javatests/src/org/chromium/base/test/util/TestFileUtil.java",
       "test/android/javatests/src/org/chromium/base/test/util/TestThreadUtils.java",
+      "test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java",
       "test/android/javatests/src/org/chromium/base/test/util/TimeoutTimer.java",
       "test/android/javatests/src/org/chromium/base/test/util/UserActionTester.java",
       "test/android/javatests/src/org/chromium/base/test/util/ViewActionOnDescendant.java",
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
index 87c463a..1fcbbe06 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumAndroidJUnitRunner.java
@@ -39,7 +39,6 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.InMemorySharedPreferencesContext;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
-import org.chromium.base.test.util.ScalableTimeout;
 import org.chromium.build.BuildConfig;
 import org.chromium.testing.TestListInstrumentationRunListener;
 
@@ -68,7 +67,6 @@
     private static final String IS_UNIT_TEST_FLAG = "BaseChromiumAndroidJUnitRunner.IsUnitTest";
     private static final String EXTRA_CLANG_COVERAGE_DEVICE_FILE =
             "BaseChromiumAndroidJUnitRunner.ClangCoverageDeviceFile";
-    private static final String EXTRA_TIMEOUT_SCALE = "BaseChromiumAndroidJUnitRunner.TimeoutScale";
     private static final String EXTRA_TRACE_FILE = "BaseChromiumAndroidJUnitRunner.TraceFile";
 
     private static final String ARGUMENT_LOG_ONLY = "log";
@@ -151,10 +149,6 @@
     @Override
     public void onStart() {
         Bundle arguments = InstrumentationRegistry.getArguments();
-        String timeoutScale = arguments.getString(EXTRA_TIMEOUT_SCALE);
-        if (timeoutScale != null) {
-            ScalableTimeout.setScale(Float.valueOf(timeoutScale));
-        }
         if (sTestListMode) {
             Log.w(
                     TAG,
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java b/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java
index 264b2af..94739f5f 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/ScalableTimeout.java
@@ -6,17 +6,24 @@
 
 /**
  * Utility class for scaling various timeouts by a common factor.
- *
- * <p>Set this value via command-line. E.g.: out/Debug/bin/run_tests --timeout-scale=3
+ * For example, to run tests under slow memory tools, you might do
+ * something like this:
+ *   adb shell "echo 20.0 > /data/local/tmp/chrome_timeout_scale"
  */
 public class ScalableTimeout {
-    private static float sTimeoutScale = 1;
-
-    public static void setScale(float value) {
-        sTimeoutScale = value;
-    }
+    private static Double sTimeoutScale;
+    public static final String PROPERTY_FILE = "/data/local/tmp/chrome_timeout_scale";
 
     public static long scaleTimeout(long timeout) {
+        if (sTimeoutScale == null) {
+            try {
+                char[] data = TestFileUtil.readUtf8File(PROPERTY_FILE, 32);
+                sTimeoutScale = Double.parseDouble(new String(data));
+            } catch (Exception e) {
+                // NumberFormatException, FileNotFoundException, IOException
+                sTimeoutScale = 1.0;
+            }
+        }
         return (long) (timeout * sTimeoutScale);
     }
 }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java
new file mode 100644
index 0000000..60547ad
--- /dev/null
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/TimeoutScale.java
@@ -0,0 +1,20 @@
+// Copyright 2012 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.test.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** This annotation can be used to scale a specific test timeout. */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TimeoutScale {
+    /**
+     * @return A number to scale the test timeout.
+     */
+    public int value();
+}
diff --git a/build/android/BUILD.gn b/build/android/BUILD.gn
index 80995f52..dd9fb94 100644
--- a/build/android/BUILD.gn
+++ b/build/android/BUILD.gn
@@ -93,18 +93,6 @@
   }
 }
 
-if (defined(sanitizer_arch)) {
-  action("generate_wrap_sh") {
-    script = "generate_wrap_sh.py"
-    outputs = [ "$target_gen_dir/$target_name/wrap.sh" ]
-    args = [
-      "--arch=$sanitizer_arch",
-      "--output",
-      rebase_path(outputs[0], root_build_dir),
-    ]
-  }
-}
-
 # TODO(go/turn-down-test-results): Remove once we turn down
 # test-results.appspot.com
 python_library("test_result_presentations_py") {
@@ -207,6 +195,9 @@
     if (enable_chrome_android_internal) {
       data += [ "//clank/tools/android/avd/proto/" ]
     }
+    if (is_asan) {
+      data_deps += [ "//tools/android/asan/third_party:asan_device_setup" ]
+    }
     if (use_full_mte) {
       data_deps += [ "//tools/android/mte:mte_device_setup" ]
     }
diff --git a/build/android/generate_wrap_sh.py b/build/android/generate_wrap_sh.py
deleted file mode 100755
index bfe2067..0000000
--- a/build/android/generate_wrap_sh.py
+++ /dev/null
@@ -1,37 +0,0 @@
-#! /usr/bin/env python3
-# Copyright 2024 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import argparse
-import pathlib
-
-
-# Disable memcmp overlap check.There are blobs (gl drivers)
-# on some android devices that use memcmp on overlapping regions,
-# nothing we can do about that.
-#EXTRA_OPTIONS = 'strict_memcmp=0,use_sigaltstack=1'
-def _generate(arch):
-  return f"""\
-#!/system/bin/sh
-# See: https://github.com/google/sanitizers/wiki/AddressSanitizerOnAndroid/\
-01f8df1ac1a447a8475cdfcb03e8b13140042dbd#running-with-wrapsh-recommended
-HERE="$(cd "$(dirname "$0")" && pwd)"
-log "Launching with ASAN enabled: $0 $@"
-export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
-export LD_PRELOAD=$HERE/libclang_rt.asan-{arch}-android.so
-exec "$@"
-"""
-
-
-def main():
-  parser = argparse.ArgumentParser()
-  parser.add_argument('--arch', required=True)
-  parser.add_argument('--output', required=True)
-  args = parser.parse_args()
-
-  pathlib.Path(args.output).write_text(_generate(args.arch))
-
-
-if __name__ == '__main__':
-  main()
diff --git a/build/android/pylib/gtest/gtest_test_instance.py b/build/android/pylib/gtest/gtest_test_instance.py
index dbed400..0398fac 100644
--- a/build/android/pylib/gtest/gtest_test_instance.py
+++ b/build/android/pylib/gtest/gtest_test_instance.py
@@ -384,9 +384,6 @@
       self._extras = {
           _EXTRA_NATIVE_TEST_ACTIVITY: self._apk_helper.GetActivityName(),
       }
-      if args.timeout_scale and args.timeout_scale != 1:
-        self._extras[_EXTRA_RUN_IN_SUB_THREAD] = 1
-
       if self._suite in RUN_IN_SUB_THREAD_TEST_SUITES:
         self._extras[_EXTRA_RUN_IN_SUB_THREAD] = 1
       if self._suite in BROWSER_TEST_SUITES:
diff --git a/build/android/pylib/instrumentation/instrumentation_test_instance_test.py b/build/android/pylib/instrumentation/instrumentation_test_instance_test.py
index 7d7b5839..f58bfd4 100755
--- a/build/android/pylib/instrumentation/instrumentation_test_instance_test.py
+++ b/build/android/pylib/instrumentation/instrumentation_test_instance_test.py
@@ -22,6 +22,10 @@
 
 class InstrumentationTestInstanceTest(unittest.TestCase):
 
+  def setUp(self):
+    options = mock.Mock()
+    options.tool = ''
+
   @staticmethod
   def createTestInstance():
     c = _INSTRUMENTATION_TEST_INSTANCE_PATH % 'InstrumentationTestInstance'
diff --git a/build/android/pylib/local/device/local_device_environment.py b/build/android/pylib/local/device/local_device_environment.py
index 84afd01..582e690 100644
--- a/build/android/pylib/local/device/local_device_environment.py
+++ b/build/android/pylib/local/device/local_device_environment.py
@@ -108,6 +108,7 @@
     self._preferred_abis = None
     self._recover_devices = args.recover_devices
     self._skip_clear_data = args.skip_clear_data
+    self._tool_name = args.tool
     self._trace_output = None
     # Must check if arg exist because this class is used by
     # //third_party/catapult's browser_options.py
@@ -255,6 +256,10 @@
     return self._skip_clear_data
 
   @property
+  def tool(self):
+    return self._tool_name
+
+  @property
   def trace_output(self):
     return self._trace_output
 
diff --git a/build/android/pylib/local/device/local_device_gtest_run.py b/build/android/pylib/local/device/local_device_gtest_run.py
index d668416..687f7748 100644
--- a/build/android/pylib/local/device/local_device_gtest_run.py
+++ b/build/android/pylib/local/device/local_device_gtest_run.py
@@ -358,7 +358,12 @@
     return constants.TEST_EXECUTABLE_DIR
 
   def Run(self, test, device, flags=None, **kwargs):
-    cmd = [posixpath.join(self._device_dist_dir, self._exe_file_name)]
+    tool = self._test_run.GetTool(device).GetTestWrapper()
+    if tool:
+      cmd = [tool]
+    else:
+      cmd = []
+    cmd.append(posixpath.join(self._device_dist_dir, self._exe_file_name))
 
     if test:
       cmd.append('--gtest_filter=%s' % ':'.join(test))
@@ -368,8 +373,7 @@
     cwd = constants.TEST_EXECUTABLE_DIR
 
     env = {
-        'LD_LIBRARY_PATH': self._device_dist_dir,
-        'UBSAN_OPTIONS': constants.UBSAN_OPTIONS,
+      'LD_LIBRARY_PATH': self._device_dist_dir
     }
 
     if self._coverage_dir:
@@ -379,6 +383,8 @@
           device_coverage_dir, self._suite, self._coverage_index)
       self._coverage_index += 1
 
+    if self._env.tool != 'asan':
+      env['UBSAN_OPTIONS'] = constants.UBSAN_OPTIONS
 
     try:
       gcov_strip_depth = os.environ['NATIVE_COVERAGE_DEPTH_STRIP']
@@ -478,7 +484,11 @@
                               check_return=True,
                               as_root=self._env.force_main_user)
 
-      def start_servers(dev):
+      def init_tool_and_start_servers(dev):
+        tool = self.GetTool(dev)
+        tool.CopyFiles(dev)
+        tool.SetupEnvironment()
+
         if self._env.disable_test_server:
           logging.warning('Not starting test server. Some tests may fail.')
           return
@@ -502,7 +512,7 @@
         if self.TestPackage() in _SUITE_REQUIRES_TEST_SERVER_SPAWNER:
           self._servers[str(dev)].append(
               local_test_server_spawner.LocalTestServerSpawner(
-                  ports.AllocateTestServerPort(), dev))
+                  ports.AllocateTestServerPort(), dev, tool))
 
         for s in self._servers[str(dev)]:
           s.SetUp()
@@ -512,8 +522,7 @@
 
       steps = [
           bind_crash_handler(s, device)
-          for s in (install_apk, push_test_data, start_servers)
-      ]
+          for s in (install_apk, push_test_data, init_tool_and_start_servers)]
       if self._env.concurrent_adb:
         reraiser_thread.RunAsync(steps)
       else:
@@ -761,7 +770,9 @@
   #override
   def _RunTest(self, device, test):
     # Run the test.
-    timeout = self._test_instance.shard_timeout * _GetDeviceTimeoutMultiplier()
+    timeout = (self._test_instance.shard_timeout *
+               self.GetTool(device).GetTimeoutScale() *
+               _GetDeviceTimeoutMultiplier())
     if self._test_instance.wait_for_java_debugger:
       timeout = None
     if self._test_instance.store_tombstones:
@@ -955,4 +966,7 @@
       for s in self._servers.get(str(dev), []):
         s.TearDown()
 
+      tool = self.GetTool(dev)
+      tool.CleanUpEnvironment()
+
     self._env.parallel_devices.pMap(individual_device_tear_down)
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 379adba0..02d3363 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -30,6 +30,7 @@
 from devil.utils import reraiser_thread
 from incremental_install import installer
 from pylib import constants
+from pylib import valgrind_tools
 from pylib.base import base_test_result
 from pylib.base import output_manager
 from pylib.constants import host_paths
@@ -69,7 +70,7 @@
 _TAG = 'test_runner_py'
 
 TIMEOUT_ANNOTATIONS = [
-    ('Manual', 1000 * 60 * 60),
+    ('Manual', 10 * 60 * 60),
     ('IntegrationTest', 10 * 60),
     ('External', 10 * 60),
     ('EnormousTest', 5 * 60),
@@ -95,8 +96,6 @@
 EXTRA_SCREENSHOT_FILE = (
     'org.chromium.base.test.ScreenshotOnFailureStatement.ScreenshotFile')
 
-EXTRA_TIMEOUT_SCALE = 'BaseChromiumAndroidJUnitRunner.TimeoutScale'
-
 EXTRA_UI_CAPTURE_DIR = (
     'org.chromium.base.test.util.Screenshooter.ScreenshotDir')
 
@@ -599,6 +598,9 @@
           logging.debug('Attempting to set WebView flags: %r', webview_flags)
           self._webview_flag_changers[str(dev)].AddFlags(webview_flags)
 
+        valgrind_tools.SetChromeTimeoutScale(
+            dev, self._test_instance.timeout_scale)
+
       install_steps += [push_test_data, create_flag_changer]
       post_install_steps += [
           set_debug_app, approve_app_links, set_vega_permissions,
@@ -687,6 +689,8 @@
         logging.info('Running custom teardown shell command: %s', cmd)
         dev.RunShellCommand(cmd, shell=True, check_return=True)
 
+      valgrind_tools.SetChromeTimeoutScale(dev, None)
+
       # If we've force approved app links for a package, undo that now.
       self._ToggleAppLinks(dev, 'STATE_NO_RESPONSE')
 
@@ -906,6 +910,7 @@
       extras[_EXTRA_PACKAGE_UNDER_TEST] = package_name
 
     flags_to_add = []
+    test_timeout_scale = None
     if self._test_instance.coverage_directory:
       coverage_basename = '%s' % ('%s_%s_group' %
                                   (test[0]['class'], test[0]['method'])
@@ -1014,10 +1019,11 @@
       timeout = FIXED_TEST_TIMEOUT_OVERHEAD + self._GetTimeoutFromAnnotations(
           test['annotations'], test_display_name)
 
-      timeout_scale = self._test_instance.timeout_scale * (
-          self._GetTimeoutScaleFromAnnotations(test['annotations']))
-      if timeout_scale != 1:
-        extras[EXTRA_TIMEOUT_SCALE] = str(self._test_instance.timeout_scale)
+      test_timeout_scale = self._GetTimeoutScaleFromAnnotations(
+          test['annotations'])
+      if test_timeout_scale and test_timeout_scale != 1:
+        valgrind_tools.SetChromeTimeoutScale(
+            device, test_timeout_scale * self._test_instance.timeout_scale)
 
     if self._test_instance.wait_for_java_debugger:
       timeout = None
@@ -1100,6 +1106,11 @@
         if flags_to_add:
           self._flag_changers[str(device)].Restore()
 
+      def restore_timeout_scale():
+        if test_timeout_scale:
+          valgrind_tools.SetChromeTimeoutScale(
+              device, self._test_instance.timeout_scale)
+
       def handle_coverage_data():
         if self._test_instance.coverage_directory:
           try:
@@ -1214,9 +1225,9 @@
       # the results! Things such as whether the test CRASHED have not yet been
       # determined.
       post_test_steps = [
-          restore_flags, stop_chrome_proxy, handle_coverage_data,
-          handle_render_test_data, pull_ui_screen_captures,
-          pull_baseline_profile
+          restore_flags, restore_timeout_scale, stop_chrome_proxy,
+          handle_coverage_data, handle_render_test_data,
+          pull_ui_screen_captures, pull_baseline_profile
       ]
       if self._env.concurrent_adb:
         reraiser_thread.RunAsync(post_test_steps)
@@ -1380,9 +1391,6 @@
             # Workaround for https://github.com/mockito/mockito/issues/922
             'notPackage': 'net.bytebuddy',
         }
-        if self._test_instance.timeout_scale != 1:
-          extras[EXTRA_TIMEOUT_SCALE] = str(self._test_instance.timeout_scale)
-
         # BaseChromiumAndroidJUnitRunner ignores this bundle value (and always
         # adds the listener). This is needed to enable the the listener when
         # using AndroidJUnitRunner directly.
@@ -1730,8 +1738,12 @@
 
           processed_template_output = _GenerateRenderTestHtml(
               render_name, given_link, closest_link, diff_link)
+          # We include the timestamp in the HTML results filename so that
+          # multiple tries from the same run do not clobber each other.
+          timestamp = time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime())
+          html_results_name = f'{render_name}_{timestamp}.html'
           with self._env.output_manager.ArchivedTempfile(
-              '%s.html' % render_name, 'gold_local_diffs',
+              html_results_name, 'gold_local_diffs',
               output_manager.Datatype.HTML) as html_results:
             html_results.write(processed_template_output)
           _SetLinkOnResults(results, full_test_name, render_name,
diff --git a/build/android/pylib/local/device/local_device_test_run.py b/build/android/pylib/local/device/local_device_test_run.py
index 165d876..218106f 100644
--- a/build/android/pylib/local/device/local_device_test_run.py
+++ b/build/android/pylib/local/device/local_device_test_run.py
@@ -19,6 +19,7 @@
 from devil.android.sdk import version_codes
 from devil.android.tools import device_recovery
 from devil.utils import signal_handler
+from pylib import valgrind_tools
 from pylib.base import base_test_result
 from pylib.base import test_collection
 from pylib.base import test_exception
@@ -47,6 +48,7 @@
 
   def __init__(self, env, test_instance):
     super().__init__(env, test_instance)
+    self._tools = {}
     # This is intended to be filled by a child class.
     self._installed_packages = []
     env.SetPreferredAbis(test_instance.GetPreferredAbis())
@@ -358,6 +360,12 @@
     return ('Batch' not in annotations
             or annotations['Batch']['value'] != 'UnitTests')
 
+  def GetTool(self, device):
+    if str(device) not in self._tools:
+      self._tools[str(device)] = valgrind_tools.CreateTool(
+          self._env.tool, device)
+    return self._tools[str(device)]
+
   def _CreateShardsForDevices(self, tests):
     raise NotImplementedError
 
diff --git a/build/android/pylib/local/local_test_server_spawner.py b/build/android/pylib/local/local_test_server_spawner.py
index a4e10ac..a37bee5 100644
--- a/build/android/pylib/local/local_test_server_spawner.py
+++ b/build/android/pylib/local/local_test_server_spawner.py
@@ -35,12 +35,12 @@
 
 
 class PortForwarderAndroid(chrome_test_server_spawner.PortForwarder):
-
-  def __init__(self, device):
+  def __init__(self, device, tool):
     self.device = device
+    self.tool = tool
 
   def Map(self, port_pairs):
-    forwarder.Forwarder.Map(port_pairs, self.device)
+    forwarder.Forwarder.Map(port_pairs, self.device, self.tool)
 
   def GetDevicePortForHostPort(self, host_port):
     return forwarder.Forwarder.DevicePortForHostPort(host_port)
@@ -60,11 +60,12 @@
 
 class LocalTestServerSpawner(test_server.TestServer):
 
-  def __init__(self, port, device):
+  def __init__(self, port, device, tool):
     super().__init__()
     self._device = device
     self._spawning_server = chrome_test_server_spawner.SpawningServer(
-        port, PortForwarderAndroid(device), MAX_TEST_SERVER_INSTANCES)
+        port, PortForwarderAndroid(device, tool), MAX_TEST_SERVER_INSTANCES)
+    self._tool = tool
 
   @property
   def server_address(self):
@@ -84,7 +85,8 @@
     self._device.WriteFile(
         '%s/net-test-server-config' % self._device.GetExternalStoragePath(),
         test_server_config)
-    forwarder.Forwarder.Map([(self.port, self.port)], self._device)
+    forwarder.Forwarder.Map(
+        [(self.port, self.port)], self._device, self._tool)
     self._spawning_server.Start()
 
   #override
diff --git a/build/android/pylib/utils/gold_utils.py b/build/android/pylib/utils/gold_utils.py
index 9dc9fe3..316cdc0 100644
--- a/build/android/pylib/utils/gold_utils.py
+++ b/build/android/pylib/utils/gold_utils.py
@@ -8,6 +8,7 @@
 
 import os
 import shutil
+import time
 
 from devil.utils import cmd_helper
 from pylib.base.output_manager import Datatype
@@ -39,6 +40,10 @@
         diff_path = filepath
     results = self._comparison_results.setdefault(image_name,
                                                   self.ComparisonResults())
+    # We include the timestamp in the PNG filename so that multiple tries from
+    # the same run do not clobber each other.
+    timestamp = _GetTimestamp()
+    image_name = f'{image_name}_{timestamp}'
     if given_path:
       with output_manager.ArchivedTempfile('given_%s.png' % image_name,
                                            'gold_local_diffs',
@@ -65,6 +70,10 @@
     return rc, stdout
 
 
+def _GetTimestamp():
+  return time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime())
+
+
 class AndroidSkiaGoldSessionManager(
     skia_gold_session_manager.SkiaGoldSessionManager):
   @staticmethod
diff --git a/build/android/pylib/utils/gold_utils_test.py b/build/android/pylib/utils/gold_utils_test.py
index 8a9f8a3..1f1ec21a 100755
--- a/build/android/pylib/utils/gold_utils_test.py
+++ b/build/android/pylib/utils/gold_utils_test.py
@@ -86,8 +86,12 @@
     self.setUpPyfakefs()
     self._working_dir = tempfile.mkdtemp()
     self._json_keys = tempfile.NamedTemporaryFile(delete=False).name
+    self._timestamp_patcher = mock.patch.object(gold_utils, '_GetTimestamp')
+    self._timestamp_mock = self._timestamp_patcher.start()
+    self.addCleanup(self._timestamp_patcher.stop)
 
   def test_outputManagerUsed(self):
+    self._timestamp_mock.return_value = 'ts0'
     args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=True)
     sgp = gold_utils.AndroidSkiaGoldProperties(args)
     session = gold_utils.AndroidSkiaGoldSession(self._working_dir, sgp,
@@ -103,9 +107,10 @@
     output_manager = AndroidSkiaGoldSessionDiffLinksTest.FakeOutputManager()
     session._StoreDiffLinks('foo', output_manager, self._working_dir)
 
-    copied_input = os.path.join(output_manager.output_dir, 'given_foo.png')
-    copied_closest = os.path.join(output_manager.output_dir, 'closest_foo.png')
-    copied_diff = os.path.join(output_manager.output_dir, 'diff_foo.png')
+    copied_input = os.path.join(output_manager.output_dir, 'given_foo_ts0.png')
+    copied_closest = os.path.join(output_manager.output_dir,
+                                  'closest_foo_ts0.png')
+    copied_diff = os.path.join(output_manager.output_dir, 'diff_foo_ts0.png')
     with open(copied_input) as f:
       self.assertEqual(f.read(), 'input')
     with open(copied_closest) as f:
@@ -118,6 +123,91 @@
                      'file://' + copied_closest)
     self.assertEqual(session.GetDiffImageLink('foo'), 'file://' + copied_diff)
 
+  def test_diffLinksDoNotClobber(self):
+    """Tests that multiple calls to store links does not clobber files."""
+
+    def side_effect():
+      side_effect.count += 1
+      return f'ts{side_effect.count}'
+
+    side_effect.count = -1
+    self._timestamp_mock.side_effect = side_effect
+
+    args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=True)
+    sgp = gold_utils.AndroidSkiaGoldProperties(args)
+    session = gold_utils.AndroidSkiaGoldSession(self._working_dir, sgp,
+                                                self._json_keys, None, None)
+    with open(os.path.join(self._working_dir, 'input-inputhash.png'), 'w') as f:
+      f.write('first input')
+    with open(os.path.join(self._working_dir, 'closest-closesthash.png'),
+              'w') as f:
+      f.write('first closest')
+    with open(os.path.join(self._working_dir, 'diff.png'), 'w') as f:
+      f.write('first diff')
+
+    output_manager = AndroidSkiaGoldSessionDiffLinksTest.FakeOutputManager()
+    session._StoreDiffLinks('foo', output_manager, self._working_dir)
+
+    # Store links normally once.
+    first_copied_input = os.path.join(output_manager.output_dir,
+                                      'given_foo_ts0.png')
+    first_copied_closest = os.path.join(output_manager.output_dir,
+                                        'closest_foo_ts0.png')
+    first_copied_diff = os.path.join(output_manager.output_dir,
+                                     'diff_foo_ts0.png')
+    with open(first_copied_input) as f:
+      self.assertEqual(f.read(), 'first input')
+    with open(first_copied_closest) as f:
+      self.assertEqual(f.read(), 'first closest')
+    with open(first_copied_diff) as f:
+      self.assertEqual(f.read(), 'first diff')
+
+    with open(os.path.join(self._working_dir, 'input-inputhash.png'), 'w') as f:
+      f.write('second input')
+    with open(os.path.join(self._working_dir, 'closest-closesthash.png'),
+              'w') as f:
+      f.write('second closest')
+    with open(os.path.join(self._working_dir, 'diff.png'), 'w') as f:
+      f.write('second diff')
+
+    self.assertEqual(session.GetGivenImageLink('foo'),
+                     'file://' + first_copied_input)
+    self.assertEqual(session.GetClosestImageLink('foo'),
+                     'file://' + first_copied_closest)
+    self.assertEqual(session.GetDiffImageLink('foo'),
+                     'file://' + first_copied_diff)
+
+    # Store links again and check that the new data is surfaced.
+    session._StoreDiffLinks('foo', output_manager, self._working_dir)
+
+    second_copied_input = os.path.join(output_manager.output_dir,
+                                       'given_foo_ts1.png')
+    second_copied_closest = os.path.join(output_manager.output_dir,
+                                         'closest_foo_ts1.png')
+    second_copied_diff = os.path.join(output_manager.output_dir,
+                                      'diff_foo_ts1.png')
+    with open(second_copied_input) as f:
+      self.assertEqual(f.read(), 'second input')
+    with open(second_copied_closest) as f:
+      self.assertEqual(f.read(), 'second closest')
+    with open(second_copied_diff) as f:
+      self.assertEqual(f.read(), 'second diff')
+
+    self.assertEqual(session.GetGivenImageLink('foo'),
+                     'file://' + second_copied_input)
+    self.assertEqual(session.GetClosestImageLink('foo'),
+                     'file://' + second_copied_closest)
+    self.assertEqual(session.GetDiffImageLink('foo'),
+                     'file://' + second_copied_diff)
+
+    # Check to make sure the first images still exist on disk and are unchanged.
+    with open(first_copied_input) as f:
+      self.assertEqual(f.read(), 'first input')
+    with open(first_copied_closest) as f:
+      self.assertEqual(f.read(), 'first closest')
+    with open(first_copied_diff) as f:
+      self.assertEqual(f.read(), 'first diff')
+
 
 if __name__ == '__main__':
   unittest.main(verbosity=2)
diff --git a/build/android/pylib/valgrind_tools.py b/build/android/pylib/valgrind_tools.py
new file mode 100644
index 0000000..8c00705b
--- /dev/null
+++ b/build/android/pylib/valgrind_tools.py
@@ -0,0 +1,116 @@
+# Copyright 2012 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# pylint: disable=R0201
+
+
+
+
+import logging
+import sys
+
+from devil.android import device_errors
+from devil.android.valgrind_tools import base_tool
+
+
+def SetChromeTimeoutScale(device, scale):
+  """Sets the timeout scale in /data/local/tmp/chrome_timeout_scale to scale."""
+  path = '/data/local/tmp/chrome_timeout_scale'
+  if not scale or scale == 1.0:
+    # Delete if scale is None/0.0/1.0 since the default timeout scale is 1.0
+    device.RemovePath(path, force=True, as_root=True)
+  else:
+    device.WriteFile(path, '%f' % scale, as_root=True)
+
+
+
+class AddressSanitizerTool(base_tool.BaseTool):
+  """AddressSanitizer tool."""
+
+  WRAPPER_NAME = '/system/bin/asanwrapper'
+  # Disable memcmp overlap check.There are blobs (gl drivers)
+  # on some android devices that use memcmp on overlapping regions,
+  # nothing we can do about that.
+  EXTRA_OPTIONS = 'strict_memcmp=0,use_sigaltstack=1'
+
+  def __init__(self, device):
+    super().__init__()
+    self._device = device
+
+  @classmethod
+  def CopyFiles(cls, device):
+    """Copies ASan tools to the device."""
+    del device
+
+  def GetTestWrapper(self):
+    return AddressSanitizerTool.WRAPPER_NAME
+
+  def GetUtilWrapper(self):
+    """Returns the wrapper for utilities, such as forwarder.
+
+    AddressSanitizer wrapper must be added to all instrumented binaries,
+    including forwarder and the like. This can be removed if such binaries
+    were built without instrumentation. """
+    return self.GetTestWrapper()
+
+  def SetupEnvironment(self):
+    try:
+      self._device.EnableRoot()
+    except device_errors.CommandFailedError as e:
+      # Try to set the timeout scale anyway.
+      # TODO(jbudorick) Handle this exception appropriately after interface
+      #                 conversions are finished.
+      logging.error(str(e))
+    SetChromeTimeoutScale(self._device, self.GetTimeoutScale())
+
+  def CleanUpEnvironment(self):
+    SetChromeTimeoutScale(self._device, None)
+
+  def GetTimeoutScale(self):
+    # Very slow startup.
+    return 20.0
+
+
+TOOL_REGISTRY = {
+    'asan': AddressSanitizerTool,
+}
+
+
+def CreateTool(tool_name, device):
+  """Creates a tool with the specified tool name.
+
+  Args:
+    tool_name: Name of the tool to create.
+    device: A DeviceUtils instance.
+  Returns:
+    A tool for the specified tool_name.
+  """
+  if not tool_name:
+    return base_tool.BaseTool()
+
+  ctor = TOOL_REGISTRY.get(tool_name)
+  if ctor:
+    return ctor(device)
+  print('Unknown tool %s, available tools: %s' %
+        (tool_name, ', '.join(sorted(TOOL_REGISTRY.keys()))))
+  sys.exit(1)
+
+
+def PushFilesForTool(tool_name, device):
+  """Pushes the files required for |tool_name| to |device|.
+
+  Args:
+    tool_name: Name of the tool to create.
+    device: A DeviceUtils instance.
+  """
+  if not tool_name:
+    return
+
+  clazz = TOOL_REGISTRY.get(tool_name)
+  if clazz:
+    clazz.CopyFiles(device)
+  else:
+    print('Unknown tool %s, available tools: %s' % (tool_name, ', '.join(
+        sorted(TOOL_REGISTRY.keys()))))
+    sys.exit(1)
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index 8ec41cd..cb265e7 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -270,9 +270,6 @@
                       help='If present, store test results on this path.')
   parser.add_argument('--isolated-script-test-perf-output',
                       help='If present, store chartjson results on this path.')
-  parser.add_argument('--timeout-scale',
-                      type=float,
-                      help='Factor by which timeouts should be scaled.')
 
   AddTestLauncherOptions(parser)
 
@@ -332,7 +329,12 @@
       '--recover-devices',
       action='store_true',
       help='Attempt to recover devices prior to the final retry. Warning: '
-      'this will cause all devices to reboot.')
+           'this will cause all devices to reboot.')
+  parser.add_argument(
+      '--tool',
+      dest='tool',
+      help='Run the test under a tool '
+           '(use --tool help to list them)')
 
   parser.add_argument(
       '--upload-logcats-file',
@@ -649,6 +651,10 @@
             'a proxy for determining if the current run is a retry without '
             'patch.'))
   parser.add_argument(
+      '--timeout-scale',
+      type=float,
+      help='Factor by which timeouts should be scaled.')
+  parser.add_argument(
       '--is-unit-test',
       action='store_true',
       help=('Specify the test suite as composed of unit tests, blocking '
diff --git a/build/android/test_runner.pydeps b/build/android/test_runner.pydeps
index b58e857..38b854f 100644
--- a/build/android/test_runner.pydeps
+++ b/build/android/test_runner.pydeps
@@ -228,5 +228,6 @@
 pylib/utils/repo_utils.py
 pylib/utils/test_filter.py
 pylib/utils/time_profile.py
+pylib/valgrind_tools.py
 test_runner.py
 tombstones.py
diff --git a/build/config/android/config.gni b/build/config/android/config.gni
index 69ff4c96..fa0de90 100644
--- a/build/config/android/config.gni
+++ b/build/config/android/config.gni
@@ -28,7 +28,6 @@
   import("//build/config/android/channel.gni")
   import("//build/config/clang/clang.gni")
   import("//build/config/dcheck_always_on.gni")
-  import("//build/config/sanitizers/sanitizers.gni")
   import("//build/toolchain/siso.gni")
   import("//build_overrides/build.gni")
   import("abi.gni")
@@ -68,9 +67,6 @@
     # The default to use for android:minSdkVersion for targets that do
     # not explicitly set it.
     default_min_sdk_version = 26
-    if (is_asan) {
-      default_min_sdk_version = 27
-    }
 
     # Static analysis can be either "on" or "off" or "build_server". This
     # controls how android lint, error-prone, bytecode checks are run. This
@@ -93,15 +89,6 @@
   # Our build system no longer supports legacy multidex.
   min_supported_sdk_version = 21
 
-  # ASAN requireds O MR1.
-  # https://github.com/google/sanitizers/wiki/AddressSanitizerOnAndroid/01f8df1ac1a447a8475cdfcb03e8b13140042dbd#running-with-wrapsh-recommended
-  if (is_asan) {
-    min_supported_sdk_version = 27
-
-    # Disable lint since increasing min_sdk_version can cause ObsoleteSdkInt warnings.
-    disable_android_lint = true
-  }
-
   assert(
       default_min_sdk_version >= min_supported_sdk_version,
       "default_min_sdk_version ($default_min_sdk_version) must be >= min_supported_sdk_version ($min_supported_sdk_version)")
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 1fe9c9c..fcfe27c 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -990,7 +990,7 @@
       executable_args += [ "--fast-local-dev" ]
     }
     if (_device_test && is_asan) {
-      executable_args += [ "--timeout-scale=4" ]
+      executable_args += [ "--tool=asan" ]
     }
 
     if (defined(invoker.modules)) {
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 299fb07e..a5032f40 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -33,9 +33,6 @@
   if (use_cfi_diag || is_ubsan || is_ubsan_security || is_ubsan_vptr) {
     _sanitizer_runtimes += [ "$clang_base_path/lib/clang/$clang_version/lib/linux/libclang_rt.ubsan_standalone-$sanitizer_arch-android.so" ]
   }
-  if (is_asan) {
-    _sanitizer_runtimes += [ "$clang_base_path/lib/clang/$clang_version/lib/linux/libclang_rt.asan-$sanitizer_arch-android.so" ]
-  }
 
   # Creates a dist directory for a native executable.
   #
@@ -2106,9 +2103,6 @@
     if (defined(invoker.min_sdk_version)) {
       _min_sdk_version = invoker.min_sdk_version
     }
-    if (is_asan && _min_sdk_version < min_supported_sdk_version) {
-      _min_sdk_version = min_supported_sdk_version
-    }
     if (defined(invoker.target_sdk_version)) {
       _target_sdk_version = invoker.target_sdk_version
     }
@@ -2586,16 +2580,9 @@
     if (defined(invoker.loadable_modules)) {
       _loadable_modules = invoker.loadable_modules
     }
-    _sanitizer_loadable_modules = []
-    _sanitizer_deps = []
-    if (_is_base_module && _native_libs_deps != [] && !_uses_static_library) {
-      _sanitizer_loadable_modules += _sanitizer_runtimes
-    }
-    if (is_asan && _is_base_module &&
-        (_uses_static_library || _native_libs_deps != [])) {
-      _sanitizer_loadable_modules +=
-          [ "$root_gen_dir/build/android/generate_wrap_sh/wrap.sh" ]
-      _sanitizer_deps += [ "//build/android:generate_wrap_sh" ]
+
+    if (_native_libs_deps != []) {
+      _loadable_modules += _sanitizer_runtimes
     }
 
     _assertions_implicitly_enabled = defined(invoker.custom_assertion_handler)
@@ -2744,11 +2731,7 @@
             _secondary_abi_shared_library_list_file
       }
 
-      if (!defined(deps)) {
-        deps = []
-      }
-      deps += _sanitizer_deps
-      loadable_modules = _loadable_modules + _sanitizer_loadable_modules
+      loadable_modules = _loadable_modules
 
       if (defined(_allowlist_r_txt_path) && _is_bundle_module) {
         # Used to write the file path to the target's .build_config.json only.
@@ -2957,7 +2940,7 @@
 
         # Need full deps rather than _non_java_deps, because loadable_modules
         # may include .so files extracted by __unpack_aar targets.
-        deps = _invoker_deps + _sanitizer_deps + [ ":$_build_config_target" ]
+        deps = _invoker_deps + [ ":$_build_config_target" ]
         if (defined(invoker.asset_deps)) {
           deps += invoker.asset_deps
         }
@@ -2976,19 +2959,15 @@
           # should be clearly named/labeled "incremental".
           output_apk_path = _incremental_apk_path
 
-          loadable_modules = _sanitizer_loadable_modules
-
           # All native libraries are side-loaded, so use a placeholder to force
           # the proper bitness for the app.
           _has_native_libs =
-              defined(_native_libs_filearg) || _loadable_modules != [] ||
-              _sanitizer_loadable_modules != []
-          if (_has_native_libs && loadable_modules == [] &&
-              !defined(native_lib_placeholders)) {
+              defined(_native_libs_filearg) || _loadable_modules != []
+          if (_has_native_libs && !defined(native_lib_placeholders)) {
             native_lib_placeholders = [ "libfix.crbug.384638.so" ]
           }
         } else {
-          loadable_modules = _loadable_modules + _sanitizer_loadable_modules
+          loadable_modules = _loadable_modules
           deps += _all_native_libs_deps + [
                     ":$_compile_resources_target",
                     ":$_merge_manifest_target",
@@ -4516,9 +4495,6 @@
     if (defined(invoker.min_sdk_version)) {
       _min_sdk_version = invoker.min_sdk_version
     }
-    if (is_asan && _min_sdk_version < min_supported_sdk_version) {
-      _min_sdk_version = min_supported_sdk_version
-    }
 
     _bundle_base_path = "$root_build_dir/apks"
     if (defined(invoker.bundle_base_path)) {
diff --git a/chrome/VERSION b/chrome/VERSION
index eeb22ec..921fe9d 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=126
 MINOR=0
-BUILD=6437
+BUILD=6438
 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryAdapter.java
index 0ff7161..62902b2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryAdapter.java
@@ -98,6 +98,7 @@
         mAreHeadersInitialized = false;
         mIsLoadingItems = true;
         mClearOnNextQueryComplete = true;
+        setAppId(null);
         if (mHostName != null) {
             mHistoryProvider.queryHistoryForHost(mHostName);
         } else {
@@ -164,6 +165,7 @@
     public void onEndSearch() {
         mQueryText = EMPTY_QUERY;
         mIsSearching = false;
+        if (mAppFilterChip != null) resetAppFilterChip();
 
         // Re-initialize the data in the adapter.
         startLoadingItems();
@@ -402,10 +404,7 @@
 
     void updateHistory(AppInfo appInfo) {
         if (appInfo == null) {
-            mAppId = null;
-            mAppFilterChip.getPrimaryTextView().setText(R.string.history_filter_by_app);
-            mAppFilterChip.setSelected(false);
-            mAppFilterChip.setIcon(ChipView.INVALID_ICON_ID, false);
+            resetAppFilterChip();
         } else {
             mAppId = appInfo.id;
             mAppFilterChip.getPrimaryTextView().setText(appInfo.label);
@@ -415,6 +414,13 @@
         search(EMPTY_QUERY);
     }
 
+    private void resetAppFilterChip() {
+        setAppId(null);
+        mAppFilterChip.getPrimaryTextView().setText(R.string.history_filter_by_app);
+        mAppFilterChip.setSelected(false);
+        mAppFilterChip.setIcon(ChipView.INVALID_ICON_ID, false);
+    }
+
     ViewGroup getPrivacyDisclaimerContainer(ViewGroup parent) {
         Context context = mManager.getContext();
         ViewGroup privacyDisclaimerContainer =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryContentManager.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryContentManager.java
index 81870af..6bd063b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryContentManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryContentManager.java
@@ -579,6 +579,7 @@
 
     /** Called when a search is ended. */
     public void onEndSearch() {
+        mCurrentApp = null;
         mHistoryAdapter.onEndSearch();
     }
 
@@ -620,7 +621,7 @@
                             this::onAppUpdated,
                             mAppInfoList);
         }
-        mAppFilterSheet.openSheet();
+        mAppFilterSheet.openSheet(mCurrentApp);
     }
 
     /** Callback from app filter sheet, with the newly chosen app to filter. */
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/history/HistoryUITest.java b/chrome/android/junit/src/org/chromium/chrome/browser/history/HistoryUITest.java
index d046beed..c595c12 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/history/HistoryUITest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/history/HistoryUITest.java
@@ -12,6 +12,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.verify;
 import static org.robolectric.Shadows.shadowOf;
 
@@ -500,7 +501,7 @@
         mAdapter.onQueryAppsComplete(result);
 
         mContentManager.onAppFilterClicked();
-        verify(mAppFilterSheet).openSheet();
+        verify(mAppFilterSheet).openSheet(eq(null));
 
         // Verify ContentManager is updated with the selected app info.
         AppInfo selected = new AppInfo("Ernie", null, appId1);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java
index 6f23e34..4059bf1 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java
@@ -57,6 +57,8 @@
 
         MediaSessionHelper.sOverriddenMediaSession = mMediaSession;
         mMediaSessionTabHelper = new MediaSessionTabHelper(mTab);
+        mMediaSessionTabHelper.mMediaSessionHelper.mWebContentsObserver.mediaSessionCreated(
+                mMediaSession);
         mMediaSessionTabHelper.mMediaSessionHelper.mLargeIconBridge = new TestLargeIconBridge();
 
         simulateNavigation(url, false);
diff --git a/chrome/android/profiles/arm.newest.txt b/chrome/android/profiles/arm.newest.txt
index b43934d..00b49e9 100644
--- a/chrome/android/profiles/arm.newest.txt
+++ b/chrome/android/profiles/arm.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-126.0.6434.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-arm-126.0.6434.0_rc-r2-merged.afdo.bz2
diff --git a/chrome/app/app_management_strings.grdp b/chrome/app/app_management_strings.grdp
index f130449..d785ebfe 100644
--- a/chrome/app/app_management_strings.grdp
+++ b/chrome/app/app_management_strings.grdp
@@ -177,6 +177,24 @@
   <message name="IDS_APP_MANAGEMENT_PERMISSION_DENIED" desc="Label explaining that access to a permission (e.g. camera access) is denied for an app.">
     Denied
   </message>
+  <message name="IDS_APP_MANAGEMENT_PERMISSION_ALLOWED_TEXT_WITH_TURN_ON_SYSTEM_CAMERA_ACCESS_BUTTON" desc="Label explaining that access to camera is allowed for an app followed by a button to turn on system camera access." translateable="false">
+    Allowed. Turn on <ph name="LINK_BEGIN">&lt;a href="#"&gt;</ph>system camera access<ph name="LINK_END">&lt;/a&gt;</ph>.
+  </message>
+  <message name="IDS_APP_MANAGEMENT_PERMISSION_ALLOWED_TEXT_WITH_TURN_ON_SYSTEM_MICROPHONE_ACCESS_BUTTON" desc="Label explaining that access to microphone is allowed for an app followed by a button to turn on system microphone access." translateable="false">
+    Allowed. Turn on <ph name="LINK_BEGIN">&lt;a href="#"&gt;</ph>system microphone access<ph name="LINK_END">&lt;/a&gt;</ph>.
+  </message>
+  <message name="IDS_APP_MANAGEMENT_PERMISSION_ALLOWED_TEXT_WITH_TURN_ON_SYSTEM_LOCATION_ACCESS_BUTTON" desc="Label explaining that access to location is allowed for an app followed by a button to turn on system location access." translateable="false">
+    Allowed. Turn on <ph name="LINK_BEGIN">&lt;a href="#"&gt;</ph>system location access<ph name="LINK_END">&lt;/a&gt;</ph>.
+  </message>
+  <message name="IDS_APP_MANAGEMENT_PERMISSION_ALLOWED_TEXT_WITH_DETAILS_AND_TURN_ON_SYSTEM_CAMERA_ACCESS_BUTTON" desc="Label explaining that access to camera is allowed for an app followed by a button to turn on system camera access." translateable="false">
+    Allowed – <ph name="PERMISSION_DETAILS">$1<ex>While in use</ex></ph>. Turn on <ph name="LINK_BEGIN">&lt;a href="#"&gt;</ph>system camera access<ph name="LINK_END">&lt;/a&gt;</ph>.
+  </message>
+  <message name="IDS_APP_MANAGEMENT_PERMISSION_ALLOWED_TEXT_WITH_DETAILS_AND_TURN_ON_SYSTEM_MICROPHONE_ACCESS_BUTTON" desc="Label explaining that access to microphone is allowed for an app followed by a button to turn on system microphone access." translateable="false">
+    Allowed – <ph name="PERMISSION_DETAILS">$1<ex>While in use</ex></ph>. Turn on <ph name="LINK_BEGIN">&lt;a href="#"&gt;</ph>system microphone access<ph name="LINK_END">&lt;/a&gt;</ph>.
+  </message>
+  <message name="IDS_APP_MANAGEMENT_PERMISSION_ALLOWED_TEXT_WITH_DETAILS_AND_TURN_ON_SYSTEM_LOCATION_ACCESS_BUTTON" desc="Label explaining that access to location is allowed for an app followed by a button to turn on system location access." translateable="false">
+    Allowed – <ph name="PERMISSION_DETAILS">$1<ex>While in use</ex></ph>. Turn on <ph name="LINK_BEGIN">&lt;a href="#"&gt;</ph>system location access<ph name="LINK_END">&lt;/a&gt;</ph>.
+  </message>
 
   <!-- File Handling -->
   <message name="IDS_APP_MANAGEMENT_FILE_HANDLING_HEADER" desc="Main text for toggling a web app's ability to use the File Handling API. This controls whether the app can appear in an 'Open With' list in a file's context menu.">
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index e69c7cc..d80d4fa 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -6743,6 +6743,30 @@
   <message name="IDS_OS_SETTINGS_PRIVACY_HUB_MICROPHONE_TOGGLE_NO_MICROPHONE_CONNECTED_TOOLTIP_TEXT" desc="Text displayed in the privacy hub subpage microphone toggle button tooltip when no microphone is connected to the device.">
     No microphone connected
   </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_ALLOW_CAMERA_ACCESS_DIALOG_TITLE" desc="The title of the dialog that asks users to enable system wide camera access." translateable="false">
+    Turn on camera access?
+  </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_ALLOW_LOCATION_ACCESS_DIALOG_TITLE" desc="The title of the dialog that asks users to enable system wide location access." translateable="false">
+    Turn on location access?
+  </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_ALLOW_MICROPHONE_ACCESS_DIALOG_TITLE" desc="The title of the dialog that asks users to enable system wide microphone access." translateable="false">
+    Turn on microphone access?
+  </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_ALLOW_CAMERA_ACCESS_DIALOG_BODY_TEXT" desc="The text displayed on the body of the dialog that asks users to enable system wide camera access." translateable="false">
+    This allows camera access for apps, websites with the camera permission, and system services
+  </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_ALLOW_LOCATION_ACCESS_DIALOG_BODY_TEXT" desc="The text displayed on the body of the dialog that asks users to enable system wide location access." translateable="false">
+    This allows location access for apps, websites with the location permission, and system services
+  </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_ALLOW_MICROPHONE_ACCESS_DIALOG_BODY_TEXT" desc="The text displayed on the body of the dialog that asks users to enable system wide microphone access." translateable="false">
+    This allows microphone access for apps, websites with the microphone permission, and system services
+  </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_DIALOG_CONFIRM_BUTTON_LABEL" desc="Label of the confirm button for the allow sensor access dialog." translateable="false">
+    Allow
+  </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_DIALOG_CANCEL_BUTTON_LABEL" desc="Label of the cancel button for the allow sensor access dialog." translateable="false">
+    Cancel
+  </message>
 
   <!-- On Startup (OS settings) -->
   <message name="IDS_OS_SETTINGS_ON_STARTUP_SETTINGS_CARD_TITLE" desc="The title of the settings card that allows the user to configure the restore apps and pages options on startup.">
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 5ae900db..b9acef7 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3980,8 +3980,6 @@
       "hid/web_hid_histograms.h",
       "hid/web_view_chooser_context.cc",
       "hid/web_view_chooser_context.h",
-      "icon_transcoder/svg_icon_transcoder.cc",
-      "icon_transcoder/svg_icon_transcoder.h",
       "importer/external_process_importer_client.cc",
       "importer/external_process_importer_client.h",
       "importer/external_process_importer_host.cc",
@@ -4643,6 +4641,7 @@
       "//chrome/browser/enterprise/connectors/analysis:features",
       "//chrome/browser/enterprise/connectors/device_trust:prefs",
       "//chrome/browser/enterprise/signals:utils",
+      "//chrome/browser/icon_transcoder",
       "//chrome/browser/image_editor:image_editor_component_util",
       "//chrome/browser/media/router/discovery/access_code:access_code_sink_service",
       "//chrome/browser/new_tab_page/chrome_colors:generate_chrome_colors_info",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 1c373fef..73e0988c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3102,8 +3102,6 @@
 constexpr char kWallpaperGooglePhotosSharedAlbumsInternalName[] =
     "wallpaper-google-photos-shared-albums";
 constexpr char kWallpaperPerDeskName[] = "per-desk-wallpaper";
-constexpr char kTimeOfDayWallpaperForcedAutoScheduleInternalName[] =
-    "time-of-day-wallpaper-forced-auto-schedule";
 constexpr char kTimeOfDayDlcInternalName[] = "time-of-day-dlc";
 constexpr char kGlanceablesV2InternalName[] = "glanceables-v2";
 constexpr char kGlanceablesV2KeyName[] = "glanceables-v2-key";
@@ -4394,11 +4392,6 @@
     {"passpoint-settings", flag_descriptions::kPasspointSettingsName,
      flag_descriptions::kPasspointSettingsDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kPasspointSettings)},
-    {kTimeOfDayWallpaperForcedAutoScheduleInternalName,
-     flag_descriptions::kTimeOfDayWallpaperForcedAutoScheduleName,
-     flag_descriptions::kTimeOfDayWallpaperForcedAutoScheduleDescription,
-     kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kTimeOfDayWallpaperForcedAutoSchedule)},
     {kTimeOfDayDlcInternalName, flag_descriptions::kTimeOfDayDlcName,
      flag_descriptions::kTimeOfDayDlcDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kTimeOfDayDlc)},
diff --git a/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler.cc b/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler.cc
index 9048ba1..5a83855 100644
--- a/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler.cc
+++ b/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler.cc
@@ -23,8 +23,7 @@
 #include "base/types/to_address.h"
 #include "chrome/browser/accessibility/accessibility_state_utils.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/screen_ai/screen_ai_service_router.h"
-#include "chrome/browser/screen_ai/screen_ai_service_router_factory.h"
+#include "chrome/browser/screen_ai/optical_character_recognizer.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -67,32 +66,23 @@
   if (!base::FeatureList::IsEnabled(ash::features::kMediaAppPdfA11yOcr)) {
     return;
   }
-  screen_ai::ScreenAIServiceRouterFactory::GetForBrowserContext(
-      base::to_address(browser_context_))
-      ->GetServiceStateAsync(
-          screen_ai::ScreenAIServiceRouter::Service::kOCR,
-          base::BindOnce(&AXMediaAppUntrustedHandler::OnOCRServiceInitialized,
-                         weak_ptr_factory_.GetWeakPtr()));
+  ocr_ = screen_ai::OpticalCharacterRecognizer::CreateWithStatusCallback(
+      Profile::FromBrowserContext(base::to_address(browser_context_)),
+      base::BindOnce(&AXMediaAppUntrustedHandler::OnOCRServiceInitialized,
+                     weak_ptr_factory_.GetWeakPtr()));
   ax_mode_observation_.Observe(&ui::AXPlatform::GetInstance());
 }
 
 AXMediaAppUntrustedHandler::~AXMediaAppUntrustedHandler() = default;
 
 bool AXMediaAppUntrustedHandler::IsOcrServiceEnabled() const {
-  return screen_ai_annotator_.is_bound();
+  return ocr_->is_ready();
 }
 
 void AXMediaAppUntrustedHandler::OnOCRServiceInitialized(bool successful) {
   if (!successful) {
     return;
   }
-  // This is expected to be called only once.
-  CHECK(!screen_ai_annotator_.is_bound());
-  screen_ai::ScreenAIServiceRouter* service_router =
-      screen_ai::ScreenAIServiceRouterFactory::GetForBrowserContext(
-          base::to_address(browser_context_));
-  service_router->BindScreenAIAnnotator(
-      screen_ai_annotator_.BindNewPipeAndPassReceiver());
   if (!dirty_page_ids_.empty()) {
     OcrNextDirtyPageIfAny();
   }
@@ -671,7 +661,7 @@
     // the ENABLE_SCREEN_AI_SERVICE buildflag. We should figure out a way to
     // mock it in tests running on bots without this flag and call
     // OnBitmapReceived() here.
-    screen_ai_annotator_->PerformOcrAndReturnAXTreeUpdate(
+    ocr_->PerformOCR(
         page_bitmap,
         base::BindOnce(&AXMediaAppUntrustedHandler::OnPageOcred,
                        weak_ptr_factory_.GetWeakPtr(), dirty_page_id));
@@ -690,7 +680,7 @@
   // value which shows up as an empty bitmap here. To prevent the entire app
   // crashing just because one page failed to render, send it to ScreenAI
   // anyway, which should just produce an empty A11y tree.
-  screen_ai_annotator_->PerformOcrAndReturnAXTreeUpdate(
+  ocr_->PerformOCR(
       bitmap, base::BindOnce(&AXMediaAppUntrustedHandler::OnPageOcred,
                              weak_ptr_factory_.GetWeakPtr(), dirty_page_id));
 }
diff --git a/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler.h b/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler.h
index 7b3f399a..540b1c6 100644
--- a/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler.h
+++ b/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler.h
@@ -25,7 +25,6 @@
 #include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "services/screen_ai/public/mojom/screen_ai_service.mojom.h"
 #include "ui/accessibility/ax_action_handler_base.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_mode.h"
@@ -50,6 +49,10 @@
 
 }  // namespace content
 
+namespace screen_ai {
+class OpticalCharacterRecognizer;
+}
+
 namespace ui {
 
 struct AXActionData;
@@ -123,7 +126,7 @@
       page_serializers_;
   std::unique_ptr<std::vector<const ui::AXTreeUpdate>>
       pending_serialized_updates_for_testing_;
-  mojo::Remote<screen_ai::mojom::ScreenAIAnnotator> screen_ai_annotator_;
+  scoped_refptr<screen_ai::OpticalCharacterRecognizer> ocr_;
 
  private:
   void SendAXTreeToAccessibilityService(const ui::AXTreeManager& manager,
diff --git a/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler_browsertest.cc b/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler_browsertest.cc
index 3341d18..df664e09 100644
--- a/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler_browsertest.cc
+++ b/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler_browsertest.cc
@@ -43,7 +43,6 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/test_support/test_utils.h"
 #include "services/screen_ai/public/mojom/screen_ai_service.mojom.h"
-#include "services/screen_ai/public/test/fake_screen_ai_annotator.h"
 #include "ui/accessibility/ax_action_data.h"
 #include "ui/accessibility/ax_event_generator.h"
 #include "ui/accessibility/ax_node.h"
@@ -98,8 +97,8 @@
     handler_->SetMediaAppForTesting(&fake_media_app_);
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
     handler_->SetIsOcrServiceEnabledForTesting();
-    handler_->SetScreenAIAnnotatorForTesting(
-        fake_annotator_.BindNewPipeAndPassRemote());
+    handler_->CreateFakeOpticalCharacterRecognizerForTesting(
+        /*return_empty=*/false);
 #endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
   }
 
@@ -120,10 +119,6 @@
 
   FakeAXMediaApp fake_media_app_;
   std::unique_ptr<TestAXMediaAppUntrustedHandler> handler_;
-#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
-  screen_ai::test::FakeScreenAIAnnotator fake_annotator_{
-      /*create_empty_result=*/false};
-#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 
  private:
   base::test::ScopedFeatureList feature_list_;
diff --git a/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler_unittest.cc b/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler_unittest.cc
index 6dd8e09..681e8b2 100644
--- a/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler_unittest.cc
+++ b/chrome/browser/accessibility/media_app/ax_media_app_untrusted_handler_unittest.cc
@@ -23,7 +23,6 @@
 
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 #include "chrome/browser/screen_ai/screen_ai_install_state.h"
-#include "services/screen_ai/public/test/fake_screen_ai_annotator.h"
 #endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 
 namespace ash::test {
@@ -69,8 +68,8 @@
 
     handler_->SetMediaAppForTesting(&fake_media_app_);
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
-    handler_->SetScreenAIAnnotatorForTesting(
-        fake_annotator_.BindNewPipeAndPassRemote());
+    handler_->CreateFakeOpticalCharacterRecognizerForTesting(
+        /*return_empty=*/true);
 #endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
     ASSERT_NE(nullptr, handler_.get());
   }
@@ -82,8 +81,6 @@
 
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
   TestScreenAIInstallState install_state_;
-  screen_ai::test::FakeScreenAIAnnotator fake_annotator_{
-      /*create_empty_result=*/true};
 #endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
   FakeAXMediaApp fake_media_app_;
   std::unique_ptr<TestAXMediaAppUntrustedHandler> handler_;
diff --git a/chrome/browser/accessibility/media_app/test/BUILD.gn b/chrome/browser/accessibility/media_app/test/BUILD.gn
index cb57e78..a1adb17 100644
--- a/chrome/browser/accessibility/media_app/test/BUILD.gn
+++ b/chrome/browser/accessibility/media_app/test/BUILD.gn
@@ -26,6 +26,6 @@
   ]
 
   if (enable_screen_ai_service) {
-    deps += [ "//services/screen_ai/public/mojom" ]
+    deps += [ "//chrome//browser/screen_ai/public:test_support" ]
   }
 }
diff --git a/chrome/browser/accessibility/media_app/test/test_ax_media_app_untrusted_handler.cc b/chrome/browser/accessibility/media_app/test/test_ax_media_app_untrusted_handler.cc
index 77756e8..2ae3d43 100644
--- a/chrome/browser/accessibility/media_app/test/test_ax_media_app_untrusted_handler.cc
+++ b/chrome/browser/accessibility/media_app/test/test_ax_media_app_untrusted_handler.cc
@@ -11,6 +11,10 @@
 #include "ui/accessibility/ax_tree_id.h"
 #include "ui/accessibility/ax_tree_update.h"
 
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+#include "chrome/browser/screen_ai/public/test/fake_optical_character_recognizer.h"
+#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+
 namespace ash::test {
 
 TestAXMediaAppUntrustedHandler::TestAXMediaAppUntrustedHandler(
@@ -35,15 +39,14 @@
 }
 
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
-void TestAXMediaAppUntrustedHandler::SetScreenAIAnnotatorForTesting(
-    mojo::PendingRemote<screen_ai::mojom::ScreenAIAnnotator>
-        screen_ai_annotator) {
-  screen_ai_annotator_.reset();
-  screen_ai_annotator_.Bind(std::move(screen_ai_annotator));
+void TestAXMediaAppUntrustedHandler::
+    CreateFakeOpticalCharacterRecognizerForTesting(bool return_empty) {
+  ocr_.reset();
+  ocr_ = screen_ai::FakeOpticalCharacterRecognizer::Create(return_empty);
 }
 
 void TestAXMediaAppUntrustedHandler::FlushForTesting() {
-  screen_ai_annotator_.FlushForTesting();  // IN-TEST
+  ocr_->FlushForTesting();  // IN-TEST
 }
 #endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 
diff --git a/chrome/browser/accessibility/media_app/test/test_ax_media_app_untrusted_handler.h b/chrome/browser/accessibility/media_app/test/test_ax_media_app_untrusted_handler.h
index 5d492526..2458278c 100644
--- a/chrome/browser/accessibility/media_app/test/test_ax_media_app_untrusted_handler.h
+++ b/chrome/browser/accessibility/media_app/test/test_ax_media_app_untrusted_handler.h
@@ -16,10 +16,6 @@
 #include "services/screen_ai/buildflags/buildflags.h"
 #include "ui/accessibility/ax_tree_manager.h"
 
-#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
-#include "services/screen_ai/public/mojom/screen_ai_service.mojom.h"
-#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
-
 namespace content {
 
 class BrowserContext;
@@ -79,9 +75,7 @@
   }
 
 #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
-  void SetScreenAIAnnotatorForTesting(
-      mojo::PendingRemote<screen_ai::mojom::ScreenAIAnnotator>
-          screen_ai_annotator);
+  void CreateFakeOpticalCharacterRecognizerForTesting(bool return_empty);
   void FlushForTesting();
 #endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 
diff --git a/chrome/browser/apps/app_service/BUILD.gn b/chrome/browser/apps/app_service/BUILD.gn
index 5a5bf95..0de7deb0 100644
--- a/chrome/browser/apps/app_service/BUILD.gn
+++ b/chrome/browser/apps/app_service/BUILD.gn
@@ -58,6 +58,7 @@
     "//chrome/browser/apps:icon_standardizer",
     "//chrome/browser/extensions",
     "//chrome/browser/favicon",
+    "//chrome/browser/icon_transcoder",
     "//chrome/browser/profiles",
     "//chrome/browser/profiles:profile",
     "//chrome/browser/resources:app_icon_resources",
diff --git a/chrome/browser/apps/app_service/app_install/app_install_service_ash.cc b/chrome/browser/apps/app_service/app_install/app_install_service_ash.cc
index 296cb53..7bc12bc 100644
--- a/chrome/browser/apps/app_service/app_install/app_install_service_ash.cc
+++ b/chrome/browser/apps/app_service/app_install/app_install_service_ash.cc
@@ -255,16 +255,7 @@
     return;
   }
 
-  if (absl::holds_alternative<AndroidAppInstallData>(data->app_type_data)) {
-    arc_app_installer_.InstallApp(surface, std::move(*data),
-                                  std::move(callback));
-  } else if (absl::holds_alternative<WebAppInstallData>(data->app_type_data)) {
-    web_app_installer_.InstallApp(surface, std::move(*data),
-                                  std::move(callback));
-  } else {
-    LOG(ERROR) << "Unsupported AppInstallData type";
-    std::move(callback).Run(false);
-  }
+  PerformInstall(surface, *data, std::move(callback));
 }
 
 void AppInstallServiceAsh::ShowDialogAndInstall(
@@ -346,7 +337,8 @@
     std::move(callback).Run(AppInstallResult::kInstallDialogNotAccepted);
     return;
   }
-  web_app_installer_.InstallApp(
+
+  PerformInstall(
       surface, data,
       base::BindOnce(&AppInstallServiceAsh::ProcessInstallResult,
                      weak_ptr_factory_.GetWeakPtr(), surface,
@@ -379,4 +371,20 @@
       std::move(data), dialog, std::move(callback)));
 }
 
+void AppInstallServiceAsh::PerformInstall(
+    AppInstallSurface surface,
+    AppInstallData data,
+    base::OnceCallback<void(bool)> install_callback) {
+  if (absl::holds_alternative<AndroidAppInstallData>(data.app_type_data)) {
+    arc_app_installer_.InstallApp(surface, std::move(data),
+                                  std::move(install_callback));
+  } else if (absl::holds_alternative<WebAppInstallData>(data.app_type_data)) {
+    web_app_installer_.InstallApp(surface, std::move(data),
+                                  std::move(install_callback));
+  } else {
+    LOG(ERROR) << "Unsupported AppInstallData type";
+    std::move(install_callback).Run(false);
+  }
+}
+
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/app_install/app_install_service_ash.h b/chrome/browser/apps/app_service/app_install/app_install_service_ash.h
index 04de47c0..5bcbd07 100644
--- a/chrome/browser/apps/app_service/app_install/app_install_service_ash.h
+++ b/chrome/browser/apps/app_service/app_install/app_install_service_ash.h
@@ -109,6 +109,13 @@
       base::OnceCallback<void(AppInstallResult)> callback,
       bool install_success);
 
+  // Installs the requested app from the given `data`. This method is
+  // called by both headless and dialog-based flows, and assumes that all
+  // relevant policy checks have already been completed.
+  void PerformInstall(AppInstallSurface surface,
+                      AppInstallData data,
+                      base::OnceCallback<void(bool)> install_callback);
+
   raw_ref<Profile> profile_;
   DeviceInfoManager device_info_manager_;
   AppInstallAlmanacConnector connector_;
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index d6c2bf1..15b237b1 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -1186,6 +1186,8 @@
     "file_manager/filesystem_api_util.h",
     "file_manager/fusebox_daemon.cc",
     "file_manager/fusebox_daemon.h",
+    "file_manager/indexing/augmented_term_table.cc",
+    "file_manager/indexing/augmented_term_table.h",
     "file_manager/indexing/file_index.h",
     "file_manager/indexing/file_index_service.cc",
     "file_manager/indexing/file_index_service.h",
@@ -4194,6 +4196,7 @@
     "//chrome/browser/devtools",
     "//chrome/browser/favicon",
     "//chrome/browser/google",
+    "//chrome/browser/icon_transcoder",
     "//chrome/browser/image_fetcher",
     "//chrome/browser/metrics/structured:features",
     "//chrome/browser/nearby_sharing/common",
@@ -5210,6 +5213,7 @@
     "../ui/webui/ash/settings/pages/a11y/pdf_ocr_handler_unittest.cc",
     "../ui/webui/ash/settings/pages/about/device_name_handler_unittest.cc",
     "../ui/webui/ash/settings/pages/apps/app_notification_handler_unittest.cc",
+    "../ui/webui/ash/settings/pages/apps/app_parental_controls_handler_unittest.cc",
     "../ui/webui/ash/settings/pages/apps/mojom/app_type_mojom_traits_unittest.cc",
     "../ui/webui/ash/settings/pages/bluetooth/bluetooth_handler_unittest.cc",
     "../ui/webui/ash/settings/pages/bluetooth/fast_pair_saved_devices_handler_unittest.cc",
diff --git a/chrome/browser/ash/app_list/search/local_image_search/image_annotation_worker.cc b/chrome/browser/ash/app_list/search/local_image_search/image_annotation_worker.cc
index 04293671..839cefa 100644
--- a/chrome/browser/ash/app_list/search/local_image_search/image_annotation_worker.cc
+++ b/chrome/browser/ash/app_list/search/local_image_search/image_annotation_worker.cc
@@ -215,7 +215,7 @@
     CHECK(profile);
     // `OpticalCharacterRecognizer` should be created on the UI thread.
     optical_character_recognizer_ =
-        base::MakeRefCounted<screen_ai::OpticalCharacterRecognizer>(profile);
+        screen_ai::OpticalCharacterRecognizer::Create(profile);
   }
 }
 
diff --git a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
index f6ed69d..4616497 100644
--- a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
@@ -26,6 +26,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "ash/constants/ash_switches.h"
+#include "ash/metrics/login_unlock_throughput_recorder.h"
 #include "ash/public/cpp/accelerators.h"
 #include "ash/public/cpp/ambient/ambient_prefs.h"
 #include "ash/public/cpp/ambient/ambient_ui_model.h"
@@ -1494,6 +1495,33 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// AutotestPrivateWaitForLoginAnimationEndFunction
+///////////////////////////////////////////////////////////////////////////////
+
+AutotestPrivateWaitForLoginAnimationEndFunction::
+    ~AutotestPrivateWaitForLoginAnimationEndFunction() = default;
+
+ExtensionFunction::ResponseAction
+AutotestPrivateWaitForLoginAnimationEndFunction::Run() {
+  DVLOG(1) << "AutotestPrivateWaitForLoginAnimationEndFunction";
+  ash::Shell::Get()
+      ->login_unlock_throughput_recorder()
+      ->post_login_deferred_task_runner()
+      ->PostTask(
+          FROM_HERE,
+          base::BindOnce(&AutotestPrivateWaitForLoginAnimationEndFunction::
+                             OnLoginAnimationEnd,
+                         this));
+  return RespondLater();
+}
+
+void AutotestPrivateWaitForLoginAnimationEndFunction::OnLoginAnimationEnd() {
+  DVLOG(1)
+      << "AutotestPrivateWaitForLoginAnimationEndFunction::OnLoginAnimationEnd";
+  Respond(NoArguments());
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // AutotestPrivateLockScreenFunction
 ///////////////////////////////////////////////////////////////////////////////
 
diff --git a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.h b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.h
index 9fa3e10..bc47ac7 100644
--- a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.h
+++ b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.h
@@ -113,6 +113,19 @@
   ResponseAction Run() override;
 };
 
+class AutotestPrivateWaitForLoginAnimationEndFunction
+    : public ExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("autotestPrivate.waitForLoginAnimationEnd",
+                             AUTOTESTPRIVATE_WAITFORLOGINANIMATIONEND)
+
+ private:
+  ~AutotestPrivateWaitForLoginAnimationEndFunction() override;
+  ResponseAction Run() override;
+
+  void OnLoginAnimationEnd();
+};
+
 class AutotestPrivateLockScreenFunction : public ExtensionFunction {
  public:
   DECLARE_EXTENSION_FUNCTION("autotestPrivate.lockScreen",
diff --git a/chrome/browser/ash/file_manager/DEPS b/chrome/browser/ash/file_manager/DEPS
index 4534fa6..7c9baf0 100644
--- a/chrome/browser/ash/file_manager/DEPS
+++ b/chrome/browser/ash/file_manager/DEPS
@@ -100,6 +100,7 @@
 
 specific_include_rules = {
   "file_manager_browsertest_base.cc": [
+    "+chrome/browser/ui/tabs/tab_strip_model.h",
     "+chrome/browser/ui/views/select_file_dialog_extension.h",
   ],
 }
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
index 28677e04..c07fccae 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
@@ -6,15 +6,22 @@
 
 #include <stddef.h>
 
+#include <compare>
+#include <iterator>
+#include <map>
 #include <memory>
 #include <optional>
+#include <ostream>
+#include <set>
 #include <string_view>
 #include <utility>
 
 #include "ash/components/arc/arc_features.h"
 #include "ash/components/arc/arc_util.h"
+#include "ash/components/arc/mojom/file_system.mojom.h"
 #include "ash/components/arc/session/arc_bridge_service.h"
 #include "ash/components/arc/session/arc_service_manager.h"
+#include "ash/components/arc/session/connection_holder.h"
 #include "ash/components/arc/test/arc_util_test_support.h"
 #include "ash/components/arc/test/connection_holder_util.h"
 #include "ash/components/arc/test/fake_file_system_instance.h"
@@ -27,41 +34,55 @@
 #include "ash/webui/file_manager/url_constants.h"
 #include "ash/webui/system_apps/public/system_web_app_type.h"
 #include "base/base_paths.h"
+#include "base/check.h"
+#include "base/check_op.h"
+#include "base/command_line.h"
 #include "base/containers/circular_deque.h"
 #include "base/containers/contains.h"
 #include "base/feature_list.h"
+#include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
+#include "base/functional/function_ref.h"
+#include "base/immediate_crash.h"
 #include "base/json/json_reader.h"
 #include "base/json/json_value_converter.h"
 #include "base/json/json_writer.h"
-#include "base/json/values_util.h"
+#include "base/location.h"
+#include "base/logging.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
 #include "base/no_destructor.h"
 #include "base/notreached.h"
+#include "base/numerics/clamped_math.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/path_service.h"
+#include "base/process/process_handle.h"
 #include "base/ranges/algorithm.h"
 #include "base/run_loop.h"
 #include "base/scoped_observation.h"
 #include "base/strings/escape.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
-#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/gtest_tags.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/thread_annotations.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
-#include "base/value_iterators.h"
-#include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
-#include "chrome/browser/apps/app_service/browser_app_launcher.h"
-#include "chrome/browser/ash/app_list/search/chrome_search_result.h"
+#include "chrome/browser/ash/app_list/search/local_image_search/annotation_storage.h"
 #include "chrome/browser/ash/app_list/search/local_image_search/local_image_search_service.h"
 #include "chrome/browser/ash/app_list/search/local_image_search/local_image_search_service_factory.h"
 #include "chrome/browser/ash/app_list/search/search_features.h"
@@ -70,83 +91,99 @@
 #include "chrome/browser/ash/base/locale_util.h"
 #include "chrome/browser/ash/crostini/crostini_manager.h"
 #include "chrome/browser/ash/crostini/crostini_pref_names.h"
+#include "chrome/browser/ash/crostini/crostini_simple_types.h"
 #include "chrome/browser/ash/crostini/crostini_util.h"
 #include "chrome/browser/ash/drive/drivefs_test_support.h"
 #include "chrome/browser/ash/drive/file_system_util.h"
 #include "chrome/browser/ash/extensions/file_manager/event_router.h"
 #include "chrome/browser/ash/extensions/file_manager/event_router_factory.h"
-#include "chrome/browser/ash/file_manager/app_id.h"
 #include "chrome/browser/ash/file_manager/copy_or_move_io_task_impl.h"
 #include "chrome/browser/ash/file_manager/file_manager_test_util.h"
-#include "chrome/browser/ash/file_manager/file_tasks.h"
 #include "chrome/browser/ash/file_manager/file_tasks_notifier.h"
 #include "chrome/browser/ash/file_manager/file_tasks_observer.h"
 #include "chrome/browser/ash/file_manager/mount_test_util.h"
 #include "chrome/browser/ash/file_manager/office_file_tasks.h"
 #include "chrome/browser/ash/file_manager/path_util.h"
+#include "chrome/browser/ash/file_manager/volume.h"
 #include "chrome/browser/ash/file_manager/volume_manager.h"
+#include "chrome/browser/ash/file_system_provider/cloud_file_info.h"
 #include "chrome/browser/ash/file_system_provider/fake_extension_provider.h"
+#include "chrome/browser/ash/file_system_provider/fake_provided_file_system.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 #include "chrome/browser/ash/file_system_provider/provider_interface.h"
+#include "chrome/browser/ash/file_system_provider/service.h"
 #include "chrome/browser/ash/guest_os/guest_id.h"
 #include "chrome/browser/ash/guest_os/guest_os_share_path.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_mount_provider.h"
+#include "chrome/browser/ash/guest_os/public/guest_os_mount_provider_registry.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_service.h"
 #include "chrome/browser/ash/guest_os/public/types.h"
+#include "chrome/browser/ash/smb_client/smb_errors.h"
 #include "chrome/browser/ash/smb_client/smb_service.h"
 #include "chrome/browser/ash/smb_client/smb_service_factory.h"
+#include "chrome/browser/ash/smb_client/smbfs_share.h"
 #include "chrome/browser/ash/system/timezone_util.h"
 #include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/download/download_dir_util.h"
 #include "chrome/browser/download/download_prefs.h"
-#include "chrome/browser/enterprise/connectors/connectors_service.h"
 #include "chrome/browser/extensions/mixin_based_extension_apitest.h"
 #include "chrome/browser/notifications/notification_display_service_tester.h"
+#include "chrome/browser/notifications/notification_handler.h"
 #include "chrome/browser/platform_util.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync_file_system/mock_remote_file_sync_service.h"
+#include "chrome/browser/sync_file_system/remote_file_sync_service.h"
 #include "chrome/browser/sync_file_system/sync_file_system_service.h"
 #include "chrome/browser/sync_file_system/sync_file_system_service_factory.h"
-#include "chrome/browser/ui/ash/sharesheet/sharesheet_bubble_view.h"
 #include "chrome/browser/ui/ash/sharesheet/sharesheet_util.h"
 #include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/views/select_file_dialog_extension.h"
-#include "chrome/browser/web_applications/web_app_helpers.h"
-#include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_switches.h"
-#include "chrome/common/extensions/api/file_manager_private.h"
+#include "chrome/common/extensions/api/file_system_provider_capabilities/file_system_provider_capabilities_handler.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/test/base/fake_gaia_mixin.h"
 #include "chrome/test/base/test_switches.h"
 #include "chromeos/ash/components/dbus/cros_disks/cros_disks_client.h"
 #include "chromeos/ash/components/dbus/cros_disks/fake_cros_disks_client.h"
 #include "chromeos/ash/components/dbus/shill/shill_service_client.h"
 #include "chromeos/ash/components/dbus/spaced/fake_spaced_client.h"
-#include "chromeos/ash/components/dbus/vm_concierge/concierge_service.pb.h"
+#include "chromeos/ash/components/dbus/vm_applications/apps.pb.h"
+#include "chromeos/ash/components/disks/disk_mount_manager.h"
 #include "chromeos/ash/components/disks/mount_point.h"
-#include "chromeos/ash/components/drivefs/drivefs_host.h"
 #include "chromeos/ash/components/drivefs/drivefs_pinning_manager.h"
 #include "chromeos/ash/components/drivefs/fake_drivefs.h"
+#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom-forward.h"
+#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom-shared.h"
 #include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h"
+#include "chromeos/ash/components/smbfs/mojom/smbfs.mojom-shared.h"
+#include "chromeos/ash/components/smbfs/mojom/smbfs.mojom.h"
 #include "chromeos/ash/components/smbfs/smbfs_host.h"
 #include "chromeos/ash/components/smbfs/smbfs_mounter.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/dbus/constants/dbus_switches.h"
 #include "components/account_id/account_id.h"
 #include "components/drive/drive_pref_names.h"
+#include "components/keyed_service/core/keyed_service.h"
 #include "components/prefs/pref_service.h"
 #include "components/services/app_service/public/cpp/app_launch_util.h"
+#include "components/services/app_service/public/cpp/instance.h"
+#include "components/services/app_service/public/cpp/instance_registry.h"
+#include "components/services/app_service/public/cpp/instance_update.h"
 #include "components/user_manager/user_manager.h"
 #include "components/variations/service/variations_service.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/storage_partition.h"
-#include "content/public/common/content_switches.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/network_connection_change_simulator.h"
 #include "content/public/test/test_navigation_observer.h"
@@ -154,31 +191,65 @@
 #include "extensions/browser/api/test/test_api.h"
 #include "extensions/browser/api/test/test_api_observer.h"
 #include "extensions/browser/api/test/test_api_observer_registry.h"
-#include "extensions/browser/app_window/app_window.h"
-#include "extensions/browser/app_window/app_window_registry.h"
-#include "extensions/browser/event_router.h"
-#include "extensions/browser/extension_function_registry.h"
-#include "extensions/common/api/test.h"
 #include "extensions/common/extension_id.h"
-#include "google_apis/common/test_util.h"
-#include "google_apis/drive/drive_api_parser.h"
 #include "media/base/media_switches.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/struct_ptr.h"
+#include "net/base/network_change_notifier.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "pdf/buildflags.h"
+#include "services/network/public/mojom/network_change_manager.mojom-shared.h"
 #include "storage/browser/file_system/copy_or_move_operation_delegate.h"
 #include "storage/browser/file_system/external_mount_points.h"
 #include "storage/browser/file_system/file_system_context.h"
+#include "storage/common/file_system/file_system_mount_option.h"
+#include "storage/common/file_system/file_system_types.h"
 #include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/input/web_mouse_event.h"
+#include "third_party/blink/public/common/storage_key/storage_key.h"
+#include "third_party/blink/public/mojom/input/input_event.mojom-shared.h"
+#include "third_party/cros_system_api/dbus/cros-disks/dbus-constants.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/events/event.h"
-#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+#include "ui/events/types/event_type.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/native_widget_types.h"
 #include "ui/message_center/public/cpp/notification.h"
 #include "ui/shell_dialogs/select_file_dialog.h"
 #include "ui/shell_dialogs/select_file_dialog_factory.h"
 #include "ui/shell_dialogs/select_file_policy.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/url_canon.h"
 #include "url/url_util.h"
 
+namespace ash {
+namespace smb_client {
+class SmbUrl;
+}  // namespace smb_client
+}  // namespace ash
+namespace content {
+class BrowserContext;
+}  // namespace content
+namespace drivefs {
+class DriveFsBootstrapListener;
+}  // namespace drivefs
+namespace extensions {
+class Extension;
+}  // namespace extensions
+namespace guest_os {
+class GuestOsFileWatcher;
+}  // namespace guest_os
+
 #if BUILDFLAG(ENABLE_PDF)
 #include "pdf/pdf_features.h"
 #endif  // BUILDFLAG(ENABLE_PDF)
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.h b/chrome/browser/ash/file_manager/file_manager_browsertest_base.h
index 7b6982fa..1e18aa9 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.h
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.h
@@ -5,29 +5,47 @@
 #ifndef CHROME_BROWSER_ASH_FILE_MANAGER_FILE_MANAGER_BROWSERTEST_BASE_H_
 #define CHROME_BROWSER_ASH_FILE_MANAGER_FILE_MANAGER_BROWSERTEST_BASE_H_
 
+#include <stdint.h>
+
+#include <iosfwd>
 #include <map>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
 #include "base/base_paths.h"
 #include "base/containers/flat_map.h"
+#include "base/files/file_path.h"
 #include "base/memory/raw_ptr.h"
+#include "base/process/kill.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/metrics/user_action_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/values.h"
 #include "chrome/browser/ash/crostini/fake_crostini_features.h"
 #include "chrome/browser/ash/drive/drive_integration_service.h"
 #include "chrome/browser/ash/login/test/logged_in_user_mixin.h"
 #include "chrome/browser/extensions/mixin_based_extension_apitest.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/test/base/devtools_listener.h"
+#include "components/account_id/account_id.h"
 #include "components/webapps/common/web_app_id.h"
+#include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/devtools_agent_host_observer.h"
 #include "storage/browser/file_system/file_system_url.h"
 
 class NotificationDisplayServiceTester;
 class SelectFileDialogExtensionTestFactory;
+class Profile;
+
+namespace base {
+class CommandLine;
+
+namespace test {
+class ScopedFeatureList;
+}  // namespace test
+}  // namespace base
 
 namespace arc {
 class FakeFileSystemInstance;
diff --git a/chrome/browser/ash/file_manager/indexing/augmented_term_table.cc b/chrome/browser/ash/file_manager/indexing/augmented_term_table.cc
new file mode 100644
index 0000000..38b42f2
--- /dev/null
+++ b/chrome/browser/ash/file_manager/indexing/augmented_term_table.cc
@@ -0,0 +1,122 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/file_manager/indexing/augmented_term_table.h"
+
+#include "sql/statement.h"
+
+namespace file_manager {
+
+namespace {
+
+#define TABLE_NAME "augmented_term_table"
+
+// The statement used to create the augmented_term table.
+static constexpr char kCreateAugmentedTermTableQuery[] =
+    // clang-format off
+    "CREATE TABLE IF NOT EXISTS " TABLE_NAME "("
+      "augmented_term_id INTEGER PRIMARY KEY AUTOINCREMENT,"
+      "field_name TEXT NOT NULL,"
+      "term_id INTEGER NOT NULL REFERENCES term_table(term_id),"
+      "UNIQUE(field_name, term_id))";
+// clang-format on
+
+// The statement used to insert a new augmented term into the table.
+static constexpr char kInsertAugmentedTermQuery[] =
+    // clang-format off
+    "INSERT OR REPLACE INTO " TABLE_NAME "(field_name, "
+    "term_id) VALUES (?, ?) RETURNING augmented_term_id";
+// clang-format on
+
+// The statement used to delete an augmented term ID from the database by
+// augmented_term_id
+static constexpr char kDeleteAugmentedTermQuery[] =
+    // clang-format off
+    "DELETE FROM " TABLE_NAME " WHERE augmented_term_id = ? "
+    "RETURNING augmented_term_id";
+// clang-format on
+
+// The statement used fetch the augmented term ID by field name and term ID.
+static constexpr char kGetAugmentedTermIdQuery[] =
+    // clang-format off
+    "SELECT augmented_term_id FROM " TABLE_NAME " "
+    "WHERE field_name = ? AND term_id = ?";
+// clang-format on
+
+}  // namespace
+
+AugmentedTermTable::AugmentedTermTable(sql::Database* db) : db_(db) {}
+AugmentedTermTable::~AugmentedTermTable() = default;
+
+bool AugmentedTermTable::Init() {
+  if (!db_->is_open()) {
+    LOG(WARNING) << "Faield to initialize augmented_term_table "
+                 << "due to closed database";
+    return false;
+  }
+  sql::Statement create_table(
+      db_->GetCachedStatement(SQL_FROM_HERE, kCreateAugmentedTermTableQuery));
+  DCHECK(create_table.is_valid()) << "Invalid create the table statement: \""
+                                  << create_table.GetSQLStatement() << "\"";
+  if (!create_table.Run()) {
+    LOG(ERROR) << "Failed to create augmented_term_table";
+    return false;
+  }
+  return true;
+}
+
+int64_t AugmentedTermTable::GetAugmentedTermId(const std::string& field_name,
+                                               int64_t term_id) const {
+  sql::Statement get_augmented_term_id(
+      db_->GetCachedStatement(SQL_FROM_HERE, kGetAugmentedTermIdQuery));
+  DCHECK(get_augmented_term_id.is_valid())
+      << "Invalid get augmented term ID statement: \""
+      << get_augmented_term_id.GetSQLStatement() << "\"";
+  get_augmented_term_id.BindString(0, field_name);
+  get_augmented_term_id.BindInt64(1, term_id);
+  if (get_augmented_term_id.Step()) {
+    return get_augmented_term_id.ColumnInt64(0);
+  }
+  return -1;
+}
+
+int64_t AugmentedTermTable::GetOrCreateAugmentedTermId(
+    const std::string& field_name,
+    int64_t term_id) {
+  int64_t augmented_term_id = GetAugmentedTermId(field_name, term_id);
+  if (augmented_term_id != -1) {
+    return augmented_term_id;
+  }
+  sql::Statement insert_augmented_term(
+      db_->GetCachedStatement(SQL_FROM_HERE, kInsertAugmentedTermQuery));
+  DCHECK(insert_augmented_term.is_valid())
+      << "Invalid insert augmented term statement: \""
+      << insert_augmented_term.GetSQLStatement() << "\"";
+  insert_augmented_term.BindString(0, field_name);
+  insert_augmented_term.BindInt64(1, term_id);
+  if (insert_augmented_term.Step()) {
+    return insert_augmented_term.ColumnInt64(0);
+  }
+  LOG(ERROR) << "Failed to insert augmented term " << field_name << ":"
+             << term_id;
+  return -1;
+}
+
+int64_t AugmentedTermTable::DeleteAugmentedTerm(int64_t augmented_term_id) {
+  sql::Statement delete_augmented_term(
+      db_->GetCachedStatement(SQL_FROM_HERE, kDeleteAugmentedTermQuery));
+  DCHECK(delete_augmented_term.is_valid())
+      << "Invalid delete augmented term statement: \""
+      << delete_augmented_term.GetSQLStatement() << "\"";
+  delete_augmented_term.BindInt64(0, augmented_term_id);
+  if (!delete_augmented_term.Step()) {
+    if (!delete_augmented_term.Succeeded()) {
+      LOG(ERROR) << "Failed to delete augmented term " << augmented_term_id;
+    }
+    return -1;
+  }
+  return delete_augmented_term.ColumnInt64(0);
+}
+
+}  // namespace file_manager
diff --git a/chrome/browser/ash/file_manager/indexing/augmented_term_table.h b/chrome/browser/ash/file_manager/indexing/augmented_term_table.h
new file mode 100644
index 0000000..b96c9db07
--- /dev/null
+++ b/chrome/browser/ash/file_manager/indexing/augmented_term_table.h
@@ -0,0 +1,56 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_FILE_MANAGER_INDEXING_AUGMENTED_TERM_TABLE_H_
+#define CHROME_BROWSER_ASH_FILE_MANAGER_INDEXING_AUGMENTED_TERM_TABLE_H_
+
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "sql/database.h"
+
+namespace file_manager {
+
+// Stores a mapping from augmented term IDs to an augmented term. The augmented
+// term is a combination of a term and a field name. The main job of this table
+// is to provide a unique ID for, say "label:starred" and "content:starred"
+// terms. The term table provides a unique value for "starred". However, we
+// need to be able to distinguish beteewn "starred" being used a, say, label,
+// vs it being part of a content. This is what this table does.
+class AugmentedTermTable {
+ public:
+  // Creates a table that maps augmented term IDs to augmented terms. An
+  // augmented term consists of the field name and a term ID.
+  explicit AugmentedTermTable(sql::Database* db);
+  ~AugmentedTermTable();
+
+  AugmentedTermTable(const AugmentedTermTable&) = delete;
+  AugmentedTermTable& operator=(const AugmentedTermTable&) = delete;
+
+  // Initializes the table. Returns true on success, and false on failure.
+  bool Init();
+
+  // Returns the ID corresponding to the given augmented term. If the augmented
+  // term cannot be located, the method returns -1.
+  int64_t GetAugmentedTermId(const std::string& field_name,
+                             int64_t term_id) const;
+
+  // Returns the ID corresponding to the augmented term. If the augmented term
+  // cannot be located, a new ID is allocated and returned.
+  int64_t GetOrCreateAugmentedTermId(const std::string& field_name,
+                                     int64_t term_id);
+
+  // Attempts to remove the given augmented term by its ID from the database.
+  // If not present, this method returns -1. Otherwise, it returns the
+  // `augmented_term_id`.
+  int64_t DeleteAugmentedTerm(int64_t augmented_term_id);
+
+ private:
+  // The pointer to a database owned by the whoever created this table.
+  raw_ptr<sql::Database> db_;
+};
+
+}  // namespace file_manager
+
+#endif  // CHROME_BROWSER_ASH_FILE_MANAGER_INDEXING_AUGMENTED_TERM_TABLE_H_
diff --git a/chrome/browser/ash/file_manager/indexing/sql_storage.cc b/chrome/browser/ash/file_manager/indexing/sql_storage.cc
index 195594d..8f09d4a 100644
--- a/chrome/browser/ash/file_manager/indexing/sql_storage.cc
+++ b/chrome/browser/ash/file_manager/indexing/sql_storage.cc
@@ -27,6 +27,7 @@
       db_path_(db_path),
       db_(sql::Database(sql::DatabaseOptions())),
       term_table_(&db_),
+      augmented_term_table_(&db_),
       url_table_(&db_),
       file_info_table_(&db_) {}
 
@@ -66,6 +67,11 @@
     base::UmaHistogramEnumeration(uma_tag_, DbOperationStatus::kTableInitError);
     return false;
   }
+  if (!augmented_term_table_.Init()) {
+    LOG(ERROR) << "Failed to initialize augmented_term_table";
+    base::UmaHistogramEnumeration(uma_tag_, DbOperationStatus::kTableInitError);
+    return false;
+  }
   if (!url_table_.Init()) {
     LOG(ERROR) << "Failed to initialize url_table";
     base::UmaHistogramEnumeration(uma_tag_, DbOperationStatus::kTableInitError);
@@ -101,9 +107,14 @@
   }
 }
 
-int64_t SqlStorage::GetTermId(const std::string& term_bytes, bool create) {
+int64_t SqlStorage::GetTermId(const std::string& term_bytes) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return term_table_.GetTermId(term_bytes, create);
+  return term_table_.GetTermId(term_bytes);
+}
+
+int64_t SqlStorage::GetOrCreateTermId(const std::string& term_bytes) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return term_table_.GetOrCreateTermId(term_bytes);
 }
 
 int64_t SqlStorage::DeleteTerm(const std::string& term_bytes) {
@@ -111,6 +122,30 @@
   return term_table_.DeleteTerm(term_bytes);
 }
 
+int64_t SqlStorage::GetAugmentedTermId(const Term& term) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  int64_t term_id = GetTermId(term.text_bytes());
+  if (term_id == -1) {
+    return -1;
+  }
+  return augmented_term_table_.GetAugmentedTermId(term.field(), term_id);
+}
+
+int64_t SqlStorage::GetOrCreateAugmentedTermId(const Term& term) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  int64_t term_id = GetOrCreateTermId(term.text_bytes());
+  if (term_id == -1) {
+    return -1;
+  }
+  return augmented_term_table_.GetOrCreateAugmentedTermId(term.field(),
+                                                          term_id);
+}
+
+int64_t SqlStorage::DeleteAugmentedTerm(int64_t augmented_term_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return augmented_term_table_.DeleteAugmentedTerm(augmented_term_id);
+}
+
 int64_t SqlStorage::GetOrCreateUrlId(const GURL& url) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   return url_table_.GetOrCreateUrlId(url);
diff --git a/chrome/browser/ash/file_manager/indexing/sql_storage.h b/chrome/browser/ash/file_manager/indexing/sql_storage.h
index d06a1b1..580973c7 100644
--- a/chrome/browser/ash/file_manager/indexing/sql_storage.h
+++ b/chrome/browser/ash/file_manager/indexing/sql_storage.h
@@ -9,8 +9,10 @@
 
 #include "base/files/file_path.h"
 #include "base/sequence_checker.h"
+#include "chrome/browser/ash/file_manager/indexing/augmented_term_table.h"
 #include "chrome/browser/ash/file_manager/indexing/file_info.h"
 #include "chrome/browser/ash/file_manager/indexing/file_info_table.h"
+#include "chrome/browser/ash/file_manager/indexing/term.h"
 #include "chrome/browser/ash/file_manager/indexing/term_table.h"
 #include "chrome/browser/ash/file_manager/indexing/url_table.h"
 #include "sql/database.h"
@@ -51,13 +53,29 @@
   bool Close();
 
   // Returns the ID corresponding to the given term bytes. If the term bytes
-  // cannot be located, we return -1, unless create is set to true.
-  int64_t GetTermId(const std::string& term_bytes, bool create);
+  // cannot be located, we return -1.
+  int64_t GetTermId(const std::string& term_bytes) const;
+
+  // Returns the ID corresponding to the given term bytes. If the term bytes
+  // cannot be located, a new ID is created and returned.
+  int64_t GetOrCreateTermId(const std::string& term_bytes);
 
   // Removes the term ID. If the term was present in the database, it returns
   // the ID that was assigned to the term. Otherwise, it returns - 1.
   int64_t DeleteTerm(const std::string& term);
 
+  // Returns the ID corresponding to the given augmented term. If the augmented
+  // term cannot be located, the method returns -1.
+  int64_t GetAugmentedTermId(const Term& term) const;
+
+  // Returns the ID corresponding to the augmented term. If the augmented term
+  // cannot be located, a new ID is allocated and returned.
+  int64_t GetOrCreateAugmentedTermId(const Term& term);
+
+  // Deletes augmented term ID by it ID. If successful, this method returns the
+  // `augmented_term_id`. Otherwise, it returns -1.
+  int64_t DeleteAugmentedTerm(int64_t augmented_term_id);
+
   // Gets an ID for the given URL. Creates a new one, if this URL is seen for
   // the first time.
   int64_t GetOrCreateUrlId(const GURL& url);
@@ -97,9 +115,12 @@
   // The actual SQL Lite database.
   sql::Database db_;
 
-  // The table that holds a mapping from tags to tag IDs.
+  // The table that holds a mapping from terms to term IDs.
   TermTable term_table_;
 
+  // The table that holds a mapping from  augmented terms to their IDs.
+  AugmentedTermTable augmented_term_table_;
+
   // The table that holds a mapping from URLs to URL IDs.
   UrlTable url_table_;
 
diff --git a/chrome/browser/ash/file_manager/indexing/sql_storage_unittest.cc b/chrome/browser/ash/file_manager/indexing/sql_storage_unittest.cc
index 1c4c533..43a4366 100644
--- a/chrome/browser/ash/file_manager/indexing/sql_storage_unittest.cc
+++ b/chrome/browser/ash/file_manager/indexing/sql_storage_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/time/time.h"
+#include "chrome/browser/ash/file_manager/indexing/term.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
@@ -26,6 +27,9 @@
 
 class SqlStorageTest : public testing::Test {
  public:
+  SqlStorageTest()
+      : pinned_("label", u"pinned"), downloaded_("label", u"downloaded") {}
+
   void SetUp() override {
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     storage_ = std::make_unique<SqlStorage>(db_file_path(), "test_uma_tag");
@@ -42,6 +46,8 @@
   }
 
  protected:
+  Term pinned_;
+  Term downloaded_;
   GURL foo_url_;
   base::Time foo_modified_time_;
   base::ScopedTempDir temp_dir_;
@@ -60,9 +66,11 @@
   // Must initialize before use.
   ASSERT_TRUE(storage_->Init());
 
-  EXPECT_EQ(storage_->GetTermId("foo", false), -1);
-  EXPECT_EQ(storage_->GetTermId("foo", true), 1);
-  EXPECT_EQ(storage_->GetTermId("foo", false), 1);
+  EXPECT_EQ(storage_->GetTermId("foo"), -1);
+  EXPECT_EQ(storage_->GetOrCreateTermId("foo"), 1);
+  EXPECT_EQ(storage_->GetTermId("foo"), 1);
+  // Adding the same term twice does not create a second version of "foo".
+  EXPECT_EQ(storage_->GetOrCreateTermId("foo"), 1);
 }
 
 TEST_F(SqlStorageTest, DeleteTerm) {
@@ -70,10 +78,30 @@
   ASSERT_TRUE(storage_->Init());
 
   EXPECT_EQ(storage_->DeleteTerm("foo"), -1);
-  EXPECT_EQ(storage_->GetTermId("foo", true), 1);
+  EXPECT_EQ(storage_->GetOrCreateTermId("foo"), 1);
   EXPECT_EQ(storage_->DeleteTerm("foo"), 1);
 }
 
+TEST_F(SqlStorageTest, GetAugmentedTermId) {
+  // Must initialize before use.
+  ASSERT_TRUE(storage_->Init());
+
+  EXPECT_EQ(storage_->GetAugmentedTermId(pinned_), -1);
+  EXPECT_EQ(storage_->GetOrCreateAugmentedTermId(pinned_), 1);
+  EXPECT_EQ(storage_->GetAugmentedTermId(pinned_), 1);
+  EXPECT_EQ(storage_->GetAugmentedTermId(downloaded_), -1);
+  EXPECT_EQ(storage_->GetOrCreateAugmentedTermId(downloaded_), 2);
+}
+
+TEST_F(SqlStorageTest, DeleteAugmentedTerm) {
+  // Must initialize before use.
+  ASSERT_TRUE(storage_->Init());
+
+  EXPECT_EQ(storage_->DeleteAugmentedTerm(1), -1);
+  EXPECT_EQ(storage_->GetOrCreateAugmentedTermId(pinned_), 1);
+  EXPECT_EQ(storage_->DeleteAugmentedTerm(1), 1);
+}
+
 TEST_F(SqlStorageTest, GetOrCreateUrlId) {
   // Must initialize before use.
   ASSERT_TRUE(storage_->Init());
diff --git a/chrome/browser/ash/file_manager/indexing/term_table.cc b/chrome/browser/ash/file_manager/indexing/term_table.cc
index d61d708..ef34ed5 100644
--- a/chrome/browser/ash/file_manager/indexing/term_table.cc
+++ b/chrome/browser/ash/file_manager/indexing/term_table.cc
@@ -52,13 +52,14 @@
   return DeleteValue(term);
 }
 
-int64_t TermTable::GetTermId(const std::string& term, bool create) {
-  if (create) {
-    return GetOrCreateValueId(term);
-  }
+int64_t TermTable::GetTermId(const std::string& term) const {
   return GetValueId(term);
 }
 
+int64_t TermTable::GetOrCreateTermId(const std::string& term) {
+  return GetOrCreateValueId(term);
+}
+
 std::unique_ptr<sql::Statement> TermTable::MakeGetStatement() const {
   return std::make_unique<sql::Statement>(
       db_->GetCachedStatement(SQL_FROM_HERE, kGetTermIdQuery));
diff --git a/chrome/browser/ash/file_manager/indexing/term_table.h b/chrome/browser/ash/file_manager/indexing/term_table.h
index 57ad0f2..09b2439 100644
--- a/chrome/browser/ash/file_manager/indexing/term_table.h
+++ b/chrome/browser/ash/file_manager/indexing/term_table.h
@@ -33,10 +33,12 @@
   // found. Otherwise, returns the ID that the term was assigned.
   int64_t DeleteTerm(const std::string& term);
 
-  // Gets the term ID for the given term. If `create` is false, and the term
-  // does not exists, this method returns -1. Otherwise it either inserts a new
-  // term and returns its ID, or returns the existing term ID.
-  int64_t GetTermId(const std::string& term, bool create);
+  // Gets the term ID for the given term. If the term cannot be found, this
+  // method returns -1.
+  int64_t GetTermId(const std::string& term) const;
+
+  // Gets or creates the unique term ID for the given term.
+  int64_t GetOrCreateTermId(const std::string& term);
 
  protected:
   std::unique_ptr<sql::Statement> MakeGetStatement() const override;
diff --git a/chrome/browser/ash/file_manager/indexing/term_table_unittest.cc b/chrome/browser/ash/file_manager/indexing/term_table_unittest.cc
index 97b9ee0..5af8d59 100644
--- a/chrome/browser/ash/file_manager/indexing/term_table_unittest.cc
+++ b/chrome/browser/ash/file_manager/indexing/term_table_unittest.cc
@@ -56,13 +56,13 @@
   TermTable table(db_.get());
   EXPECT_TRUE(table.Init());
 
-  EXPECT_EQ(table.GetTermId("hello", false), -1);
-  EXPECT_EQ(table.GetTermId("hello", true), 1);
-  EXPECT_EQ(table.GetTermId("hello", false), 1);
-  EXPECT_EQ(table.GetTermId("there", true), 2);
-  EXPECT_EQ(table.GetTermId("there", false), 2);
-  EXPECT_EQ(table.GetTermId("O'Neill", false), -1);
-  EXPECT_EQ(table.GetTermId("O'Neill", true), 3);
+  EXPECT_EQ(table.GetTermId("hello"), -1);
+  EXPECT_EQ(table.GetOrCreateTermId("hello"), 1);
+  EXPECT_EQ(table.GetTermId("hello"), 1);
+  EXPECT_EQ(table.GetOrCreateTermId("there"), 2);
+  EXPECT_EQ(table.GetTermId("there"), 2);
+  EXPECT_EQ(table.GetTermId("O'Neill"), -1);
+  EXPECT_EQ(table.GetOrCreateTermId("O'Neill"), 3);
 }
 
 TEST_F(TermTableTest, DeleteTerm) {
@@ -70,7 +70,7 @@
   EXPECT_TRUE(table.Init());
 
   EXPECT_EQ(table.DeleteTerm("hello"), -1);
-  EXPECT_EQ(table.GetTermId("hello", true), 1);
+  EXPECT_EQ(table.GetOrCreateTermId("hello"), 1);
   EXPECT_EQ(table.DeleteTerm("hello"), 1);
 }
 
diff --git a/chrome/browser/ash/file_system_provider/cloud_file_system_unittest.cc b/chrome/browser/ash/file_system_provider/cloud_file_system_unittest.cc
index 81b688e..57344b71 100644
--- a/chrome/browser/ash/file_system_provider/cloud_file_system_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/cloud_file_system_unittest.cc
@@ -83,6 +83,7 @@
                FileErrorCallback callback),
               (override));
   MOCK_METHOD(void, LoadFromDisk, (base::OnceClosure callback), (override));
+  MOCK_METHOD(std::vector<base::FilePath>, GetCachedFilePaths, (), (override));
 
   base::WeakPtr<MockContentCache> GetWeakPtr() {
     return weak_ptr_factory_.GetWeakPtr();
diff --git a/chrome/browser/ash/file_system_provider/content_cache/content_cache.h b/chrome/browser/ash/file_system_provider/content_cache/content_cache.h
index c0cf432c..1c7d967 100644
--- a/chrome/browser/ash/file_system_provider/content_cache/content_cache.h
+++ b/chrome/browser/ash/file_system_provider/content_cache/content_cache.h
@@ -59,6 +59,10 @@
   // event files have been orphaned (i.e. they are on disk with no DB entry or
   // vice versa) then prune them appropriately.
   virtual void LoadFromDisk(base::OnceClosure callback) = 0;
+
+  // Returns the file paths of the cached files on disk, in their most recently
+  // used order.
+  virtual std::vector<base::FilePath> GetCachedFilePaths() = 0;
 };
 
 }  // namespace ash::file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.cc b/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.cc
index b871e60..34155adac 100644
--- a/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.cc
+++ b/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.cc
@@ -440,4 +440,13 @@
   std::move(callback).Run();
 }
 
+std::vector<base::FilePath> ContentCacheImpl::GetCachedFilePaths() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::vector<base::FilePath> cached_file_paths;
+  for (const auto& [file_path, cache_file_context] : lru_cache_) {
+    cached_file_paths.push_back(file_path);
+  }
+  return cached_file_paths;
+}
+
 }  // namespace ash::file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.h b/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.h
index faacf13..3968a439 100644
--- a/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.h
+++ b/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.h
@@ -6,7 +6,6 @@
 #define CHROME_BROWSER_ASH_FILE_SYSTEM_PROVIDER_CONTENT_CACHE_CONTENT_CACHE_IMPL_H_
 
 #include "base/files/file_error_or.h"
-#include "base/gtest_prod_util.h"
 #include "base/sequence_checker.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/threading/sequence_bound.h"
@@ -57,6 +56,8 @@
 
   void LoadFromDisk(base::OnceClosure callback) override;
 
+  std::vector<base::FilePath> GetCachedFilePaths() override;
+
  private:
   void OnBytesRead(
       const base::FilePath& file_path,
@@ -110,10 +111,6 @@
   size_t max_cache_size_;
 
   base::WeakPtrFactory<ContentCacheImpl> weak_ptr_factory_{this};
-
-  FRIEND_TEST_ALL_PREFIXES(
-      FileSystemProviderContentCacheImplTest,
-      FilesOnDiskAndInDbAreInitializedInTheDatabaseAccessedTimeOrder);
 };
 
 }  // namespace ash::file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl_unittest.cc b/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl_unittest.cc
index 3fc3aecd..9dbe6c1 100644
--- a/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl_unittest.cc
+++ b/chrome/browser/ash/file_system_provider/content_cache/content_cache_impl_unittest.cc
@@ -139,6 +139,8 @@
 
   // The first write to the disk should use the database ID as the file name.
   EXPECT_TRUE(base::PathExists(temp_dir_.GetPath().Append("1")));
+  EXPECT_THAT(content_cache_->GetCachedFilePaths(),
+              ElementsAre(base::FilePath("random-path")));
 }
 
 TEST_F(FileSystemProviderContentCacheImplTest,
@@ -219,6 +221,10 @@
   WriteFileToCache(base::FilePath("random-path2"), /*version_tag=*/"versionA",
                    kDefaultChunkSize);
 
+  EXPECT_THAT(content_cache_->GetCachedFilePaths(),
+              ElementsAre(base::FilePath("random-path2"),
+                          base::FilePath("random-path1")));
+
   // Expect third insertion to fail.
   OpenedCloudFile file3(base::FilePath("random-path3"),
                         OpenFileMode::OPEN_FILE_MODE_READ,
@@ -239,6 +245,9 @@
                                               future.GetCallback()));
   // Contiguous file write should succeed.
   EXPECT_EQ(future.Get(), base::File::FILE_OK);
+  EXPECT_THAT(content_cache_->GetCachedFilePaths(),
+              ElementsAre(base::FilePath("random-path1"),
+                          base::FilePath("random-path2")));
 }
 
 TEST_F(FileSystemProviderContentCacheImplTest,
@@ -432,8 +441,6 @@
 
 TEST_F(FileSystemProviderContentCacheImplTest,
        FilesOnDiskAndInDbAreInitializedInTheDatabaseAccessedTimeOrder) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(content_cache_->sequence_checker_);
-
   // This is the oldest accessed item on disk, we should order this last in the
   // LRU cache.
   base::Time older_time;
@@ -449,11 +456,8 @@
   content_cache_->LoadFromDisk(future.GetCallback());
   ASSERT_TRUE(future.Wait());
 
-  // TODO(b/328679426): Once eviction logic has been created, we can assert on
-  // this. For now let's inspect the underlying `lru_cache_` instead.
-  EXPECT_THAT(content_cache_->lru_cache_,
-              ElementsAre(Key(base::FilePath("/b.txt")),
-                          Key(base::FilePath("/a.txt"))));
+  EXPECT_THAT(content_cache_->GetCachedFilePaths(),
+              ElementsAre(base::FilePath("/b.txt"), base::FilePath("/a.txt")));
 }
 
 TEST_F(FileSystemProviderContentCacheImplTest,
diff --git a/chrome/browser/ash/language_packs/language_pack_font_service.cc b/chrome/browser/ash/language_packs/language_pack_font_service.cc
index c591686b..7f9aff6e 100644
--- a/chrome/browser/ash/language_packs/language_pack_font_service.cc
+++ b/chrome/browser/ash/language_packs/language_pack_font_service.cc
@@ -29,13 +29,12 @@
     : LanguagePackFontService(prefs, base::BindRepeating(&gfx::AddAppFontDir)) {
 }
 
-LanguagePackFontService::LanguagePackFontService(
-    PrefService* prefs,
-    base::RepeatingCallback<bool(base::FilePath)> add_font_dir)
+LanguagePackFontService::LanguagePackFontService(PrefService* prefs,
+                                                 AddFontDir add_font_dir)
     : prefs_(CHECK_DEREF(prefs)), add_font_dir_(std::move(add_font_dir)) {
   pref_accept_language_.Init(
       language::prefs::kPreferredLanguages, &*prefs_,
-      base::BindRepeating(&LanguagePackFontService::InstallFontDlcs,
+      base::BindRepeating(&LanguagePackFontService::OnAcceptLanguageChanged,
                           weak_factory_.GetWeakPtr()));
 
   // Add installed fonts to fontconfig.
@@ -47,9 +46,8 @@
   for (std::string& language_pack : GetLanguagePacksForAcceptLanguage()) {
     LanguagePackManager::GetPackState(
         kFontsFeatureId, language_pack,
-        base::BindOnce(
-            &LanguagePackFontService::AddDlcFontDirsToFontConfigPackCallback,
-            weak_factory_.GetWeakPtr()));
+        base::BindOnce(&LanguagePackFontService::GetPackStateOnInitCallback,
+                       weak_factory_.GetWeakPtr()));
   }
 }
 
@@ -74,14 +72,14 @@
   return language_packs;
 }
 
-void LanguagePackFontService::InstallFontDlcs() {
+void LanguagePackFontService::OnAcceptLanguageChanged() {
   for (std::string& language_pack : GetLanguagePacksForAcceptLanguage()) {
     LanguagePackManager::InstallPack(kFontsFeatureId, language_pack,
                                      base::DoNothing());
   }
 }
 
-void LanguagePackFontService::AddDlcFontDirsToFontConfigPackCallback(
+void LanguagePackFontService::GetPackStateOnInitCallback(
     const PackResult& result) {
   if (result.pack_state != PackResult::StatusCode::kInstalled &&
       !result.language_code.empty()) {
diff --git a/chrome/browser/ash/language_packs/language_pack_font_service.h b/chrome/browser/ash/language_packs/language_pack_font_service.h
index 21ff74f34..7c4aeba1 100644
--- a/chrome/browser/ash/language_packs/language_pack_font_service.h
+++ b/chrome/browser/ash/language_packs/language_pack_font_service.h
@@ -42,8 +42,8 @@
 
  private:
   base::flat_set<std::string> GetLanguagePacksForAcceptLanguage();
-  void InstallFontDlcs();
-  void AddDlcFontDirsToFontConfigPackCallback(const PackResult& result);
+  void OnAcceptLanguageChanged();
+  void GetPackStateOnInitCallback(const PackResult& result);
 
   // Not owned by this class
   const raw_ref<PrefService> prefs_;
diff --git a/chrome/browser/ash/language_packs/language_pack_font_service_factory.cc b/chrome/browser/ash/language_packs/language_pack_font_service_factory.cc
index bcefc59..207c162 100644
--- a/chrome/browser/ash/language_packs/language_pack_font_service_factory.cc
+++ b/chrome/browser/ash/language_packs/language_pack_font_service_factory.cc
@@ -4,11 +4,14 @@
 
 #include "chrome/browser/ash/language_packs/language_pack_font_service_factory.h"
 
+#include <memory>
+
 #include "ash/constants/ash_features.h"
 #include "base/feature_list.h"
 #include "base/no_destructor.h"
 #include "chrome/browser/ash/language_packs/language_pack_font_service.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
 #include "chrome/browser/profiles/profile_selections.h"
 
 class KeyedService;
diff --git a/chrome/browser/ash/login/login_screen_policy_browsertest.cc b/chrome/browser/ash/login/login_screen_policy_browsertest.cc
index e27d151..9f6acbb 100644
--- a/chrome/browser/ash/login/login_screen_policy_browsertest.cc
+++ b/chrome/browser/ash/login/login_screen_policy_browsertest.cc
@@ -210,7 +210,7 @@
   profile_network_context->ConfigureNetworkContextParams(
       /*in_memory=*/true, empty_relative_partition_path,
       &network_context_params, &cert_verifier_creation_params);
-  ASSERT_EQ(network_context_params.accept_language, "fr-FR");
+  ASSERT_EQ(network_context_params.accept_language, "fr-FR,fr;q=0.9");
 }
 
 class LoginScreenButtonsLocalePolicy : public LoginScreenLocalePolicyTestBase {
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_time_of_day_browsertest.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_time_of_day_browsertest.cc
index 31991235..fe32682 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_time_of_day_browsertest.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_time_of_day_browsertest.cc
@@ -112,11 +112,8 @@
       public testing::WithParamInterface<TimeOfDayTestParams> {
  public:
   PersonalizationAppTimeOfDayBrowserTest() {
-    std::vector<base::test::FeatureRef> enabled_features =
-        personalization_app::GetTimeOfDayEnabledFeatures();
-    enabled_features.emplace_back(
-        features::kTimeOfDayWallpaperForcedAutoSchedule);
-    scoped_feature_list_.InitWithFeatures(enabled_features, {});
+    scoped_feature_list_.InitWithFeatures(
+        personalization_app::GetTimeOfDayEnabledFeatures(), {});
     base::Time start_time = StartTime();
     clock_.SetNow(start_time);
     tick_clock_.SetNowTicks(base::TimeTicks() + (start_time - base::Time()));
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl.cc
index 6f6cc84..38d81e6f 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl.cc
@@ -519,7 +519,7 @@
   client->RecordWallpaperSourceUMA(ash::WallpaperType::kOnline);
 
   if (IsTimeOfDayWallpaper(collection_id) &&
-      features::IsTimeOfDayWallpaperForcedAutoScheduleEnabled()) {
+      features::IsTimeOfDayWallpaperEnabled()) {
     // Records the display count of the time of day wallpaper dialog when the
     // user selects one to determine whether to show it the next time.
     contextual_tooltip::HandleGesturePerformed(
@@ -804,7 +804,7 @@
     ShouldShowTimeOfDayWallpaperDialog(
         ShouldShowTimeOfDayWallpaperDialogCallback callback) {
   std::move(callback).Run(
-      features::IsTimeOfDayWallpaperForcedAutoScheduleEnabled() &&
+      features::IsTimeOfDayWallpaperEnabled() &&
       contextual_tooltip::ShouldShowNudge(
           profile_->GetPrefs(),
           contextual_tooltip::TooltipType::kTimeOfDayWallpaperDialog,
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc
index 0120510..4afcccc8 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc
@@ -572,8 +572,7 @@
        ShouldShowTimeOfDayWallpaperDialog) {
   test_wallpaper_controller()->ClearCounts();
   base::test::ScopedFeatureList features;
-  features.InitWithFeatures({features::kFeatureManagementTimeOfDayWallpaper,
-                             features::kTimeOfDayWallpaperForcedAutoSchedule},
+  features.InitWithFeatures({features::kFeatureManagementTimeOfDayWallpaper},
                             {});
 
   auto image_info = GetDefaultImageInfo();
diff --git a/chrome/browser/autofill/android/personal_data_manager_android.cc b/chrome/browser/autofill/android/personal_data_manager_android.cc
index 24e3001..d319698d 100644
--- a/chrome/browser/autofill/android/personal_data_manager_android.cc
+++ b/chrome/browser/autofill/android/personal_data_manager_android.cc
@@ -205,8 +205,9 @@
 ScopedJavaLocalRef<jobject> PersonalDataManagerAndroid::GetProfileByGUID(
     JNIEnv* env,
     const JavaParamRef<jstring>& jguid) {
-  AutofillProfile* profile = personal_data_manager_->GetProfileByGUID(
-      ConvertJavaStringToUTF8(env, jguid));
+  AutofillProfile* profile =
+      personal_data_manager_->address_data_manager().GetProfileByGUID(
+          ConvertJavaStringToUTF8(env, jguid));
   if (!profile)
     return ScopedJavaLocalRef<jobject>();
 
@@ -243,7 +244,8 @@
   std::string guid = ConvertJavaStringToUTF8(env, jguid);
 
   AutofillProfile profile = AutofillProfile::CreateFromJavaObject(
-      jprofile, personal_data_manager_->GetProfileByGUID(guid),
+      jprofile,
+      personal_data_manager_->address_data_manager().GetProfileByGUID(guid),
       g_browser_process->GetApplicationLocale());
 
   if (guid.empty()) {
@@ -260,7 +262,7 @@
     const JavaParamRef<jobject>& jprofile,
     const JavaParamRef<jstring>& jguid) {
   const AutofillProfile* target_profile =
-      personal_data_manager_->GetProfileByGUID(
+      personal_data_manager_->address_data_manager().GetProfileByGUID(
           ConvertJavaStringToUTF8(env, jguid));
   AutofillProfile profile = AutofillProfile::CreateFromJavaObject(
       jprofile, target_profile, g_browser_process->GetApplicationLocale());
@@ -316,7 +318,7 @@
 
   AutofillProfile profile = AutofillProfile::CreateFromJavaObject(
       jprofile,
-      personal_data_manager_->GetProfileByGUID(
+      personal_data_manager_->address_data_manager().GetProfileByGUID(
           ConvertJavaStringToUTF8(env, jguid)),
       g_browser_process->GetApplicationLocale());
 
@@ -431,8 +433,9 @@
 void PersonalDataManagerAndroid::RecordAndLogProfileUse(
     JNIEnv* env,
     const JavaParamRef<jstring>& jguid) {
-  AutofillProfile* profile = personal_data_manager_->GetProfileByGUID(
-      ConvertJavaStringToUTF8(env, jguid));
+  AutofillProfile* profile =
+      personal_data_manager_->address_data_manager().GetProfileByGUID(
+          ConvertJavaStringToUTF8(env, jguid));
   if (profile) {
     personal_data_manager_->address_data_manager().RecordUseOf(*profile);
   }
@@ -445,8 +448,9 @@
     jint days_since_last_used) {
   DCHECK(count >= 0 && days_since_last_used >= 0);
 
-  AutofillProfile* profile = personal_data_manager_->GetProfileByGUID(
-      ConvertJavaStringToUTF8(env, jguid));
+  AutofillProfile* profile =
+      personal_data_manager_->address_data_manager().GetProfileByGUID(
+          ConvertJavaStringToUTF8(env, jguid));
   profile->set_use_count(static_cast<size_t>(count));
   profile->set_use_date(AutofillClock::Now() -
                         base::Days(days_since_last_used));
@@ -457,16 +461,18 @@
 jint PersonalDataManagerAndroid::GetProfileUseCountForTesting(
     JNIEnv* env,
     const base::android::JavaParamRef<jstring>& jguid) {
-  AutofillProfile* profile = personal_data_manager_->GetProfileByGUID(
-      ConvertJavaStringToUTF8(env, jguid));
+  AutofillProfile* profile =
+      personal_data_manager_->address_data_manager().GetProfileByGUID(
+          ConvertJavaStringToUTF8(env, jguid));
   return profile->use_count();
 }
 
 jlong PersonalDataManagerAndroid::GetProfileUseDateForTesting(
     JNIEnv* env,
     const base::android::JavaParamRef<jstring>& jguid) {
-  AutofillProfile* profile = personal_data_manager_->GetProfileByGUID(
-      ConvertJavaStringToUTF8(env, jguid));
+  AutofillProfile* profile =
+      personal_data_manager_->address_data_manager().GetProfileByGUID(
+          ConvertJavaStringToUTF8(env, jguid));
   return profile->use_date().ToTimeT();
 }
 
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index b390d993..0c86fe48 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -362,6 +362,7 @@
 #include "chrome/browser/ui/webui/ash/set_time_ui.h"
 #include "chrome/browser/ui/webui/ash/settings/os_settings_ui.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_notification_handler.mojom.h"
+#include "chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_parental_controls_handler.mojom.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider.mojom.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.mojom.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/files/mojom/google_drive_handler.mojom.h"
@@ -1448,6 +1449,10 @@
       ash::settings::OSSettingsUI>(map);
 
   RegisterWebUIControllerInterfaceBinder<
+      ash::settings::app_parental_controls::mojom::AppParentalControlsHandler,
+      ash::settings::OSSettingsUI>(map);
+
+  RegisterWebUIControllerInterfaceBinder<
       ash::settings::mojom::InputDeviceSettingsProvider,
       ash::settings::OSSettingsUI>(map);
 
diff --git a/chrome/browser/chromeos/mahi/mahi_web_contents_manager.cc b/chrome/browser/chromeos/mahi/mahi_web_contents_manager.cc
index 635bc92..61a917e1 100644
--- a/chrome/browser/chromeos/mahi/mahi_web_contents_manager.cc
+++ b/chrome/browser/chromeos/mahi/mahi_web_contents_manager.cc
@@ -57,8 +57,6 @@
 MahiWebContentsManager::~MahiWebContentsManager() = default;
 
 void MahiWebContentsManager::Initialize() {
-  // TODO(b/333993475): This should be called sooner to avoid client_ is null in
-  // some cases.
   client_ = std::make_unique<
       MahiBrowserClientImpl>(/*request_content_callback=*/
                              base::BindRepeating(
diff --git a/chrome/browser/content_language/content_language_browsertest.cc b/chrome/browser/content_language/content_language_browsertest.cc
index c0b85bf..9b166be 100644
--- a/chrome/browser/content_language/content_language_browsertest.cc
+++ b/chrome/browser/content_language/content_language_browsertest.cc
@@ -22,7 +22,6 @@
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/url_loader_interceptor.h"
 #include "net/base/features.h"
-#include "services/network/public/cpp/features.h"
 #include "third_party/blink/public/common/metrics/accept_language_and_content_language_usage.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "url/origin.h"
@@ -91,13 +90,7 @@
 // https://httpwg.org/specs/rfc7231.html#header.content-language
 class RecordLanguagesMetricsBrowserTest : public InProcessBrowserTest {
  public:
-  RecordLanguagesMetricsBrowserTest() {
-    // TODO(crbug.com/334954143) This tests is used to verify the metrics before
-    // we launch ReduceAcceptLanguage. Fix the tests when turning on the reduce
-    // accept-language feature.
-    scoped_feature_list_.InitWithFeatures(
-        {}, {network::features::kReduceAcceptLanguage});
-  }
+  RecordLanguagesMetricsBrowserTest() = default;
 
   static constexpr const char kOriginUrl[] = "https://127.0.0.1:44444";
   static constexpr const char kLanguageHistorgramName[] =
@@ -280,7 +273,6 @@
   std::set<GURL> expected_request_urls_;
   RecordLanguageMetricTestOptions test_options_;
   std::unique_ptr<language::LanguagePrefs> language_prefs_;
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 constexpr const char RecordLanguagesMetricsBrowserTest::kOriginUrl[];
@@ -337,7 +329,7 @@
                        ContentLanguageMatchesAnyAcceptLanguage) {
   SetTestOptions({/*has_content_language_in_parent=*/true,
                   /*has_content_language_in_child=*/false,
-                  /*parent_content_language_value=*/"en-US",
+                  /*parent_content_language_value=*/"en",
                   /*child_content_language_value=*/""},
                  {content_language_url()});
 
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl.cc b/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
index ba2df4b..35c6b42 100644
--- a/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
@@ -469,8 +469,9 @@
 autofill::AutofillProfile*
 FastCheckoutClientImpl::GetSelectedAutofillProfile() {
   autofill::AutofillProfile* autofill_profile =
-      personal_data_helper_->GetPersonalDataManager()->GetProfileByGUID(
-          selected_autofill_profile_guid_.value());
+      personal_data_helper_->GetPersonalDataManager()
+          ->address_data_manager()
+          .GetProfileByGUID(selected_autofill_profile_guid_.value());
   if (!autofill_profile) {
     OnRunComplete(FastCheckoutRunOutcome::kAutofillProfileDeleted);
   }
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc b/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
index cedb0c2..150f6f4 100644
--- a/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
@@ -370,7 +370,7 @@
         std::move(autofill_profile_unique_ptr),
         std::move(credit_card_unique_ptr));
     return {
-        personal_data_manager()->GetProfileByGUID(
+        personal_data_manager()->address_data_manager().GetProfileByGUID(
             fast_checkout_client()->selected_autofill_profile_guid_.value()),
         (local_card
              ? personal_data_manager()->GetCreditCardByGUID(
diff --git a/chrome/browser/fast_checkout/fast_checkout_personal_data_helper_impl.cc b/chrome/browser/fast_checkout/fast_checkout_personal_data_helper_impl.cc
index 8873e05..dcea833 100644
--- a/chrome/browser/fast_checkout/fast_checkout_personal_data_helper_impl.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_personal_data_helper_impl.cc
@@ -28,7 +28,9 @@
 
 std::vector<autofill::AutofillProfile*>
 FastCheckoutPersonalDataHelperImpl::GetProfilesToSuggest() const {
-  return GetPersonalDataManager()->GetProfilesToSuggest();
+  return GetPersonalDataManager()
+      ->address_data_manager()
+      .GetProfilesToSuggest();
 }
 
 std::vector<autofill::CreditCard*>
@@ -73,7 +75,7 @@
   autofill::PersonalDataManager* pdm = GetPersonalDataManager();
   // Trigger only if there is at least 1 complete address profile on file.
   std::vector<autofill::AutofillProfile*> profiles =
-      pdm->GetProfilesToSuggest();
+      pdm->address_data_manager().GetProfilesToSuggest();
 
   std::erase_if(profiles,
                 [&pdm, this](const autofill::AutofillProfile* profile) {
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index b4102f6d..40f6d30 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -4677,7 +4677,7 @@
   {
     "name": "fuse-box-debug",
     "owners": [ "nigeltao@chromium.org", "simmonsjosh@google.com" ],
-    "expiry_milestone": 126
+    "expiry_milestone": 138
   },
   {
     "name": "gainmap-hdr-images",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index fd1c4823..0bae2ca 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -7162,13 +7162,6 @@
     "Enables Instant Tethering. Instant Tethering allows your nearby Google "
     "phone to share its Internet connection with this device.";
 
-const char kTimeOfDayWallpaperForcedAutoScheduleName[] =
-    "Time of Day Wallpaper Forced Auto Schedule";
-const char kTimeOfDayWallpaperForcedAutoScheduleDescription[] =
-    "Forces the time of day wallpaper to change on an automatic "
-    "sunset-to-sunrise schedule, regardless of what dark/light mode settings "
-    "are active. Not used if time of day wallpaper is not enabled.";
-
 const char kTimeOfDayDlcName[] = "Time of Day Dlc";
 const char kTimeOfDayDlcDescription[] =
     "Enables downloading Time of Day Screen Saver assets from DLC rather than "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 909f1da..af601ffe 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -4161,9 +4161,6 @@
 extern const char kTetherName[];
 extern const char kTetherDescription[];
 
-extern const char kTimeOfDayWallpaperForcedAutoScheduleName[];
-extern const char kTimeOfDayWallpaperForcedAutoScheduleDescription[];
-
 extern const char kTimeOfDayDlcName[];
 extern const char kTimeOfDayDlcDescription[];
 
diff --git a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java
index db6ba57..5ea30365 100644
--- a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java
+++ b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java
@@ -11,6 +11,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -141,9 +142,15 @@
         }
     }
 
-    /** Open app filter bottom sheet. */
-    public void openSheet() {
+    /**
+     * Open app filter bottom sheet.
+     *
+     * @param currentApp Initial app to be selected at the beginning. If {@code null}, no app will
+     *     be selected.
+     */
+    public void openSheet(@Nullable AppInfo currentApp) {
         updateSheetHeight();
+        mMediator.resetState(currentApp);
         mBottomSheetController.requestShowContent(mSheetContent, true);
     }
 
@@ -189,4 +196,8 @@
     void setCurrentAppForTesting(String appId) {
         mMediator.setCurrentAppForTesting(appId); // IN-TEST
     }
+
+    String getCurrentAppIdForTesting() {
+        return mMediator.getCurrentAppIdForTesting(); // IN-TEST
+    }
 }
diff --git a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinatorTest.java b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinatorTest.java
index 3344341..0994e54 100644
--- a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinatorTest.java
+++ b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinatorTest.java
@@ -47,9 +47,6 @@
 @Batch(Batch.PER_CLASS)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class AppFilterCoordinatorTest {
-    /** {@link AppInfo} indicating that no app is selected i.e. full history. */
-    private static final AppInfo APP_NOTSELECTED = new AppInfo(null, null, null);
-
     private static final String APPID_YOUTUBE = "com.google.android.youtube";
     private static final String APPID_CHROME = "com.android.chrome";
     private static final String APPID_CALENDAR = "com.google.android.calendar";
@@ -66,9 +63,7 @@
 
     private BottomSheetController mBottomSheetController;
     private AppFilterCoordinator mAppFilterSheet;
-    private Drawable mIcon;
-    private String mAppId;
-    private CharSequence mAppLabel;
+    private AppInfo mCurrentApp;
 
     @Before
     public void setUp() throws InterruptedException {
@@ -79,12 +74,12 @@
                 () -> {
                     mBottomSheetController = createBottomSheetController();
 
-                    mIcon = activity.getResources().getDrawable(R.drawable.ic_devices_16dp);
+                    Drawable icon = activity.getResources().getDrawable(R.drawable.ic_devices_16dp);
                     List<AppInfo> apps = new ArrayList<>();
-                    apps.add(new AppInfo(APPID_YOUTUBE, mIcon, APPLABEL_YOUTUBE));
-                    apps.add(new AppInfo(APPID_CHROME, mIcon, APPLABEL_CHROME));
-                    apps.add(new AppInfo(APPID_CALENDAR, mIcon, APPLABEL_CALENDAR));
-                    apps.add(new AppInfo(APPID_MESSAGE, mIcon, APPLABEL_MESSAGE));
+                    apps.add(new AppInfo(APPID_YOUTUBE, icon, APPLABEL_YOUTUBE));
+                    apps.add(new AppInfo(APPID_CHROME, icon, APPLABEL_CHROME));
+                    apps.add(new AppInfo(APPID_CALENDAR, icon, APPLABEL_CALENDAR));
+                    apps.add(new AppInfo(APPID_MESSAGE, icon, APPLABEL_MESSAGE));
                     mAppFilterSheet =
                             new AppFilterCoordinator(
                                     activity,
@@ -123,14 +118,11 @@
     }
 
     private void onAppUpdated(AppInfo appInfo) {
-        mAppId = appInfo != null ? appInfo.id : null;
-        mAppLabel = appInfo != null ? appInfo.label : null;
+        mCurrentApp = appInfo;
     }
 
     private void setCurrentAppInfo(String appId, CharSequence appLabel) {
-        mAppId = appId;
-        mAppLabel = appLabel;
-        mAppFilterSheet.setCurrentAppForTesting(appId);
+        mCurrentApp = appId == null ? null : new AppInfo(appId, null, appLabel);
     }
 
     private int calcSheetHeight(int rowHeight, int baseViewHeight, int rowCount) {
@@ -172,66 +164,106 @@
     @Test
     @MediumTest
     public void testFullHistoryToApp() {
-        assertEquals("Selected app is not correct.", null, mAppId);
+        assertEquals("Selected app is not correct.", null, mCurrentApp);
 
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> {
-                    mAppFilterSheet.openSheet();
+                    mAppFilterSheet.openSheet(mCurrentApp);
                     mAppFilterSheet.clickItemForTesting(APPID_MESSAGE);
                 });
 
         // Tapping an app selects it.
-        assertEquals("Chosen app is not correct.", APPID_MESSAGE, mAppId);
-        assertEquals("Chosen app is not correct.", APPLABEL_MESSAGE, mAppLabel);
+        assertEquals("Chosen app is not correct.", APPID_MESSAGE, mCurrentApp.id);
+        assertEquals("Chosen label is not correct.", APPLABEL_MESSAGE, mCurrentApp.label);
     }
 
     @Test
     @MediumTest
     public void testSelectNewApp() {
         setCurrentAppInfo(APPID_CALENDAR, APPLABEL_CALENDAR);
-        assertEquals("Selected app is not correct.", APPID_CALENDAR, mAppId);
+        assertEquals("Selected app is not correct.", APPID_CALENDAR, mCurrentApp.id);
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> {
-                    mAppFilterSheet.openSheet();
+                    mAppFilterSheet.openSheet(mCurrentApp);
                     mAppFilterSheet.clickItemForTesting(APPID_CHROME);
                 });
 
         // Tapping an app makes it a newly selected one.
-        assertEquals("Chosen app is not correct.", APPID_CHROME, mAppId);
-        assertEquals("Chosen app is not correct.", APPLABEL_CHROME, mAppLabel);
+        assertEquals("Chosen app is not correct.", APPID_CHROME, mCurrentApp.id);
+        assertEquals("Chosen label is not correct.", APPLABEL_CHROME, mCurrentApp.label);
     }
 
     @Test
     @MediumTest
     public void testUnselectApp() {
         setCurrentAppInfo(APPID_CALENDAR, APPLABEL_CALENDAR);
-        assertEquals("Selected app is not correct.", APPID_CALENDAR, mAppId);
+        assertEquals("Selected app is not correct.", APPID_CALENDAR, mCurrentApp.id);
 
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> {
-                    mAppFilterSheet.openSheet();
+                    mAppFilterSheet.openSheet(mCurrentApp);
                     mAppFilterSheet.clickItemForTesting(APPID_CALENDAR);
                 });
 
         // Tapping the already selected app unselects it.
-        assertEquals("Chosen app is not correct.", APP_NOTSELECTED.id, mAppId);
-        assertEquals("Chosen app is not correct.", APP_NOTSELECTED.label, mAppLabel);
+        assertEquals("Chosen app is not correct.", null, mCurrentApp);
+
+        // Open the sheet once more and select the app that was unselected right before.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mAppFilterSheet.openSheet(mCurrentApp);
+                    mAppFilterSheet.clickItemForTesting(APPID_CALENDAR);
+                });
+        assertEquals("Chosen app is not correct.", APPID_CALENDAR, mCurrentApp.id);
+        assertEquals("Chosen label is not correct.", APPLABEL_CALENDAR, mCurrentApp.label);
+    }
+
+    @Test
+    @MediumTest
+    public void testResetSheetAtOpen() {
+        assertEquals("Selected app is not correct.", null, mCurrentApp);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mAppFilterSheet.openSheet(mCurrentApp);
+                    mAppFilterSheet.clickItemForTesting(APPID_CALENDAR);
+                });
+        assertEquals("Chosen app should be Calendar.", APPID_CALENDAR, mCurrentApp.id);
+
+        // Caller resets its state and opens the sheet again. The sheet should be reset in sync.
+        setCurrentAppInfo(null, null);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mAppFilterSheet.openSheet(mCurrentApp);
+                });
+        assertEquals(
+                "No app should be selected.", null, mAppFilterSheet.getCurrentAppIdForTesting());
+
+        setCurrentAppInfo(APPID_YOUTUBE, APPLABEL_YOUTUBE);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mAppFilterSheet.openSheet(mCurrentApp);
+                });
+        assertEquals(
+                "Chosen app should be YouTube.",
+                APPID_YOUTUBE,
+                mAppFilterSheet.getCurrentAppIdForTesting());
     }
 
     @Test
     @MediumTest
     public void testCloseSheetWithoutSelection() {
         setCurrentAppInfo(APPID_CALENDAR, APPLABEL_CALENDAR);
-        assertEquals("Selected app is not correct.", APPID_CALENDAR, mAppId);
+        assertEquals("Selected app is not correct.", APPID_CALENDAR, mCurrentApp.id);
 
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> {
-                    mAppFilterSheet.openSheet();
+                    mAppFilterSheet.openSheet(mCurrentApp);
                     mAppFilterSheet.clickCloseButtonForTesting();
                 });
 
         // Closing the sheet preserves the previously selected app.
-        assertEquals("Chosen app is not correct.", APPID_CALENDAR, mAppId);
-        assertEquals("Chosen app is not correct.", APPLABEL_CALENDAR, mAppLabel);
+        assertEquals("Chosen app is not correct.", APPID_CALENDAR, mCurrentApp.id);
+        assertEquals("Chosen label is not correct.", APPLABEL_CALENDAR, mCurrentApp.label);
     }
 }
diff --git a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterMediator.java b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterMediator.java
index 107eba3..c2cb34d 100644
--- a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterMediator.java
+++ b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterMediator.java
@@ -49,6 +49,17 @@
         return model;
     }
 
+    void resetState(AppInfo currentApp) {
+        if (mSelectedModel != null) {
+            mSelectedModel.set(AppFilterProperties.SELECTED, false);
+            mSelectedModel = null;
+        }
+        if (currentApp != null) {
+            mSelectedModel = getModelForAppId(currentApp.id);
+            mSelectedModel.set(AppFilterProperties.SELECTED, true);
+        }
+    }
+
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     void handleClick(PropertyModel model) {
         PropertyModel prevModel = mSelectedModel;
@@ -57,7 +68,7 @@
         boolean toFullHistory = prevModel != null && prevModel == model;
 
         if (prevModel != null) prevModel.set(AppFilterProperties.SELECTED, false);
-        mSelectedModel = model;
+        mSelectedModel = toFullHistory ? null : model;
         if (toFullHistory) {
             mCloseCallback.onAppUpdated(null);
         } else {
@@ -67,7 +78,7 @@
         }
     }
 
-    private PropertyModel getModelForAppIdForTesting(String appId) {
+    private PropertyModel getModelForAppId(String appId) {
         for (SimpleRecyclerViewAdapter.ListItem item : mModelList) {
             if (appId.equals(item.model.get(AppFilterProperties.ID))) {
                 return item.model;
@@ -77,10 +88,14 @@
     }
 
     void clickItemForTesting(String appId) {
-        handleClick(getModelForAppIdForTesting(appId));
+        handleClick(getModelForAppId(appId));
     }
 
     void setCurrentAppForTesting(String appId) {
-        mSelectedModel = getModelForAppIdForTesting(appId);
+        mSelectedModel = getModelForAppId(appId);
+    }
+
+    String getCurrentAppIdForTesting() {
+        return mSelectedModel != null ? mSelectedModel.get(AppFilterProperties.ID) : null;
     }
 }
diff --git a/chrome/browser/icon_transcoder/BUILD.gn b/chrome/browser/icon_transcoder/BUILD.gn
new file mode 100644
index 0000000..a3f6ae5bf
--- /dev/null
+++ b/chrome/browser/icon_transcoder/BUILD.gn
@@ -0,0 +1,39 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//chrome/version.gni")
+
+source_set("icon_transcoder") {
+  sources = [
+    "svg_icon_transcoder.cc",
+    "svg_icon_transcoder.h",
+  ]
+
+  deps = [
+    "//base",
+    "//content/public/browser",
+    "//skia",
+    "//ui/gfx",
+    "//url",
+  ]
+}
+
+source_set("browser_tests") {
+  testonly = true
+
+  defines = [
+    "HAS_OUT_OF_PROC_TEST_RUNNER",
+  ]
+
+  sources = [
+    "svg_icon_transcoder_browsertest.cc",
+  ]
+
+  deps = [
+    ":icon_transcoder",
+    "//base",
+    "//chrome/browser/ui",
+    "//chrome/test:test_support",
+  ]
+}
diff --git a/chrome/browser/icon_transcoder/svg_icon_transcoder.cc b/chrome/browser/icon_transcoder/svg_icon_transcoder.cc
index 838cf87..ec00850a 100644
--- a/chrome/browser/icon_transcoder/svg_icon_transcoder.cc
+++ b/chrome/browser/icon_transcoder/svg_icon_transcoder.cc
@@ -7,7 +7,7 @@
 #include "base/base64.h"
 #include "base/files/file_util.h"
 #include "base/task/thread_pool.h"
-#include "chrome/browser/profiles/profile.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/codec/png_codec.h"
@@ -48,7 +48,8 @@
 
 }  // namespace
 
-SvgIconTranscoder::SvgIconTranscoder(Profile* profile) : profile_(profile) {}
+SvgIconTranscoder::SvgIconTranscoder(content::BrowserContext* context)
+    : browser_context_(context) {}
 
 SvgIconTranscoder::~SvgIconTranscoder() {
   RemoveObserver();
@@ -114,7 +115,7 @@
 
 void SvgIconTranscoder::MaybeCreateWebContents() {
   if (!web_contents_) {
-    auto params = content::WebContents::CreateParams(profile_);
+    auto params = content::WebContents::CreateParams(browser_context_);
     params.initially_hidden = true;
     params.desired_renderer_state =
         content::WebContents::CreateParams::kInitializeAndWarmupRendererProcess;
diff --git a/chrome/browser/icon_transcoder/svg_icon_transcoder.h b/chrome/browser/icon_transcoder/svg_icon_transcoder.h
index 233acc1..ddffe7b9 100644
--- a/chrome/browser/icon_transcoder/svg_icon_transcoder.h
+++ b/chrome/browser/icon_transcoder/svg_icon_transcoder.h
@@ -18,9 +18,8 @@
 #include "ui/gfx/image/image.h"
 #include "url/gurl.h"
 
-class Profile;
-
 namespace content {
+class BrowserContext;
 class WebContents;
 }  // namespace content
 
@@ -39,7 +38,7 @@
 // SvgIconTranscoder always checks if its WebContents must be recreated.
 class SvgIconTranscoder : public content::RenderProcessHostObserver {
  public:
-  explicit SvgIconTranscoder(Profile* profile);
+  explicit SvgIconTranscoder(content::BrowserContext* context);
 
   SvgIconTranscoder(const SvgIconTranscoder&) = delete;
   SvgIconTranscoder& operator=(const SvgIconTranscoder&) = delete;
@@ -89,7 +88,7 @@
                        const std::vector<SkBitmap>& bitmaps,
                        const std::vector<gfx::Size>& sizes);
 
-  const raw_ptr<Profile, DanglingUntriaged> profile_;
+  const raw_ptr<content::BrowserContext, DanglingUntriaged> browser_context_;
   std::unique_ptr<content::WebContents> web_contents_;
   bool web_contents_ready_{false};
   base::WeakPtrFactory<SvgIconTranscoder> weak_ptr_factory_{this};
diff --git a/chrome/browser/icon_transcoder/svg_icon_transcoder_browsertest.cc b/chrome/browser/icon_transcoder/svg_icon_transcoder_browsertest.cc
index 468af32..cee2b24 100644
--- a/chrome/browser/icon_transcoder/svg_icon_transcoder_browsertest.cc
+++ b/chrome/browser/icon_transcoder/svg_icon_transcoder_browsertest.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/icon_transcoder/svg_icon_transcoder.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_test.h"
 
 constexpr char kSvgData[] =
diff --git a/chrome/browser/net/chrome_shared_dictionary_browsertest.cc b/chrome/browser/net/chrome_shared_dictionary_browsertest.cc
index a63a0a91..9d1fcf19 100644
--- a/chrome/browser/net/chrome_shared_dictionary_browsertest.cc
+++ b/chrome/browser/net/chrome_shared_dictionary_browsertest.cc
@@ -7,15 +7,12 @@
 #include <string_view>
 #include <utility>
 
-#include "base/files/file_util.h"
 #include "base/functional/callback.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
-#include "base/threading/thread_restrictions.h"
-#include "build/build_config.h"
 #include "chrome/browser/browsing_data/counters/site_data_counting_helper.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -30,10 +27,6 @@
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
-#include "content/public/test/test_devtools_protocol_client.h"
-#include "net/dns/mock_host_resolver.h"
-#include "net/extras/shared_dictionary/shared_dictionary_usage_info.h"
-#include "net/test/embedded_test_server/controllable_http_response.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/shared_dictionary_encoding_names.h"
 #include "services/network/public/mojom/network_context.mojom.h"
@@ -161,30 +154,6 @@
       expected_used_count_with_zstd_d);
 }
 
-std::string FetchUrlScript(const GURL& url) {
-  return content::JsReplace(R"(
-          (async () => {
-            try {
-              await fetch($1);
-            } catch (e) {
-            }
-          })();
-        )",
-                            url);
-}
-
-std::string FetchUrlWithNoCorsModeScript(const GURL& url) {
-  return content::JsReplace(R"(
-          (async () => {
-            try {
-              await fetch($1, {mode: 'no-cors'});
-            } catch (e) {
-            }
-          })();
-        )",
-                            url);
-}
-
 }  // namespace
 
 // `ChromeSharedDictionaryBrowserTest` is required to test Chrome
@@ -713,388 +682,3 @@
   EXPECT_EQ(1,
             GetSiteDataCount(response_time, response_time + base::Seconds(1)));
 }
-
-class SharedDictionaryDevToolsBrowserTest
-    : public InProcessBrowserTest,
-      public content::TestDevToolsProtocolClient {
- public:
-  SharedDictionaryDevToolsBrowserTest() = default;
-  ~SharedDictionaryDevToolsBrowserTest() override = default;
-  void SetUpOnMainThread() override {
-    InProcessBrowserTest::SetUpOnMainThread();
-    host_resolver()->AddRule("*", "127.0.0.1");
-    embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
-    embedded_https_test_server().ServeFilesFromSourceDirectory(
-        "content/test/data");
-  }
-  void TearDownOnMainThread() override {
-    DetachProtocolClient();
-    InProcessBrowserTest::TearDownOnMainThread();
-  }
-
- protected:
-  content::RenderFrameHost* GetPrimaryMainFrame() {
-    return browser()
-        ->tab_strip_model()
-        ->GetActiveWebContents()
-        ->GetPrimaryMainFrame();
-  }
-  void NavigateAndEnableAudits(const GURL& url) {
-    EXPECT_TRUE(NavigateToURL(
-        browser()->tab_strip_model()->GetActiveWebContents(), url));
-    AttachToWebContents(browser()->tab_strip_model()->GetActiveWebContents());
-    SendCommandSync("Network.enable");
-    SendCommandSync("Audits.enable");
-  }
-  base::Value::Dict WaitForSharedDictionaryIssueAdded(
-      const std::string& expected_error_type) {
-    auto matcher = [](const base::Value::Dict& params) {
-      const std::string* maybe_issue_code =
-          params.FindStringByDottedPath("issue.code");
-      return maybe_issue_code && *maybe_issue_code == "SharedDictionaryIssue";
-    };
-    base::Value::Dict notification = WaitForMatchingNotification(
-        "Audits.issueAdded", base::BindRepeating(matcher));
-    EXPECT_EQ(*notification.FindStringByDottedPath("issue.code"),
-              "SharedDictionaryIssue");
-    const std::string* maybe_error_type = notification.FindStringByDottedPath(
-        "issue.details.sharedDictionaryIssueDetails.sharedDictionaryError");
-    CHECK(maybe_error_type);
-    EXPECT_EQ(*maybe_error_type, expected_error_type);
-    return notification;
-  }
-  void RunCustomHeaderTest(
-      const std::string& expected_erorr_type,
-      const std::string& use_as_dictionary_header,
-      const std::string& cache_control_header = "max-age=3600") {
-    embedded_https_test_server().RegisterRequestHandler(
-        base::BindLambdaForTesting(
-            [&](const net::test_server::HttpRequest& request)
-                -> std::unique_ptr<net::test_server::HttpResponse> {
-              if (request.relative_url == "/test.dict") {
-                auto response =
-                    std::make_unique<net::test_server::BasicHttpResponse>();
-                response->AddCustomHeader("use-as-dictionary",
-                                          use_as_dictionary_header);
-                response->AddCustomHeader("cache-control",
-                                          cache_control_header);
-                response->set_content("dict");
-                return response;
-              }
-              return nullptr;
-            }));
-    auto handle = embedded_https_test_server().StartAndReturnHandle();
-    ASSERT_TRUE(handle);
-    NavigateAndEnableAudits(
-        embedded_https_test_server().GetURL("/shared_dictionary/blank.html"));
-    EXPECT_TRUE(ExecJs(
-        GetPrimaryMainFrame(),
-        FetchUrlScript(embedded_https_test_server().GetURL("/test.dict"))));
-    WaitForSharedDictionaryIssueAdded(expected_erorr_type);
-  }
-
-  std::vector<net::SharedDictionaryUsageInfo> GetSharedDictionaryUsageInfo() {
-    base::test::TestFuture<const std::vector<net::SharedDictionaryUsageInfo>&>
-        result;
-    browser()
-        ->profile()
-        ->GetDefaultStoragePartition()
-        ->GetNetworkContext()
-        ->GetSharedDictionaryUsageInfo(result.GetCallback());
-    return result.Get();
-  }
-
-  void WaitUntilDictionaryRegistered() {
-    while (GetSharedDictionaryUsageInfo().empty()) {
-      base::PlatformThread::Sleep(base::Milliseconds(10));
-    }
-  }
-};
-
-class DevToolsSharedDictionaryFeatureDisabledBrowserTest
-    : public SharedDictionaryDevToolsBrowserTest {
- public:
-  DevToolsSharedDictionaryFeatureDisabledBrowserTest() {
-    scoped_feature_list_.InitAndDisableFeature(
-        network::features::kCompressionDictionaryTransport);
-  }
-  ~DevToolsSharedDictionaryFeatureDisabledBrowserTest() override = default;
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       UseErrorCrossOriginNoCorsRequest) {
-  const std::string kHostName = "www.example.com";
-  const std::string kCrossOriginHostName = "other.example.com";
-  embedded_https_test_server().SetCertHostnames(
-      {kHostName, kCrossOriginHostName});
-  ASSERT_TRUE(embedded_https_test_server().Start());
-  NavigateAndEnableAudits(embedded_https_test_server().GetURL(
-      kHostName, "/shared_dictionary/blank.html"));
-  content::RenderFrameHost* rfh = GetPrimaryMainFrame();
-  EXPECT_TRUE(
-      ExecJs(rfh, FetchUrlScript(embedded_https_test_server().GetURL(
-                      kCrossOriginHostName, "/shared_dictionary/test.dict"))));
-  WaitUntilDictionaryRegistered();
-  EXPECT_TRUE(ExecJs(
-      rfh, FetchUrlWithNoCorsModeScript(embedded_https_test_server().GetURL(
-               kCrossOriginHostName, "/shared_dictionary/path/target"))));
-  WaitForSharedDictionaryIssueAdded("UseErrorCrossOriginNoCorsRequest");
-}
-
-// Can't cause the dictionary load failure by deletaing the disk cache directory
-// on Windows.
-#if !BUILDFLAG(IS_WIN)
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       UseErrorDictionaryLoadFailure) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  NavigateAndEnableAudits(
-      embedded_test_server()->GetURL("/shared_dictionary/blank.html"));
-  content::RenderFrameHost* rfh = GetPrimaryMainFrame();
-  EXPECT_TRUE(ExecJs(rfh, FetchUrlScript(embedded_test_server()->GetURL(
-                              "/shared_dictionary/test.dict"))));
-  WaitUntilDictionaryRegistered();
-  {
-    base::ScopedAllowBlockingForTesting allow_blocking;
-    EXPECT_TRUE(base::DeletePathRecursively(
-        browser()->profile()->GetDefaultStoragePartition()->GetPath().Append(
-            FILE_PATH_LITERAL("Shared Dictionary/cache/"))));
-  }
-  EXPECT_TRUE(ExecJs(rfh, FetchUrlScript(embedded_test_server()->GetURL(
-                              "/shared_dictionary/path/compressed.data"))));
-  WaitForSharedDictionaryIssueAdded("UseErrorDictionaryLoadFailure");
-}
-#endif  // !BUILDFLAG(IS_WIN)
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       UseErrorMatchingDictionaryNotUsed) {
-  embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
-      [](const net::test_server::HttpRequest& request)
-          -> std::unique_ptr<net::test_server::HttpResponse> {
-        if (request.relative_url == "/shared_dictionary/path/target") {
-          auto response =
-              std::make_unique<net::test_server::BasicHttpResponse>();
-          response->set_content("data");
-          return response;
-        }
-        return nullptr;
-      }));
-  ASSERT_TRUE(embedded_test_server()->Start());
-  NavigateAndEnableAudits(
-      embedded_test_server()->GetURL("/shared_dictionary/blank.html"));
-  content::RenderFrameHost* rfh = GetPrimaryMainFrame();
-  EXPECT_TRUE(ExecJs(rfh, FetchUrlScript(embedded_test_server()->GetURL(
-                              "/shared_dictionary/test.dict"))));
-  WaitUntilDictionaryRegistered();
-  EXPECT_TRUE(ExecJs(rfh, FetchUrlScript(embedded_test_server()->GetURL(
-                              "/shared_dictionary/path/target"))));
-  WaitForSharedDictionaryIssueAdded("UseErrorMatchingDictionaryNotUsed");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       UseErrorUnexpectedContentDictionaryHeader) {
-  embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
-      [](const net::test_server::HttpRequest& request)
-          -> std::unique_ptr<net::test_server::HttpResponse> {
-        if (request.relative_url == "/shared_dictionary/path/target") {
-          auto response =
-              std::make_unique<net::test_server::BasicHttpResponse>();
-          response->AddCustomHeader("Content-Encoding", "br-d");
-          response->AddCustomHeader("Content-Dictionary",
-                                    "Invalid Content-Dictionary");
-          return response;
-        }
-        return nullptr;
-      }));
-  ASSERT_TRUE(embedded_test_server()->Start());
-  NavigateAndEnableAudits(
-      embedded_test_server()->GetURL("/shared_dictionary/blank.html"));
-  content::RenderFrameHost* rfh = GetPrimaryMainFrame();
-  EXPECT_TRUE(ExecJs(rfh, FetchUrlScript(embedded_test_server()->GetURL(
-                              "/shared_dictionary/test.dict"))));
-  WaitUntilDictionaryRegistered();
-  EXPECT_TRUE(ExecJs(rfh, FetchUrlScript(embedded_test_server()->GetURL(
-                              "/shared_dictionary/path/target"))));
-  WaitForSharedDictionaryIssueAdded(
-      "UseErrorUnexpectedContentDictionaryHeader");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorCossOriginNoCorsRequest) {
-  const std::string kCrossOriginHostName = "example.com";
-  ASSERT_TRUE(embedded_test_server()->Start());
-  NavigateAndEnableAudits(
-      embedded_test_server()->GetURL("/shared_dictionary/blank.html"));
-  EXPECT_TRUE(
-      ExecJs(GetPrimaryMainFrame(),
-             FetchUrlWithNoCorsModeScript(embedded_test_server()->GetURL(
-                 kCrossOriginHostName, "/shared_dictionary/test.dict"))));
-  WaitForSharedDictionaryIssueAdded("WriteErrorCossOriginNoCorsRequest");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorDisallowedBySettings) {
-  const std::string kHostName = "www.example.com";
-  const std::string kCrossOriginHostName = "other.example.com";
-  embedded_https_test_server().SetCertHostnames(
-      {kHostName, kCrossOriginHostName});
-  ASSERT_TRUE(embedded_https_test_server().Start());
-
-  content_settings::CookieSettings* settings =
-      CookieSettingsFactory::GetForProfile(browser()->profile()).get();
-  settings->SetCookieSetting(
-      embedded_https_test_server().GetURL(kCrossOriginHostName, "/"),
-      CONTENT_SETTING_BLOCK);
-
-  NavigateAndEnableAudits(embedded_https_test_server().GetURL(
-      kHostName, "/shared_dictionary/blank.html"));
-  EXPECT_TRUE(
-      ExecJs(browser()
-                 ->tab_strip_model()
-                 ->GetActiveWebContents()
-                 ->GetPrimaryMainFrame(),
-             FetchUrlScript(embedded_https_test_server().GetURL(
-                 kCrossOriginHostName, "/shared_dictionary/test.dict"))));
-  WaitForSharedDictionaryIssueAdded("WriteErrorDisallowedBySettings");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorExpiredResponse) {
-  RunCustomHeaderTest("WriteErrorExpiredResponse",
-                      /*use_as_dictionary_header=*/"match=\"/test/*\"",
-                      /*cache_control_header=*/"no-store");
-}
-
-IN_PROC_BROWSER_TEST_F(DevToolsSharedDictionaryFeatureDisabledBrowserTest,
-                       WriteErrorFeatureDisabled) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  NavigateAndEnableAudits(
-      embedded_test_server()->GetURL("/shared_dictionary/blank.html"));
-  EXPECT_TRUE(ExecJs(GetPrimaryMainFrame(),
-                     FetchUrlScript(embedded_test_server()->GetURL(
-                         "/shared_dictionary/test.dict"))));
-  WaitForSharedDictionaryIssueAdded("WriteErrorFeatureDisabled");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorInvalidMatchField) {
-  RunCustomHeaderTest("WriteErrorInvalidMatchField", "match=\"(\"");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorInvalidStructuredHeader) {
-  RunCustomHeaderTest("WriteErrorInvalidStructuredHeader", "match=\"");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorNavigationRequest) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  NavigateAndEnableAudits(
-      embedded_test_server()->GetURL("/shared_dictionary/blank.html"));
-  EXPECT_TRUE(NavigateToURL(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      embedded_test_server()->GetURL("/shared_dictionary/test_dict.html")));
-  WaitForSharedDictionaryIssueAdded("WriteErrorNavigationRequest");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorNoMatchField) {
-  RunCustomHeaderTest("WriteErrorNoMatchField", "id=\"dict_id\"");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorNonListMatchDestField) {
-  RunCustomHeaderTest("WriteErrorNonListMatchDestField",
-                      "match=\"/test/*\", match-dest=document");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorNonSecureContext) {
-  const std::string kHostName = "example.com";
-  ASSERT_TRUE(embedded_test_server()->Start());
-  NavigateAndEnableAudits(embedded_test_server()->GetURL(
-      kHostName, "/shared_dictionary/blank.html"));
-  EXPECT_TRUE(ExecJs(GetPrimaryMainFrame(),
-                     FetchUrlScript(embedded_test_server()->GetURL(
-                         kHostName, "/shared_dictionary/test.dict"))));
-  WaitForSharedDictionaryIssueAdded("WriteErrorNonSecureContext");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorNonStringIdField) {
-  RunCustomHeaderTest("WriteErrorNonStringIdField",
-                      "match=\"/test/*\", id=dict_id");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorNonStringInMatchDestList) {
-  RunCustomHeaderTest("WriteErrorNonStringInMatchDestList",
-                      "match=\"/test/*\", match-dest=(document)");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorNonStringMatchField) {
-  RunCustomHeaderTest("WriteErrorNonStringMatchField", "match=test");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorNonTokenTypeField) {
-  RunCustomHeaderTest("WriteErrorNonTokenTypeField",
-                      "match=\"/test/*\", type=\"raw\"");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorRequestAborted) {
-  auto dictionary_response =
-      std::make_unique<net::test_server::ControllableHttpResponse>(
-          embedded_test_server(), "/test.dict");
-  ASSERT_TRUE(embedded_test_server()->Start());
-  NavigateAndEnableAudits(
-      embedded_test_server()->GetURL("/shared_dictionary/blank.html"));
-  content::RenderFrameHost* rfh = GetPrimaryMainFrame();
-  EXPECT_TRUE(ExecJs(
-      rfh, content::JsReplace(R"(
-          (() => {
-            const controller = new AbortController();
-            window.FETCH_ABORT_CONTROLLER = controller;
-            window.FETCH_RESULT = fetch($1, {signal: controller.signal});
-          })();
-        )",
-                              embedded_test_server()->GetURL("/test.dict"))));
-  dictionary_response->WaitForRequest();
-  dictionary_response->Send(
-      "HTTP/1.1 200 OK\r\n"
-      "Content-Type: text/plain\r\n"
-      "Use-As-Dictionary: match=\"/test/*\"\r\n"
-      "Cache-Control: max-age=3600\r\n"
-      "\r\n"
-      "dict");
-  EXPECT_TRUE(ExecJs(rfh, R"(
-          (async () => {
-            try {
-              const response = await window.FETCH_RESULT;
-              const reader = response.body.getReader();
-              const result = await reader.read();
-              window.FETCH_ABORT_CONTROLLER.abort();
-            } catch (e) {
-            }
-          })();
-        )"));
-  dictionary_response->Done();
-  WaitForSharedDictionaryIssueAdded("WriteErrorRequestAborted");
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorTooLongIdField) {
-  RunCustomHeaderTest(
-      "WriteErrorTooLongIdField",
-      base::StrCat({"match=\"/test/*\", id=\"", std::string(1025, 'a'), "\""}));
-}
-
-IN_PROC_BROWSER_TEST_F(SharedDictionaryDevToolsBrowserTest,
-                       WriteErrorUnsupportedType) {
-  RunCustomHeaderTest("WriteErrorUnsupportedType",
-                      "match=\"/test/*\", type=unsupported");
-}
diff --git a/chrome/browser/net/errorpage_browsertest.cc b/chrome/browser/net/errorpage_browsertest.cc
index 8dc3981..b97f9b3f 100644
--- a/chrome/browser/net/errorpage_browsertest.cc
+++ b/chrome/browser/net/errorpage_browsertest.cc
@@ -994,23 +994,12 @@
   static const char kHostname[];
   static const char kHostnameJSUnicode[];
 
-  ErrorPageForIDNTest() {
-    // TODO(crbug.com/334954143) This test clears the AcceptLanguage Prefs which
-    // causes Accept-Language to not work correctly. Fix the tests when turning
-    // on the reduce accept-language feature.
-    scoped_feature_list_.InitWithFeatures(
-        {}, {network::features::kReduceAcceptLanguage});
-  }
-
   // InProcessBrowserTest:
   void SetUpOnMainThread() override {
     // Clear AcceptLanguages to force punycode decoding.
     browser()->profile()->GetPrefs()->SetString(
         language::prefs::kAcceptLanguages, std::string());
   }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 const char ErrorPageForIDNTest::kHostname[] =
diff --git a/chrome/browser/net/network_context_configuration_browsertest.cc b/chrome/browser/net/network_context_configuration_browsertest.cc
index 8dc0354..aaaae8a 100644
--- a/chrome/browser/net/network_context_configuration_browsertest.cc
+++ b/chrome/browser/net/network_context_configuration_browsertest.cc
@@ -1305,7 +1305,7 @@
   std::string accept_language, user_agent;
   // Check default.
   ASSERT_TRUE(FetchHeaderEcho("accept-language", &accept_language));
-  EXPECT_EQ(system ? kNoAcceptLanguage : "en-US", accept_language);
+  EXPECT_EQ(system ? kNoAcceptLanguage : "en-US,en;q=0.9", accept_language);
   ASSERT_TRUE(FetchHeaderEcho("user-agent", &user_agent));
   EXPECT_EQ(embedder_support::GetUserAgent(), user_agent);
 
@@ -1326,19 +1326,25 @@
   FlushNetworkInterface();
   std::string accept_language3, user_agent3;
   ASSERT_TRUE(FetchHeaderEcho("accept-language", &accept_language3));
-  EXPECT_EQ(system ? kNoAcceptLanguage : "zu-ZA", accept_language3);
+  EXPECT_EQ(system ? kNoAcceptLanguage : "zu-ZA,zu;q=0.9", accept_language3);
   ASSERT_TRUE(FetchHeaderEcho("user-agent", &user_agent3));
   EXPECT_EQ(embedder_support::GetUserAgent(), user_agent3);
 
-  // Third, a list with multiple languages. ReduceAcceptLanguage turns on
-  // experiment, only returns one language.
+  // Third, a list with multiple languages. Incognito mode should return only
+  // the first.
   browser()->profile()->GetPrefs()->SetString(language::prefs::kAcceptLanguages,
                                               "ar,am,en-GB,ru,zu");
   FlushNetworkInterface();
   std::string accept_language4;
   std::string user_agent4;
   ASSERT_TRUE(FetchHeaderEcho("accept-language", &accept_language4));
-  EXPECT_EQ(system ? kNoAcceptLanguage : "ar", accept_language4);
+  if (GetProfile()->IsOffTheRecord()) {
+    EXPECT_EQ(system ? kNoAcceptLanguage : "ar", accept_language4);
+  } else {
+    EXPECT_EQ(system ? kNoAcceptLanguage
+                     : "ar,am;q=0.9,en-GB;q=0.8,en;q=0.7,ru;q=0.6,zu;q=0.5",
+              accept_language4);
+  }
   ASSERT_TRUE(FetchHeaderEcho("user-agent", &user_agent4));
   EXPECT_EQ(embedder_support::GetUserAgent(), user_agent4);
 }
diff --git a/chrome/browser/platform_experience/win b/chrome/browser/platform_experience/win
index d33c073..c92c8d2 160000
--- a/chrome/browser/platform_experience/win
+++ b/chrome/browser/platform_experience/win
@@ -1 +1 @@
-Subproject commit d33c073148ceea6b4fe7ec64fb8379519bfea545
+Subproject commit c92c8d2a9a4b96aa46b63395584bdd654cd551ac
diff --git a/chrome/browser/resources/ash/settings/BUILD.gn b/chrome/browser/resources/ash/settings/BUILD.gn
index 80d5288..8bd6cfb 100644
--- a/chrome/browser/resources/ash/settings/BUILD.gn
+++ b/chrome/browser/resources/ash/settings/BUILD.gn
@@ -300,6 +300,7 @@
     "os_privacy_page/metrics_consent_toggle_button.ts",
     "os_privacy_page/os_privacy_page.ts",
     "os_privacy_page/peripheral_data_access_protection_dialog.ts",
+    "os_privacy_page/privacy_hub_allow_sensor_access_dialog.ts",
     "os_privacy_page/privacy_hub_app_permission_row.ts",
     "os_privacy_page/privacy_hub_camera_subpage.ts",
     "os_privacy_page/privacy_hub_geolocation_advanced_subpage.ts",
@@ -418,6 +419,7 @@
     "os_apps_page/app_management_page/plugin_vm_page/plugin_vm_browser_proxy.ts",
     "os_apps_page/app_management_page/util.ts",
     "os_apps_page/app_notifications_page/mojo_interface_provider.ts",
+    "os_apps_page/app_parental_controls/mojo_interface_provider.ts",
     "os_bluetooth_page/os_bluetooth_devices_subpage_browser_proxy.ts",
     "os_bluetooth_page/settings_fast_pair_constants.ts",
     "os_languages_page/input_method_settings.ts",
@@ -502,6 +504,7 @@
     "$root_gen_dir/ash/webui/settings/public/constants/routes.mojom-webui.ts",
     "$root_gen_dir/ash/webui/settings/public/constants/setting.mojom-webui.ts",
     "$root_gen_dir/chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_notification_handler.mojom-webui.ts",
+    "$root_gen_dir/chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_parental_controls_handler.mojom-webui.ts",
     "$root_gen_dir/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.mojom-webui.ts",
     "$root_gen_dir/chrome/browser/ui/webui/ash/settings/pages/files/mojom/google_drive_handler.mojom-webui.ts",
     "$root_gen_dir/chrome/browser/ui/webui/ash/settings/pages/files/mojom/one_drive_handler.mojom-webui.ts",
diff --git a/chrome/browser/resources/ash/settings/lazy_load.ts b/chrome/browser/resources/ash/settings/lazy_load.ts
index 42ad868a..f3e7ffb6 100644
--- a/chrome/browser/resources/ash/settings/lazy_load.ts
+++ b/chrome/browser/resources/ash/settings/lazy_load.ts
@@ -265,6 +265,7 @@
 export {AppNotificationRowElement} from './os_apps_page/app_notifications_page/app_notification_row.js';
 export {SettingsAppNotificationsManagerSubpage} from './os_apps_page/app_notifications_page/app_notifications_manager_subpage.js';
 export {AppNotificationsSubpage} from './os_apps_page/app_notifications_page/app_notifications_subpage.js';
+export {SettingsAppParentalControlsSubpageElement} from './os_apps_page/app_parental_controls/app_parental_controls_subpage.js';
 export {ManageIsolatedWebAppsSubpageElement} from './os_apps_page/manage_isolated_web_apps_page/manage_isolated_web_apps_subpage.js';
 export {SettingsBluetoothChangeDeviceNameDialogElement} from './os_bluetooth_page/os_bluetooth_change_device_name_dialog.js';
 export {SettingsBluetoothDeviceDetailSubpageElement} from './os_bluetooth_page/os_bluetooth_device_detail_subpage.js';
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/app_detail_view.html b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/app_detail_view.html
index 2752e9d..e6863c5 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/app_detail_view.html
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/app_detail_view.html
@@ -3,7 +3,9 @@
 <app-management-dom-switch id="viewSelector"
     route="[[getSelectedRouteId_(app_)]]">
   <template>
-    <app-management-pwa-detail-view route-id="pwa-detail-view">
+    <app-management-pwa-detail-view
+        route-id="pwa-detail-view"
+        prefs="{{prefs}}">
     </app-management-pwa-detail-view>
     <app-management-arc-detail-view
         route-id="arc-detail-view"
@@ -12,9 +14,13 @@
     <app-management-chrome-app-detail-view
         route-id="chrome-app-detail-view">
     </app-management-chrome-app-detail-view>
-    <app-management-plugin-vm-detail-view route-id="plugin-vm-detail-view">
+    <app-management-plugin-vm-detail-view
+        route-id="plugin-vm-detail-view"
+        prefs="{{prefs}}">
     </app-management-plugin-vm-detail-view>
-    <app-management-borealis-detail-view route-id="borealis-detail-view">
+    <app-management-borealis-detail-view
+        route-id="borealis-detail-view"
+        prefs="{{prefs}}">
     </app-management-borealis-detail-view>
   </template>
 </app-management-dom-switch>
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/arc_detail_view.html b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/arc_detail_view.html
index 8d96dc66..e56040c0 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/arc_detail_view.html
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/arc_detail_view.html
@@ -43,7 +43,8 @@
             app="[[app_]]"
             icon="[[item.icon]]"
             permission-label="[[i18n(item.labelId)]]"
-            permission-type="[[item.type]]">
+            permission-type="[[item.type]]"
+            prefs="{{prefs}}">
           </app-management-permission-item>
         </template>
       </template>
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/borealis_page/borealis_detail_view.html b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/borealis_page/borealis_detail_view.html
index 2adad05..6b639bcd 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/borealis_page/borealis_detail_view.html
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/borealis_page/borealis_detail_view.html
@@ -46,7 +46,8 @@
           app="[[app_]]"
           class="subpermission-row" icon="app-management:microphone"
           permission-label="$i18n{appManagementMicrophonePermissionLabel}"
-          permission-type="kMicrophone">
+          permission-type="kMicrophone"
+          prefs="{{prefs}}">
       </app-management-permission-item>
     </div>
   </div>
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/borealis_page/borealis_detail_view.ts b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/borealis_page/borealis_detail_view.ts
index 0bd6c8a..f4aa411 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/borealis_page/borealis_detail_view.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/borealis_page/borealis_detail_view.ts
@@ -13,6 +13,7 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {AppManagementStoreMixin} from '../../../common/app_management/store_mixin.js';
+import {PrefsState} from '../../../common/types.js';
 import {Router, routes} from '../../../router.js';
 
 import {getTemplate} from './borealis_detail_view.html.js';
@@ -34,12 +35,18 @@
 
   static get properties() {
     return {
+      prefs: {
+        type: Object,
+        notify: true,
+      },
+
       app_: {
         type: Object,
       },
     };
   }
 
+  prefs: PrefsState;
   private app_: App;
 
   override connectedCallback(): void {
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/permission_item.html b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/permission_item.html
index efe8630e..ecbe2597 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/permission_item.html
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/permission_item.html
@@ -26,9 +26,23 @@
       aria-description="Click to toggle [[permissionLabel]] permissions."
       i18n-aria-descrirption="Label for toggle button to change [[permissionLabel]] permissions.">
     <template is="dom-if" if="[[showPermissionDescriptionString_]]">
-      <div id="permissionDescription" slot="description">
-        [[getPermissionDescriptionString_(app, permissionType)]]
-      </div>
+      <localized-link
+          id="permissionDescription"
+          slot="description"
+          localized-string="[[getPermissionDescriptionString_(app,
+            permissionType, prefs.ash.user.camera_allowed.value,
+            prefs.ash.user.microphone_allowed.value,
+            prefs.ash.user.geolocation_access_level.value)]]"
+          on-link-clicked="launchAllowSensorAccessDialog_">
+      </localized-link>
     </template>
   </app-management-toggle-row>
+  <template is="dom-if" if="[[showAllowSensorAccessDialog_]]" restamp>
+    <settings-privacy-hub-allow-sensor-access-dialog
+        id="dialog"
+        prefs="{{prefs}}"
+        permission-type="[[permissionType]]"
+        on-close="onAllowSensorAccessDialogClose_">
+    </settings-privacy-hub-allow-sensor-access-dialog>
+  </template>
 </template>
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/permission_item.ts b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/permission_item.ts
index 50cddd3..24824a33 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/permission_item.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/permission_item.ts
@@ -3,8 +3,10 @@
 // found in the LICENSE file.
 import './app_management_cros_shared_style.css.js';
 import './toggle_row.js';
+import '../../os_privacy_page/privacy_hub_allow_sensor_access_dialog.js';
 
 import {assert, assertNotReached} from '//resources/js/assert.js';
+import {PrefsMixin} from '/shared/settings/prefs/prefs_mixin.js';
 import {App, InstallReason, Permission, PermissionType, TriState} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
 import {BrowserProxy} from 'chrome://resources/cr_components/app_management/browser_proxy.js';
 import {AppManagementUserAction} from 'chrome://resources/cr_components/app_management/constants.js';
@@ -14,11 +16,16 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {GeolocationAccessLevel} from '../../os_privacy_page/privacy_hub_geolocation_subpage.js';
+
 import {getTemplate} from './permission_item.html.js';
 import {AppManagementToggleRowElement} from './toggle_row.js';
 import {getPermissionDescriptionString} from './util.js';
 
-export class AppManagementPermissionItemElement extends PolymerElement {
+const AppManagementPermissionItemElementBase = PrefsMixin(PolymerElement);
+
+export class AppManagementPermissionItemElement extends
+    AppManagementPermissionItemElementBase {
   static get is() {
     return 'app-management-permission-item';
   }
@@ -79,6 +86,11 @@
         type: Boolean,
         computed: 'computeShowPermissionDescriptionString_(permissionType)',
       },
+
+      showAllowSensorAccessDialog_: {
+        type: Boolean,
+        value: false,
+      },
     };
   }
 
@@ -89,6 +101,7 @@
   private syncPermissionManually: boolean;
   private available_: boolean;
   private disabled_: boolean;
+  private showAllowSensorAccessDialog_: boolean;
   private showPermissionDescriptionString_: boolean;
 
   override ready(): void {
@@ -304,10 +317,49 @@
     }
   }
 
+  private isSensorBlocked_(permissionType: PermissionTypeIndex|
+                           undefined): boolean {
+    if (permissionType === undefined || !this.prefs) {
+      return false;
+    }
+
+    switch (PermissionType[permissionType]) {
+      case PermissionType.kCamera:
+        return !this.getPref('ash.user.camera_allowed').value;
+      case PermissionType.kLocation:
+        return loadTimeData.getBoolean(
+                   'privacyHubLocationAccessControlEnabled') &&
+            this.getPref<GeolocationAccessLevel>(
+                    'ash.user.geolocation_access_level')
+                .value !== GeolocationAccessLevel.ALLOWED;
+      case PermissionType.kMicrophone:
+        return !this.getPref('ash.user.microphone_allowed').value;
+      case PermissionType.kContacts:
+      case PermissionType.kStorage:
+      case PermissionType.kNotifications:
+      case PermissionType.kPrinting:
+        return false;
+      default:
+        assertNotReached();
+    }
+  }
+
   private getPermissionDescriptionString_(
       app: App|undefined,
       permissionType: PermissionTypeIndex|undefined): string {
-    return getPermissionDescriptionString(app, permissionType);
+    return getPermissionDescriptionString(
+        app, permissionType, this.isSensorBlocked_(permissionType));
+  }
+
+  private launchAllowSensorAccessDialog_(e: CustomEvent): void {
+    e.detail.event.preventDefault();
+    e.stopPropagation();
+
+    this.showAllowSensorAccessDialog_ = true;
+  }
+
+  private onAllowSensorAccessDialogClose_(): void {
+    this.showAllowSensorAccessDialog_ = false;
   }
 }
 
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.ts b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.ts
index b352200..441d34d5 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.ts
@@ -9,15 +9,16 @@
 import 'chrome://resources/ash/common/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/ash/common/cr_elements/icons.html.js';
 
+import {WebUiListenerMixin} from 'chrome://resources/ash/common/cr_elements/web_ui_listener_mixin.js';
 import {App} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
 import {getSelectedApp} from 'chrome://resources/cr_components/app_management/util.js';
-import {WebUiListenerMixin} from 'chrome://resources/ash/common/cr_elements/web_ui_listener_mixin.js';
 import {assertNotReached} from 'chrome://resources/js/assert.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {cast} from '../../../assert_extras.js';
 import {AppManagementStoreMixin} from '../../../common/app_management/store_mixin.js';
+import {PrefsState} from '../../../common/types.js';
 import {Router, routes} from '../../../router.js';
 import {AppManagementPermissionItemElement} from '../permission_item.js';
 
@@ -39,6 +40,11 @@
 
   static get properties() {
     return {
+      prefs: {
+        type: Object,
+        notify: true,
+      },
+
       app_: Object,
 
       showDialog_: {
@@ -52,6 +58,7 @@
     };
   }
 
+  prefs: PrefsState;
   private app_: App;
   private dialogText_: string;
   private pendingPermissionItem_: AppManagementPermissionItemElement;
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/pwa_detail_view.html b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/pwa_detail_view.html
index d6b0039..177b866 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/pwa_detail_view.html
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/pwa_detail_view.html
@@ -19,18 +19,21 @@
     </app-management-permission-heading>
     <div class="permission-list indented-permission-block">
       <app-management-permission-item id="location"
+          prefs="{{prefs}}"
           class="subpermission-row" icon="app-management:location"
           app="[[app_]]"
           permission-label="$i18n{appManagementLocationPermissionLabel}"
           permission-type="kLocation">
       </app-management-permission-item>
       <app-management-permission-item id="camera" class="subpermission-row"
+          prefs="{{prefs}}"
           icon="app-management:camera"
           app="[[app_]]"
           permission-label="$i18n{appManagementCameraPermissionLabel}"
           permission-type="kCamera">
       </app-management-permission-item>
       <app-management-permission-item id="microphone"
+          prefs="{{prefs}}"
           class="subpermission-row" icon="app-management:microphone"
           app="[[app_]]"
           permission-label="$i18n{appManagementMicrophonePermissionLabel}"
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/pwa_detail_view.ts b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/pwa_detail_view.ts
index e2ff35c..17f6cc5e 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/pwa_detail_view.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/pwa_detail_view.ts
@@ -13,14 +13,15 @@
 import './supported_links_item.js';
 import 'chrome://resources/ash/common/cr_elements/icons.html.js';
 
+import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
 import {App} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
 import {AppMap} from 'chrome://resources/cr_components/app_management/constants.js';
 import {getSelectedApp} from 'chrome://resources/cr_components/app_management/util.js';
-import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {AppManagementStoreMixin} from '../../common/app_management/store_mixin.js';
 import {isRevampWayfindingEnabled} from '../../common/load_time_booleans.js';
+import {PrefsState} from '../../common/types.js';
 
 import {getTemplate} from './pwa_detail_view.html.js';
 
@@ -39,6 +40,11 @@
 
   static get properties() {
     return {
+      prefs: {
+        type: Object,
+        notify: true,
+      },
+
       isRevampWayfindingEnabled_: {
         type: Boolean,
         value() {
@@ -52,6 +58,7 @@
     };
   }
 
+  prefs: PrefsState;
   private app_: App;
   private apps_: AppMap;
   private isRevampWayfindingEnabled_: boolean;
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/read_only_permission_item.ts b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/read_only_permission_item.ts
index 7013bdc..bc91015a 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/read_only_permission_item.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/read_only_permission_item.ts
@@ -77,7 +77,8 @@
   private getPermissionDescriptionString_(
       app: App|undefined,
       permissionType: PermissionTypeIndex|undefined): string {
-    return getPermissionDescriptionString(app, permissionType);
+    return getPermissionDescriptionString(
+        app, permissionType, /*isSensorBlocked=*/ false);
   }
 
   private isManaged_(
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/util.ts b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/util.ts
index 9071633..549b856 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/util.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/util.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import {assert} from '//resources/js/assert.js';
-import {App, TriState} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
+import {App, PermissionType, TriState} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
 import {PermissionTypeIndex} from 'chrome://resources/cr_components/app_management/permission_constants.js';
 import {getPermission, getPermissionValueAsTriState} from 'chrome://resources/cr_components/app_management/util.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
@@ -26,8 +26,14 @@
   Router.getInstance().navigateTo(routes.APP_MANAGEMENT);
 }
 
+/**
+ * @param isSensorBlocked - Access to Camera, Microphone and Location can be
+ * blocked system wide from privacy hub. This input parameter indicates whether
+ * the relevant sensor is blocked.
+ */
 export function getPermissionDescriptionString(
-    app: App|undefined, permissionType: PermissionTypeIndex|undefined): string {
+    app: App|undefined, permissionType: PermissionTypeIndex|undefined,
+    isSensorBlocked: boolean): string {
   if (app === undefined || permissionType === undefined) {
     return '';
   }
@@ -37,6 +43,31 @@
 
   const value = getPermissionValueAsTriState(app, permissionType);
 
+  if (value === TriState.kAllow && isSensorBlocked) {
+    if (PermissionType[permissionType] === PermissionType.kCamera) {
+      return permission.details ?
+          loadTimeData.getStringF(
+              'permissionAllowedTextWithDetailsAndTurnOnCameraAccessButton',
+              permission.details) :
+          loadTimeData.getString(
+              'permissionAllowedTextWithTurnOnCameraAccessButton');
+    } else if (PermissionType[permissionType] === PermissionType.kLocation) {
+      return permission.details ?
+          loadTimeData.getStringF(
+              'permissionAllowedTextWithDetailsAndTurnOnLocationAccessButton',
+              permission.details) :
+          loadTimeData.getString(
+              'permissionAllowedTextWithTurnOnLocationAccessButton');
+    } else if (PermissionType[permissionType] === PermissionType.kMicrophone) {
+      return permission.details ?
+          loadTimeData.getStringF(
+              'permissionAllowedTextWithDetailsAndTurnOnMicrophoneAccessButton',
+              permission.details) :
+          loadTimeData.getString(
+              'permissionAllowedTextWithTurnOnMicrophoneAccessButton');
+    }
+  }
+
   if (value === TriState.kAllow && permission.details) {
     return loadTimeData.getStringF(
         'appManagementPermissionAllowedWithDetails', permission.details);
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_parental_controls_subpage.html b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_parental_controls_subpage.html
index cfb6c9a..3d93052 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_parental_controls_subpage.html
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_parental_controls_subpage.html
@@ -1,3 +1,36 @@
-<style include="settings-shared"></style>
+<style include="settings-shared">
+  .cr-row {
+    align-items: center;
+    border-bottom: var(--card-separator);
+    color: var(--cros-text-color-primary);
+    display: flex;
+    flex-direction: row;
+    font-weight: var(--cros-body-2-font-weight);
+    height: 48px;
+    padding: 0;
+  }
 
-<div id="mainContainer"></div>
\ No newline at end of file
+  .app-title {
+    flex: 1;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+
+  .app-icon {
+    height: 32px;
+    margin-inline-end: 20px;
+    margin-inline-start: 24px;
+    width: 32px;
+  }
+</style>
+
+<div id="appParentalControlsList" class="hr">
+  <template is="dom-repeat" items="[[appList_]]" as="app"
+      sort="alphabeticalSort_">
+    <div class="cr-row">
+      <img class="app-icon" src="chrome://app-icon/[[app.id]]/64"
+          alt="[[app.title]] app icon." aria-hidden="true">
+      <div class="app-title" aria-hidden="true">[[app.title]]</div>
+    </div>
+  </template>
+</div>
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_parental_controls_subpage.ts b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_parental_controls_subpage.ts
index 8a26c2f..2228997f3 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_parental_controls_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/app_parental_controls_subpage.ts
@@ -6,7 +6,10 @@
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {App, AppParentalControlsHandlerInterface} from '../../mojom-webui/app_parental_controls_handler.mojom-webui.js';
+
 import {getTemplate} from './app_parental_controls_subpage.html.js';
+import {getAppParentalControlsProvider} from './mojo_interface_provider.js';
 
 export class SettingsAppParentalControlsSubpageElement extends PolymerElement {
   static get is() {
@@ -16,6 +19,35 @@
   static get template() {
     return getTemplate();
   }
+
+  static get properties() {
+    return {
+      appList_: {
+        type: Array,
+        value: [],
+      },
+    };
+  }
+
+  private appList_: App[];
+  private mojoInterfaceProvider: AppParentalControlsHandlerInterface;
+
+  constructor() {
+    super();
+    this.mojoInterfaceProvider = getAppParentalControlsProvider();
+  }
+
+  override connectedCallback(): void {
+    super.connectedCallback();
+
+    this.mojoInterfaceProvider.getApps().then((result) => {
+      this.appList_ = result.apps;
+    });
+  }
+
+  private alphabeticalSort_(first: App, second: App): number {
+    return first.title!.localeCompare(second.title!);
+  }
 }
 
 customElements.define(
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/mojo_interface_provider.ts b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/mojo_interface_provider.ts
new file mode 100644
index 0000000..34209345
--- /dev/null
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_parental_controls/mojo_interface_provider.ts
@@ -0,0 +1,23 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {AppParentalControlsHandler, AppParentalControlsHandlerInterface} from '../../mojom-webui/app_parental_controls_handler.mojom-webui.js';
+
+let appParentalControlsProvider: AppParentalControlsHandlerInterface|null =
+    null;
+
+export function setAppParentalControlsProviderForTesting(
+    testProvider: AppParentalControlsHandlerInterface): void {
+  appParentalControlsProvider = testProvider;
+}
+
+export function getAppParentalControlsProvider():
+    AppParentalControlsHandlerInterface {
+  // For testing only.
+  if (appParentalControlsProvider) {
+    return appParentalControlsProvider;
+  }
+  appParentalControlsProvider = AppParentalControlsHandler.getRemote();
+  return appParentalControlsProvider;
+}
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_allow_sensor_access_dialog.html b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_allow_sensor_access_dialog.html
new file mode 100644
index 0000000..90c3fde
--- /dev/null
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_allow_sensor_access_dialog.html
@@ -0,0 +1,19 @@
+<style include="settings-shared"></style>
+<cr-dialog id="allowSensorAccessDialog" show-on-attach>
+  <div slot="title">[[title_]]</div>
+  <div slot="body" id="dialogBody">[[body_]]</div>
+  <div slot="button-container">
+    <cr-button
+        id="cancelButton"
+        class="cancel-button"
+        on-click="onCancelButtonClick_">
+      $i18n{privacyHubDialogCancelButtonLabel}
+    </cr-button>
+    <cr-button
+        id="confirmButton"
+        class="action-button"
+        on-click="onConfirmButtonClick_">
+      $i18n{privacyHubDialogConfirmButtonLabel}
+    </cr-button>
+  </div>
+</cr-dialog>
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_allow_sensor_access_dialog.ts b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_allow_sensor_access_dialog.ts
new file mode 100644
index 0000000..5794eee
--- /dev/null
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_allow_sensor_access_dialog.ts
@@ -0,0 +1,139 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This dialog asks users to enable system wide camera, microphone
+ * or location access.
+ */
+
+import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
+import 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
+import '../settings_shared.css.js';
+
+import {assertNotReached} from '//resources/js/assert.js';
+import {PrefsMixin} from '/shared/settings/prefs/prefs_mixin.js';
+import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
+import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
+import {PermissionType} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
+import {PermissionTypeIndex} from 'chrome://resources/cr_components/app_management/permission_constants.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {castExists} from '../assert_extras.js';
+
+import {getTemplate} from './privacy_hub_allow_sensor_access_dialog.html.js';
+import {GeolocationAccessLevel} from './privacy_hub_geolocation_subpage.js';
+
+const PrivacyHubAllowSensorAccessDialogBase =
+    PrefsMixin(I18nMixin(PolymerElement));
+
+class PrivacyHubAllowSensorAccessDialog extends
+    PrivacyHubAllowSensorAccessDialogBase {
+  static get is() {
+    return 'settings-privacy-hub-allow-sensor-access-dialog' as const;
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      /**
+       * A string version of the permission type. Must be a value of the
+       * permission type enum in appManagement.mojom.PermissionType.
+       */
+      permissionType: {
+        type: String,
+        reflectToAttribute: true,
+      },
+
+      title_: {
+        type: String,
+        computed: 'computeTitle_(permissionType)',
+      },
+
+      body_: {
+        type: String,
+        computed: 'computeBody_(permissionType)',
+      },
+    };
+  }
+
+  permissionType: PermissionTypeIndex;
+  private body_: string;
+  private title_: string;
+
+  override ready(): void {
+    super.ready();
+
+    this.addEventListener('click', this.onClick_.bind(this));
+  }
+
+  private onClick_(e: Event): void {
+    e.stopPropagation();
+  }
+
+  private computeBody_(): string {
+    switch (PermissionType[this.permissionType]) {
+      case PermissionType.kCamera:
+        return this.i18n('privacyHubAllowCameraAccessDialogBodyText');
+      case PermissionType.kLocation:
+        return this.i18n('privacyHubAllowLocationAccessDialogBodyText');
+      case PermissionType.kMicrophone:
+        return this.i18n('privacyHubAllowMicrophoneAccessDialogBodyText');
+      default:
+        assertNotReached();
+    }
+  }
+
+  private computeTitle_(): string {
+    switch (PermissionType[this.permissionType]) {
+      case PermissionType.kCamera:
+        return this.i18n('privacyHubAllowCameraAccessDialogTitle');
+      case PermissionType.kLocation:
+        return this.i18n('privacyHubAllowLocationAccessDialogTitle');
+      case PermissionType.kMicrophone:
+        return this.i18n('privacyHubAllowMicrophoneAccessDialogTitle');
+      default:
+        assertNotReached();
+    }
+  }
+
+  private onConfirmButtonClick_(): void {
+    this.getDialog_().close();
+    switch (PermissionType[this.permissionType]) {
+      case PermissionType.kCamera:
+        this.setPrefValue('ash.user.camera_allowed', true);
+        return;
+      case PermissionType.kLocation:
+        this.setPrefValue(
+            'ash.user.geolocation_access_level',
+            GeolocationAccessLevel.ALLOWED);
+        return;
+      case PermissionType.kMicrophone:
+        this.setPrefValue('ash.user.microphone_allowed', true);
+        return;
+      default:
+        assertNotReached();
+    }
+  }
+
+  private onCancelButtonClick_(): void {
+    this.getDialog_().close();
+  }
+
+  private getDialog_(): CrDialogElement {
+    return castExists(this.shadowRoot!.querySelector<CrDialogElement>(
+        '#allowSensorAccessDialog'));
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    [PrivacyHubAllowSensorAccessDialog.is]: PrivacyHubAllowSensorAccessDialog;
+  }
+}
+
+customElements.define(
+    PrivacyHubAllowSensorAccessDialog.is, PrivacyHubAllowSensorAccessDialog);
diff --git a/chrome/browser/resources/ash/settings/os_settings.ts b/chrome/browser/resources/ash/settings/os_settings.ts
index e9aa0b5a..bf98bdc0 100644
--- a/chrome/browser/resources/ash/settings/os_settings.ts
+++ b/chrome/browser/resources/ash/settings/os_settings.ts
@@ -183,6 +183,7 @@
 export {PageDisplayerElement} from './main_page_container/page_displayer.js';
 export {recordClick, recordNavigation, recordPageBlur, recordPageFocus, recordSearch, recordSettingChange, setUserActionRecorderForTesting} from './metrics_recorder.js';
 export * as appNotificationHandlerMojom from './mojom-webui/app_notification_handler.mojom-webui.js';
+export * as appParentalControlsHandlerMojom from './mojom-webui/app_parental_controls_handler.mojom-webui.js';
 export * as appPermissionHandlerMojom from './mojom-webui/app_permission_handler.mojom-webui.js';
 export * as crosAudioConfigMojom from './mojom-webui/cros_audio_config.mojom-webui.js';
 export * as displaySettingsProviderMojom from './mojom-webui/display_settings_provider.mojom-webui.js';
@@ -219,6 +220,7 @@
 export {AppManagementSupportedLinksItemElement} from './os_apps_page/app_management_page/supported_links_item.js';
 export {AppManagementToggleRowElement} from './os_apps_page/app_management_page/toggle_row.js';
 export {setAppNotificationProviderForTesting} from './os_apps_page/app_notifications_page/mojo_interface_provider.js';
+export {setAppParentalControlsProviderForTesting} from './os_apps_page/app_parental_controls/mojo_interface_provider.js';
 export {OsSettingsAppsPageElement} from './os_apps_page/os_apps_page.js';
 export {OsBluetoothDevicesSubpageBrowserProxy, OsBluetoothDevicesSubpageBrowserProxyImpl} from './os_bluetooth_page/os_bluetooth_devices_subpage_browser_proxy.js';
 export {SettingsBluetoothPageElement} from './os_bluetooth_page/os_bluetooth_page.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn
index 421ca0e7..1f9561e 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn
@@ -195,16 +195,8 @@
 js2gtest("switch_access_mv3_extjs_tests") {
   test_type = "extension"
   parameterized = "true"
-  sources = [ "focus_ring_manager_test.js" ]
-  gen_include_files = test_includes
-  deps = test_deps
-  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
-}
-
-js2gtest("switch_access_extjs_tests") {
-  test_type = "extension"
   sources = [
-    "auto_scan_manager_test.js",
+    "focus_ring_manager_test.js",
     "item_scan_manager_test.js",
     "nodes/basic_node_test.js",
     "nodes/desktop_node_test.js",
@@ -219,3 +211,11 @@
   deps = test_deps
   defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
 }
+
+js2gtest("switch_access_extjs_tests") {
+  test_type = "extension"
+  sources = [ "auto_scan_manager_test.js" ]
+  gen_include_files = test_includes
+  deps = test_deps
+  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+}
diff --git a/chrome/browser/resources/side_panel/read_anything/app.ts b/chrome/browser/resources/side_panel/read_anything/app.ts
index b4ccc41..0b0fa36 100644
--- a/chrome/browser/resources/side_panel/read_anything/app.ts
+++ b/chrome/browser/resources/side_panel/read_anything/app.ts
@@ -20,7 +20,7 @@
 import {getTemplate} from './app.html.js';
 import {validatedFontName} from './common.js';
 import type {ReadAnythingToolbarElement} from './read_anything_toolbar.js';
-import {convertLangOrLocaleForVoicePackManager, createInitialListOfEnabledLanguages, mojoVoicePackStatusToVoicePackStatusEnum, VoicePackStatus} from './voice_language_util.js';
+import {convertLangOrLocaleForVoicePackManager, createInitialListOfEnabledLanguages, isNatural, mojoVoicePackStatusToVoicePackStatusEnum, VoicePackStatus} from './voice_language_util.js';
 
 const ReadAnythingElementBase = WebUiListenerMixin(PolymerElement);
 
@@ -126,15 +126,18 @@
 
   chrome.readingMode.updateVoicePackStatus = (lang: string, status: string) => {
     const readAnythingApp = document.querySelector('read-anything-app');
-    assert(readAnythingApp, 'no app');
-    readAnythingApp.updateVoicePackStatus(lang, status);
+    if (readAnythingApp) {
+      readAnythingApp.updateVoicePackStatus(lang, status);
+    }
   };
 
   chrome.readingMode.updateVoicePackStatusFromInstallResponse =
       (lang: string, status: string) => {
         const readAnythingApp = document.querySelector('read-anything-app');
-        assert(readAnythingApp, 'no app');
-        readAnythingApp.updateVoicePackStatusFromInstallResponse(lang, status);
+        if (readAnythingApp) {
+          readAnythingApp.updateVoicePackStatusFromInstallResponse(
+              lang, status);
+        }
       };
 
   chrome.readingMode.updateTheme = () => {
@@ -747,34 +750,60 @@
   }
 
   updateVoicePackStatusFromInstallResponse(lang: string, status: string) {
-    // Do not rely on this status from Install response. It has responded
-    // "installed" for voices that are not installed. Instead, rely on the
-    // status from GetVoicePackStatus.
-    // TODO (b/323159502) Trigger ChromeOS system notification that voice has
-    // been installed
-    if (lang && status === 'kInstalled') {
-      // TODO (b/335472298) Handle voice menu downloading voice spinners. Keep
-      // in mind that this status is not reliable, and to explicitly check that
-      // the Natural voices have been installed.
+    if (!lang) {
+      return;
     }
+
+    const voicePackStatus = mojoVoicePackStatusToVoicePackStatusEnum(status);
+    if (voicePackStatus === VoicePackStatus.INSTALL_ERROR) {
+      // TODO (b/331795122) Handle install errors on the UI
+      return;
+    }
+
+
+    // Do not rely on the status from Install response. It has responded
+    // "installed" for voices that are not installed. Instead, request the
+    // status from GetVoicePackInfo. The result will be returned in
+    // updateVoicePackStatus().
+    this.sendGetVoicePackInfoRequest(lang);
   }
 
   updateVoicePackStatus(lang: string, status: string) {
     if (!lang) {
       return;
     }
-    const voicePackStatus = mojoVoicePackStatusToVoicePackStatusEnum(status);
-    if (voicePackStatus === VoicePackStatus.EXISTS &&
-        this.languagesForVoiceDownloads.has(lang)) {
-      chrome.readingMode.sendInstallVoicePackRequest(lang);
-      // TODO(b/326130935): Hide the message when installation completes or
-      // show an error message if something fails.
-      this.voicePackInstallStatus[lang] = VoicePackStatus.INSTALLING;
-    }
 
-    this.voicePackInstallStatus =
-        {...this.voicePackInstallStatus, [lang]: voicePackStatus};
-    // TODO (b/335472298) Handle voice menu downloading voice spinners
+    const voicePackStatus = mojoVoicePackStatusToVoicePackStatusEnum(status);
+    if (voicePackStatus === VoicePackStatus.EXISTS) {
+      if (this.voicePackInstallStatus[lang] === VoicePackStatus.DOWNLOADED) {
+        // If the language pack is uninstalled but we still think it is
+        // installed, then the user removed the language pack outside of reading
+        // mode and we don't want to reinstall.
+        this.setVoicePackStatus_(lang, VoicePackStatus.REMOVED_BY_USER);
+      } else if (this.languagesForVoiceDownloads.has(lang)) {
+        chrome.readingMode.sendInstallVoicePackRequest(lang);
+
+        // TODO(b/326130935): Hide the message when installation completes or
+        // show an error message if something fails.
+        this.setVoicePackStatus_(lang, VoicePackStatus.INSTALLING);
+      }
+    } else if (voicePackStatus === VoicePackStatus.DOWNLOADED) {
+      // If we've never seen the voice pack for this language, then it was
+      // already downloaded so mark it as such.
+      if (!this.voicePackInstallStatus[lang]) {
+        // TODO: (b/325962407) - Trigger ChromeOS system notification that voice
+        // has been installed.
+        this.setVoicePackStatus_(lang, voicePackStatus);
+      }
+
+      // Force a refresh of the voices list since we might not get an update the
+      // voices have changed.
+      this.getVoices(true);
+      return;
+    } else {
+      this.setVoicePackStatus_(lang, voicePackStatus);
+      // TODO (b/335472298) Handle voice menu downloading voice spinners
+    }
   }
 
   private onSpeechRateChange_(event: CustomEvent<{rate: number}>) {
@@ -828,6 +857,19 @@
       this.availableLangs = [...new Set(availableVoices.map(({lang}) => lang))];
 
       this.populateDisplayNamesForLocaleCodes();
+
+      // Update natural voices install status if we're refreshing the list.
+      if (refresh) {
+        const isNaturalVoiceDownloaded = (voice: SpeechSynthesisVoice) =>
+            isNatural(voice) &&
+            (this.voicePackInstallStatus[voice.lang] ===
+             VoicePackStatus.DOWNLOADED);
+        availableVoices.filter(isNaturalVoiceDownloaded)
+            .forEach(downloadedNaturalVoice => {
+              this.setVoicePackStatus_(
+                  downloadedNaturalVoice.lang, VoicePackStatus.INSTALLED);
+            });
+      }
     }
     return this.availableVoices;
   }
@@ -1751,19 +1793,23 @@
         convertLangOrLocaleForVoicePackManager(langOrLocale);
 
     if (!langCodeForVoicePackManager) {
-      this.voicePackInstallStatus = {
-        ...this.voicePackInstallStatus,
-        [langOrLocale]: VoicePackStatus.NONE,
-      };
+      this.setVoicePackStatus_(langOrLocale, VoicePackStatus.NONE);
       return;
     }
 
-    if (this.voicePackInstallStatus[langCodeForVoicePackManager] !==
-        VoicePackStatus.INSTALLED) {
+    const statusForLang =
+        this.voicePackInstallStatus[langCodeForVoicePackManager];
+    if (!statusForLang || (statusForLang === VoicePackStatus.EXISTS)) {
       this.languagesForVoiceDownloads.add(langCodeForVoicePackManager);
       // Inquire if the voice pack is downloaded. If not, it'll trigger a
-      // download when we get the response in updateVoicePackStatus()
+      // download when we get the response in updateVoicePackStatus().
       this.sendGetVoicePackInfoRequest(langCodeForVoicePackManager);
+      this.setVoicePackStatus_(
+          langCodeForVoicePackManager, VoicePackStatus.EXISTS);
+    } else if (statusForLang === VoicePackStatus.DOWNLOADED) {
+      // Force a refresh of the voices list since we might not get an update the
+      // voices have changed.
+      this.getVoices(/*refresh=*/ true);
     }
   }
 
@@ -1773,6 +1819,13 @@
       this.onPlayPauseClick_();
     }
   }
+
+  private setVoicePackStatus_(lang: string, status: VoicePackStatus) {
+    this.voicePackInstallStatus = {
+      ...this.voicePackInstallStatus,
+      [lang]: status,
+    };
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html
index 1ef8fb6..834773e 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html
+++ b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.html
@@ -73,6 +73,13 @@
     max-height: 48px;
     display: inline-block;
   }
+  @media (min-resolution: 125dpi) and (max-resolution: 200dpi) {
+    .toolbar-container {
+      overflow: visible;
+      white-space: normal;
+      max-height: none;
+    }
+  }
   #more {
     display: none;
   }
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
index 5d31cc8..33ed170 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
@@ -332,7 +332,7 @@
   private colorSuffix_: string = '';
 
   private toolbarContainerObserver_: ResizeObserver|null;
-  private dragResizeCallback_: () => void;
+  private windowResizeCallback_: () => void;
 
   // If Read Aloud is in the paused state. This is set from the parent element
   // via one way data binding.
@@ -381,9 +381,8 @@
       this.toolbarContainerObserver_ =
           new ResizeObserver(this.onToolbarResize_);
       this.toolbarContainerObserver_.observe(this.$.toolbarContainer);
-
-      this.dragResizeCallback_ = this.onDragResize_.bind(this);
-      window.addEventListener('resize', this.dragResizeCallback_);
+      this.windowResizeCallback_ = this.onWindowResize_.bind(this);
+      window.addEventListener('resize', this.windowResizeCallback_);
     }
     this.textStyleOptions_ =
         this.textStyleOptions_.concat(this.moreOptionsButtons_);
@@ -396,8 +395,8 @@
 
   override disconnectedCallback() {
     super.disconnectedCallback();
-    if (this.dragResizeCallback_) {
-      window.removeEventListener('resize', this.dragResizeCallback_);
+    if (this.windowResizeCallback_) {
+      window.removeEventListener('resize', this.windowResizeCallback_);
     }
     this.toolbarContainerObserver_?.disconnect();
   }
@@ -427,7 +426,7 @@
     this.areFontsLoaded_ = true;
   }
 
-  private onDragResize_() {
+  private onWindowResize_() {
     ReadAnythingToolbarElement.maybeUpdateMoreOptions(this.$.toolbarContainer);
   }
 
diff --git a/chrome/browser/resources/side_panel/read_anything/voice_language_util.ts b/chrome/browser/resources/side_panel/read_anything/voice_language_util.ts
index c50f2b9..dad4b52 100644
--- a/chrome/browser/resources/side_panel/read_anything/voice_language_util.ts
+++ b/chrome/browser/resources/side_panel/read_anything/voice_language_util.ts
@@ -12,6 +12,14 @@
   INSTALL_ERROR,
 }
 
+// This string is not localized and will be in English, even for non-English
+// Natural voices.
+const NATURAL_STRING_IDENTIFIER = '(Natural)';
+
+export function isNatural(voice: SpeechSynthesisVoice) {
+  return voice.name.includes(NATURAL_STRING_IDENTIFIER);
+}
+
 export function createInitialListOfEnabledLanguages(
     browserOrPageBaseLang: string, storedLanguagesPref: string[],
     availableLangs: string[], langOfDefaultVoice: string|undefined): string[] {
@@ -85,7 +93,7 @@
   } else if (mojoPackStatus === 'kInstalling') {
     return VoicePackStatus.INSTALLING;
   } else if (mojoPackStatus === 'kInstalled') {
-    return VoicePackStatus.INSTALLED;
+    return VoicePackStatus.DOWNLOADED;
   }
   // The success statuses were not sent so return an Error
   // TODO (b/331795122) Handle install errors on the UI
diff --git a/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.ts b/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.ts
index 65af6de6..7cd404f 100644
--- a/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.ts
+++ b/chrome/browser/resources/side_panel/read_anything/voice_selection_menu.ts
@@ -20,6 +20,7 @@
 import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import type {LanguageMenuElement} from './language_menu.js';
+import {isNatural} from './voice_language_util.js';
 import {getTemplate} from './voice_selection_menu.html.js';
 
 export interface VoiceSelectionMenuElement {
@@ -47,9 +48,6 @@
 // Events emitted from the voice selection menu to the app
 export const PLAY_PREVIEW_EVENT = 'preview-voice';
 
-// This string is not localized and will be in English, even for non-English
-// Natural voices.
-const NATURAL_STRING_IDENTIFIER = '(Natural)';
 
 const VoiceSelectionMenuElementBase = WebUiListenerMixin(PolymerElement);
 
@@ -271,16 +269,16 @@
     voice1: VoiceDropdownItem,
     voice2: VoiceDropdownItem,
     ): number {
-  if (isNatural(voice1) && isNatural(voice2)) {
+  if (isNatural(voice1.voice) && isNatural(voice2.voice)) {
     return 0;
   }
 
-  if (!isNatural(voice1) && !isNatural(voice2)) {
+  if (!isNatural(voice1.voice) && !isNatural(voice2.voice)) {
     return 0;
   }
 
   // voice1 is a Natural voice and voice2 is not
-  if (isNatural(voice1)) {
+  if (isNatural(voice1.voice)) {
     return -1;
   }
 
@@ -299,10 +297,6 @@
       voice1.name === voice2.name && voice1.voiceURI === voice2.voiceURI;
 }
 
-function isNatural(voiceDropdownItem: VoiceDropdownItem) {
-  return voiceDropdownItem.voice.name.includes(NATURAL_STRING_IDENTIFIER);
-}
-
 declare global {
   interface HTMLElementTagNameMap {
     'voice-selection-menu': VoiceSelectionMenuElement;
diff --git a/chrome/browser/screen_ai/optical_character_recognizer.cc b/chrome/browser/screen_ai/optical_character_recognizer.cc
index 8b4e2cf..1cab2cc1 100644
--- a/chrome/browser/screen_ai/optical_character_recognizer.cc
+++ b/chrome/browser/screen_ai/optical_character_recognizer.cc
@@ -27,27 +27,52 @@
 
 namespace screen_ai {
 
+// static
+scoped_refptr<screen_ai::OpticalCharacterRecognizer>
+OpticalCharacterRecognizer::Create(Profile* profile) {
+  return CreateWithStatusCallback(profile, base::NullCallbackAs<void(bool)>());
+}
+
+// static
+scoped_refptr<screen_ai::OpticalCharacterRecognizer>
+OpticalCharacterRecognizer::CreateWithStatusCallback(
+    Profile* profile,
+    base::OnceCallback<void(bool)> status_callback) {
+  auto ocr =
+      base::MakeRefCounted<screen_ai::OpticalCharacterRecognizer>(profile);
+  ocr->Initialize(std::move(status_callback));
+  return ocr;
+}
+
 OpticalCharacterRecognizer::OpticalCharacterRecognizer(Profile* profile)
     : RefCountedDeleteOnSequence<OpticalCharacterRecognizer>(
           content::GetUIThreadTaskRunner()),
       profile_(profile) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+}
 
+void OpticalCharacterRecognizer::Initialize(
+    base::OnceCallback<void(bool)> status_callback) {
+  CHECK(profile_);
   ScreenAIServiceRouter* router =
-      ScreenAIServiceRouterFactory::GetForBrowserContext(profile);
+      ScreenAIServiceRouterFactory::GetForBrowserContext(profile_);
 
   // Trigger service initialization to get a feedback when it's ready.
   scoped_refptr<OpticalCharacterRecognizer> ref_ptr(this);
   router->GetServiceStateAsync(
       ScreenAIServiceRouter::Service::kOCR,
       base::BindOnce(&OpticalCharacterRecognizer::OnOCRInitializationCallback,
-                     ref_ptr));
+                     ref_ptr, std::move(status_callback)));
   profile_observer_.Observe(profile_);
 }
 
-void OpticalCharacterRecognizer::OnOCRInitializationCallback(bool successful) {
+void OpticalCharacterRecognizer::OnOCRInitializationCallback(
+    base::OnceCallback<void(bool)> status_callback,
+    bool successful) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  std::move(status_callback).Run(successful && profile_);
+
   // If the profile is already destroyed, stop here.
   if (!profile_) {
     ready_ = false;
@@ -136,4 +161,20 @@
               ref_ptr, std::move(callback)))));
 }
 
+void OpticalCharacterRecognizer::PerformOCR(
+    const SkBitmap& image,
+    base::OnceCallback<void(const ui::AXTreeUpdate&)> callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (!screen_ai_annotator_) {
+    VLOG(0)
+        << "PerformOCR called before the service is ready, returning empty.";
+    std::move(callback).Run(ui::AXTreeUpdate());
+    return;
+  }
+
+  (*screen_ai_annotator_)
+      ->PerformOcrAndReturnAXTreeUpdate(image, std::move(callback));
+}
+
 }  // namespace screen_ai
diff --git a/chrome/browser/screen_ai/optical_character_recognizer.h b/chrome/browser/screen_ai/optical_character_recognizer.h
index 1e2de90..68a13e0 100644
--- a/chrome/browser/screen_ai/optical_character_recognizer.h
+++ b/chrome/browser/screen_ai/optical_character_recognizer.h
@@ -29,10 +29,21 @@
       public base::RefCountedDeleteOnSequence<OpticalCharacterRecognizer> {
  public:
   // Creates OCR using ScreenAI service instance for `profile`. If needed,
+  // triggers download and initialization of the component. Calls
+  // `status_callback` when service initialization status is known.
+  static scoped_refptr<screen_ai::OpticalCharacterRecognizer>
+  CreateWithStatusCallback(Profile* profile,
+                           base::OnceCallback<void(bool)> status_callback);
+
+  // Creates OCR using ScreenAI service instance for `profile`. If needed,
   // triggers download and initialization of the component.
-  // TODO(crbug.com/327181467): Try add a constructor that receives a callback
-  // when initialization is completed.
-  explicit OpticalCharacterRecognizer(Profile* profile);
+  static scoped_refptr<screen_ai::OpticalCharacterRecognizer> Create(
+      Profile* profile);
+
+  // Creates OCR for testing. The object will not be connected to ScreenAI
+  // service and always returns empty results.
+  static scoped_refptr<screen_ai::OpticalCharacterRecognizer>
+  CreateForTesting();
 
   OpticalCharacterRecognizer(const OpticalCharacterRecognizer&) = delete;
   OpticalCharacterRecognizer& operator=(const OpticalCharacterRecognizer&) =
@@ -44,22 +55,42 @@
   // Returns true if OCR service is ready.
   bool is_ready() { return ready_ && *ready_; }
 
-  // Performs OCR on the given image and returns the results. If the client is
-  // not in the browser process, it needs to implement this function in its
-  // own process.
-  void PerformOCR(
+  // Performs OCR on the given image and returns the results as a
+  // `VisualAnnotation` struct. If the client is not in the browser process, it
+  // needs to implement this function in its own process.
+  virtual void PerformOCR(
       const SkBitmap& image,
       base::OnceCallback<void(mojom::VisualAnnotationPtr)> callback);
 
-  // TODO(crbug.com/327181467): Add more infterfaces for a11y tree OCR output.
+  // Performs OCR on the given image and returns the results as an accessibility
+  // tree update. If the client is not in the browser process, it needs to
+  // implement this function in its own process.
+  virtual void PerformOCR(
+      const SkBitmap& image,
+      base::OnceCallback<void(const ui::AXTreeUpdate& tree_update)> callback);
+
+  // Ensures all posted tasks are completed in tests.
+  virtual void FlushForTesting() {}
+
+ protected:
+  explicit OpticalCharacterRecognizer(Profile* profile);
+  ~OpticalCharacterRecognizer() override;
+
+  // OCR Service is ready to use.
+  std::optional<bool> ready_;
 
  private:
   friend class base::RefCountedDeleteOnSequence<OpticalCharacterRecognizer>;
   friend class base::DeleteHelper<OpticalCharacterRecognizer>;
+  template <typename T, typename... Args>
+  friend scoped_refptr<T> base::MakeRefCounted(Args&&... args);
 
-  ~OpticalCharacterRecognizer() override;
+  void Initialize(base::OnceCallback<void(bool)> status_callback);
 
-  void OnOCRInitializationCallback(bool successful);
+  // `status_callback` will receive a copy of `successful`.
+  void OnOCRInitializationCallback(
+      base::OnceCallback<void(bool)> status_callback,
+      bool successful);
 
   // Is initialized in the constructor and is cleared if profile gets destroyed
   // while this object still exists, or after it is used in
@@ -73,9 +104,6 @@
   std::unique_ptr<base::SequenceBound<SequenceBoundReceiver>>
       sequence_bound_receiver_;
 
-  // OCR Service is ready to use.
-  std::optional<bool> ready_;
-
   std::unique_ptr<mojo::Remote<mojom::ScreenAIAnnotator>> screen_ai_annotator_;
 
   base::ScopedObservation<Profile, ProfileObserver> profile_observer_{this};
diff --git a/chrome/browser/screen_ai/public/BUILD.gn b/chrome/browser/screen_ai/public/BUILD.gn
new file mode 100644
index 0000000..15e1b7e
--- /dev/null
+++ b/chrome/browser/screen_ai/public/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# TODO(crbug.com/327181467): Move `optical_character_recognizer` to this file,
+# and add another interface for main content extraction.
+
+source_set("test_support") {
+  testonly = true
+  sources = [
+    "test/fake_optical_character_recognizer.cc",
+    "test/fake_optical_character_recognizer.h",
+  ]
+
+  deps = [ "//base" ]
+  public_deps = [ "//chrome/browser/screen_ai:optical_character_recognizer" ]
+
+  configs += [ "//build/config/compiler:wexit_time_destructors" ]
+}
diff --git a/chrome/browser/screen_ai/public/test/fake_optical_character_recognizer.cc b/chrome/browser/screen_ai/public/test/fake_optical_character_recognizer.cc
new file mode 100644
index 0000000..f8cbc13
--- /dev/null
+++ b/chrome/browser/screen_ai/public/test/fake_optical_character_recognizer.cc
@@ -0,0 +1,73 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/screen_ai/public/test/fake_optical_character_recognizer.h"
+
+#include "base/task/single_thread_task_runner.h"
+
+namespace screen_ai {
+
+// static
+scoped_refptr<screen_ai::OpticalCharacterRecognizer>
+FakeOpticalCharacterRecognizer::Create(bool return_empty) {
+  return base::MakeRefCounted<screen_ai::FakeOpticalCharacterRecognizer>(
+      return_empty);
+}
+
+FakeOpticalCharacterRecognizer::FakeOpticalCharacterRecognizer(
+    bool return_empty)
+    : OpticalCharacterRecognizer(/*profile=*/nullptr),
+      return_empty_(return_empty) {
+  ready_ = true;
+}
+
+FakeOpticalCharacterRecognizer::~FakeOpticalCharacterRecognizer() = default;
+
+void FakeOpticalCharacterRecognizer::PerformOCR(
+    const ::SkBitmap& image,
+    base::OnceCallback<void(screen_ai::mojom::VisualAnnotationPtr)> callback) {
+  CHECK(return_empty_)
+      << "Preset results not defined, please add them here needed.";
+  std::move(callback).Run(mojom::VisualAnnotation::New());
+}
+
+void FakeOpticalCharacterRecognizer::PerformOCR(
+    const SkBitmap& image,
+    base::OnceCallback<void(const ui::AXTreeUpdate&)> callback) {
+  ui::AXTreeUpdate update;
+  if (!return_empty_) {
+    update.has_tree_data = true;
+    // TODO(nektar): Add a tree ID as well and update tests.
+    // update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
+    update.tree_data.title = "Screen AI";
+    update.root_id = next_node_id_;
+    ui::AXNodeData node;
+    node.id = next_node_id_;
+    node.role = ax::mojom::Role::kStaticText;
+    node.SetNameChecked("Testing");
+    node.relative_bounds.bounds = gfx::RectF(1.0f, 2.0f, 1.0f, 2.0f);
+    update.nodes = {node};
+    --next_node_id_;
+  }
+
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](base::OnceCallback<void(const ui::AXTreeUpdate&)> callback,
+             const ui::AXTreeUpdate update) {
+            std::move(callback).Run(update);
+          },
+          std::move(callback), std::move(update)));
+}
+
+void FakeOpticalCharacterRecognizer::FlushForTesting() {
+  base::RunLoop run_loop;
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE,
+      base::BindOnce([](base::RunLoop* run_loop) { run_loop->Quit(); },
+                     &run_loop));
+  run_loop.Run();
+}
+
+}  // namespace screen_ai
diff --git a/chrome/browser/screen_ai/public/test/fake_optical_character_recognizer.h b/chrome/browser/screen_ai/public/test/fake_optical_character_recognizer.h
new file mode 100644
index 0000000..c34c6b8
--- /dev/null
+++ b/chrome/browser/screen_ai/public/test/fake_optical_character_recognizer.h
@@ -0,0 +1,44 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SCREEN_AI_PUBLIC_TEST_FAKE_OPTICAL_CHARACTER_RECOGNIZER_H_
+#define CHROME_BROWSER_SCREEN_AI_PUBLIC_TEST_FAKE_OPTICAL_CHARACTER_RECOGNIZER_H_
+
+#include "base/memory/scoped_refptr.h"
+#include "chrome/browser/screen_ai/optical_character_recognizer.h"
+
+namespace screen_ai {
+
+class FakeOpticalCharacterRecognizer : public OpticalCharacterRecognizer {
+ public:
+  static scoped_refptr<screen_ai::OpticalCharacterRecognizer> Create(
+      bool return_empty);
+
+  void PerformOCR(
+      const SkBitmap& image,
+      base::OnceCallback<void(mojom::VisualAnnotationPtr)> callback) override;
+
+  void PerformOCR(const SkBitmap& image,
+                  base::OnceCallback<void(const ui::AXTreeUpdate& tree_update)>
+                      callback) override;
+
+  void FlushForTesting() override;
+
+ private:
+  template <typename T, typename... Args>
+  friend scoped_refptr<T> base::MakeRefCounted(Args&&... args);
+
+  explicit FakeOpticalCharacterRecognizer(bool return_empty);
+  ~FakeOpticalCharacterRecognizer() override;
+
+  // A negative ID for ui::AXNodeID needs to start from -2 as using -1 for this
+  // node id is still incorrectly treated as invalid.
+  ui::AXNodeID next_node_id_ = -2;
+
+  bool return_empty_;
+};
+
+}  // namespace screen_ai
+
+#endif  // CHROME_BROWSER_SCREEN_AI_PUBLIC_TEST_FAKE_OPTICAL_CHARACTER_RECOGNIZER_H_
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 5866be87..12add77b 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3514,6 +3514,8 @@
       "webui/ash/settings/pages/apps/android_apps_handler.h",
       "webui/ash/settings/pages/apps/app_notification_handler.cc",
       "webui/ash/settings/pages/apps/app_notification_handler.h",
+      "webui/ash/settings/pages/apps/app_parental_controls_handler.cc",
+      "webui/ash/settings/pages/apps/app_parental_controls_handler.h",
       "webui/ash/settings/pages/apps/apps_section.cc",
       "webui/ash/settings/pages/apps/apps_section.h",
       "webui/ash/settings/pages/apps/plugin_vm_handler.cc",
diff --git a/chrome/browser/ui/download/download_bubble_row_view_info.cc b/chrome/browser/ui/download/download_bubble_row_view_info.cc
index 91ef7b39..cd4a148 100644
--- a/chrome/browser/ui/download/download_bubble_row_view_info.cc
+++ b/chrome/browser/ui/download/download_bubble_row_view_info.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/ui/download/download_item_mode.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/download/public/common/download_danger_type.h"
+#include "components/safe_browsing/core/common/features.h"
 #include "components/safe_browsing/core/common/proto/csd.pb.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -368,5 +369,8 @@
   return model_->GetDangerType() ==
              download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING &&
          safe_browsing::IsEnhancedProtectionEnabled(
-             *model_->profile()->GetPrefs());
+             *model_->profile()->GetPrefs()) &&
+         base::FeatureList::IsEnabled(
+             safe_browsing::kDeepScanningPromptRemoval);
+  ;
 }
diff --git a/chrome/browser/ui/download/download_bubble_row_view_info_unittest.cc b/chrome/browser/ui/download/download_bubble_row_view_info_unittest.cc
index 733ecc30..18c09c5c 100644
--- a/chrome/browser/ui/download/download_bubble_row_view_info_unittest.cc
+++ b/chrome/browser/ui/download/download_bubble_row_view_info_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/download/download_bubble_row_view_info.h"
 
 #include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/download/bubble/download_bubble_prefs.h"
 #include "chrome/browser/download/download_item_model.h"
 #include "chrome/browser/download/download_ui_model.h"
@@ -429,6 +430,8 @@
 
 TEST_F(DownloadBubbleRowViewInfoTest,
        ShouldShowNoticeForEnhancedProtectionScan) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(safe_browsing::kDeepScanningPromptRemoval);
   EXPECT_CALL(item(), GetDangerType())
       .WillRepeatedly(
           Return(download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING));
@@ -439,7 +442,21 @@
 }
 
 TEST_F(DownloadBubbleRowViewInfoTest,
-       ShouldShowNoticeForAdvancedProtectionScan) {
+       ShouldNotShowNoticeForAdvancedProtectionScan) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(safe_browsing::kDeepScanningPromptRemoval);
+  EXPECT_CALL(item(), GetDangerType())
+      .WillRepeatedly(
+          Return(download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING));
+  safe_browsing::SetSafeBrowsingState(
+      profile()->GetPrefs(),
+      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);
+  EXPECT_FALSE(info().ShouldShowDeepScanNotice());
+}
+
+TEST_F(DownloadBubbleRowViewInfoTest, ShouldNotShowNoticeWithoutFlag) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(safe_browsing::kDeepScanningPromptRemoval);
   EXPECT_CALL(item(), GetDangerType())
       .WillRepeatedly(
           Return(download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING));
diff --git a/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc b/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
index a4723cb..6916f8d 100644
--- a/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
+++ b/chrome/browser/ui/exclusive_access/fullscreen_controller_interactive_browsertest.cc
@@ -4,6 +4,8 @@
 
 #include "base/command_line.h"
 #include "base/feature_list.h"
+#include "base/functional/callback_forward.h"
+#include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -118,6 +120,23 @@
     run_loop.Run();
   }
 
+  void WaitForPointerLockBubbleToHide() {
+    PointerLockController* pointer_lock_controller =
+        browser()->exclusive_access_manager()->pointer_lock_controller();
+    base::RunLoop run_loop;
+    pointer_lock_controller->set_bubble_hide_callback_for_test(
+        base::BindRepeating(
+            [](base::RunLoop* run_loop,
+               ExclusiveAccessBubbleHideReason reason) {
+              ASSERT_EQ(reason, ExclusiveAccessBubbleHideReason::kTimeout);
+              run_loop->Quit();
+            },
+            &run_loop));
+    run_loop.Run();
+    pointer_lock_controller->set_bubble_hide_callback_for_test(
+        base::NullCallback());
+  }
+
  private:
   void ToggleTabFullscreen_Internal(bool enter_fullscreen,
                                     bool retry_until_success);
@@ -515,6 +534,9 @@
   ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
   ASSERT_TRUE(IsPointerLocked());
   ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
+  // Wait for the bubble to be shown for its full duration. This allows
+  // the page to lock the pointer without showing the bubble later.
+  WaitForPointerLockBubbleToHide();
 
   // Unlock the pointer from target, make sure it's unlocked.
   PressKeyAndWaitForPointerLockRequest(ui::VKEY_U);
@@ -532,6 +554,40 @@
   ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
 }
 
+// TODO: crbug.com/336428594 - Flaky on Lacros.
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#define MAYBE_SecondPointerLockShowsBubble DISABLED_SecondPointerLockShowsBubble
+#else
+#define MAYBE_SecondPointerLockShowsBubble SecondPointerLockShowsBubble
+#endif
+IN_PROC_BROWSER_TEST_F(FullscreenControllerInteractiveTest,
+                       MAYBE_SecondPointerLockShowsBubble) {
+  SetWebContentsGrantedSilentPointerLockPermission();
+  auto test_server_handle = embedded_test_server()->StartAndReturnHandle();
+  ASSERT_TRUE(test_server_handle);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), embedded_test_server()->GetURL(kFullscreenPointerLockHTML)));
+
+  ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
+
+  // Lock the pointer with a user gesture.
+  PressKeyAndWaitForPointerLockRequest(ui::VKEY_1);
+  ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
+  ASSERT_TRUE(IsPointerLocked());
+  ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
+
+  // Unlock the pointer from target, make sure it's unlocked.
+  PressKeyAndWaitForPointerLockRequest(ui::VKEY_U);
+  ASSERT_FALSE(IsPointerLocked());
+  ASSERT_FALSE(IsExclusiveAccessBubbleDisplayed());
+
+  // Lock the pointer again. The bubble wasn't shown for its full duration last
+  // time, so it gets shown again.
+  PressKeyAndWaitForPointerLockRequest(ui::VKEY_1);
+  ASSERT_TRUE(IsPointerLocked());
+  ASSERT_TRUE(IsExclusiveAccessBubbleDisplayed());
+}
+
 // Tests pointer lock is exited on page navigation.
 #if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) && defined(USE_AURA)
 // https://crbug.com/1191964
diff --git a/chrome/browser/ui/exclusive_access/pointer_lock_controller.cc b/chrome/browser/ui/exclusive_access/pointer_lock_controller.cc
index 8fa675be..ecef78c3 100644
--- a/chrome/browser/ui/exclusive_access/pointer_lock_controller.cc
+++ b/chrome/browser/ui/exclusive_access/pointer_lock_controller.cc
@@ -215,8 +215,11 @@
 
   // Allow silent pointer lock if the bubble has been display for a period of
   // time and dismissed due to timeout.
-  if (reason == ExclusiveAccessBubbleHideReason::kTimeout)
+  if (reason == ExclusiveAccessBubbleHideReason::kTimeout) {
     web_contents_granted_silent_pointer_lock_permission_ = web_contents;
+  } else {
+    web_contents_granted_silent_pointer_lock_permission_ = nullptr;
+  }
 }
 
 bool PointerLockController::ShouldSuppressBubbleReshowForStateChange() {
diff --git a/chrome/browser/ui/exclusive_access/pointer_lock_controller.h b/chrome/browser/ui/exclusive_access/pointer_lock_controller.h
index bf35287..37de4901 100644
--- a/chrome/browser/ui/exclusive_access/pointer_lock_controller.h
+++ b/chrome/browser/ui/exclusive_access/pointer_lock_controller.h
@@ -48,6 +48,11 @@
 
   void UnlockPointer();
 
+  void set_bubble_hide_callback_for_test(
+      ExclusiveAccessBubbleHideCallbackForTest callback) {
+    bubble_hide_callback_for_test_ = std::move(callback);
+  }
+
   void set_lock_state_callback_for_test(base::OnceClosure callback) {
     lock_state_callback_for_test_ = std::move(callback);
   }
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.cc b/chrome/browser/ui/lens/lens_overlay_controller.cc
index e65ff07..3eafa47 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller.cc
@@ -117,16 +117,14 @@
     : tab_(tab),
       variations_client_(variations_client),
       identity_manager_(identity_manager) {
-  if (tab_->GetContents()) {
-    LensOverlayControllerTabLookup::CreateForWebContents(tab_->GetContents(),
-                                                         this);
-  }
+  LensOverlayControllerTabLookup::CreateForWebContents(tab_->GetContents(),
+                                                       this);
 
   tab_subscriptions_.push_back(tab_->RegisterDidEnterForeground(
       base::BindRepeating(&LensOverlayController::TabForegrounded,
                           weak_factory_.GetWeakPtr())));
-  tab_subscriptions_.push_back(tab_->RegisterDidEnterBackground(
-      base::BindRepeating(&LensOverlayController::TabBackgrounded,
+  tab_subscriptions_.push_back(tab_->RegisterWillEnterBackground(
+      base::BindRepeating(&LensOverlayController::TabWillEnterBackground,
                           weak_factory_.GetWeakPtr())));
   tab_subscriptions_.push_back(tab_->RegisterDidAddContents(base::BindRepeating(
       &LensOverlayController::DidAddContents, weak_factory_.GetWeakPtr())));
@@ -136,12 +134,19 @@
 }
 
 LensOverlayController::~LensOverlayController() {
-  CloseUI();
-  lens_overlay_query_controller_.reset();
-  if (tab_->GetContents()) {
-    tab_->GetContents()->RemoveUserData(
-        LensOverlayControllerTabLookup::UserDataKey());
+  // In the event that the tab is being closed or backgrounded, and the window
+  // is not closing, TabWillEnterBackground() will be called and the UI will be
+  // torn down via CloseUI(). This code path is only relevant for the case where
+  // the whole window is being torn down. In that case we need to clear the
+  // WebContents::SupportsUserData since it's technically possible for a
+  // WebContents to outlive the window, but we do not want to run through the
+  // usual teardown since the window is half-destroyed.
+  while (!glued_webviews_.empty()) {
+    RemoveGlueForWebView(glued_webviews_.front());
   }
+  glued_webviews_.clear();
+  tab_->GetContents()->RemoveUserData(
+      LensOverlayControllerTabLookup::UserDataKey());
 }
 
 DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(LensOverlayController, kOverlayId);
@@ -659,7 +664,7 @@
   }
 }
 
-void LensOverlayController::TabBackgrounded(tabs::TabInterface* tab) {
+void LensOverlayController::TabWillEnterBackground(tabs::TabInterface* tab) {
   // If the current tab was already backgrounded, do nothing.
   if (state_ == State::kBackground) {
     return;
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.h b/chrome/browser/ui/lens/lens_overlay_controller.h
index 5e90c10..b9f5eb9 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.h
+++ b/chrome/browser/ui/lens/lens_overlay_controller.h
@@ -308,8 +308,8 @@
   // Called when the associated tab enters the foreground.
   void TabForegrounded(tabs::TabInterface* tab);
 
-  // Called when the associated tab enters the background.
-  void TabBackgrounded(tabs::TabInterface* tab);
+  // Called when the associated tab will enter the background.
+  void TabWillEnterBackground(tabs::TabInterface* tab);
 
   // Called when the tab's WebContents are removed.
   void WillRemoveContents(tabs::TabInterface* tab,
diff --git a/chrome/browser/ui/tabs/public/tab_interface.h b/chrome/browser/ui/tabs/public/tab_interface.h
index e05f18e..5fbcb24 100644
--- a/chrome/browser/ui/tabs/public/tab_interface.h
+++ b/chrome/browser/ui/tabs/public/tab_interface.h
@@ -56,18 +56,16 @@
   // and this WebContents will not change.
   virtual bool IsInForeground() const = 0;
 
-  // Register for these two callbacks to detect changes to IsForegrounded().
-  // DidEnterBackgroundCallback can be called repeatedly while the Tab remains
-  // in the background.
+  // Register for these two callbacks to detect changes to IsInForeground().
   using DidEnterForegroundCallback =
       base::RepeatingCallback<void(TabInterface*)>;
   virtual base::CallbackListSubscription RegisterDidEnterForeground(
       DidEnterForegroundCallback callback) = 0;
 
-  using DidEnterBackgroundCallback =
+  using WillEnterBackgroundCallback =
       base::RepeatingCallback<void(TabInterface*)>;
-  virtual base::CallbackListSubscription RegisterDidEnterBackground(
-      DidEnterBackgroundCallback callback) = 0;
+  virtual base::CallbackListSubscription RegisterWillEnterBackground(
+      WillEnterBackgroundCallback callback) = 0;
 
   // Features that want to show tab-modal UI are mutually exclusive. Before
   // showing a modal UI first check `CanShowModal`. Then call ShowModal() and
diff --git a/chrome/browser/ui/tabs/tab_collection_storage_unittest.cc b/chrome/browser/ui/tabs/tab_collection_storage_unittest.cc
index c3226d7..b01d9435 100644
--- a/chrome/browser/ui/tabs/tab_collection_storage_unittest.cc
+++ b/chrome/browser/ui/tabs/tab_collection_storage_unittest.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_renderer_host.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 class TabCollectionStorageTest : public ::testing::Test {
@@ -43,7 +44,8 @@
   void AddTabs(int num) {
     for (int i = 0; i < num; i++) {
       std::unique_ptr<tabs::TabModel> tab_model =
-          std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+          std::make_unique<tabs::TabModel>(MakeWebContents(),
+                                           GetTabStripModel());
       tabs::TabModel* tab_model_ptr = tab_model.get();
 
       tabs::TabModel* inserted_tab_model_ptr =
@@ -109,21 +111,27 @@
     return ids;
   }
 
+  std::unique_ptr<content::WebContents> MakeWebContents() {
+    return content::WebContents::Create(
+        content::WebContents::CreateParams(testing_profile_.get()));
+  }
+
  private:
   content::BrowserTaskEnvironment task_environment_;
   base::test::ScopedFeatureList scoped_feature_list_;
+  content::RenderViewHostTestEnabler test_enabler_;
+  std::unique_ptr<Profile> testing_profile_;
   std::unique_ptr<tabs::PinnedTabCollection> pinned_collection_;
   std::unique_ptr<TabStripModel> tab_strip_model_;
-  std::unique_ptr<Profile> testing_profile_;
   std::unique_ptr<TestTabStripModelDelegate> tab_strip_model_delegate_;
   std::map<std::string, std::string> storage_children_to_id_map_;
 };
 
 TEST_F(TabCollectionStorageTest, AddTabOperation) {
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   auto tab_model_two =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
 
   tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
   tabs::TabModel* tab_model_two_ptr = tab_model_two.get();
@@ -153,7 +161,7 @@
 
 TEST_F(TabCollectionStorageTest, RemoveTabOperation) {
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
 
   tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();
@@ -177,7 +185,7 @@
 
 TEST_F(TabCollectionStorageTest, CloseTabOperation) {
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
 
   tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();
@@ -199,7 +207,7 @@
 
 TEST_F(TabCollectionStorageTest, MoveTabOperation) {
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
 
   tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();
@@ -230,13 +238,14 @@
 // TODO(b/332586827): Re-enable death testing.
 TEST_F(TabCollectionStorageTest, DISABLED_InvalidArgumentsTabOperations) {
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();
   std::unique_ptr<tabs::TabModel> empty_ptr;
 
   EXPECT_DEATH_IF_SUPPORTED(
-      collection_storage->AddTab(
-          std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 10ul),
+      collection_storage->AddTab(std::make_unique<tabs::TabModel>(
+                                     MakeWebContents(), GetTabStripModel()),
+                                 10ul),
       "");
   EXPECT_DEATH_IF_SUPPORTED(
       collection_storage->AddTab(std::move(empty_ptr), 1ul), "");
@@ -355,7 +364,7 @@
 
 TEST_F(TabCollectionStorageTest, MoveMixedTabAndCollectionOperation) {
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
 
   auto tab_collection_one = std::make_unique<tabs::TabGroupTabCollection>(
diff --git a/chrome/browser/ui/tabs/tab_collection_unittest.cc b/chrome/browser/ui/tabs/tab_collection_unittest.cc
index 182342a..243f121 100644
--- a/chrome/browser/ui/tabs/tab_collection_unittest.cc
+++ b/chrome/browser/ui/tabs/tab_collection_unittest.cc
@@ -19,47 +19,9 @@
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_renderer_host.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace {
-void AddTabsToPinnedContainer(tabs::PinnedTabCollection* collection,
-                              TabStripModel* tab_strip_model,
-                              int num) {
-  for (int i = 0; i < num; i++) {
-    std::unique_ptr<tabs::TabModel> tab_model =
-        std::make_unique<tabs::TabModel>(nullptr, tab_strip_model);
-    tabs::TabModel* tab_model_ptr = tab_model.get();
-    collection->AppendTab(std::move(tab_model));
-    EXPECT_EQ(collection->GetIndexOfTabRecursive(tab_model_ptr),
-              collection->ChildCount() - 1);
-  }
-}
-
-void AddTabsToGroupContainer(tabs::TabGroupTabCollection* collection,
-                             TabStripModel* tab_strip_model,
-                             int num) {
-  for (int i = 0; i < num; i++) {
-    std::unique_ptr<tabs::TabModel> tab_model =
-        std::make_unique<tabs::TabModel>(nullptr, tab_strip_model);
-    tabs::TabModel* tab_model_ptr = tab_model.get();
-    collection->AppendTab(std::move(tab_model));
-    EXPECT_EQ(collection->GetIndexOfTabRecursive(tab_model_ptr),
-              collection->ChildCount() - 1);
-  }
-}
-
-void AddTabsToUnpinnedContainer(tabs::UnpinnedTabCollection* collection,
-                                TabStripModel* tab_strip_model,
-                                int num) {
-  for (int i = 0; i < num; i++) {
-    std::unique_ptr<tabs::TabModel> tab_model =
-        std::make_unique<tabs::TabModel>(nullptr, tab_strip_model);
-    collection->AppendTab(std::move(tab_model));
-  }
-}
-
-}  // namespace
-
 class TabCollectionBaseTest : public ::testing::Test {
  public:
   TabCollectionBaseTest() {
@@ -92,11 +54,53 @@
     return tab_collection_ptr ? tab_collection_ptr->get() : nullptr;
   }
 
+  std::unique_ptr<content::WebContents> MakeWebContents() {
+    return content::WebContents::Create(
+        content::WebContents::CreateParams(testing_profile_.get()));
+  }
+
+  void AddTabsToPinnedContainer(tabs::PinnedTabCollection* collection,
+                                TabStripModel* tab_strip_model,
+                                int num) {
+    for (int i = 0; i < num; i++) {
+      std::unique_ptr<tabs::TabModel> tab_model =
+          std::make_unique<tabs::TabModel>(MakeWebContents(), tab_strip_model);
+      tabs::TabModel* tab_model_ptr = tab_model.get();
+      collection->AppendTab(std::move(tab_model));
+      EXPECT_EQ(collection->GetIndexOfTabRecursive(tab_model_ptr),
+                collection->ChildCount() - 1);
+    }
+  }
+
+  void AddTabsToGroupContainer(tabs::TabGroupTabCollection* collection,
+                               TabStripModel* tab_strip_model,
+                               int num) {
+    for (int i = 0; i < num; i++) {
+      std::unique_ptr<tabs::TabModel> tab_model =
+          std::make_unique<tabs::TabModel>(MakeWebContents(), tab_strip_model);
+      tabs::TabModel* tab_model_ptr = tab_model.get();
+      collection->AppendTab(std::move(tab_model));
+      EXPECT_EQ(collection->GetIndexOfTabRecursive(tab_model_ptr),
+                collection->ChildCount() - 1);
+    }
+  }
+
+  void AddTabsToUnpinnedContainer(tabs::UnpinnedTabCollection* collection,
+                                  TabStripModel* tab_strip_model,
+                                  int num) {
+    for (int i = 0; i < num; i++) {
+      std::unique_ptr<tabs::TabModel> tab_model =
+          std::make_unique<tabs::TabModel>(MakeWebContents(), tab_strip_model);
+      collection->AppendTab(std::move(tab_model));
+    }
+  }
+
  private:
   content::BrowserTaskEnvironment task_environment_;
   base::test::ScopedFeatureList scoped_feature_list_;
-  std::unique_ptr<TabStripModel> tab_strip_model_;
+  content::RenderViewHostTestEnabler test_enabler_;
   std::unique_ptr<Profile> testing_profile_;
+  std::unique_ptr<TabStripModel> tab_strip_model_;
   std::unique_ptr<TestTabStripModelDelegate> tab_strip_model_delegate_;
 };
 
@@ -120,9 +124,9 @@
 TEST_F(PinnedTabCollectionTest, AddOperation) {
   // Setup phase of keeping track of two tabs.
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   auto tab_model_two =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
 
   tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
   tabs::TabModel* tab_model_two_ptr = tab_model_two.get();
@@ -152,7 +156,7 @@
 TEST_F(PinnedTabCollectionTest, RemoveOperation) {
   // Setup phase of keeping track of a tab.
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
 
   tabs::PinnedTabCollection* pinned_collection = GetPinnedCollection();
@@ -178,7 +182,7 @@
 TEST_F(PinnedTabCollectionTest, MoveOperation) {
   // Setup phase of keeping track of a tab.
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
 
   tabs::PinnedTabCollection* pinned_collection = GetPinnedCollection();
@@ -224,9 +228,9 @@
 TEST_F(TabGroupTabCollectionTest, AddOperation) {
   // Setup phase of keeping track of two tabs.
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   auto tab_model_two =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
 
   tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
   tabs::TabModel* tab_model_two_ptr = tab_model_two.get();
@@ -255,7 +259,7 @@
 TEST_F(TabGroupTabCollectionTest, RemoveOperation) {
   // Setup phase of keeping track of a tab.
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
 
   tabs::TabGroupTabCollection* grouped_collection = GetCollection();
@@ -281,7 +285,7 @@
 TEST_F(TabGroupTabCollectionTest, MoveOperation) {
   // Setup phase of keeping track of a tab.
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
 
   tabs::TabGroupTabCollection* grouped_collection = GetCollection();
@@ -351,7 +355,7 @@
   // Use the basic setup scenario and track a tab and group.
   PerformBasicSetup();
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tab_groups::TabGroupId group_id = tab_groups::TabGroupId::GenerateNew();
   auto tab_group_one = std::make_unique<tabs::TabGroupTabCollection>(group_id);
 
@@ -381,12 +385,12 @@
             tab_group_one_ptr);
 
   auto tab_model_in_group =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tabs::TabModel* tab_model_in_group_ptr = tab_model_in_group.get();
 
   // Add tabs to the group and validate index and size. Track one of the tabs.
   tab_group_one_ptr->AppendTab(
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()));
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel()));
   tab_group_one_ptr->AppendTab(std::move(tab_model_in_group));
 
   EXPECT_EQ(unpinned_collection->GetIndexOfTabRecursive(tab_model_in_group_ptr),
@@ -399,7 +403,7 @@
   // Use the basic setup scenario and track a tab and group.
   PerformBasicSetup();
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tab_groups::TabGroupId group_id = tab_groups::TabGroupId::GenerateNew();
   auto tab_group_one = std::make_unique<tabs::TabGroupTabCollection>(group_id);
 
@@ -438,7 +442,7 @@
   // Use the basic setup scenario and track a tab.
   PerformBasicSetup();
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
 
   tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
   tabs::UnpinnedTabCollection* unpinned_collection = GetCollection();
@@ -471,7 +475,7 @@
   // Use the basic setup scenario and track a tab and a group.
   PerformBasicSetup();
   auto tab_model_one =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tab_groups::TabGroupId group_id = tab_groups::TabGroupId::GenerateNew();
   auto tab_group_one = std::make_unique<tabs::TabGroupTabCollection>(group_id);
 
@@ -526,8 +530,8 @@
     unpinned_collection->AddTabGroup(std::move(group_one), 2);
 
     // Add one more tab.
-    unpinned_collection->AppendTab(
-        std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()));
+    unpinned_collection->AppendTab(std::make_unique<tabs::TabModel>(
+        MakeWebContents(), GetTabStripModel()));
 
     tabs::TabCollectionStorage* pinned_storage =
         pinned_collection->GetTabCollectionStorageForTesting();
@@ -606,7 +610,7 @@
   // Add one tab, a group with two tabs and another tab to the unpinned
   // collection.
   unpinned_collection->AppendTab(
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()));
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel()));
 
   std::unique_ptr<tabs::TabGroupTabCollection> group_one =
       std::make_unique<tabs::TabGroupTabCollection>(
@@ -616,10 +620,10 @@
 
   unpinned_collection->AddTabGroup(std::move(group_one), 1ul);
   unpinned_collection->AppendTab(
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()));
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel()));
 
   std::unique_ptr<tabs::TabModel> tab_not_present =
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel());
   tabs::TabCollectionStorage* group_storage =
       group_one_ptr->GetTabCollectionStorageForTesting();
 
@@ -678,53 +682,53 @@
   // Insert Recursive checks -
   // 1. Add to pinned container.
   tab_strip_collection->AddTabRecursive(
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 2,
-      std::nullopt, true);
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel()),
+      2, std::nullopt, true);
   EXPECT_EQ(pinned_collection->TabCountRecursive(), 5ul);
   // 2. Add as a tab to unpinned container. Now pinned container has 5 tabs.
   tab_strip_collection->AddTabRecursive(
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 5,
-      std::nullopt, false);
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel()),
+      5, std::nullopt, false);
   EXPECT_EQ(unpinned_collection->TabCountRecursive(), 6ul);
   EXPECT_EQ(unpinned_collection->ChildCount(), 5ul);
 
   // 3. Add to the end of the unpinned container.
   tab_strip_collection->AddTabRecursive(
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 11,
-      std::nullopt, false);
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel()),
+      11, std::nullopt, false);
   EXPECT_EQ(unpinned_collection->TabCountRecursive(), 7ul);
   EXPECT_EQ(unpinned_collection->ChildCount(), 6ul);
 
   // 4. Add to group container.
   tab_strip_collection->AddTabRecursive(
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 9,
-      group_one_ptr->GetTabGroupId(), false);
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel()),
+      9, group_one_ptr->GetTabGroupId(), false);
   EXPECT_EQ(group_one_ptr->TabCountRecursive(), 3ul);
   EXPECT_EQ(unpinned_collection->TabCountRecursive(), 8ul);
 
   // 5. Corner case add to boundary of group container.
   tab_strip_collection->AddTabRecursive(
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 8,
-      group_one_ptr->GetTabGroupId(), false);
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel()),
+      8, group_one_ptr->GetTabGroupId(), false);
   EXPECT_EQ(group_one_ptr->TabCountRecursive(), 4ul);
   EXPECT_EQ(unpinned_collection->TabCountRecursive(), 9ul);
 
   tab_strip_collection->AddTabRecursive(
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 8,
-      std::nullopt, false);
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel()),
+      8, std::nullopt, false);
   EXPECT_EQ(group_one_ptr->TabCountRecursive(), 4ul);
   EXPECT_EQ(unpinned_collection->TabCountRecursive(), 10ul);
 
   // Now group has 4. And 4 unpinned before the group.
   tab_strip_collection->AddTabRecursive(
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 13,
-      group_one_ptr->GetTabGroupId(), false);
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel()),
+      13, group_one_ptr->GetTabGroupId(), false);
   EXPECT_EQ(group_one_ptr->TabCountRecursive(), 5ul);
   EXPECT_EQ(unpinned_collection->TabCountRecursive(), 11ul);
 
   tab_strip_collection->AddTabRecursive(
-      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 14,
-      std::nullopt, false);
+      std::make_unique<tabs::TabModel>(MakeWebContents(), GetTabStripModel()),
+      14, std::nullopt, false);
   EXPECT_EQ(group_one_ptr->TabCountRecursive(), 5ul);
   EXPECT_EQ(unpinned_collection->TabCountRecursive(), 12ul);
 }
@@ -772,32 +776,32 @@
   tabs::TabStripCollection* tab_strip_collection = GetCollection();
 
   // Try to add an index OOB
-  EXPECT_DEATH_IF_SUPPORTED(
-      tab_strip_collection->AddTabRecursive(
-          std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 20ul,
-          std::nullopt, false),
-      "");
+  EXPECT_DEATH_IF_SUPPORTED(tab_strip_collection->AddTabRecursive(
+                                std::make_unique<tabs::TabModel>(
+                                    MakeWebContents(), GetTabStripModel()),
+                                20ul, std::nullopt, false),
+                            "");
 
   // Try to add a pinned tab to unpinned container index location.
-  EXPECT_DEATH_IF_SUPPORTED(
-      tab_strip_collection->AddTabRecursive(
-          std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 5ul,
-          std::nullopt, true),
-      "");
+  EXPECT_DEATH_IF_SUPPORTED(tab_strip_collection->AddTabRecursive(
+                                std::make_unique<tabs::TabModel>(
+                                    MakeWebContents(), GetTabStripModel()),
+                                5ul, std::nullopt, true),
+                            "");
 
   // Try to add a unpinned tab to pinned container index location.
-  EXPECT_DEATH_IF_SUPPORTED(
-      tab_strip_collection->AddTabRecursive(
-          std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 1ul,
-          std::nullopt, false),
-      "");
+  EXPECT_DEATH_IF_SUPPORTED(tab_strip_collection->AddTabRecursive(
+                                std::make_unique<tabs::TabModel>(
+                                    MakeWebContents(), GetTabStripModel()),
+                                1ul, std::nullopt, false),
+                            "");
 
   // Try to add a tab to pinned container index location.
-  EXPECT_DEATH_IF_SUPPORTED(
-      tab_strip_collection->AddTabRecursive(
-          std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 1ul,
-          std::nullopt, false),
-      "");
+  EXPECT_DEATH_IF_SUPPORTED(tab_strip_collection->AddTabRecursive(
+                                std::make_unique<tabs::TabModel>(
+                                    MakeWebContents(), GetTabStripModel()),
+                                1ul, std::nullopt, false),
+                            "");
 
   // Try to add a tab to pinned container index location with a group.
   tabs::TabGroupTabCollection* group_one_ptr =
@@ -807,30 +811,32 @@
                   ->GetUnpinnedCollection()
                   ->GetTabCollectionStorageForTesting(),
               2));
-  EXPECT_DEATH_IF_SUPPORTED(
-      tab_strip_collection->AddTabRecursive(
-          std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 1ul,
-          group_one_ptr->GetTabGroupId(), true),
-      "");
+  EXPECT_DEATH_IF_SUPPORTED(tab_strip_collection->AddTabRecursive(
+                                std::make_unique<tabs::TabModel>(
+                                    MakeWebContents(), GetTabStripModel()),
+                                1ul, group_one_ptr->GetTabGroupId(), true),
+                            "");
 
   // Try to add a tab to unpinned container index that should not be a part of a
   // group but a group value is passed.
+  EXPECT_DEATH_IF_SUPPORTED(tab_strip_collection->AddTabRecursive(
+                                std::make_unique<tabs::TabModel>(
+                                    MakeWebContents(), GetTabStripModel()),
+                                5ul, group_one_ptr->GetTabGroupId(), true),
+                            "");
   EXPECT_DEATH_IF_SUPPORTED(
       tab_strip_collection->AddTabRecursive(
-          std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 5ul,
-          group_one_ptr->GetTabGroupId(), true),
-      "");
-  EXPECT_DEATH_IF_SUPPORTED(
-      tab_strip_collection->AddTabRecursive(
-          std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 6ul,
-          tab_groups::TabGroupId::GenerateNew(), true),
+          std::make_unique<tabs::TabModel>(MakeWebContents(),
+                                           GetTabStripModel()),
+          6ul, tab_groups::TabGroupId::GenerateNew(), true),
       "");
 
   // Try to add a tab to unpinned container index that should not be a part of a
   // group but a different group id.
   EXPECT_DEATH_IF_SUPPORTED(
       tab_strip_collection->AddTabRecursive(
-          std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 7ul,
-          tab_groups::TabGroupId::GenerateNew(), true),
+          std::make_unique<tabs::TabModel>(MakeWebContents(),
+                                           GetTabStripModel()),
+          7ul, tab_groups::TabGroupId::GenerateNew(), true),
       "");
 }
diff --git a/chrome/browser/ui/tabs/tab_model.cc b/chrome/browser/ui/tabs/tab_model.cc
index 45f0bb3..4f5de3b 100644
--- a/chrome/browser/ui/tabs/tab_model.cc
+++ b/chrome/browser/ui/tabs/tab_model.cc
@@ -49,9 +49,6 @@
   // Going through each field here:
   // Keep `contents_`, obviously.
 
-  // We are now unowned. In this case no UI is shown, which is functionally
-  // equivalent to being in the background.
-  did_enter_background_callback_list_.Notify(this);
   owning_model_->RemoveObserver(this);
   owning_model_ = nullptr;
 
@@ -80,6 +77,10 @@
   parent_collection_ = parent;
 }
 
+void TabModel::WillEnterBackground(base::PassKey<TabStripModel>) {
+  will_enter_background_callback_list_.Notify(this);
+}
+
 content::WebContents* TabModel::GetContents() const {
   return contents();
 }
@@ -103,9 +104,9 @@
   return did_enter_foreground_callback_list_.Add(std::move(callback));
 }
 
-base::CallbackListSubscription TabModel::RegisterDidEnterBackground(
-    TabInterface::DidEnterBackgroundCallback callback) {
-  return did_enter_background_callback_list_.Add(std::move(callback));
+base::CallbackListSubscription TabModel::RegisterWillEnterBackground(
+    TabInterface::WillEnterBackgroundCallback callback) {
+  return will_enter_background_callback_list_.Add(std::move(callback));
 }
 
 bool TabModel::CanShowModalUI() const {
@@ -136,10 +137,6 @@
     did_enter_foreground_callback_list_.Notify(this);
     return;
   }
-
-  if (selection.old_contents == contents()) {
-    did_enter_background_callback_list_.Notify(this);
-  }
 }
 
 TabModel::ScopedTabModalUIImpl::ScopedTabModalUIImpl(TabModel* tab)
@@ -161,6 +158,9 @@
 
 std::unique_ptr<content::WebContents> TabModel::ReplaceContents(
     std::unique_ptr<content::WebContents> contents) {
+  // We do not call will_enter_background_callback_list_ because it is
+  // guaranteed that if a tab is being discarded, it is already in the
+  // background.
   std::unique_ptr<content::WebContents> old_contents = RemoveContents();
   SetContents(std::move(contents));
   return old_contents;
diff --git a/chrome/browser/ui/tabs/tab_model.h b/chrome/browser/ui/tabs/tab_model.h
index 3be8644..424ac446 100644
--- a/chrome/browser/ui/tabs/tab_model.h
+++ b/chrome/browser/ui/tabs/tab_model.h
@@ -95,6 +95,11 @@
   // tab hierarchy, maintaining consistent organization.
   void OnReparented(TabCollection* parent, base::PassKey<TabCollection>);
 
+  // Called by TabStripModel when a tab is going to be backgrounded (any
+  // operation that makes the tab no longer visible, including removal from the
+  // TabStripModel). Not called if TabStripModel is being destroyed.
+  void WillEnterBackground(base::PassKey<TabStripModel>);
+
   // TabInterface overrides:
   content::WebContents* GetContents() const override;
   base::CallbackListSubscription RegisterDidAddContents(
@@ -104,8 +109,8 @@
   bool IsInForeground() const override;
   base::CallbackListSubscription RegisterDidEnterForeground(
       TabInterface::DidEnterForegroundCallback callback) override;
-  base::CallbackListSubscription RegisterDidEnterBackground(
-      TabInterface::DidEnterBackgroundCallback callback) override;
+  base::CallbackListSubscription RegisterWillEnterBackground(
+      TabInterface::WillEnterBackgroundCallback callback) override;
   bool CanShowModalUI() const override;
   std::unique_ptr<ScopedTabModalUI> ShowModalUI() override;
   bool IsInNormalWindow() const override;
@@ -162,9 +167,9 @@
       base::RepeatingCallbackList<void(TabInterface*)>;
   DidEnterForegroundCallbackList did_enter_foreground_callback_list_;
 
-  using DidEnterBackgroundCallbackList =
+  using WillEnterBackgroundCallbackList =
       base::RepeatingCallbackList<void(TabInterface*)>;
-  DidEnterBackgroundCallbackList did_enter_background_callback_list_;
+  WillEnterBackgroundCallbackList will_enter_background_callback_list_;
 
   // Tracks whether a modal UI is showing.
   bool showing_modal_ui_ = false;
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index 41baff0c..25dc4b0 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -486,6 +486,10 @@
                                       "trying to detach web contents.";
   WebContents* initially_active_web_contents =
       GetWebContentsAtImpl(active_index());
+  if (index == active_index() && !closing_all_) {
+    GetTabAtIndex(active_index())
+        ->WillEnterBackground(base::PassKey<TabStripModel>());
+  }
 
   DetachNotifications notifications(initially_active_web_contents,
                                     selection_model_);
@@ -2012,6 +2016,13 @@
   const bool pin = (add_types & ADD_PINNED) != 0;
   index = ConstrainInsertionIndex(index, pin);
 
+  // If there's already an active tab, and the new tab will become active, send
+  // a notification.
+  if (selection_model_.active().has_value() && active && !closing_all_) {
+    GetTabAtIndex(active_index())
+        ->WillEnterBackground(base::PassKey<TabStripModel>());
+  }
+
   // Have to get the active contents before we monkey with the contents
   // otherwise we run into problems when we try to change the active contents
   // since the old contents and the new contents will be the same...
@@ -2142,6 +2153,14 @@
   if (items.empty())
     return true;
 
+  for (size_t i = 0; i < items.size(); ++i) {
+    int index = GetIndexOfWebContents(items[i]);
+    if (index == active_index() && !closing_all_) {
+      GetTabAtIndex(active_index())
+          ->WillEnterBackground(base::PassKey<TabStripModel>());
+    }
+  }
+
   // We only try the fast shutdown path if the whole browser process is *not*
   // shutting down. Fast shutdown during browser termination is handled in
   // browser_shutdown::OnShutdownStarting.
@@ -2223,6 +2242,12 @@
   selection.new_model = new_model;
   selection.reason = reason;
 
+  if (selection_model_.active().has_value() && new_model.active().has_value() &&
+      selection_model_.active().value() != new_model.active().value()) {
+    GetTabAtIndex(active_index())
+        ->WillEnterBackground(base::PassKey<TabStripModel>());
+  }
+
   // Validate that |new_model| only selects tabs that actually exist.
   CHECK(empty() || new_model.active().has_value(), base::NotFatalUntil::M124);
   CHECK(empty() || ContainsIndex(new_model.active().value()),
@@ -2398,6 +2423,10 @@
     int to_position,
     bool select_after_move,
     WebContents* web_contents) {
+  if (select_after_move && GetActiveWebContents() != web_contents) {
+    GetTabAtIndex(active_index())
+        ->WillEnterBackground(base::PassKey<TabStripModel>());
+  }
   TabStripSelectionChange selection(GetActiveWebContents(), selection_model_);
 
   selection_model_.Move(index, to_position, 1);
diff --git a/chrome/browser/ui/views/exclusive_access_bubble_views.cc b/chrome/browser/ui/views/exclusive_access_bubble_views.cc
index c107eb7..80661d03 100644
--- a/chrome/browser/ui/views/exclusive_access_bubble_views.cc
+++ b/chrome/browser/ui/views/exclusive_access_bubble_views.cc
@@ -132,7 +132,7 @@
   DCHECK(EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE != params.type ||
          params.has_download);
   if (params_.type == params.type && params_.url == params.url &&
-      !params.force_update) {
+      !params.force_update && (IsShowing() || IsVisible())) {
     return;
   }
 
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index f161235..f364bdd8d 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -2000,13 +2000,22 @@
     // Perform the destroy async. State updates in the exclusive access bubble
     // view may call back into this method. This otherwise results in premature
     // deletion of the bubble view and UAFs. See crbug.com/1426521.
-    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(&BrowserView::DestroyAnyExclusiveAccessBubble,
-                                  GetAsWeakPtr()));
+    exclusive_access_bubble_destruction_task_id_ =
+        exclusive_access_bubble_cancelable_task_tracker_.PostTask(
+            base::SingleThreadTaskRunner::GetCurrentDefault().get(), FROM_HERE,
+            base::BindOnce(&BrowserView::DestroyAnyExclusiveAccessBubble,
+                           GetAsWeakPtr()));
     return;
   }
 
   if (exclusive_access_bubble_) {
+    if (exclusive_access_bubble_destruction_task_id_) {
+      // We previously posted a destruction task, but now we want to reuse the
+      // bubble. Cancel the destruction task.
+      exclusive_access_bubble_cancelable_task_tracker_.TryCancel(
+          exclusive_access_bubble_destruction_task_id_.value());
+      exclusive_access_bubble_destruction_task_id_.reset();
+    }
     exclusive_access_bubble_->Update(params, std::move(first_hide_callback));
     return;
   }
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index b749163..ef42948 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -15,6 +15,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
+#include "base/task/cancelable_task_tracker.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "build/branding_buildflags.h"
@@ -109,7 +110,7 @@
 namespace views {
 class ExternalFocusTracker;
 class WebView;
-}
+}  // namespace views
 
 namespace webapps {
 enum class InstallableWebAppCheckResult;
@@ -981,9 +982,8 @@
 
   // Calls |method| which is either WebContents::Cut, ::Copy, or ::Paste on
   // the given WebContents, returning true if it consumed the event.
-  bool DoCutCopyPasteForWebContents(
-      content::WebContents* contents,
-      void (content::WebContents::*method)());
+  bool DoCutCopyPasteForWebContents(content::WebContents* contents,
+                                    void (content::WebContents::*method)());
 
   // Shows the next app-modal dialog box, if there is one to be shown, or moves
   // an existing showing one to the front.
@@ -1269,6 +1269,10 @@
   bool in_process_fullscreen_ = false;
 
   std::unique_ptr<ExclusiveAccessBubbleViews> exclusive_access_bubble_;
+  // Tracks the task to asynchronously destroy the exclusive access bubble.
+  base::CancelableTaskTracker exclusive_access_bubble_cancelable_task_tracker_;
+  std::optional<base::CancelableTaskTracker::TaskId>
+      exclusive_access_bubble_destruction_task_id_;
 
   // True when we do not want to allow exiting fullscreen, e.g. in Chrome OS
   // Kiosk session.
diff --git a/chrome/browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc b/chrome/browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc
index 03b65f7..e31b3e2 100644
--- a/chrome/browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_tab_strip_browsertest.cc
@@ -342,7 +342,7 @@
 
 IN_PROC_BROWSER_TEST_F(WebAppTabStripBrowserTest, InstallingPinsHomeTab) {
   GURL start_url = embedded_test_server()->GetURL(
-      "/web_apps/tab_strip_customizations.html?some_query#blah");
+      "/web_apps/tab_strip_customizations.html#blah");
   webapps::AppId app_id = InstallTestWebApp(start_url);
   Browser* app_browser = FindWebAppBrowser(browser()->profile(), app_id);
   TabStripModel* tab_strip = app_browser->tab_strip_model();
@@ -351,7 +351,7 @@
 
   EXPECT_EQ(tab_strip->count(), 1);
   EXPECT_TRUE(tab_strip->IsTabPinned(0));
-  // The URL of the pinned home tab should include the query params.
+  // The URL of the pinned home tab should include the fragment.
   EXPECT_EQ(tab_strip->GetWebContentsAt(0)->GetVisibleURL(), start_url);
   EXPECT_EQ(tab_strip->active_index(), 0);
 
@@ -833,16 +833,37 @@
   // Expect only the home tab was opened.
   EXPECT_EQ(tab_strip->count(), 1);
   EXPECT_TRUE(tab_strip->IsTabPinned(0));
+  EXPECT_EQ(tab_strip->active_index(), 0);
   EXPECT_EQ(tab_strip->GetWebContentsAt(0)->GetVisibleURL(), start_url);
 
-  // Navigate to start_url without query params.
-  OpenUrlAndWait(app_browser,
-                 embedded_test_server()->GetURL("/web_apps/get_manifest.html"));
+  // Navigate to start_url with different query params.
+  OpenUrlAndWait(app_browser, embedded_test_server()->GetURL(
+                                  "/web_apps/get_manifest.html?query=param"));
 
-  // Expect navigation to happen in home tab.
-  EXPECT_EQ(tab_strip->count(), 1);
+  // Expect navigation to happen in a new tab in that window.
+  EXPECT_EQ(tab_strip->count(), 2);
+  EXPECT_TRUE(tab_strip->IsTabPinned(0));
+  EXPECT_EQ(tab_strip->active_index(), 1);
+  EXPECT_EQ(tab_strip->GetWebContentsAt(0)->GetVisibleURL(), start_url);
+  EXPECT_EQ(tab_strip->GetWebContentsAt(1)->GetVisibleURL(),
+            embedded_test_server()->GetURL(
+                "/web_apps/get_manifest.html?query=param"));
+
+  // Navigate to start_url.
+  GURL start_url_with_fragment = embedded_test_server()->GetURL(
+      "/web_apps/"
+      "get_manifest.html?tab_strip_query_params_in_start_url.json#param");
+  OpenUrlAndWait(app_browser, start_url_with_fragment);
+
+  // Expect that the home tab is focused and the new tab.
+  EXPECT_EQ(tab_strip->count(), 2);
+  EXPECT_EQ(tab_strip->active_index(), 0);
+  EXPECT_TRUE(tab_strip->IsTabPinned(0));
   EXPECT_EQ(tab_strip->GetWebContentsAt(0)->GetVisibleURL(),
-            embedded_test_server()->GetURL("/web_apps/get_manifest.html"));
+            start_url_with_fragment);
+  EXPECT_EQ(tab_strip->GetWebContentsAt(1)->GetVisibleURL(),
+            embedded_test_server()->GetURL(
+                "/web_apps/get_manifest.html?query=param"));
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppTabStripBrowserTest,
diff --git a/chrome/browser/ui/views/webauthn/pin_textfield.cc b/chrome/browser/ui/views/webauthn/pin_textfield.cc
index a2372576..41d344e 100644
--- a/chrome/browser/ui/views/webauthn/pin_textfield.cc
+++ b/chrome/browser/ui/views/webauthn/pin_textfield.cc
@@ -153,7 +153,8 @@
   }
 }
 
-gfx::Size PinTextfield::CalculatePreferredSize() const {
+gfx::Size PinTextfield::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   return gfx::Size(
       pin_digits_count_ * kCellWidth + (pin_digits_count_ - 1) * kCellSpacing,
       kCellHeight);
diff --git a/chrome/browser/ui/views/webauthn/pin_textfield.h b/chrome/browser/ui/views/webauthn/pin_textfield.h
index d3b3014..095e7296 100644
--- a/chrome/browser/ui/views/webauthn/pin_textfield.h
+++ b/chrome/browser/ui/views/webauthn/pin_textfield.h
@@ -39,7 +39,8 @@
 
   // views::View:
   void OnPaint(gfx::Canvas* canvas) override;
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
   void OnThemeChanged() override;
 
  private:
diff --git a/chrome/browser/ui/web_applications/web_app_browser_controller.cc b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
index dc1fb875..d4ada979 100644
--- a/chrome/browser/ui/web_applications/web_app_browser_controller.cc
+++ b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
@@ -531,16 +531,17 @@
     return false;
   }
 
+  // Retrieve the start URL for the app. Start URL is always in home tab scope.
+  // TODO(b/330640982): rename GetAppPinnedHomeTabUrl() to something more
+  // sensible.
   std::optional<GURL> pinned_home_url =
       registrar().GetAppPinnedHomeTabUrl(app_id());
   if (!pinned_home_url) {
     return false;
   }
 
-  // We ignore query params and hash ref when deciding what should be
-  // opened as the home tab.
+  // We ignore hash ref when deciding what should be opened as the home tab.
   GURL::Replacements replacements;
-  replacements.ClearQuery();
   replacements.ClearRef();
   if (url.ReplaceComponents(replacements) ==
       pinned_home_url.value().ReplaceComponents(replacements)) {
diff --git a/chrome/browser/ui/webui/ash/settings/os_settings_ui.cc b/chrome/browser/ui/webui/ash/settings/os_settings_ui.cc
index 21e90e1..ac420cf 100644
--- a/chrome/browser/ui/webui/ash/settings/os_settings_ui.cc
+++ b/chrome/browser/ui/webui/ash/settings/os_settings_ui.cc
@@ -35,6 +35,7 @@
 #include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h"
 #include "chrome/browser/nearby_sharing/nearby_sharing_service_impl.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/apps/app_notification_handler.h"
+#include "chrome/browser/ui/webui/ash/settings/pages/apps/app_parental_controls_handler.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/privacy/app_permission_handler.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/storage/device_storage_handler.h"
 #include "chrome/browser/ui/webui/ash/settings/pref_names.h"
@@ -222,6 +223,14 @@
 }
 
 void OSSettingsUI::BindInterface(
+    mojo::PendingReceiver<
+        app_parental_controls::mojom::AppParentalControlsHandler> receiver) {
+  OsSettingsManagerFactory::GetForProfile(Profile::FromWebUI(web_ui()))
+      ->app_parental_controls_handler()
+      ->BindInterface(std::move(receiver));
+}
+
+void OSSettingsUI::BindInterface(
     mojo::PendingReceiver<app_permission::mojom::AppPermissionsHandler>
         receiver) {
   OsSettingsManagerFactory::GetForProfile(Profile::FromWebUI(web_ui()))
diff --git a/chrome/browser/ui/webui/ash/settings/os_settings_ui.h b/chrome/browser/ui/webui/ash/settings/os_settings_ui.h
index 18e684b..429078a 100644
--- a/chrome/browser/ui/webui/ash/settings/os_settings_ui.h
+++ b/chrome/browser/ui/webui/ash/settings/os_settings_ui.h
@@ -15,6 +15,7 @@
 #include "chrome/browser/ui/webui/app_management/app_management_page_handler_base.h"
 #include "chrome/browser/ui/webui/app_management/app_management_page_handler_factory.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_notification_handler.mojom-forward.h"
+#include "chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_parental_controls_handler.mojom-forward.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider.mojom.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.mojom.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/files/google_drive_page_handler_factory.h"
@@ -125,6 +126,12 @@
       mojo::PendingReceiver<app_notification::mojom::AppNotificationsHandler>
           receiver);
 
+  // Instantiates implementor of the mojom::AppParentalControlsHandler mojo
+  // interface passing the pending receiver that will be internally bound.
+  void BindInterface(
+      mojo::PendingReceiver<
+          app_parental_controls::mojom::AppParentalControlsHandler> receiver);
+
   // Instantiates implementor of the mojom::AppPermissionsHandler mojo interface
   // passing the pending receiver that will be internally bound.
   void BindInterface(
diff --git a/chrome/browser/ui/webui/ash/settings/pages/apps/app_parental_controls_handler.cc b/chrome/browser/ui/webui/ash/settings/pages/apps/app_parental_controls_handler.cc
new file mode 100644
index 0000000..a43d7e7
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/settings/pages/apps/app_parental_controls_handler.cc
@@ -0,0 +1,73 @@
+// Copyright 2024 The Chromium Authors
+// 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/ash/settings/pages/apps/app_parental_controls_handler.h"
+
+#include <utility>
+#include <vector>
+
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_parental_controls_handler.mojom.h"
+#include "components/services/app_service/public/cpp/app_update.h"
+#include "components/services/app_service/public/cpp/types_util.h"
+
+namespace ash::settings {
+
+namespace {
+app_parental_controls::mojom::AppPtr CreateAppPtr(
+    const apps::AppUpdate& update) {
+  auto app = app_parental_controls::mojom::App::New();
+  app->id = update.AppId();
+  app->title = update.Name();
+  return app;
+}
+
+bool ShouldIncludeApp(const apps::AppUpdate& update) {
+  // Only apps shown in the App Management page should be shown.
+  return update.ShowInManagement().value_or(false) &&
+         update.AppType() == apps::AppType::kArc;
+}
+}  // namespace
+
+AppParentalControlsHandler::AppParentalControlsHandler(
+    apps::AppServiceProxy* app_service_proxy)
+    : app_service_proxy_(app_service_proxy) {
+  app_registry_cache_observer_.Observe(&app_service_proxy_->AppRegistryCache());
+}
+
+AppParentalControlsHandler::~AppParentalControlsHandler() = default;
+
+void AppParentalControlsHandler::BindInterface(
+    mojo::PendingReceiver<
+        app_parental_controls::mojom::AppParentalControlsHandler> receiver) {
+  if (receiver_.is_bound()) {
+    receiver_.reset();
+  }
+  receiver_.Bind(std::move(receiver));
+}
+
+void AppParentalControlsHandler::GetApps(GetAppsCallback callback) {
+  std::move(callback).Run(GetAppList());
+}
+
+std::vector<app_parental_controls::mojom::AppPtr>
+AppParentalControlsHandler::GetAppList() {
+  std::vector<app_parental_controls::mojom::AppPtr> apps;
+  app_service_proxy_->AppRegistryCache().ForEachApp(
+      [&apps](const apps::AppUpdate& update) {
+        if (ShouldIncludeApp(update) &&
+            apps_util::IsInstalled(update.Readiness())) {
+          apps.push_back(CreateAppPtr(update));
+        }
+      });
+  return apps;
+}
+
+void AppParentalControlsHandler::OnAppRegistryCacheWillBeDestroyed(
+    apps::AppRegistryCache* cache) {
+  app_registry_cache_observer_.Reset();
+}
+
+}  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/ash/settings/pages/apps/app_parental_controls_handler.h b/chrome/browser/ui/webui/ash/settings/pages/apps/app_parental_controls_handler.h
new file mode 100644
index 0000000..003a8df
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/settings/pages/apps/app_parental_controls_handler.h
@@ -0,0 +1,54 @@
+// Copyright 2024 The Chromium Authors
+// 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_ASH_SETTINGS_PAGES_APPS_APP_PARENTAL_CONTROLS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_ASH_SETTINGS_PAGES_APPS_APP_PARENTAL_CONTROLS_HANDLER_H_
+
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_forward.h"
+#include "chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_parental_controls_handler.mojom.h"
+#include "components/services/app_service/public/cpp/app_registry_cache.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+
+namespace ash::settings {
+
+class AppParentalControlsHandler
+    : public app_parental_controls::mojom::AppParentalControlsHandler,
+      public apps::AppRegistryCache::Observer {
+ public:
+  explicit AppParentalControlsHandler(apps::AppServiceProxy* app_service_proxy);
+  ~AppParentalControlsHandler() override;
+
+  // app_parental_controls::mojom::AppParentalControlsHandler:
+  void GetApps(GetAppsCallback callback) override;
+
+  void BindInterface(
+      mojo::PendingReceiver<
+          app_parental_controls::mojom::AppParentalControlsHandler> receiver);
+
+ private:
+  // apps::AppRegistryCache::Observer:
+  void OnAppRegistryCacheWillBeDestroyed(
+      apps::AppRegistryCache* cache) override;
+
+  std::vector<app_parental_controls::mojom::AppPtr> GetAppList();
+
+  raw_ptr<apps::AppServiceProxy> app_service_proxy_ = nullptr;
+
+  base::ScopedObservation<apps::AppRegistryCache,
+                          apps::AppRegistryCache::Observer>
+      app_registry_cache_observer_{this};
+
+  mojo::Receiver<app_parental_controls::mojom::AppParentalControlsHandler>
+      receiver_{this};
+};
+
+}  // namespace ash::settings
+
+#endif  // CHROME_BROWSER_UI_WEBUI_ASH_SETTINGS_PAGES_APPS_APP_PARENTAL_CONTROLS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/ash/settings/pages/apps/app_parental_controls_handler_unittest.cc b/chrome/browser/ui/webui/ash/settings/pages/apps/app_parental_controls_handler_unittest.cc
new file mode 100644
index 0000000..d24972c
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/settings/pages/apps/app_parental_controls_handler_unittest.cc
@@ -0,0 +1,85 @@
+// Copyright 2024 The Chromium Authors
+// 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/ash/settings/pages/apps/app_parental_controls_handler.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_parental_controls_handler.mojom.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/services/app_service/public/cpp/app_update.h"
+#include "components/services/app_service/public/cpp/types_util.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash::settings {
+
+class AppParentalControlsHandlerTest : public testing::Test {
+ public:
+  AppParentalControlsHandlerTest() {}
+  ~AppParentalControlsHandlerTest() override = default;
+
+  void SetUp() override {
+    app_service_proxy_ = apps::AppServiceProxyFactory::GetForProfile(&profile_);
+    handler_ =
+        std::make_unique<AppParentalControlsHandler>(app_service_proxy_.get());
+  }
+
+  void TearDown() override { handler_.reset(); }
+
+ protected:
+  void CreateAndStoreFakeApp(std::string fake_id,
+                             apps::AppType app_type,
+                             bool shown_in_management) {
+    std::vector<apps::AppPtr> fake_apps;
+    apps::AppPtr fake_app = std::make_unique<apps::App>(app_type, fake_id);
+    fake_app->show_in_management = shown_in_management;
+    fake_app->readiness = apps::Readiness::kReady;
+    fake_apps.push_back(std::move(fake_app));
+
+    UpdateAppRegistryCache(fake_apps, app_type);
+  }
+
+  void UpdateAppRegistryCache(std::vector<apps::AppPtr>& fake_apps,
+                              apps::AppType app_type) {
+    app_service_proxy_->OnApps(std::move(fake_apps), app_type, false);
+  }
+
+ protected:
+  std::unique_ptr<AppParentalControlsHandler> handler_;
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  TestingProfile profile_;
+  raw_ptr<apps::AppServiceProxy> app_service_proxy_ = nullptr;
+};
+
+TEST_F(AppParentalControlsHandlerTest, TestOnlyManageableArcAppsFetched) {
+  CreateAndStoreFakeApp("arcApp1", apps::AppType::kArc,
+                        /*shown_in_management=*/true);
+  CreateAndStoreFakeApp("webApp", apps::AppType::kWeb,
+                        /*shown_in_management=*/true);
+  CreateAndStoreFakeApp("arcApp2", apps::AppType::kArc,
+                        /*shown_in_management=*/true);
+  CreateAndStoreFakeApp("unmanageableArcApp", apps::AppType::kArc,
+                        /*shown_in_management=*/false);
+
+  base::RunLoop run_loop;
+  handler_->GetApps(base::BindLambdaForTesting(
+      [&](std::vector<app_parental_controls::mojom::AppPtr> apps) -> void {
+        EXPECT_EQ(apps.size(), 2u);
+        EXPECT_EQ(apps[0]->id, "arcApp1");
+        EXPECT_EQ(apps[1]->id, "arcApp2");
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+}
+
+}  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/ash/settings/pages/apps/apps_section.cc b/chrome/browser/ui/webui/ash/settings/pages/apps/apps_section.cc
index b45fead..e9162f4 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/apps/apps_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/apps/apps_section.cc
@@ -526,6 +526,18 @@
       {"enableIsolatedWebAppsToggleLabel",
        IDS_SETTINGS_ENABLE_ISOLATED_WEB_APPS_LABEL},
       {"appManagementAppLanguageLabel", IDS_APP_MANAGEMENT_APP_LANGUAGE_LABEL},
+      {"permissionAllowedTextWithTurnOnCameraAccessButton",
+       IDS_APP_MANAGEMENT_PERMISSION_ALLOWED_TEXT_WITH_TURN_ON_SYSTEM_CAMERA_ACCESS_BUTTON},
+      {"permissionAllowedTextWithTurnOnMicrophoneAccessButton",
+       IDS_APP_MANAGEMENT_PERMISSION_ALLOWED_TEXT_WITH_TURN_ON_SYSTEM_MICROPHONE_ACCESS_BUTTON},
+      {"permissionAllowedTextWithTurnOnLocationAccessButton",
+       IDS_APP_MANAGEMENT_PERMISSION_ALLOWED_TEXT_WITH_TURN_ON_SYSTEM_LOCATION_ACCESS_BUTTON},
+      {"permissionAllowedTextWithDetailsAndTurnOnCameraAccessButton",
+       IDS_APP_MANAGEMENT_PERMISSION_ALLOWED_TEXT_WITH_DETAILS_AND_TURN_ON_SYSTEM_CAMERA_ACCESS_BUTTON},
+      {"permissionAllowedTextWithDetailsAndTurnOnMicrophoneAccessButton",
+       IDS_APP_MANAGEMENT_PERMISSION_ALLOWED_TEXT_WITH_DETAILS_AND_TURN_ON_SYSTEM_MICROPHONE_ACCESS_BUTTON},
+      {"permissionAllowedTextWithDetailsAndTurnOnLocationAccessButton",
+       IDS_APP_MANAGEMENT_PERMISSION_ALLOWED_TEXT_WITH_DETAILS_AND_TURN_ON_SYSTEM_LOCATION_ACCESS_BUTTON},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
@@ -564,6 +576,9 @@
   html_source->AddBoolean("privacyHubAppPermissionsV2Enabled",
                           features::IsCrosPrivacyHubAppPermissionsV2Enabled());
 
+  html_source->AddBoolean("privacyHubLocationAccessControlEnabled",
+                          ash::features::IsCrosPrivacyHubLocationEnabled());
+
   html_source->AddBoolean("isAppParentalControlsFeatureAvailable",
                           on_device_controls::AppControlsServiceFactory::
                               IsOnDeviceAppControlsAvailable(profile()));
diff --git a/chrome/browser/ui/webui/ash/settings/pages/apps/mojom/BUILD.gn b/chrome/browser/ui/webui/ash/settings/pages/apps/mojom/BUILD.gn
index 8aee48a5..ac85455 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/apps/mojom/BUILD.gn
+++ b/chrome/browser/ui/webui/ash/settings/pages/apps/mojom/BUILD.gn
@@ -8,7 +8,10 @@
 assert(is_chromeos_ash, "Non-ChromeOS builds cannot depend on //ash")
 
 mojom("mojom") {
-  sources = [ "app_notification_handler.mojom" ]
+  sources = [
+    "app_notification_handler.mojom",
+    "app_parental_controls_handler.mojom",
+  ]
 
   webui_module_path = "/"
 
diff --git a/chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_parental_controls_handler.mojom b/chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_parental_controls_handler.mojom
new file mode 100644
index 0000000..68db0c4
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_parental_controls_handler.mojom
@@ -0,0 +1,23 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module ash.settings.app_parental_controls.mojom;
+
+// App representation for on-device parental controls settings. Contains the app
+// id and title. Represents Apps that can be managed.
+struct App {
+  // Unique identifier of the App.
+  string id;
+
+  // The title of the app.
+  // This field may be null when this struct is used to signal updates.
+  string? title;
+};
+
+// Interface for fetching and setting the state of apps for on-device parental
+// controls in OS Settings.
+interface AppParentalControlsHandler {
+  // Get the list of installed apps.
+  GetApps() => (array<App> apps);
+};
diff --git a/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc b/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc
index ae79b62d..2646608 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc
@@ -608,6 +608,22 @@
        IDS_OS_SETTINGS_PRIVACY_HUB_CAMERA_TOGGLE_NO_CAMERA_CONNECTED_TOOLTIP_TEXT},
       {"privacyHubNoMicrophoneConnectedTooltipText",
        IDS_OS_SETTINGS_PRIVACY_HUB_MICROPHONE_TOGGLE_NO_MICROPHONE_CONNECTED_TOOLTIP_TEXT},
+      {"privacyHubAllowCameraAccessDialogTitle",
+       IDS_OS_SETTINGS_PRIVACY_HUB_ALLOW_CAMERA_ACCESS_DIALOG_TITLE},
+      {"privacyHubAllowLocationAccessDialogTitle",
+       IDS_OS_SETTINGS_PRIVACY_HUB_ALLOW_LOCATION_ACCESS_DIALOG_TITLE},
+      {"privacyHubAllowMicrophoneAccessDialogTitle",
+       IDS_OS_SETTINGS_PRIVACY_HUB_ALLOW_MICROPHONE_ACCESS_DIALOG_TITLE},
+      {"privacyHubAllowCameraAccessDialogBodyText",
+       IDS_OS_SETTINGS_PRIVACY_HUB_ALLOW_CAMERA_ACCESS_DIALOG_BODY_TEXT},
+      {"privacyHubAllowLocationAccessDialogBodyText",
+       IDS_OS_SETTINGS_PRIVACY_HUB_ALLOW_LOCATION_ACCESS_DIALOG_BODY_TEXT},
+      {"privacyHubAllowMicrophoneAccessDialogBodyText",
+       IDS_OS_SETTINGS_PRIVACY_HUB_ALLOW_MICROPHONE_ACCESS_DIALOG_BODY_TEXT},
+      {"privacyHubDialogConfirmButtonLabel",
+       IDS_OS_SETTINGS_PRIVACY_HUB_DIALOG_CONFIRM_BUTTON_LABEL},
+      {"privacyHubDialogCancelButtonLabel",
+       IDS_OS_SETTINGS_PRIVACY_HUB_DIALOG_CANCEL_BUTTON_LABEL},
   };
 
   html_source->AddLocalizedStrings(kLocalizedStrings);
diff --git a/chrome/browser/ui/webui/ash/settings/services/settings_manager/os_settings_manager.cc b/chrome/browser/ui/webui/ash/settings/services/settings_manager/os_settings_manager.cc
index 4c8a0cec..4232ab30 100644
--- a/chrome/browser/ui/webui/ash/settings/services/settings_manager/os_settings_manager.cc
+++ b/chrome/browser/ui/webui/ash/settings/services/settings_manager/os_settings_manager.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/apps/app_notification_handler.h"
+#include "chrome/browser/ui/webui/ash/settings/pages/apps/app_parental_controls_handler.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/os_settings_sections.h"
@@ -62,6 +63,8 @@
           std::make_unique<AppNotificationHandler>(app_service_proxy)),
       app_permission_handler_(
           std::make_unique<AppPermissionHandler>(app_service_proxy)),
+      app_parental_controls_handler_(
+          std::make_unique<AppParentalControlsHandler>(app_service_proxy)),
       input_device_settings_provider_(
           std::make_unique<InputDeviceSettingsProvider>()),
       display_settings_provider_(std::make_unique<DisplaySettingsProvider>()),
diff --git a/chrome/browser/ui/webui/ash/settings/services/settings_manager/os_settings_manager.h b/chrome/browser/ui/webui/ash/settings/services/settings_manager/os_settings_manager.h
index 366da13..d908c26 100644
--- a/chrome/browser/ui/webui/ash/settings/services/settings_manager/os_settings_manager.h
+++ b/chrome/browser/ui/webui/ash/settings/services/settings_manager/os_settings_manager.h
@@ -49,6 +49,7 @@
 
 class AppNotificationHandler;
 class AppPermissionHandler;
+class AppParentalControlsHandler;
 class Hierarchy;
 class OsSettingsSections;
 class SearchHandler;
@@ -115,6 +116,10 @@
     return app_permission_handler_.get();
   }
 
+  AppParentalControlsHandler* app_parental_controls_handler() {
+    return app_parental_controls_handler_.get();
+  }
+
   InputDeviceSettingsProvider* input_device_settings_provider() {
     return input_device_settings_provider_.get();
   }
@@ -152,6 +157,7 @@
   std::unique_ptr<SearchHandler> search_handler_;
   std::unique_ptr<AppNotificationHandler> app_notification_handler_;
   std::unique_ptr<AppPermissionHandler> app_permission_handler_;
+  std::unique_ptr<AppParentalControlsHandler> app_parental_controls_handler_;
   std::unique_ptr<InputDeviceSettingsProvider> input_device_settings_provider_;
   std::unique_ptr<DisplaySettingsProvider> display_settings_provider_;
   std::unique_ptr<ShortcutInputProvider> shortcut_input_provider_;
diff --git a/chrome/browser/ui/webui/history/history_ui.cc b/chrome/browser/ui/webui/history/history_ui.cc
index 03cc636..5ddf1da 100644
--- a/chrome/browser/ui/webui/history/history_ui.cc
+++ b/chrome/browser/ui/webui/history/history_ui.cc
@@ -171,6 +171,8 @@
       {"historyEmbeddingsDisclaimer", IDS_HISTORY_EMBEDDINGS_DISCLAIMER},
       {"historyEmbeddingsPromoHeading", IDS_HISTORY_EMBEDDINGS_PROMO_HEADING},
       {"historyEmbeddingsPromoBody", IDS_HISTORY_EMBEDDINGS_PROMO_BODY},
+      {"historyEmbeddingsShowByDate", IDS_HISTORY_EMBEDDINGS_SHOW_BY_DATE},
+      {"historyEmbeddingsShowByGroup", IDS_HISTORY_EMBEDDINGS_SHOW_BY_GROUP},
       {"historyEmbeddingsSuggestion1", IDS_HISTORY_EMBEDDINGS_SUGGESTION_1},
       {"historyEmbeddingsSuggestion2", IDS_HISTORY_EMBEDDINGS_SUGGESTION_2},
       {"historyEmbeddingsSuggestion3", IDS_HISTORY_EMBEDDINGS_SUGGESTION_3},
diff --git a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
index 0b0323d..63e4baf61 100644
--- a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
@@ -105,7 +105,7 @@
   }
 }
 
-// Called when LanguagePackManager::GetPackState is complete.
+// Called when LanguagePackManager::GetPackState or ::InstallPack is complete.
 void OnLanguagePackManagerResponse(
     read_anything::mojom::UntrustedPageHandler::GetVoicePackInfoCallback
         mojo_remote_callback,
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index 28e70af..fdb5595 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1713895175-5a9655dfd30cdec8f9e7ab4d6a75f1f4279c0993-2080fef512e6cf81edc334d047c7552a6005798e.profdata
+chrome-android32-main-1713938365-86a69eb7bfbcd8197929e69027446b72a911223d-b75e949e3c03ad9114ddfaee3050d679db3be729.profdata
diff --git a/chrome/build/lacros-arm64.pgo.txt b/chrome/build/lacros-arm64.pgo.txt
index 7505bf38..b363a520 100644
--- a/chrome/build/lacros-arm64.pgo.txt
+++ b/chrome/build/lacros-arm64.pgo.txt
@@ -1 +1 @@
-chrome-chromeos-arm64-generic-main-1713787146-bc2187921b56b925911e6ed8a84d4bc8d3ccb8ad-1035b0924d4df19c3c3afe9fe312bd38ba2864ed.profdata
+chrome-chromeos-arm64-generic-main-1713916645-e2333cd423bfde1526f36a6e496980f9f5085e90-fccc19e1196d83e5127b1a5418d13393b432b969.profdata
diff --git a/chrome/build/lacros64.pgo.txt b/chrome/build/lacros64.pgo.txt
index b3f1638..46a4a6a 100644
--- a/chrome/build/lacros64.pgo.txt
+++ b/chrome/build/lacros64.pgo.txt
@@ -1 +1 @@
-chrome-chromeos-amd64-generic-main-1713873578-7dff6303b17f42f2da4d35ec308e443928775f2a-84072da101f658e760ecf93157948db2192a1d5f.profdata
+chrome-chromeos-amd64-generic-main-1713917103-7a1c5a9fbb6d63f0e70557620c62cc85e0e1373b-86d4f5ac009ae85511478fb742097386152b1226.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 316d8315..663dff6 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1713873578-48a0c0d0df863946e182190ac5d8e83c1f290df7-84072da101f658e760ecf93157948db2192a1d5f.profdata
+chrome-linux-main-1713916645-ef8409a6ba075115f19763c93235b44c432a0c66-fccc19e1196d83e5127b1a5418d13393b432b969.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index 477d482..071c9c024 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1713895175-31736d22faed0788ff04da182ea392e51cff3fa2-2080fef512e6cf81edc334d047c7552a6005798e.profdata
+chrome-win-arm64-main-1713938365-5d542d33db06f8ccd9aa7ff0e02fb3eb95771e8a-b75e949e3c03ad9114ddfaee3050d679db3be729.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 574cdeee..738d034a 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1713895175-62475c4a9c9e20cfb5ad1132d19f32099ffa774c-2080fef512e6cf81edc334d047c7552a6005798e.profdata
+chrome-win32-main-1713927574-20f792ec54250f82724cc744bc79e4383598819a-2db5711b84b41501aae88c4e2f31268b8b556312.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 624fb4e..b229090d 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1713895175-a56923f6b946bc2192f13562f37392f9920ac717-2080fef512e6cf81edc334d047c7552a6005798e.profdata
+chrome-win64-main-1713916645-b4fd896a1a845d5aea916db524ab2074d4db4891-fccc19e1196d83e5127b1a5418d13393b432b969.profdata
diff --git a/chrome/common/extensions/api/autotest_private.idl b/chrome/common/extensions/api/autotest_private.idl
index b20e0ed5..3b51e90 100644
--- a/chrome/common/extensions/api/autotest_private.idl
+++ b/chrome/common/extensions/api/autotest_private.idl
@@ -866,6 +866,10 @@
     // Get login status.
     static void loginStatus(LoginStatusCallback callback);
 
+    // Waits for the post login animation to be complete and then triggers the
+    // callback.
+    static void waitForLoginAnimationEnd(VoidCallback callback);
+
     // Locks the screen.
     static void lockScreen();
 
diff --git a/chrome/common/extensions/api/extension.json b/chrome/common/extensions/api/extension.json
index a64ee51b..8cda71f 100644
--- a/chrome/common/extensions/api/extension.json
+++ b/chrome/common/extensions/api/extension.json
@@ -39,7 +39,6 @@
         "nocompile": true,
         "deprecated": "Please use $(ref:runtime.sendMessage).",
         "type": "function",
-        "allowAmbiguousOptionalArguments": true,
         "description": "Sends a single request to other listeners within the extension. Similar to $(ref:runtime.connect), but only sends a single request with an optional response. The $(ref:extension.onRequest) event is fired in each page of the extension.",
         "parameters": [
           {"type": "string", "name": "extensionId", "optional": true, "description": "The extension ID of the extension you want to connect to. If omitted, default is your own extension."},
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index b4605ee..ebe400d 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1726,6 +1726,7 @@
       "//chrome/browser/favicon",
       "//chrome/browser/first_party_sets",
       "//chrome/browser/headless:browser_tests",
+      "//chrome/browser/icon_transcoder:browser_tests",
       "//chrome/browser/image_decoder:browser_tests",
       "//chrome/browser/image_fetcher",
       "//chrome/browser/ip_protection:browser_tests",
@@ -2359,7 +2360,6 @@
       "../browser/history_clusters/history_clusters_metrics_browsertest.cc",
       "../browser/history_embeddings/history_embeddings_service_browsertest.cc",
       "../browser/icon_loader_browsertest.cc",
-      "../browser/icon_transcoder/svg_icon_transcoder_browsertest.cc",
       "../browser/idle/idle_browsertest.cc",
       "../browser/iframe_browsertest.cc",
       "../browser/image_fetcher/image_fetcher_impl_browsertest.cc",
@@ -3028,6 +3028,7 @@
       deps += [
         "//chrome/browser/screen_ai:screen_ai_install_state",
         "//chrome/browser/screen_ai:screen_ai_service_router_factory",
+        "//chrome/browser/screen_ai/public:test_support",
         "//services/screen_ai:test_support",
         "//services/screen_ai/public/cpp:utilities",
         "//services/screen_ai/public/mojom",
@@ -8752,6 +8753,7 @@
       "//chrome/browser/policy:unit_tests",
       "//chrome/browser/push_notification:push_notification",
       "//chrome/browser/screen_ai:unit_tests",
+      "//chrome/browser/screen_ai/public:test_support",
       "//chrome/browser/ui/ash/holding_space:test_support",
       "//chrome/browser/ui/chromeos/read_write_cards",
       "//chrome/browser/ui/quick_answers",
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.cc b/chrome/test/chromedriver/chrome/chrome_impl.cc
index 63ed9a4..8d4c303 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_impl.cc
@@ -587,8 +587,6 @@
     WebViewsInfo views_info;
     status = target_utils::GetWebViewsInfo(*devtools_websocket_client_,
                                            &timeout, views_info);
-    if (status.code() == kChromeNotReachable)
-      return Status(kOk);
     if (status.code() == kDisconnected)  // The closed target has gone
       return Status(kOk);
     if (status.IsError())
diff --git a/chrome/test/chromedriver/chrome_launcher.cc b/chrome/test/chromedriver/chrome_launcher.cc
index 962c53d..064216d 100644
--- a/chrome/test/chromedriver/chrome_launcher.cc
+++ b/chrome/test/chromedriver/chrome_launcher.cc
@@ -121,6 +121,13 @@
 
 enum ChromeType { Remote, Desktop, Android, Replay };
 
+Status WrapStatusIfNeeded(Status status, StatusCode code) {
+  if (status.code() != code) {
+    return Status{code, status};
+  }
+  return status;
+}
+
 Status PrepareDesktopCommandLine(const Capabilities& capabilities,
                                  bool enable_chrome_logs,
                                  base::ScopedTempDir& user_data_dir_temp_dir,
@@ -406,7 +413,7 @@
       devtools_http_client, retry);
   if (status.IsError()) {
     return Status(
-        kUnknownError,
+        kSessionNotCreated,
         base::StringPrintf("cannot connect to %s at %s",
                            base::ToLowerASCII(kBrowserShortName).c_str(),
                            capabilities.debugger_address.ToString().c_str()),
@@ -423,6 +430,9 @@
   status = CreateBrowserwideDevToolsClientAndConnect(
       std::move(socket), devtools_event_listeners, browser_info.web_socket_url,
       devtools_websocket_client);
+  if (status.IsError()) {
+    return WrapStatusIfNeeded(status, kSessionNotCreated);
+  }
 
   chrome = std::make_unique<ChromeRemoteImpl>(
       browser_info, capabilities.window_types,
@@ -452,7 +462,7 @@
     bool conversion_result = base::StringToInt(port_switch, &devtools_port);
     if (!conversion_result || devtools_port < 0 || 65535 < devtools_port) {
       return Status(
-          kUnknownError,
+          kSessionNotCreated,
           "remote-debugging-port flag has invalid value: " + port_switch);
     }
   }
@@ -461,7 +471,7 @@
     status = internal::RemoveOldDevToolsActivePortFile(base::FilePath(
         capabilities.switches.GetSwitchValueNative("user-data-dir")));
     if (status.IsError()) {
-      return status;
+      return Status{kSessionNotCreated, status};
     }
   }
   const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
@@ -470,7 +480,7 @@
       capabilities, enable_chrome_logs, user_data_dir_temp_dir, extension_dir,
       command, extension_bg_pages, user_data_dir);
   if (status.IsError())
-    return status;
+    return WrapStatusIfNeeded(status, kSessionNotCreated);
 
   if (command.HasSwitch("remote-debugging-port") &&
       PipeBuilder::PlatformIsSupported()) {
@@ -524,7 +534,7 @@
         command.GetSwitchValueASCII("remote-debugging-pipe"));
     status = pipe_builder.SetUpPipes(&options, &command);
     if (status.IsError()) {
-      return status;
+      return WrapStatusIfNeeded(status, kSessionNotCreated);
     }
   }
 
@@ -537,7 +547,7 @@
     // confuse users.
     devnull.reset(HANDLE_EINTR(open("/dev/null", O_WRONLY)));
     if (!devnull.is_valid())
-      return Status(kUnknownError, "couldn't open /dev/null");
+      return Status(kSessionNotCreated, "couldn't open /dev/null");
     options.fds_to_remap.emplace_back(devnull.get(), STDERR_FILENO);
   }
 #elif BUILDFLAG(IS_WIN)
@@ -576,7 +586,7 @@
   base::Process process = base::LaunchProcess(command, options);
   if (!process.IsValid())
     return Status(
-        kUnknownError,
+        kSessionNotCreated,
         base::StringPrintf("Failed to create %s process.", kBrowserShortName));
 
   // Attempt to connect to devtools in order to send commands to Chrome. If
@@ -722,11 +732,14 @@
     if (!process.Terminate(0, true)) {
       if (base::GetTerminationStatus(process.Handle(), &exit_code) ==
           base::TERMINATION_STATUS_STILL_RUNNING)
-        return Status(kUnknownError,
+        return Status(kSessionNotCreated,
                       base::StringPrintf("cannot kill %s", kBrowserShortName),
                       status);
     }
-    return status;
+
+    // For example kChromeNotReachable must be wrapped into statndard
+    // compatible kSessionNotCreated
+    return WrapStatusIfNeeded(status, kSessionNotCreated);
   }
 
   std::unique_ptr<ChromeDesktopImpl> chrome_desktop =
@@ -746,7 +759,7 @@
           extension_bg_pages[i], capabilities.extension_load_timeout, &web_view,
           w3c_compliant);
       if (status.IsError()) {
-        return Status(kUnknownError,
+        return Status(kSessionNotCreated,
                       "failed to wait for extension background page to load: " +
                           extension_bg_pages[i],
                       status);
@@ -774,7 +787,7 @@
         capabilities.android_device_serial, &device);
   }
   if (status.IsError())
-    return status;
+    return WrapStatusIfNeeded(status, kSessionNotCreated);
 
   Switches switches(capabilities.switches);
   for (auto* common_switch : kCommonSwitches)
@@ -794,7 +807,7 @@
       capabilities.android_keep_app_data_dir, &devtools_port);
   if (status.IsError()) {
     device->TearDown();
-    return status;
+    return WrapStatusIfNeeded(status, kSessionNotCreated);
   }
 
   std::unique_ptr<DevToolsHttpClient> devtools_http_client;
@@ -805,7 +818,7 @@
       devtools_http_client, retry);
   if (status.IsError()) {
     device->TearDown();
-    return status;
+    return WrapStatusIfNeeded(status, kSessionNotCreated);
   }
 
   std::unique_ptr<DevToolsClient> devtools_websocket_client;
@@ -844,7 +857,7 @@
     status = internal::RemoveOldDevToolsActivePortFile(base::FilePath(
         capabilities.switches.GetSwitchValueNative("user-data-dir")));
     if (status.IsError()) {
-      return status;
+      return WrapStatusIfNeeded(status, kSessionNotCreated);
     }
   }
 
@@ -860,7 +873,7 @@
       DevToolsEndpoint(0), factory, capabilities, Timeout(base::Seconds(1)),
       ChromeType::Replay, devtools_http_client, retry);
   if (status.IsError())
-    return status;
+    return WrapStatusIfNeeded(status, kSessionNotCreated);
   const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
   base::CommandLine::StringType log_path_str =
       cmd_line->GetSwitchValueNative("devtools-replay");
@@ -895,7 +908,7 @@
           extension_bg_pages[i], capabilities.extension_load_timeout, &web_view,
           w3c_compliant);
       if (status.IsError()) {
-        return Status(kUnknownError,
+        return Status(kSessionNotCreated,
                       "failed to wait for extension background page to load: " +
                           extension_bg_pages[i],
                       status);
diff --git a/chrome/test/chromedriver/commands.cc b/chrome/test/chromedriver/commands.cc
index 560d1bf..9f93fc3a 100644
--- a/chrome/test/chromedriver/commands.cc
+++ b/chrome/test/chromedriver/commands.cc
@@ -334,8 +334,7 @@
           std::list<std::string> web_view_ids;
           Status status_tmp = session->chrome->GetWebViewIds(
               &web_view_ids, session->w3c_compliant);
-          if (status_tmp.IsError() &&
-              status_tmp.code() != kChromeNotReachable) {
+          if (status_tmp.IsError()) {
             status.AddDetails("failed to check if window was closed: " +
                               status_tmp.message());
           } else if (!base::Contains(web_view_ids, session->window)) {
diff --git a/chrome/test/chromedriver/server/http_handler.cc b/chrome/test/chromedriver/server/http_handler.cc
index e2b92bb..8fd52ea 100644
--- a/chrome/test/chromedriver/server/http_handler.cc
+++ b/chrome/test/chromedriver/server/http_handler.cc
@@ -1511,7 +1511,6 @@
       response =
           std::make_unique<net::HttpServerResponseInfo>(net::HTTP_BAD_REQUEST);
       break;
-    case kChromeNotReachable:
     case kDisconnected:
     case kTabCrashed:
       response = std::make_unique<net::HttpServerResponseInfo>(
@@ -1528,6 +1527,8 @@
 
     default:
       DCHECK(false);
+      // Examples of unexpected codes:
+      // * kChromeNotReachable - kSessionNotCreated must be returned instead
       response = std::make_unique<net::HttpServerResponseInfo>(
           net::HTTP_INTERNAL_SERVER_ERROR);
       break;
diff --git a/chrome/test/data/web_apps/tab_strip_fixed_home_scope.json b/chrome/test/data/web_apps/tab_strip_fixed_home_scope.json
index b0306ca..73117102 100644
--- a/chrome/test/data/web_apps/tab_strip_fixed_home_scope.json
+++ b/chrome/test/data/web_apps/tab_strip_fixed_home_scope.json
@@ -8,7 +8,7 @@
       "purpose": "any monochrome"
     }
   ],
-  "start_url": "get_manifest.html",
+  "start_url": "get_manifest.html?tab_strip_fixed_home_scope.json",
   "display": "standalone",
   "display_override": [ "tabbed" ],
   "theme_color": "black",
diff --git a/chrome/test/data/web_apps/tab_strip_query_params_in_start_url.json b/chrome/test/data/web_apps/tab_strip_query_params_in_start_url.json
index 6930f267..f68749b 100644
--- a/chrome/test/data/web_apps/tab_strip_query_params_in_start_url.json
+++ b/chrome/test/data/web_apps/tab_strip_query_params_in_start_url.json
@@ -7,7 +7,7 @@
       "type": "image/png"
     }
   ],
-  "start_url": "get_manifest.html?query=params",
+  "start_url": "get_manifest.html?tab_strip_query_params_in_start_url.json",
   "display": "standalone",
   "display_override": [ "tabbed" ],
   "tab_strip": {
diff --git a/chrome/test/data/web_apps/tab_strip_wildcard_home_scope.json b/chrome/test/data/web_apps/tab_strip_wildcard_home_scope.json
index 8abe56a..b9fa01d 100644
--- a/chrome/test/data/web_apps/tab_strip_wildcard_home_scope.json
+++ b/chrome/test/data/web_apps/tab_strip_wildcard_home_scope.json
@@ -7,7 +7,7 @@
       "type": "image/png"
     }
   ],
-  "start_url": "/web_apps/get_manifest.html",
+  "start_url": "/web_apps/get_manifest.html?tab_strip_wildcard_home_scope.json",
   "scope": "/",
   "display": "standalone",
   "display_override": [ "tabbed" ],
diff --git a/chrome/test/data/webui/chromeos/diagnostics/BUILD.gn b/chrome/test/data/webui/chromeos/diagnostics/BUILD.gn
index 1c7814f..36917f7 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/BUILD.gn
+++ b/chrome/test/data/webui/chromeos/diagnostics/BUILD.gn
@@ -25,7 +25,7 @@
     "diagnostics_network_icon_test.ts",
     "diagnostics_sticky_banner_test.ts",
     "diagnostics_test_utils.js",
-    "diagnostics_utils_test.js",
+    "diagnostics_utils_test.ts",
     "drawing_provider_test.js",
     "drawing_provider_utils_test.js",
     "ethernet_info_test.js",
diff --git a/chrome/test/data/webui/chromeos/diagnostics/diagnostics_utils_test.js b/chrome/test/data/webui/chromeos/diagnostics/diagnostics_utils_test.ts
similarity index 93%
rename from chrome/test/data/webui/chromeos/diagnostics/diagnostics_utils_test.js
rename to chrome/test/data/webui/chromeos/diagnostics/diagnostics_utils_test.ts
index d789172..1696677 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/diagnostics_utils_test.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/diagnostics_utils_test.ts
@@ -7,9 +7,10 @@
 
 import {convertKibToGibDecimalString, getNetworkCardTitle, getRoutineGroups, getSignalStrength, getSubnetMaskFromRoutingPrefix, setDisplayStateInTitleForTesting} from 'chrome://diagnostics/diagnostics_utils.js';
 import {NetworkType} from 'chrome://diagnostics/network_health_provider.mojom-webui.js';
-import {RoutineGroup} from 'chrome://diagnostics/routine_group.js';
 import {RoutineType} from 'chrome://diagnostics/system_routine_controller.mojom-webui.js';
-import {assertArrayEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
+import {assert} from 'chrome://resources/js/assert.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
+import {RoutineGroup} from 'chrome://diagnostics/routine_group.js';
 
 suite('diagnosticsUtilsTestSuite', function() {
   test('ProperlyConvertsKibToGib', () => {
@@ -63,7 +64,7 @@
   });
 
   test('AllRoutineGroupsPresent', () => {
-    const routineGroups = getRoutineGroups(NetworkType.kWiFi);
+    const routineGroups: RoutineGroup[] = getRoutineGroups(NetworkType.kWiFi);
     const [
       localNetworkGroup,
        nameResolutionGroup,
@@ -76,14 +77,17 @@
     assertEquals(routineGroups.length, 4);
 
     // WiFi group should exist and all three WiFi routines should be present.
+    assert(wifiGroup);
     assertEquals(wifiGroup.routines.length, 3);
     assertEquals(wifiGroup.groupName, 'wifiGroupLabel');
 
     // ARC routines should be present in their categories.
+    assert(nameResolutionGroup);
     assertTrue(
         nameResolutionGroup.routines.includes(RoutineType.kArcDnsResolution));
-
+    assert(localNetworkGroup);
     assertTrue(localNetworkGroup.routines.includes(RoutineType.kArcPing));
+    assert(internetConnectivityGroup);
     assertTrue(
         internetConnectivityGroup.routines.includes(RoutineType.kArcHttp));
   });
diff --git a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_images_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_images_element_test.ts
index 75a23a28a..e6282d7 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_images_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_images_element_test.ts
@@ -343,7 +343,7 @@
 
   test('shows dialog when clicking on a time of day wallpaper', async () => {
     loadTimeData.overrideValues({
-      isTimeOfDayWallpaperForcedAutoScheduleEnabled: true,
+      isTimeOfDayWallpaperEnabled: true,
     });
     personalizationStore.setReducersEnabled(true);
     personalizationStore.data.theme.colorModeAutoScheduleEnabled = false;
@@ -362,7 +362,7 @@
       'clicking cancel dismisses the time of day wallpaper dialog',
       async () => {
         loadTimeData.overrideValues({
-          isTimeOfDayWallpaperForcedAutoScheduleEnabled: true,
+          isTimeOfDayWallpaperEnabled: true,
         });
         personalizationStore.setReducersEnabled(true);
         personalizationStore.data.theme.colorModeAutoScheduleEnabled = false;
@@ -389,7 +389,7 @@
 
   test('clicking confirm on the time of day wallpaper dialog', async () => {
     loadTimeData.overrideValues({
-      isTimeOfDayWallpaperForcedAutoScheduleEnabled: true,
+      isTimeOfDayWallpaperEnabled: true,
     });
     personalizationStore.setReducersEnabled(true);
     personalizationStore.data.theme.colorModeAutoScheduleEnabled = false;
@@ -416,7 +416,7 @@
 
   test('do not show time of day dialog with proper settings', async () => {
     loadTimeData.overrideValues({
-      isTimeOfDayWallpaperForcedAutoScheduleEnabled: true,
+      isTimeOfDayWallpaperEnabled: true,
     });
     personalizationStore.setReducersEnabled(true);
     wallpaperProvider.shouldShowTimeOfDayWallpaperDialogResponse = false;
diff --git a/chrome/test/data/webui/chromeos/settings/BUILD.gn b/chrome/test/data/webui/chromeos/settings/BUILD.gn
index f30a0062f..cf8cbd8be 100644
--- a/chrome/test/data/webui/chromeos/settings/BUILD.gn
+++ b/chrome/test/data/webui/chromeos/settings/BUILD.gn
@@ -238,6 +238,9 @@
     "os_apps_page/app_notifications_page/app_notifications_subpage_test.ts",
     "os_apps_page/app_notifications_page/fake_app_notification_handler.ts",
 
+    "os_apps_page/app_parental_controls_page/app_parental_controls_subpage_test.ts",
+    "os_apps_page/app_parental_controls_page/fake_app_parental_controls_handler.ts",
+
     "os_apps_page/manage_isolated_web_apps_page/manage_isolated_web_apps_subpage_test.ts",
 
     "os_apps_page/os_apps_page_test.ts",
diff --git a/chrome/test/data/webui/chromeos/settings/os_apps_page/app_management_page/permission_item_test.ts b/chrome/test/data/webui/chromeos/settings/os_apps_page/app_management_page/permission_item_test.ts
index ff40aa5..da25c06 100644
--- a/chrome/test/data/webui/chromeos/settings/os_apps_page/app_management_page/permission_item_test.ts
+++ b/chrome/test/data/webui/chromeos/settings/os_apps_page/app_management_page/permission_item_test.ts
@@ -6,7 +6,7 @@
 import 'chrome://os-settings/lazy_load.js';
 
 import {AppManagementPermissionItemElement} from 'chrome://os-settings/lazy_load.js';
-import {AppManagementStore, updateSelectedAppId} from 'chrome://os-settings/os_settings.js';
+import {AppManagementStore, CrButtonElement, GeolocationAccessLevel, LocalizedLinkElement, updateSelectedAppId} from 'chrome://os-settings/os_settings.js';
 import {App, Permission, PermissionType, TriState} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
 import {AppManagementUserAction} from 'chrome://resources/cr_components/app_management/constants.js';
 import {PermissionTypeIndex} from 'chrome://resources/cr_components/app_management/permission_constants.js';
@@ -34,6 +34,21 @@
     permissionItem = document.createElement('app-management-permission-item');
     permissionItem.app = getApp();
     permissionItem.permissionType = permissionType;
+    permissionItem.prefs = {
+      'ash': {
+        'user': {
+          'camera_allowed': {
+            value: true,
+          },
+          'microphone_allowed': {
+            value: true,
+          },
+          'geolocation_access_level': {
+            value: GeolocationAccessLevel.ALLOWED,
+          },
+        },
+      },
+    };
     document.body.appendChild(permissionItem);
     flush();
   }
@@ -79,19 +94,25 @@
 
   suite('Permission item with description', () => {
     setup(() => {
-      loadTimeData.overrideValues({'privacyHubAppPermissionsV2Enabled': true});
+      loadTimeData.overrideValues({
+        'privacyHubAppPermissionsV2Enabled': true,
+        'privacyHubLocationAccessControlEnabled': true,
+      });
 
       createPermissionItem();
     });
 
     teardown(() => {
-      loadTimeData.overrideValues({'privacyHubAppPermissionsV2Enabled': false});
+      loadTimeData.overrideValues({
+        'privacyHubAppPermissionsV2Enabled': false,
+        'privacyHubLocationAccessControlEnabled': false,
+      });
     });
 
     function getPermissionDescriptionString(): string {
       return permissionItem.shadowRoot!
-          .querySelector<HTMLElement>(
-              '#permissionDescription')!.innerText.trim();
+          .querySelector<LocalizedLinkElement>(
+              '#permissionDescription')!.localizedString.toString();
     }
 
     async function togglePermission(): Promise<void> {
@@ -117,5 +138,115 @@
           loadTimeData.getString('appManagementPermissionDenied'),
           getPermissionDescriptionString());
     });
+
+    test('Turn on sensor system access button displayed', async () => {
+      assertEquals(
+          loadTimeData.getString('appManagementPermissionAsk'),
+          getPermissionDescriptionString());
+
+      await togglePermission();
+
+      assertEquals(
+          loadTimeData.getString('appManagementPermissionAllowed'),
+          getPermissionDescriptionString());
+
+      permissionItem.set(
+          'prefs.ash.user.geolocation_access_level.value',
+          GeolocationAccessLevel.DISALLOWED);
+
+      assertEquals(
+          loadTimeData.getString(
+              'permissionAllowedTextWithTurnOnLocationAccessButton'),
+          getPermissionDescriptionString());
+
+      permissionItem.set(
+          'prefs.ash.user.geolocation_access_level.value',
+          GeolocationAccessLevel.ALLOWED);
+
+      assertEquals(
+          loadTimeData.getString('appManagementPermissionAllowed'),
+          getPermissionDescriptionString());
+    });
+
+    function getDialogElement(): HTMLElement|null {
+      return permissionItem.shadowRoot!.querySelector<HTMLElement>('#dialog');
+    }
+
+    async function openDialog(): Promise<void> {
+      const permissionDescription =
+          permissionItem.shadowRoot!.querySelector<LocalizedLinkElement>(
+              '#permissionDescription');
+      assertTrue(!!permissionDescription);
+      const link = permissionDescription.shadowRoot!.querySelector('a');
+      assertTrue(!!link);
+      link.click();
+      await flushTasks();
+    }
+
+    test('Open dialog and close using cancel button', async () => {
+      await togglePermission();
+
+      permissionItem.set(
+          'prefs.ash.user.geolocation_access_level.value',
+          GeolocationAccessLevel.DISALLOWED);
+
+      assertEquals(
+          loadTimeData.getString(
+              'permissionAllowedTextWithTurnOnLocationAccessButton'),
+          getPermissionDescriptionString());
+
+      // Dialog not visible initially.
+      assertNull(getDialogElement());
+
+      await openDialog();
+
+      // Dialog is visible.
+      assertTrue(!!getDialogElement());
+
+      // Close dialog.
+      const cancelButton =
+          getDialogElement()!.shadowRoot!.querySelector<CrButtonElement>(
+              '#cancelButton');
+      assertTrue(!!cancelButton);
+      cancelButton.click();
+      await flushTasks();
+
+      // Dialog not visible anymore.
+      assertNull(getDialogElement());
+    });
+
+    test('Open dialog and turn on sensor access', async () => {
+      await togglePermission();
+
+      permissionItem.set(
+          'prefs.ash.user.geolocation_access_level.value',
+          GeolocationAccessLevel.DISALLOWED);
+
+      assertEquals(
+          loadTimeData.getString(
+              'permissionAllowedTextWithTurnOnLocationAccessButton'),
+          getPermissionDescriptionString());
+
+      // Dialog not visible initially.
+      assertNull(getDialogElement());
+
+      await openDialog();
+
+      // Dialog is visible.
+      assertTrue(!!getDialogElement());
+
+      // Turn on system sensor access.
+      const confirmButton =
+          getDialogElement()!.shadowRoot!.querySelector<CrButtonElement>(
+              '#confirmButton');
+      assertTrue(!!confirmButton);
+      confirmButton.click();
+      await flushTasks();
+
+      // Sensor access is turned ON.
+      assertEquals(
+          GeolocationAccessLevel.ALLOWED,
+          permissionItem.prefs.ash.user.geolocation_access_level.value);
+    });
   });
 });
diff --git a/chrome/test/data/webui/chromeos/settings/os_apps_page/app_parental_controls_page/app_parental_controls_subpage_test.ts b/chrome/test/data/webui/chromeos/settings/os_apps_page/app_parental_controls_page/app_parental_controls_subpage_test.ts
new file mode 100644
index 0000000..c70e575
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/settings/os_apps_page/app_parental_controls_page/app_parental_controls_subpage_test.ts
@@ -0,0 +1,60 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://os-settings/lazy_load.js';
+
+import {SettingsAppParentalControlsSubpageElement} from 'chrome://os-settings/lazy_load.js';
+import {appParentalControlsHandlerMojom, Router, routes, setAppParentalControlsProviderForTesting} from 'chrome://os-settings/os_settings.js';
+import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
+import {isVisible} from 'chrome://webui-test/test_util.js';
+
+import {FakeAppParentalControlsHandler} from './fake_app_parental_controls_handler.js';
+
+suite('AppParentalControlsPageTest', () => {
+  let page: SettingsAppParentalControlsSubpageElement;
+  let handler: FakeAppParentalControlsHandler;
+
+  async function createPage(): Promise<void> {
+    Router.getInstance().navigateTo(routes.APP_PARENTAL_CONTROLS);
+    page = new SettingsAppParentalControlsSubpageElement();
+    document.body.appendChild(page);
+    await flushTasks();
+  }
+
+  suiteSetup(() => {
+    handler = new FakeAppParentalControlsHandler();
+    setAppParentalControlsProviderForTesting(handler);
+  });
+
+  teardown(() => {
+    Router.getInstance().resetRouteForTesting();
+  });
+
+  function createApp(
+      id: string, title: string): appParentalControlsHandlerMojom.App {
+    return {id, title};
+  }
+
+  test('App list is in alphabetical order', async () => {
+    const appTitle1 = 'Files';
+    const appTitle2 = 'Chrome';
+    handler.addAppForTesting(createApp('file-id', appTitle1));
+    handler.addAppForTesting(createApp('chrome-id', appTitle2));
+
+    await createPage();
+
+    const appList = page.shadowRoot!.querySelector('#appParentalControlsList');
+    assertTrue(!!appList);
+    assertTrue(isVisible(appList));
+
+    const apps = appList.querySelectorAll<HTMLElement>('.cr-row');
+    assertEquals(2, apps.length);
+    assertTrue(!!apps[0]);
+    assertTrue(!!apps[1]);
+
+    assertEquals(apps[0].innerText, appTitle2);
+    assertEquals(apps[1].innerText, appTitle1);
+  });
+});
diff --git a/chrome/test/data/webui/chromeos/settings/os_apps_page/app_parental_controls_page/fake_app_parental_controls_handler.ts b/chrome/test/data/webui/chromeos/settings/os_apps_page/app_parental_controls_page/fake_app_parental_controls_handler.ts
new file mode 100644
index 0000000..5d35fbf
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/settings/os_apps_page/app_parental_controls_page/fake_app_parental_controls_handler.ts
@@ -0,0 +1,32 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {appParentalControlsHandlerMojom} from 'chrome://os-settings/os_settings.js';
+import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
+
+type App = appParentalControlsHandlerMojom.App;
+type AppParentalControlsHandlerInterface =
+    appParentalControlsHandlerMojom.AppParentalControlsHandlerInterface;
+
+export class FakeAppParentalControlsHandler extends TestBrowserProxy implements
+    AppParentalControlsHandlerInterface {
+  private apps_: App[];
+
+  constructor() {
+    super([
+      'getApps',
+    ]);
+
+    this.apps_ = [];
+  }
+
+  getApps(): Promise<{apps: App[]}> {
+    this.methodCalled('getApps');
+    return Promise.resolve({apps: this.apps_});
+  }
+
+  addAppForTesting(app: App) {
+    this.apps_.push(app);
+  }
+}
diff --git a/chrome/test/data/webui/chromeos/settings/os_settings_browsertest.cc b/chrome/test/data/webui/chromeos/settings/os_settings_browsertest.cc
index 0eee9a1..9ff6bd0 100644
--- a/chrome/test/data/webui/chromeos/settings/os_settings_browsertest.cc
+++ b/chrome/test/data/webui/chromeos/settings/os_settings_browsertest.cc
@@ -1560,6 +1560,14 @@
 }
 
 IN_PROC_BROWSER_TEST_P(
+    OSSettingsRevampMochaTestAppParentalControlsEnabled,
+    OsAppsPageAppParentalControlsPageAppParentalControlsSubpage) {
+  RunSettingsTest(
+      "os_apps_page/app_parental_controls_page/"
+      "app_parental_controls_subpage_test.js");
+}
+
+IN_PROC_BROWSER_TEST_P(
     OSSettingsRevampMochaTest,
     OsAppsPageManageIsolatedWebAppsPageManageIsolatedWebAppsSubpage) {
   RunSettingsTest(
diff --git a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/button_bar_test.ts b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/button_bar_test.ts
index 9b8f722..648b49f 100644
--- a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/button_bar_test.ts
+++ b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/button_bar_test.ts
@@ -26,7 +26,6 @@
 
   function setStateForAllButtons(state: ButtonState): void {
     buttonBar.buttonState = {
-      backward: state,
       cancel: state,
       forward: state,
     };
@@ -48,16 +47,12 @@
   test('individual buttons appear if enabled', function() {
     setStateForAllButtons(ButtonState.ENABLED);
 
-    const backward =
-        buttonBar.shadowRoot!.querySelector<CrButtonElement>('#backward');
     const cancel =
         buttonBar.shadowRoot!.querySelector<CrButtonElement>('#cancel');
     const forward =
         buttonBar.shadowRoot!.querySelector<CrButtonElement>('#forward');
-    assertTrue(!!backward);
     assertTrue(!!cancel);
     assertTrue(!!forward);
-    assertTrue(isButtonShownAndEnabled(backward));
     assertTrue(isButtonShownAndEnabled(cancel));
     assertTrue(isButtonShownAndEnabled(forward));
   });
@@ -65,32 +60,24 @@
   test('individual buttons appear but are disabled', function() {
     setStateForAllButtons(ButtonState.DISABLED);
 
-    const backward =
-        buttonBar.shadowRoot!.querySelector<CrButtonElement>('#backward');
     const cancel =
         buttonBar.shadowRoot!.querySelector<CrButtonElement>('#cancel');
     const forward =
         buttonBar.shadowRoot!.querySelector<CrButtonElement>('#forward');
-    assertTrue(!!backward);
     assertTrue(!!cancel);
     assertTrue(!!forward);
-    assertTrue(isButtonShownAndDisabled(backward));
     assertTrue(isButtonShownAndDisabled(cancel));
     assertTrue(isButtonShownAndDisabled(forward));
   });
 
   test('individual buttons are hidden', function() {
     setStateForAllButtons(ButtonState.HIDDEN);
-    const backward =
-        buttonBar.shadowRoot!.querySelector<CrButtonElement>('#backward');
     const cancel =
         buttonBar.shadowRoot!.querySelector<CrButtonElement>('#cancel');
     const forward =
         buttonBar.shadowRoot!.querySelector<CrButtonElement>('#forward');
-    assertTrue(!!backward);
     assertTrue(!!cancel);
     assertTrue(!!forward);
-    assertTrue(isButtonHidden(backward));
     assertTrue(isButtonHidden(cancel));
     assertTrue(isButtonHidden(forward));
   });
@@ -108,8 +95,7 @@
 
   test('default focus is on first button if rest are hidden', function() {
     buttonBar.buttonState = {
-      backward: ButtonState.ENABLED,
-      cancel: ButtonState.HIDDEN,
+      cancel: ButtonState.ENABLED,
       forward: ButtonState.HIDDEN,
     };
     buttonBar.focusDefaultButton();
@@ -118,15 +104,14 @@
 
     assertEquals(
         buttonBar.shadowRoot!.activeElement,
-        buttonBar.shadowRoot!.querySelector('#backward'));
+        buttonBar.shadowRoot!.querySelector('#cancel'));
   });
 
   test(
       'default focus is on first button if rest are visible but disabled',
       function() {
         buttonBar.buttonState = {
-          backward: ButtonState.ENABLED,
-          cancel: ButtonState.DISABLED,
+          cancel: ButtonState.ENABLED,
           forward: ButtonState.DISABLED,
         };
         buttonBar.focusDefaultButton();
@@ -135,6 +120,6 @@
 
         assertEquals(
             buttonBar.shadowRoot!.activeElement,
-            buttonBar.shadowRoot!.querySelector('#backward'));
+            buttonBar.shadowRoot!.querySelector('#cancel'));
       });
 });
diff --git a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/esim_flow_ui_test.ts b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/esim_flow_ui_test.ts
index d41f7b18..b19ad71c 100644
--- a/chrome/test/data/webui/cr_components/chromeos/cellular_setup/esim_flow_ui_test.ts
+++ b/chrome/test/data/webui/cr_components/chromeos/cellular_setup/esim_flow_ui_test.ts
@@ -166,12 +166,10 @@
    */
   async function navigateForwardForInstall(page: HTMLElement) {
     assertEquals(eSimPage.buttonState.forward, ButtonState.ENABLED);
-    assertEquals(eSimPage.buttonState.backward, ButtonState.HIDDEN);
 
     eSimPage.navigateForward();
 
     assertEquals(eSimPage.buttonState.forward, ButtonState.DISABLED);
-    assertEquals(eSimPage.buttonState.backward, ButtonState.HIDDEN);
     assertEquals(eSimPage.buttonState.cancel, ButtonState.DISABLED);
 
     if (page !== profileLoadingPage && page !== profileDiscoveryConsentPage &&
@@ -203,7 +201,6 @@
     assertEquals(
         !!finalPage.shadowRoot!.querySelector('.error'), shouldBeShowingError);
     assertEquals(ButtonState.ENABLED, eSimPage.buttonState.forward);
-    assertEquals(ButtonState.HIDDEN, eSimPage.buttonState.backward);
     assertEquals(ButtonState.HIDDEN, eSimPage.buttonState.cancel);
     assertEquals(eSimPage.forwardButtonLabel, 'Done');
     let exitCellularSetupEventFired = false;
@@ -218,7 +215,6 @@
 
   function assertButtonState(forwardButtonShouldBeEnabled: boolean) {
     const buttonState = eSimPage.buttonState;
-    assertEquals(buttonState.backward, ButtonState.HIDDEN);
     assertEquals(buttonState.cancel, ButtonState.ENABLED);
     assertEquals(
         buttonState.forward,
diff --git a/chrome/test/data/webui/cr_components/history_embeddings/filter_chips_test.ts b/chrome/test/data/webui/cr_components/history_embeddings/filter_chips_test.ts
index f61b05b..9b1eb70 100644
--- a/chrome/test/data/webui/cr_components/history_embeddings/filter_chips_test.ts
+++ b/chrome/test/data/webui/cr_components/history_embeddings/filter_chips_test.ts
@@ -28,29 +28,29 @@
     return flushTasks();
   });
 
-  test('UpdatesByGroupChipByBinding', () => {
-    assertFalse(element.$.byGroupChip.hasAttribute('selected'));
-    assertEquals('history-embeddings:by-group', element.$.byGroupChipIcon.icon);
+  test('UpdatesShowByMenuByBinding', () => {
+    assertEquals('false', element.$.showByGroupSelectMenu.value);
     element.showResultsByGroup = true;
-    assertTrue(element.$.byGroupChip.hasAttribute('selected'));
-    assertEquals('cr:check', element.$.byGroupChipIcon.icon);
+    assertEquals('true', element.$.showByGroupSelectMenu.value);
   });
 
-  test('UpdatesByGroupChipByClicking', async () => {
+  test('UpdatesShowByGroupSelectMenu', async () => {
     let notifyEventPromise =
         eventToPromise('show-results-by-group-changed', element);
-    element.$.byGroupChip.click();
+    element.$.showByGroupSelectMenu.value = 'true';
+    element.$.showByGroupSelectMenu.dispatchEvent(new Event('change'));
     let notifyEvent = await notifyEventPromise;
     assertTrue(element.showResultsByGroup);
-    assertTrue(element.$.byGroupChip.hasAttribute('selected'));
+    assertEquals('true', element.$.showByGroupSelectMenu.value);
     assertTrue(notifyEvent.detail.value);
 
     notifyEventPromise =
         eventToPromise('show-results-by-group-changed', element);
-    element.$.byGroupChip.click();
+    element.$.showByGroupSelectMenu.value = 'false';
+    element.$.showByGroupSelectMenu.dispatchEvent(new Event('change'));
     notifyEvent = await notifyEventPromise;
     assertFalse(element.showResultsByGroup);
-    assertFalse(element.$.byGroupChip.hasAttribute('selected'));
+    assertEquals('false', element.$.showByGroupSelectMenu.value);
     assertFalse(notifyEvent.detail.value);
   });
 
diff --git a/chrome/test/data/webui/cr_components/managed_footnote_test.ts b/chrome/test/data/webui/cr_components/managed_footnote_test.ts
index bb300a5..9636c94 100644
--- a/chrome/test/data/webui/cr_components/managed_footnote_test.ts
+++ b/chrome/test/data/webui/cr_components/managed_footnote_test.ts
@@ -60,7 +60,7 @@
 
     assertNotEquals('none', getComputedStyle(footnote).display);
     assertEquals(
-        footnote.shadowRoot!.querySelector('iron-icon')!.icon,
+        footnote.shadowRoot!.querySelector('cr-icon')!.icon,
         'cr:jumping_fox');
     assertTrue(footnote.shadowRoot!.textContent!.includes(browserMessage));
   });
diff --git a/chrome/test/data/webui/side_panel/read_anything/BUILD.gn b/chrome/test/data/webui/side_panel/read_anything/BUILD.gn
index 2f12f8b7..735972b 100644
--- a/chrome/test/data/webui/side_panel/read_anything/BUILD.gn
+++ b/chrome/test/data/webui/side_panel/read_anything/BUILD.gn
@@ -42,6 +42,7 @@
     "update_content_selection.ts",
     "speech_uses_max_text_length.ts",
     "word_boundaries_used_for_speech.ts",
+    "update_voice_pack_test.ts",
   ]
 
   ts_definitions = [
diff --git a/chrome/test/data/webui/side_panel/read_anything/fake_reading_mode.ts b/chrome/test/data/webui/side_panel/read_anything/fake_reading_mode.ts
index ded878c..f464b89 100644
--- a/chrome/test/data/webui/side_panel/read_anything/fake_reading_mode.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/fake_reading_mode.ts
@@ -306,6 +306,12 @@
   // toolbar and are ready to consume.
   updateTheme() {}
 
+  // Called with the response of sendGetVoicePackInfoRequest()
+  updateVoicePackStatus(_lang: string, _status: string) {}
+
+  // Called with the response of sendInstallVoicePackRequest()
+  updateVoicePackStatusFromInstallResponse() {}
+
   // Ping that the theme choices of the user have been retrieved from
   // preferences and can be used to set up the page.
   restoreSettingsFromPrefs() {}
diff --git a/chrome/test/data/webui/side_panel/read_anything/language_change_test.ts b/chrome/test/data/webui/side_panel/read_anything/language_change_test.ts
index e1f75f93..b72a18b 100644
--- a/chrome/test/data/webui/side_panel/read_anything/language_change_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/language_change_test.ts
@@ -15,6 +15,7 @@
 
 import {suppressInnocuousErrors} from './common.js';
 import {FakeReadingMode} from './fake_reading_mode.js';
+import {FakeSpeechSynthesis} from './fake_speech_synthesis.js';
 import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';
 
 suite('LanguageChanged', () => {
@@ -133,22 +134,86 @@
     });
   });
 
-  test('updates voice pack status to none if unsupported', () => {
-    chrome.readingMode.baseLanguageForSpeech = 'zh';
-    let sentRequest = false;
-    chrome.readingMode.sendGetVoicePackInfoRequest = () => {
-      sentRequest = true;
-    };
+  suite('tries to install voice pack', () => {
+    let sentRequest: boolean;
 
-    app.languageChanged();
+    function setInstallStatus(lang: string, status: VoicePackStatus) {
+      // @ts-ignore
+      app.setVoicePackStatus_(lang, status);
+    }
 
-    // Use this check to ensure this stays updated if the supported languages
-    // changes.
-    assertFalse(PACK_MANAGER_SUPPORTED_LANGS_AND_LOCALES.has(
-        chrome.readingMode.baseLanguageForSpeech));
-    assertFalse(sentRequest);
-    assertEquals(
-        voicePackInstallStatus()[chrome.readingMode.baseLanguageForSpeech],
-        VoicePackStatus.NONE);
+    setup(() => {
+      sentRequest = false;
+      chrome.readingMode.sendGetVoicePackInfoRequest = () => {
+        sentRequest = true;
+      };
+    });
+
+    test('but doesn\'t if the language is unsupported', () => {
+      chrome.readingMode.baseLanguageForSpeech = 'zh';
+
+      app.languageChanged();
+
+      // Use this check to ensure this stays updated if the supported languages
+      // changes.
+      assertFalse(PACK_MANAGER_SUPPORTED_LANGS_AND_LOCALES.has(
+          chrome.readingMode.baseLanguageForSpeech));
+      assertFalse(sentRequest);
+      assertEquals(
+          voicePackInstallStatus()[chrome.readingMode.baseLanguageForSpeech],
+          VoicePackStatus.NONE);
+    });
+
+    test('but doesn\'t if the pack was removed by the user', () => {
+      const lang = 'ko';
+      chrome.readingMode.baseLanguageForSpeech = lang;
+      setInstallStatus(lang, VoicePackStatus.REMOVED_BY_USER);
+
+      app.languageChanged();
+
+      assertFalse(sentRequest);
+      assertEquals(
+          voicePackInstallStatus()[lang], VoicePackStatus.REMOVED_BY_USER);
+    });
+
+    test('and refreshes voice list if already downloaded', () => {
+      const lang = 'it';
+      chrome.readingMode.baseLanguageForSpeech = lang;
+      app.synth = new FakeSpeechSynthesis();
+      const voices = app.synth.getVoices();
+      app.synth.getVoices = () => {
+        return voices.concat(
+            {lang: lang, name: 'Wall-e (Natural)'} as SpeechSynthesisVoice,
+            {lang: lang, name: 'Andy (Natural)'} as SpeechSynthesisVoice,
+        );
+      };
+      setInstallStatus(lang, VoicePackStatus.DOWNLOADED);
+
+      app.languageChanged();
+
+      assertFalse(sentRequest);
+      assertEquals(voicePackInstallStatus()[lang], VoicePackStatus.INSTALLED);
+    });
+
+    test('and gets voice pack info if no status yet', () => {
+      const lang = 'bn';
+      chrome.readingMode.baseLanguageForSpeech = lang;
+
+      app.languageChanged();
+
+      assertTrue(sentRequest);
+      assertEquals(voicePackInstallStatus()[lang], VoicePackStatus.EXISTS);
+    });
+
+    test('and gets voice pack info if we know it exists', () => {
+      const lang = 'de';
+      chrome.readingMode.baseLanguageForSpeech = lang;
+
+      app.languageChanged();
+
+      assertTrue(sentRequest);
+      assertEquals(voicePackInstallStatus()[lang], VoicePackStatus.EXISTS);
+    });
   });
+
 });
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc b/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc
index 7039ce9..d906f5a1 100644
--- a/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc
+++ b/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc
@@ -148,6 +148,11 @@
   RunSidePanelTest("side_panel/read_anything/speech_test.js", "mocha.run()");
 }
 
+IN_PROC_BROWSER_TEST_F(ReadAnythingMochaTest, UpdateVoicePack) {
+  RunSidePanelTest("side_panel/read_anything/update_voice_pack_test.js",
+                   "mocha.run()");
+}
+
 // Integration tests that need the actual Read Aloud flag enabled because they
 // use the full C++ pipeline
 class ReadAnythingReadAloudMochaTest : public ReadAnythingMochaBrowserTest {
diff --git a/chrome/test/data/webui/side_panel/read_anything/update_voice_pack_test.ts b/chrome/test/data/webui/side_panel/read_anything/update_voice_pack_test.ts
new file mode 100644
index 0000000..3683b81
--- /dev/null
+++ b/chrome/test/data/webui/side_panel/read_anything/update_voice_pack_test.ts
@@ -0,0 +1,119 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+import 'chrome-untrusted://read-anything-side-panel.top-chrome/voice_language_util.js';
+
+import {BrowserProxy} from '//resources/cr_components/color_change_listener/browser_proxy.js';
+import type {ReadAnythingElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/app.js';
+import {VoicePackStatus} from 'chrome-untrusted://read-anything-side-panel.top-chrome/voice_language_util.js';
+import {assertEquals} from 'chrome-untrusted://webui-test/chai_assert.js';
+
+import {FakeReadingMode} from './fake_reading_mode.js';
+import {FakeSpeechSynthesis} from './fake_speech_synthesis.js';
+import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';
+
+suite('UpdateVoicePack', () => {
+  let app: ReadAnythingElement;
+
+  function setInstallStatus(lang: string, status: VoicePackStatus) {
+    // @ts-ignore
+    app.setVoicePackStatus_(lang, status);
+  }
+
+  function getInstallStatus(lang: string) {
+    // @ts-ignore
+    return app.voicePackInstallStatus[lang];
+  }
+
+  setup(() => {
+    BrowserProxy.setInstance(new TestColorUpdaterBrowserProxy());
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    const readingMode = new FakeReadingMode();
+    chrome.readingMode = readingMode as unknown as typeof chrome.readingMode;
+    app = document.createElement('read-anything-app');
+    document.body.appendChild(app);
+  });
+
+  suite('updateVoicePackStatus', () => {
+    let sentInstallRequestFor: string = '';
+
+    suite('voice pack not installed', () => {
+      setup(() => {
+        chrome.readingMode.sendInstallVoicePackRequest = (lang) => {
+          sentInstallRequestFor = lang;
+        };
+      });
+
+      test('mark as removed if we think it\'s installed', () => {
+        const lang = 'en';
+        setInstallStatus(lang, VoicePackStatus.DOWNLOADED);
+
+        app.updateVoicePackStatus(lang, 'kNotInstalled');
+
+        assertEquals(getInstallStatus(lang), VoicePackStatus.REMOVED_BY_USER);
+        assertEquals(sentInstallRequestFor.length, 0);
+      });
+
+      test('request install if we need to', () => {
+        const lang = 'it';
+        chrome.readingMode.baseLanguageForSpeech = lang;
+        app.$.toolbar.updateFonts = () => {};
+        app.languageChanged();
+
+        app.updateVoicePackStatus(lang, 'kNotInstalled');
+
+        assertEquals(getInstallStatus(lang), VoicePackStatus.INSTALLING);
+        assertEquals(
+            sentInstallRequestFor, chrome.readingMode.baseLanguageForSpeech);
+      });
+    });
+  });
+
+  suite('voice pack status is', () => {
+    function addNaturalVoicesForLang(lang: string) {
+      app.synth = new FakeSpeechSynthesis();
+      const voices = app.synth.getVoices();
+      app.synth.getVoices = () => {
+        return voices.concat(
+            {lang: lang, name: 'Wall-e (Natural)'} as SpeechSynthesisVoice,
+            {lang: lang, name: 'Andy (Natural)'} as SpeechSynthesisVoice,
+        );
+      };
+    }
+
+    test('downloaded if voices not updated yet', () => {
+      const lang = 'fr';
+
+      app.updateVoicePackStatus(lang, 'kInstalled');
+
+      assertEquals(getInstallStatus(lang), VoicePackStatus.DOWNLOADED);
+    });
+
+    test('downloaded if non-natural voices added for this lang', () => {
+      const lang = 'en';
+      app.synth = new FakeSpeechSynthesis();
+
+      app.updateVoicePackStatus(lang, 'kInstalled');
+
+      assertEquals(getInstallStatus(lang), VoicePackStatus.DOWNLOADED);
+    });
+
+    test('downloaded if natural voices are added for a different lang', () => {
+      const lang = 'fr';
+      addNaturalVoicesForLang('it');
+
+      app.updateVoicePackStatus(lang, 'kInstalled');
+
+      assertEquals(getInstallStatus(lang), VoicePackStatus.DOWNLOADED);
+    });
+
+    test('installed if natural voices are added for this lang', () => {
+      const lang = 'en';
+      addNaturalVoicesForLang(lang);
+
+      app.updateVoicePackStatus(lang, 'kInstalled');
+
+      assertEquals(getInstallStatus(lang), VoicePackStatus.INSTALLED);
+    });
+  });
+});
diff --git a/chrome/test/data/webui/side_panel/read_anything/voice_language_util_test.ts b/chrome/test/data/webui/side_panel/read_anything/voice_language_util_test.ts
index c22cfa9..e674bc05 100644
--- a/chrome/test/data/webui/side_panel/read_anything/voice_language_util_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/voice_language_util_test.ts
@@ -14,7 +14,7 @@
         VoicePackStatus.EXISTS);
     assertEquals(
         (mojoVoicePackStatusToVoicePackStatusEnum('kInstalled')),
-        VoicePackStatus.INSTALLED);
+        VoicePackStatus.DOWNLOADED);
     assertEquals(
         (mojoVoicePackStatusToVoicePackStatusEnum('kInstalling')),
         VoicePackStatus.INSTALLING);
diff --git a/chrome/test/data/webui/side_panel/read_anything/voice_selection_menu_test.ts b/chrome/test/data/webui/side_panel/read_anything/voice_selection_menu_test.ts
index 2901da4d..f829624 100644
--- a/chrome/test/data/webui/side_panel/read_anything/voice_selection_menu_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/voice_selection_menu_test.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome-untrusted://read-anything-side-panel.top-chrome/voice_selection_menu.js';
+import 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything_toolbar.js';
 
 import type {CrIconButtonElement} from '//resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import {flush} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chromeos/ash/components/auth_panel/impl/views/login_textfield.cc b/chromeos/ash/components/auth_panel/impl/views/login_textfield.cc
index 31659ca..1d3894ad 100644
--- a/chromeos/ash/components/auth_panel/impl/views/login_textfield.cc
+++ b/chromeos/ash/components/auth_panel/impl/views/login_textfield.cc
@@ -54,7 +54,8 @@
       std::nullopt});
 }
 
-gfx::Size LoginTextfield::CalculatePreferredSize() const {
+gfx::Size LoginTextfield::CalculatePreferredSize(
+    const views::SizeBounds& available_size) const {
   return gfx::Size(kPasswordTotalWidthDp, kIconSizeDp);
 }
 
diff --git a/chromeos/ash/components/auth_panel/impl/views/login_textfield.h b/chromeos/ash/components/auth_panel/impl/views/login_textfield.h
index 634df848..cd9c904 100644
--- a/chromeos/ash/components/auth_panel/impl/views/login_textfield.h
+++ b/chromeos/ash/components/auth_panel/impl/views/login_textfield.h
@@ -33,7 +33,8 @@
   void OnFocus() override;
   // This is useful when the display password button is not shown. In such a
   // case, the login text field needs to define its size.
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const views::SizeBounds& available_size) const override;
 
   void OnStateChanged(
       const AuthFactorStore::State::PasswordViewState& password_view_state);
diff --git a/chromeos/ash/components/growth/campaigns_model.cc b/chromeos/ash/components/growth/campaigns_model.cc
index 823b9fa..4c56169 100644
--- a/chromeos/ash/components/growth/campaigns_model.cc
+++ b/chromeos/ash/components/growth/campaigns_model.cc
@@ -105,6 +105,24 @@
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 inline constexpr gfx::Size kBubbleIconSizeDip = gfx::Size(kIconSize, kIconSize);
+
+std::optional<int> GetBuiltInImageResourceId(
+    const std::optional<BuiltInIcon>& icon) {
+  if (!icon) {
+    return std::nullopt;
+  }
+
+  if (icon == BuiltInIcon::kContainerApp) {
+    return IDR_GROWTH_FRAMEWORK_CONTAINER_APP_PNG;
+  }
+
+  if (icon == BuiltInIcon::kG1) {
+    return IDR_GROWTH_FRAMEWORK_G1_PNG;
+  }
+
+  return std::nullopt;
+}
+
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
 std::optional<BuiltInIcon> GetBuiltInIconType(
@@ -497,10 +515,11 @@
 
   const auto icon = GetBuiltInIconType(image_dict_);
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  if (icon == BuiltInIcon::kContainerApp) {
+  const auto resource_id = GetBuiltInImageResourceId(icon);
+  if (resource_id) {
     gfx::ImageSkia* image =
         ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
-            IDR_GROWTH_FRAMEWORK_CONTAINER_APP_PNG);
+            resource_id.value());
     gfx::ImageSkia resized_image = gfx::ImageSkiaOperations::CreateResizedImage(
         *image, skia::ImageOperations::RESIZE_BEST, kBubbleIconSizeDip);
     resized_image.EnsureRepsForSupportedScales();
diff --git a/chromeos/ash/components/growth/campaigns_model.h b/chromeos/ash/components/growth/campaigns_model.h
index 27151bb..4a1c7273 100644
--- a/chromeos/ash/components/growth/campaigns_model.h
+++ b/chromeos/ash/components/growth/campaigns_model.h
@@ -40,7 +40,7 @@
 
 // These values are deserialized from Growth Campaign, so entries should not
 // be renumbered and numeric values should never be reused.
-enum class BuiltInIcon { kRedeem, kContainerApp };
+enum class BuiltInIcon { kRedeem, kContainerApp, kG1 };
 
 // Supported window anchor element.
 // These values are deserialized from Growth Campaign, so entries should not
diff --git a/chromeos/ash/components/growth/resources/logo_one_color_2x_web_96dp.png b/chromeos/ash/components/growth/resources/logo_one_color_2x_web_96dp.png
new file mode 100644
index 0000000..84cafd6
--- /dev/null
+++ b/chromeos/ash/components/growth/resources/logo_one_color_2x_web_96dp.png
Binary files differ
diff --git a/chromeos/ash/resources/growth_framework_resources.grdp b/chromeos/ash/resources/growth_framework_resources.grdp
index 41862c92..bf76cb8 100644
--- a/chromeos/ash/resources/growth_framework_resources.grdp
+++ b/chromeos/ash/resources/growth_framework_resources.grdp
@@ -2,4 +2,5 @@
 <grit-part>
   <!-- Resources for Growth Framework. -->
   <include name="IDR_GROWTH_FRAMEWORK_CONTAINER_APP_PNG" file="../resources/internal/container_app.png" type="BINDATA" />
+  <include name="IDR_GROWTH_FRAMEWORK_G1_PNG" file="../components/growth/resources/logo_one_color_2x_web_96dp.png" type="BINDATA" />
 </grit-part>
\ No newline at end of file
diff --git a/clank b/clank
index 0fb8a71..5adefb93 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 0fb8a71ee651916fff95a11702236ff8d559206f
+Subproject commit 5adefb939901b88ee5b674e74bc2223694aa45b1
diff --git a/components/BUILD.gn b/components/BUILD.gn
index a1be8620..8ad5a2c 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -983,6 +983,7 @@
       "//components/dom_distiller/core:test_support",
       "//components/error_page/content/browser:browser_tests",
       "//components/facilitated_payments/content/renderer:browser_tests",
+      "//components/js_injection/browser:browser_tests",
       "//components/metrics:content",
       "//components/optimization_guide/content/renderer",
       "//components/paint_preview/renderer",
diff --git a/components/autofill/core/browser/autofill_compose_delegate.h b/components/autofill/core/browser/autofill_compose_delegate.h
index cc5b38f..141269c 100644
--- a/components/autofill/core/browser/autofill_compose_delegate.h
+++ b/components/autofill/core/browser/autofill_compose_delegate.h
@@ -5,6 +5,9 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_COMPOSE_DELEGATE_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_COMPOSE_DELEGATE_H_
 
+#include <optional>
+
+#include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/autofill/core/common/aliases.h"
 #include "components/autofill/core/common/unique_ids.h"
 
@@ -31,15 +34,6 @@
     kAutofillPopup,
     kContextMenu,
   };
-  // Returns whether the compose popup is available for this `trigger_field`.
-  virtual bool ShouldOfferComposePopup(
-      const FormFieldData& trigger_field,
-      AutofillSuggestionTriggerSource trigger_source) = 0;
-
-  // Returns whether the `trigger_field_id` has an existing state saved for
-  // `trigger_field_id`. Saved state allows the user to return to a field and
-  // resume where they left off.
-  virtual bool HasSavedState(const FieldGlobalId& trigger_field_id) = 0;
 
   // Opens the Compose UI from the `ui_entry_point` given the 'driver',
   // 'form_id', and 'field_id'.
@@ -47,6 +41,12 @@
                            FormGlobalId form_id,
                            FieldGlobalId field_id,
                            UiEntryPoint ui_entry_point) = 0;
+
+  // Returns a suggestion if the compose service is available for
+  // `field`.
+  virtual std::optional<autofill::Suggestion> GetSuggestion(
+      const autofill::FormFieldData& field,
+      autofill::AutofillSuggestionTriggerSource trigger_source) = 0;
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc
index a09c720a..e7bed21 100644
--- a/components/autofill/core/browser/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -1169,8 +1169,12 @@
   if (should_offer_other_suggestions &&
       (field.form_control_type() == FormControlType::kTextArea ||
        field.form_control_type() == FormControlType::kContentEditable)) {
-    if (std::optional<Suggestion> maybe_compose_suggestion =
-            MaybeGetComposeSuggestion(field, trigger_source)) {
+    AutofillComposeDelegate* compose_delegate = client().GetComposeDelegate();
+    std::optional<Suggestion> maybe_compose_suggestion =
+        compose_delegate
+            ? compose_delegate->GetSuggestion(field, trigger_source)
+            : std::nullopt;
+    if (maybe_compose_suggestion) {
       suggestions.push_back(*std::move(maybe_compose_suggestion));
     }
   }
@@ -2809,40 +2813,6 @@
   return true;
 }
 
-std::optional<Suggestion> BrowserAutofillManager::MaybeGetComposeSuggestion(
-    const FormFieldData& field,
-    AutofillSuggestionTriggerSource trigger_source) {
-  AutofillComposeDelegate* compose_delegate = client().GetComposeDelegate();
-  if (!compose_delegate ||
-      !compose_delegate->ShouldOfferComposePopup(field, trigger_source)) {
-    return std::nullopt;
-  }
-  std::u16string suggestion_text;
-  std::u16string label_text;
-  PopupItemId popup_item_id = PopupItemId::kCompose;
-  if (compose_delegate->HasSavedState(field.global_id())) {
-    // The nudge text indicates that the user can resume where they left off in
-    // the Compose dialog.
-    suggestion_text =
-        l10n_util::GetStringUTF16(IDS_COMPOSE_SUGGESTION_SAVED_TEXT);
-    label_text = l10n_util::GetStringUTF16(IDS_COMPOSE_SUGGESTION_SAVED_LABEL);
-    if (trigger_source ==
-        AutofillSuggestionTriggerSource::kComposeDialogLostFocus) {
-      popup_item_id = PopupItemId::kComposeSavedStateNotification;
-    }
-  } else {
-    // Text for a new Compose session.
-    suggestion_text =
-        l10n_util::GetStringUTF16(IDS_COMPOSE_SUGGESTION_MAIN_TEXT);
-    label_text = l10n_util::GetStringUTF16(IDS_COMPOSE_SUGGESTION_LABEL);
-  }
-  Suggestion suggestion(std::move(suggestion_text));
-  suggestion.labels = {{Suggestion::Text(std::move(label_text))}};
-  suggestion.popup_item_id = popup_item_id;
-  suggestion.icon = Suggestion::Icon::kPenSpark;
-  return suggestion;
-}
-
 void BrowserAutofillManager::LogEventCountsUMAMetric(
     const FormStructure& form_structure) {
   size_t num_ask_for_values_to_fill_event = 0;
diff --git a/components/autofill/core/browser/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
index 6679841..f2b098a 100644
--- a/components/autofill/core/browser/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
@@ -6982,7 +6982,7 @@
   // The third field is meant to correspond to address line 1. For that (unlike
   // for first and last name), parsing also derives that type if it is a
   // textarea.
-  EXPECT_CALL(compose_delegate, ShouldOfferComposePopup).Times(0);
+  EXPECT_CALL(compose_delegate, GetSuggestion).Times(0);
   GetAutofillSuggestions(form, form.fields[3]);
   external_delegate()->CheckSuggestions(
       form.fields[3].global_id(),
@@ -7009,12 +7009,12 @@
 
   EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
       .Times(0);
-  EXPECT_CALL(compose_delegate, ShouldOfferComposePopup(
-                                    Property(&FormFieldData::global_id,
-                                             Eq(form.fields[3].global_id())),
-                                    autofill::AutofillSuggestionTriggerSource::
-                                        kTextareaFocusedWithoutClick))
-      .WillOnce(Return(true));
+  EXPECT_CALL(compose_delegate,
+              GetSuggestion(Property(&FormFieldData::global_id,
+                                     Eq(form.fields[3].global_id())),
+                            autofill::AutofillSuggestionTriggerSource::
+                                kTextareaFocusedWithoutClick))
+      .WillOnce(Return(Suggestion(u"Help me write", PopupItemId::kCompose)));
   GetAutofillSuggestions(
       form, form.fields[3],
       AutofillSuggestionTriggerSource::kTextareaFocusedWithoutClick);
@@ -7040,12 +7040,10 @@
       .Times(0);
   EXPECT_CALL(
       compose_delegate,
-      ShouldOfferComposePopup(
+      GetSuggestion(
           Property(&FormFieldData::global_id, Eq(form.fields[0].global_id())),
           autofill::AutofillSuggestionTriggerSource::kTextFieldDidChange))
-      .WillOnce(Return(true));
-  EXPECT_CALL(compose_delegate, HasSavedState(form.fields[0].global_id()))
-      .WillOnce(Return(true));
+      .WillOnce(Return(Suggestion(u"Help me write", PopupItemId::kCompose)));
   GetAutofillSuggestions(form, form.fields[0]);
   external_delegate()->CheckSuggestionCount(form.fields[0].global_id(), 1);
 }
diff --git a/components/autofill/core/browser/mock_autofill_compose_delegate.h b/components/autofill/core/browser/mock_autofill_compose_delegate.h
index dd5712e0..766ff57 100644
--- a/components/autofill/core/browser/mock_autofill_compose_delegate.h
+++ b/components/autofill/core/browser/mock_autofill_compose_delegate.h
@@ -5,7 +5,10 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_MOCK_AUTOFILL_COMPOSE_DELEGATE_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_MOCK_AUTOFILL_COMPOSE_DELEGATE_H_
 
+#include <optional>
+
 #include "components/autofill/core/browser/autofill_compose_delegate.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace autofill {
@@ -15,16 +18,15 @@
   MockAutofillComposeDelegate();
   ~MockAutofillComposeDelegate() override;
 
-  MOCK_METHOD(bool,
-              ShouldOfferComposePopup,
-              (const FormFieldData&, AutofillSuggestionTriggerSource),
-              (override));
   MOCK_METHOD(
       void,
       OpenCompose,
       (autofill::AutofillDriver&, FormGlobalId, FieldGlobalId, UiEntryPoint),
       (override));
-  MOCK_METHOD(bool, HasSavedState, (const FieldGlobalId&), (override));
+  MOCK_METHOD(std::optional<autofill::Suggestion>,
+              GetSuggestion,
+              (const FormFieldData&, AutofillSuggestionTriggerSource),
+              (override));
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/personal_data_manager.cc b/components/autofill/core/browser/personal_data_manager.cc
index b38835f..9b99a1a 100644
--- a/components/autofill/core/browser/personal_data_manager.cc
+++ b/components/autofill/core/browser/personal_data_manager.cc
@@ -155,22 +155,6 @@
   address_data_manager_->UpdateProfile(profile);
 }
 
-AutofillProfile* PersonalDataManager::GetProfileByGUID(
-    const std::string& guid) const {
-  return address_data_manager_->GetProfileByGUID(guid);
-}
-
-bool PersonalDataManager::IsCountryEligibleForAccountStorage(
-    std::string_view country_code) const {
-  return address_data_manager_->IsCountryEligibleForAccountStorage(
-      country_code);
-}
-
-void PersonalDataManager::MigrateProfileToAccount(
-    const AutofillProfile& profile) {
-  address_data_manager_->MigrateProfileToAccount(profile);
-}
-
 std::string PersonalDataManager::AddAsLocalIban(Iban iban) {
   return payments_data_manager_->AddAsLocalIban(std::move(iban));
 }
@@ -244,12 +228,6 @@
   return address_data_manager_->GetProfiles(order);
 }
 
-std::vector<AutofillProfile*> PersonalDataManager::GetProfilesFromSource(
-    AutofillProfile::Source profile_source,
-    ProfileOrder order) const {
-  return address_data_manager_->GetProfilesFromSource(profile_source, order);
-}
-
 std::vector<CreditCard*> PersonalDataManager::GetLocalCreditCards() const {
   return payments_data_manager_->GetLocalCreditCards();
 }
@@ -309,16 +287,6 @@
   payments_data_manager_->Refresh();
 }
 
-std::vector<AutofillProfile*> PersonalDataManager::GetProfilesToSuggest()
-    const {
-  return address_data_manager_->GetProfilesToSuggest();
-}
-
-std::vector<AutofillProfile*> PersonalDataManager::GetProfilesForSettings()
-    const {
-  return address_data_manager_->GetProfilesForSettings();
-}
-
 std::vector<CreditCard*> PersonalDataManager::GetCreditCardsToSuggest() const {
   return payments_data_manager_->GetCreditCardsToSuggest();
 }
diff --git a/components/autofill/core/browser/personal_data_manager.h b/components/autofill/core/browser/personal_data_manager.h
index 809fea8a..681a89f9 100644
--- a/components/autofill/core/browser/personal_data_manager.h
+++ b/components/autofill/core/browser/personal_data_manager.h
@@ -181,9 +181,6 @@
   bool IsPaymentsDownloadActive() const;
   void AddProfile(const AutofillProfile& profile);
   void UpdateProfile(const AutofillProfile& profile);
-  AutofillProfile* GetProfileByGUID(const std::string& guid) const;
-  bool IsCountryEligibleForAccountStorage(std::string_view country_code) const;
-  void MigrateProfileToAccount(const AutofillProfile& profile);
   std::string AddAsLocalIban(Iban iban);
   void AddCreditCard(const CreditCard& credit_card);
   void UpdateCreditCard(const CreditCard& credit_card);
@@ -197,9 +194,6 @@
   void AddCreditCardBenefitForTest(CreditCardBenefit benefit);
   std::vector<AutofillProfile*> GetProfiles(
       ProfileOrder order = ProfileOrder::kNone) const;
-  std::vector<AutofillProfile*> GetProfilesFromSource(
-      AutofillProfile::Source profile_source,
-      ProfileOrder order = ProfileOrder::kNone) const;
   std::vector<CreditCard*> GetLocalCreditCards() const;
   std::vector<CreditCard*> GetServerCreditCards() const;
   std::vector<CreditCard*> GetCreditCards() const;
@@ -210,8 +204,6 @@
   GetActiveAutofillPromoCodeOffersForOrigin(GURL origin) const;
   GURL GetCardArtURL(const CreditCard& credit_card) const;
   gfx::Image* GetCreditCardArtImageForUrl(const GURL& card_art_url) const;
-  std::vector<AutofillProfile*> GetProfilesToSuggest() const;
-  std::vector<AutofillProfile*> GetProfilesForSettings() const;
   std::vector<CreditCard*> GetCreditCardsToSuggest() const;
   std::vector<VirtualCardUsageData*> GetVirtualCardUsageData() const;
   bool HasPendingPaymentQueriesForTesting() const;
diff --git a/components/autofill/core/browser/ui/suggestion.h b/components/autofill/core/browser/ui/suggestion.h
index f86f37a3..9c629f4 100644
--- a/components/autofill/core/browser/ui/suggestion.h
+++ b/components/autofill/core/browser/ui/suggestion.h
@@ -116,6 +116,10 @@
     kCardVisa,
   };
 
+  // TODO(b/335194240): Consolidate expected param types for these constructors.
+  // Some expect UTF16 strings and others UTF8, while internally we only use
+  // UTF16. The ones expecting UTF8 are only used by tests and could be easily
+  // refactored.
   Suggestion();
   explicit Suggestion(std::u16string main_text);
   explicit Suggestion(PopupItemId popup_item_id);
diff --git a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
index a784c5f..beead409e 100644
--- a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
+++ b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
@@ -290,12 +290,14 @@
 
         if (mWebContentsObserver != null) mWebContentsObserver.destroy();
 
+        mMediaImageManager.setWebContents(mWebContents);
+
         if (webContents == null) {
             mWebContentsObserver = null;
             cleanupMediaSessionObserver();
-            mMediaImageManager.setWebContents(webContents);
             return;
         }
+
         mWebContentsObserver =
                 new WebContentsObserver(webContents) {
                     @Override
@@ -342,16 +344,24 @@
                     public void wasShown() {
                         mDelegate.activateAndroidMediaSession();
                     }
+
+                    @Override
+                    public void mediaSessionCreated(MediaSession mediaSession) {
+                        setUpMediaSessionObserver(mediaSession);
+                    }
                 };
 
         MediaSession mediaSession = getMediaSession(webContents);
+        setUpMediaSessionObserver(mediaSession);
+    }
+
+    private void setUpMediaSessionObserver(MediaSession mediaSession) {
         if (mMediaSessionObserver != null
                 && mediaSession == mMediaSessionObserver.getMediaSession()) {
             return;
         }
 
         cleanupMediaSessionObserver();
-        mMediaImageManager.setWebContents(webContents);
         if (mediaSession != null) {
             mMediaSessionObserver = createMediaSessionObserver(mediaSession);
         }
diff --git a/components/commerce/core/compare/candidate_product.h b/components/commerce/core/compare/candidate_product.h
index 819ae188..c11b16dc 100644
--- a/components/commerce/core/compare/candidate_product.h
+++ b/components/commerce/core/compare/candidate_product.h
@@ -28,9 +28,6 @@
   // potentially be clustered into one product group.
   std::set<GURL> similar_candidate_products_urls;
 
-  // Set of IDs of product groups that are similar to this product.
-  std::set<std::string> similar_product_group_ids;
-
   // Category information about the product.
   CategoryData category_data;
 };
diff --git a/components/commerce/core/compare/cluster_manager.cc b/components/commerce/core/compare/cluster_manager.cc
index 96fcb67..80c48443 100644
--- a/components/commerce/core/compare/cluster_manager.cc
+++ b/components/commerce/core/compare/cluster_manager.cc
@@ -156,6 +156,28 @@
   RemoveCandidateProductURLIfNotOpen(from_url);
 }
 
+std::optional<ProductGroup> ClusterManager::GetProductGroupForCandidateProduct(
+    const GURL& product_url) {
+  if (candidate_product_map_.find(product_url) ==
+      candidate_product_map_.end()) {
+    return std::nullopt;
+  }
+
+  CandidateProduct* candidate = candidate_product_map_[product_url].get();
+  for (const auto& product_group : product_group_map_) {
+    if (product_group.second->member_products.find(product_url) !=
+        product_group.second->member_products.end()) {
+      // The product is already in this group, ignore it.
+      continue;
+    }
+    if (IsProductSimilarToGroup(candidate->category_data,
+                                product_group.second->categories)) {
+      return *(product_group.second);
+    }
+  }
+  return std::nullopt;
+}
+
 void ClusterManager::OnProductInfoRetrieved(
     const GURL& url,
     const std::optional<const ProductInfo>& product_info) {
@@ -235,4 +257,13 @@
   return candidate_products;
 }
 
+std::set<GURL> ClusterManager::FindSimilarCandidateProducts(
+    const GURL& product_url) {
+  if (candidate_product_map_.find(product_url) ==
+      candidate_product_map_.end()) {
+    return std::set<GURL>();
+  }
+  return candidate_product_map_[product_url]->similar_candidate_products_urls;
+}
+
 }  // namespace commerce
diff --git a/components/commerce/core/compare/cluster_manager.h b/components/commerce/core/compare/cluster_manager.h
index a9b1d2dc7..09353fb 100644
--- a/components/commerce/core/compare/cluster_manager.h
+++ b/components/commerce/core/compare/cluster_manager.h
@@ -52,13 +52,25 @@
   void DidNavigatePrimaryMainFrame(const GURL& url);
   // A notification that the user navigated away from `from_url`.
   void DidNavigateAway(const GURL& from_url);
- private:
-  friend class ClusterManagerTest;
 
-  // Find similar candidate products for a product group.
+  // Gets a product group that the given product can be clustered into,
+  // or returns the product group this product already belongs to.
+  // TODO(qinmin): Check if we need to support the case that a candidate
+  // product can belong to multiple product groups.
+  std::optional<ProductGroup> GetProductGroupForCandidateProduct(
+      const GURL& product_url);
+
+  // Finds similar candidate products for a product group.
   std::vector<GURL> FindSimilarCandidateProductsForProductGroup(
       const base::Uuid& uuid);
 
+  // Finds similar candidate products for a candidate product. The returned
+  // URLs doesn't include the `product_url`.
+  std::set<GURL> FindSimilarCandidateProducts(const GURL& product_url);
+
+ private:
+  friend class ClusterManagerTest;
+
   // Called when information about a product is retrieved.
   void OnProductInfoRetrieved(
       const GURL& url,
diff --git a/components/commerce/core/compare/cluster_manager_unittest.cc b/components/commerce/core/compare/cluster_manager_unittest.cc
index cf296d83..a30a9060 100644
--- a/components/commerce/core/compare/cluster_manager_unittest.cc
+++ b/components/commerce/core/compare/cluster_manager_unittest.cc
@@ -152,16 +152,19 @@
   base::RunLoop().RunUntilIdle();
   ASSERT_EQ(3u, GetCandidateProductMap()->size());
 
-  CandidateProduct* product1 = (*GetCandidateProductMap())[foo1].get();
-  ASSERT_EQ(product1->similar_candidate_products_urls.size(), 1u);
-  ASSERT_EQ(product1->similar_candidate_products_urls.count(foo3), 1u);
+  std::set<GURL> similar_candidate_products =
+      cluster_manager_->FindSimilarCandidateProducts(foo1);
+  ASSERT_EQ(similar_candidate_products.size(), 1u);
+  ASSERT_EQ(similar_candidate_products.count(foo3), 1u);
 
-  CandidateProduct* product2 = (*GetCandidateProductMap())[foo2].get();
-  ASSERT_EQ(product2->similar_candidate_products_urls.size(), 0u);
+  similar_candidate_products =
+      cluster_manager_->FindSimilarCandidateProducts(foo2);
+  ASSERT_EQ(similar_candidate_products.size(), 0u);
 
-  CandidateProduct* product3 = (*GetCandidateProductMap())[foo3].get();
-  ASSERT_EQ(product3->similar_candidate_products_urls.size(), 1u);
-  ASSERT_EQ(product3->similar_candidate_products_urls.count(foo1), 1u);
+  similar_candidate_products =
+      cluster_manager_->FindSimilarCandidateProducts(foo3);
+  ASSERT_EQ(similar_candidate_products.size(), 1u);
+  ASSERT_EQ(similar_candidate_products.count(foo1), 1u);
 }
 
 TEST_F(ClusterManagerTest, CandidateProductWithMultipleLabelsClustered) {
@@ -184,18 +187,21 @@
   base::RunLoop().RunUntilIdle();
   ASSERT_EQ(3u, GetCandidateProductMap()->size());
 
-  CandidateProduct* product1 = (*GetCandidateProductMap())[foo1].get();
-  ASSERT_EQ(product1->similar_candidate_products_urls.size(), 2u);
-  ASSERT_EQ(product1->similar_candidate_products_urls.count(foo2), 1u);
-  ASSERT_EQ(product1->similar_candidate_products_urls.count(foo3), 1u);
+  std::set<GURL> similar_candidate_products =
+      cluster_manager_->FindSimilarCandidateProducts(foo1);
+  ASSERT_EQ(similar_candidate_products.size(), 2u);
+  ASSERT_EQ(similar_candidate_products.count(foo2), 1u);
+  ASSERT_EQ(similar_candidate_products.count(foo3), 1u);
 
-  CandidateProduct* product2 = (*GetCandidateProductMap())[foo2].get();
-  ASSERT_EQ(product2->similar_candidate_products_urls.size(), 1u);
-  ASSERT_EQ(product2->similar_candidate_products_urls.count(foo1), 1u);
+  similar_candidate_products =
+      cluster_manager_->FindSimilarCandidateProducts(foo2);
+  ASSERT_EQ(similar_candidate_products.size(), 1u);
+  ASSERT_EQ(similar_candidate_products.count(foo1), 1u);
 
-  CandidateProduct* product3 = (*GetCandidateProductMap())[foo3].get();
-  ASSERT_EQ(product3->similar_candidate_products_urls.size(), 1u);
-  ASSERT_EQ(product3->similar_candidate_products_urls.count(foo1), 1u);
+  similar_candidate_products =
+      cluster_manager_->FindSimilarCandidateProducts(foo3);
+  ASSERT_EQ(similar_candidate_products.size(), 1u);
+  ASSERT_EQ(similar_candidate_products.count(foo1), 1u);
 }
 
 TEST_F(ClusterManagerTest, RemoveClusteredCandidateProduct) {
@@ -216,11 +222,13 @@
   cluster_manager_->DidNavigateAway(foo3);
   ASSERT_EQ(2u, GetCandidateProductMap()->size());
 
-  CandidateProduct* product1 = (*GetCandidateProductMap())[foo1].get();
-  ASSERT_EQ(product1->similar_candidate_products_urls.size(), 0u);
+  std::set<GURL> similar_candidate_products =
+      cluster_manager_->FindSimilarCandidateProducts(foo1);
+  ASSERT_EQ(similar_candidate_products.size(), 0u);
 
-  CandidateProduct* product2 = (*GetCandidateProductMap())[foo2].get();
-  ASSERT_EQ(product2->similar_candidate_products_urls.size(), 0u);
+  similar_candidate_products =
+      cluster_manager_->FindSimilarCandidateProducts(foo2);
+  ASSERT_EQ(similar_candidate_products.size(), 0u);
 }
 
 TEST_F(ClusterManagerTest,
@@ -246,8 +254,9 @@
   base::RunLoop().RunUntilIdle();
   ASSERT_EQ(2u, GetCandidateProductMap()->size());
 
-  CandidateProduct* product1 = (*GetCandidateProductMap())[foo1].get();
-  ASSERT_EQ(product1->similar_candidate_products_urls.size(), 0u);
+  std::set<GURL> similar_candidate_products =
+      cluster_manager_->FindSimilarCandidateProducts(foo1);
+  ASSERT_EQ(similar_candidate_products.size(), 0u);
 }
 
 TEST_F(ClusterManagerTest, FindSimilarCandidateProductsForProductGroup) {
@@ -360,4 +369,42 @@
   ASSERT_EQ(3u, GetCandidateProductMap()->size());
 }
 
+TEST_F(ClusterManagerTest, GetProductGroupForCandidateProduct) {
+  base::Uuid uuid = AddProductSpecificationSet();
+  ProductGroup* product_group = (*GetProductGroupMap())[uuid].get();
+  ASSERT_EQ(1u, product_group->member_products.size());
+  base::RunLoop().RunUntilIdle();
+  GURL foo1(kTestUrl1);
+  ASSERT_FALSE(cluster_manager_->GetProductGroupForCandidateProduct(foo1));
+
+  UpdateUrlInfos(std::vector<GURL>{foo1});
+  cluster_manager_->DidNavigatePrimaryMainFrame(foo1);
+  base::RunLoop().RunUntilIdle();
+  auto possible_product_group =
+      cluster_manager_->GetProductGroupForCandidateProduct(foo1);
+  ASSERT_TRUE(possible_product_group);
+  ASSERT_EQ(possible_product_group->uuid, product_group->uuid);
+
+  UpdateUrlInfos(std::vector<GURL>());
+  cluster_manager_->DidNavigateAway(foo1);
+  ASSERT_FALSE(cluster_manager_->GetProductGroupForCandidateProduct(foo1));
+}
+
+TEST_F(ClusterManagerTest, AddCandidateProductAlreadyInProductGroups) {
+  base::Uuid uuid = AddProductSpecificationSet();
+  ProductGroup* product_group = (*GetProductGroupMap())[uuid].get();
+  ASSERT_EQ(1u, product_group->member_products.size());
+  GURL foo1(kProductUrl);
+  UpdateUrlInfos(std::vector<GURL>{foo1});
+
+  cluster_manager_->DidNavigatePrimaryMainFrame(foo1);
+  base::RunLoop().RunUntilIdle();
+  std::vector<GURL> candidates =
+      FindSimilarCandidateProductsForProductGroup(uuid);
+  ASSERT_EQ(0u, candidates.size());
+
+  ASSERT_FALSE(cluster_manager_->GetProductGroupForCandidateProduct(foo1));
+  ASSERT_EQ(1u, product_group->member_products.size());
+}
+
 }  // namespace commerce
diff --git a/components/commerce/core/compare/product_group.cc b/components/commerce/core/compare/product_group.cc
index e15ec31..e9cfbc3 100644
--- a/components/commerce/core/compare/product_group.cc
+++ b/components/commerce/core/compare/product_group.cc
@@ -12,4 +12,8 @@
 
 ProductGroup::~ProductGroup() = default;
 
+ProductGroup::ProductGroup(const ProductGroup&) = default;
+
+ProductGroup& ProductGroup::operator=(const ProductGroup&) = default;
+
 }  // namespace commerce
diff --git a/components/commerce/core/compare/product_group.h b/components/commerce/core/compare/product_group.h
index 0ab7385..65037ee 100644
--- a/components/commerce/core/compare/product_group.h
+++ b/components/commerce/core/compare/product_group.h
@@ -20,8 +20,8 @@
   ProductGroup(const base::Uuid& uuid, const std::vector<GURL>& urls);
   ~ProductGroup();
 
-  ProductGroup(const ProductGroup&) = delete;
-  ProductGroup& operator=(const ProductGroup&) = delete;
+  ProductGroup(const ProductGroup&);
+  ProductGroup& operator=(const ProductGroup&);
 
   // Unique ID to identify the product group.
   base::Uuid uuid;
diff --git a/components/compose/core/browser/BUILD.gn b/components/compose/core/browser/BUILD.gn
index ae1ba01..62668c5e 100644
--- a/components/compose/core/browser/BUILD.gn
+++ b/components/compose/core/browser/BUILD.gn
@@ -36,6 +36,7 @@
     "//components/autofill/core/browser",
     "//components/autofill/core/common",
     "//components/keyed_service/core",
+    "//components/strings",
     "//services/metrics/public/cpp:metrics_cpp",
     "//services/metrics/public/cpp:ukm_builders",
   ]
@@ -57,6 +58,7 @@
     "//components/autofill/core/browser",
     "//components/autofill/core/browser:test_support",
     "//components/autofill/core/common:test_support",
+    "//components/strings",
     "//components/ukm:test_support",
     "//services/metrics/public/cpp:ukm_builders",
     "//testing/gmock",
diff --git a/components/compose/core/browser/DEPS b/components/compose/core/browser/DEPS
index 63faa8c..ac9b8c7 100644
--- a/components/compose/core/browser/DEPS
+++ b/components/compose/core/browser/DEPS
@@ -1,5 +1,7 @@
 include_rules = [
   "+components/autofill/core",
+  "+components/strings/grit",
   "+components/ukm",
-  "+services/metrics/public/cpp"
+  "+services/metrics/public/cpp",
+  "+ui/base",
 ]
diff --git a/components/compose/core/browser/compose_manager_impl.cc b/components/compose/core/browser/compose_manager_impl.cc
index ba40b03..9f734eb 100644
--- a/components/compose/core/browser/compose_manager_impl.cc
+++ b/components/compose/core/browser/compose_manager_impl.cc
@@ -14,14 +14,27 @@
 #include "components/autofill/core/browser/autofill_client.h"
 #include "components/autofill/core/browser/autofill_manager.h"
 #include "components/autofill/core/browser/browser_autofill_manager.h"
+#include "components/autofill/core/browser/ui/popup_item_ids.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/compose/core/browser/compose_client.h"
 #include "components/compose/core/browser/compose_features.h"
 #include "components/compose/core/browser/compose_metrics.h"
 #include "components/compose/core/browser/compose_utils.h"
 #include "components/compose/core/browser/config.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace compose {
 
 namespace {
 
+using autofill::AutofillDriver;
+using autofill::AutofillSuggestionTriggerSource;
+using autofill::FieldGlobalId;
+using autofill::FormGlobalId;
+using autofill::PopupItemId;
+using autofill::Suggestion;
+
 // Passes the autofill `text` back into the `field` the dialog was opened on.
 // Called upon insertion.
 void FillTextWithAutofill(base::WeakPtr<autofill::AutofillManager> manager,
@@ -36,36 +49,19 @@
   static_cast<autofill::BrowserAutofillManager*>(manager.get())
       ->FillOrPreviewField(autofill::mojom::ActionPersistence::kFill,
                            autofill::mojom::FieldActionType::kReplaceSelection,
-                           form, field, trimmed_text,
-                           autofill::PopupItemId::kCompose);
+                           form, field, trimmed_text, PopupItemId::kCompose);
 }
 
 }  // namespace
 
-namespace compose {
-
 ComposeManagerImpl::ComposeManagerImpl(ComposeClient* client)
     : client_(*client) {}
 
 ComposeManagerImpl::~ComposeManagerImpl() = default;
 
-bool ComposeManagerImpl::ShouldOfferComposePopup(
-    const autofill::FormFieldData& trigger_field,
-    autofill::AutofillSuggestionTriggerSource trigger_source) {
-  return client_->ShouldTriggerPopup(trigger_field, trigger_source);
-}
-
-bool ComposeManagerImpl::HasSavedState(
-    const autofill::FieldGlobalId& trigger_field_id) {
-  // State is saved as a ComposeSession in the ComposeClient. A user can resume
-  // where they left off in a field if the ComposeClient has a ComposeSession
-  // for that field.
-  return client_->HasSession(trigger_field_id);
-}
-
-void ComposeManagerImpl::OpenCompose(autofill::AutofillDriver& driver,
-                                     autofill::FormGlobalId form_id,
-                                     autofill::FieldGlobalId field_id,
+void ComposeManagerImpl::OpenCompose(AutofillDriver& driver,
+                                     FormGlobalId form_id,
+                                     FieldGlobalId field_id,
                                      UiEntryPoint entry_point) {
   if (entry_point == UiEntryPoint::kContextMenu) {
     client_->getPageUkmTracker()->MenuItemClicked();
@@ -79,9 +75,9 @@
 }
 
 void ComposeManagerImpl::OpenComposeWithUpdatedSelection(
-    autofill::FieldGlobalId field_id,
+    FieldGlobalId field_id,
     compose::ComposeManagerImpl::UiEntryPoint ui_entry_point,
-    autofill::AutofillDriver* driver,
+    AutofillDriver* driver,
     const std::optional<autofill::FormData>& form_data) {
   if (!form_data) {
     compose::LogOpenComposeDialogResult(
@@ -126,9 +122,9 @@
 }
 
 void ComposeManagerImpl::OpenComposeWithFormData(
-    autofill::FieldGlobalId field_id,
+    FieldGlobalId field_id,
     compose::ComposeManagerImpl::UiEntryPoint ui_entry_point,
-    autofill::AutofillDriver* driver,
+    AutofillDriver* driver,
     const std::optional<autofill::FormData>& form_data) {
   if (!form_data) {
     compose::LogOpenComposeDialogResult(
@@ -166,4 +162,39 @@
                              popup_screen_location, std::move(callback));
 }
 
+std::optional<Suggestion> ComposeManagerImpl::GetSuggestion(
+    const autofill::FormFieldData& field,
+    AutofillSuggestionTriggerSource trigger_source) {
+  if (!client_->ShouldTriggerPopup(field, trigger_source)) {
+    return std::nullopt;
+  }
+  std::u16string suggestion_text;
+  std::u16string label_text;
+  PopupItemId popup_item_id = PopupItemId::kCompose;
+  // State is saved as a `ComposeSession` in the `ComposeClient`. A user can
+  // resume where they left off in a field if the `ComposeClient` has a
+  // `ComposeSession` for that field.
+  if (client_->HasSession(field.global_id())) {
+    // The nudge text indicates that the user can resume where they left off in
+    // the Compose dialog.
+    suggestion_text =
+        l10n_util::GetStringUTF16(IDS_COMPOSE_SUGGESTION_SAVED_TEXT);
+    label_text = l10n_util::GetStringUTF16(IDS_COMPOSE_SUGGESTION_SAVED_LABEL);
+    if (trigger_source ==
+        AutofillSuggestionTriggerSource::kComposeDialogLostFocus) {
+      popup_item_id = PopupItemId::kComposeSavedStateNotification;
+    }
+  } else {
+    // Text for a new Compose session.
+    suggestion_text =
+        l10n_util::GetStringUTF16(IDS_COMPOSE_SUGGESTION_MAIN_TEXT);
+    label_text = l10n_util::GetStringUTF16(IDS_COMPOSE_SUGGESTION_LABEL);
+  }
+  Suggestion suggestion(std::move(suggestion_text));
+  suggestion.labels = {{Suggestion::Text(std::move(label_text))}};
+  suggestion.popup_item_id = popup_item_id;
+  suggestion.icon = Suggestion::Icon::kPenSpark;
+  return suggestion;
+}
+
 }  // namespace compose
diff --git a/components/compose/core/browser/compose_manager_impl.h b/components/compose/core/browser/compose_manager_impl.h
index cd2efdd..b914612c 100644
--- a/components/compose/core/browser/compose_manager_impl.h
+++ b/components/compose/core/browser/compose_manager_impl.h
@@ -5,8 +5,11 @@
 #ifndef COMPONENTS_COMPOSE_CORE_BROWSER_COMPOSE_MANAGER_IMPL_H_
 #define COMPONENTS_COMPOSE_CORE_BROWSER_COMPOSE_MANAGER_IMPL_H_
 
+#include <optional>
+
 #include "base/memory/raw_ref.h"
 #include "components/autofill/core/browser/autofill_driver.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/autofill/core/common/unique_ids.h"
 #include "components/compose/core/browser/compose_client.h"
 #include "components/compose/core/browser/compose_manager.h"
@@ -23,10 +26,6 @@
   ~ComposeManagerImpl() override;
 
   // AutofillComposeDelegate
-  bool ShouldOfferComposePopup(
-      const autofill::FormFieldData& trigger_field,
-      autofill::AutofillSuggestionTriggerSource trigger_source) override;
-  bool HasSavedState(const autofill::FieldGlobalId& trigger_field_id) override;
   void OpenCompose(autofill::AutofillDriver& driver,
                    autofill::FormGlobalId form_id,
                    autofill::FieldGlobalId field_id,
@@ -38,6 +37,9 @@
       const autofill::FormFieldData& trigger_field,
       std::optional<PopupScreenLocation> popup_screen_location,
       ComposeCallback callback) override;
+  std::optional<autofill::Suggestion> GetSuggestion(
+      const autofill::FormFieldData& field,
+      autofill::AutofillSuggestionTriggerSource trigger_source) override;
 
  private:
   bool IsEnabled() const;
diff --git a/components/compose/core/browser/compose_manager_impl_unittest.cc b/components/compose/core/browser/compose_manager_impl_unittest.cc
index 9ea2497..9e01439 100644
--- a/components/compose/core/browser/compose_manager_impl_unittest.cc
+++ b/components/compose/core/browser/compose_manager_impl_unittest.cc
@@ -13,15 +13,19 @@
 #include "components/autofill/core/browser/mock_autofill_manager.h"
 #include "components/autofill/core/browser/test_autofill_client.h"
 #include "components/autofill/core/browser/test_autofill_driver.h"
+#include "components/autofill/core/browser/ui/popup_item_ids.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/autofill/core/common/autofill_test_utils.h"
 #include "components/autofill/core/common/form_data.h"
 #include "components/compose/core/browser/compose_client.h"
 #include "components/compose/core/browser/compose_metrics.h"
+#include "components/strings/grit/components_strings.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
 
 using testing::_;
@@ -89,11 +93,26 @@
             testing::Invoke([&](const autofill::FormFieldData& trigger_field) {
               last_form_field_to_client_ = trigger_field;
             })));
-
+    ON_CALL(mock_compose_client(), ShouldTriggerPopup)
+        .WillByDefault(testing::Return(true));
     compose_manager_impl_ =
         std::make_unique<compose::ComposeManagerImpl>(&mock_compose_client());
   }
 
+  // Helper method to retrieve compose suggestions, if it exists.
+  // `has_session` defines whether a previous session exists for the triggering
+  // field.
+  std::optional<autofill::Suggestion> GetSuggestion(
+      autofill::AutofillSuggestionTriggerSource trigger_source,
+      bool has_session) {
+    ON_CALL(mock_compose_client(), HasSession)
+        .WillByDefault(testing::Return(has_session));
+    return compose_manager_impl().GetSuggestion(
+        autofill::test::CreateTestFormField(
+            "label0", "name0", "value0", autofill::FormControlType::kTextArea),
+        trigger_source);
+  }
+
   void SimulateComposeSessionEnd() { page_ukm_tracker_.reset(); }
 
  protected:
@@ -143,6 +162,63 @@
   std::unique_ptr<compose::ComposeManagerImpl> compose_manager_impl_;
 };
 
+TEST_F(
+    ComposeManagerImplTest,
+    SuggestionGeneration_HasSession_ComposeLostFocus_ApplyExpectedTextAndLabel) {
+  std::optional<autofill::Suggestion> suggestion = GetSuggestion(
+      autofill::AutofillSuggestionTriggerSource::kComposeDialogLostFocus,
+      /*has_session=*/true);
+  ASSERT_TRUE(suggestion.has_value());
+  EXPECT_EQ(*suggestion,
+            autofill::Suggestion(
+                l10n_util::GetStringUTF8(IDS_COMPOSE_SUGGESTION_SAVED_TEXT),
+                {{autofill::Suggestion::Text(l10n_util::GetStringUTF16(
+                    IDS_COMPOSE_SUGGESTION_SAVED_LABEL))}},
+                autofill::Suggestion::Icon::kPenSpark,
+                autofill::PopupItemId::kComposeSavedStateNotification));
+}
+
+TEST_F(
+    ComposeManagerImplTest,
+    SuggestionGeneration_HasSession_ControlElementClicked_ApplyExpectedTextAndLabel) {
+  std::optional<autofill::Suggestion> suggestion = GetSuggestion(
+      autofill::AutofillSuggestionTriggerSource::kFormControlElementClicked,
+      /*has_session=*/true);
+  ASSERT_TRUE(suggestion.has_value());
+  EXPECT_EQ(*suggestion,
+            autofill::Suggestion(
+                l10n_util::GetStringUTF8(IDS_COMPOSE_SUGGESTION_SAVED_TEXT),
+                {{autofill::Suggestion::Text(l10n_util::GetStringUTF16(
+                    IDS_COMPOSE_SUGGESTION_SAVED_LABEL))}},
+                autofill::Suggestion::Icon::kPenSpark,
+                autofill::PopupItemId::kCompose));
+}
+
+TEST_F(ComposeManagerImplTest,
+       SuggestionGeneration_DoesNotHaveSession_ApplyExpectedTextAndLabel) {
+  std::optional<autofill::Suggestion> suggestion = GetSuggestion(
+      autofill::AutofillSuggestionTriggerSource::kFormControlElementClicked,
+      /*has_session=*/false);
+  ASSERT_TRUE(suggestion.has_value());
+  EXPECT_EQ(*suggestion,
+            autofill::Suggestion(
+                l10n_util::GetStringUTF8(IDS_COMPOSE_SUGGESTION_MAIN_TEXT),
+                {{autofill::Suggestion::Text(
+                    l10n_util::GetStringUTF16(IDS_COMPOSE_SUGGESTION_LABEL))}},
+                autofill::Suggestion::Icon::kPenSpark,
+                autofill::PopupItemId::kCompose));
+}
+
+TEST_F(ComposeManagerImplTest,
+       SuggestionGeneration_ShouldNotTriggerPopup_NoSuggestionReturned) {
+  ON_CALL(mock_compose_client(), ShouldTriggerPopup)
+      .WillByDefault(testing::Return(false));
+  std::optional<autofill::Suggestion> suggestion = GetSuggestion(
+      autofill::AutofillSuggestionTriggerSource::kFormControlElementClicked,
+      /*has_session=*/false);
+  EXPECT_FALSE(suggestion.has_value());
+}
+
 TEST_F(ComposeManagerImplTest, TestOpenCompose_Success) {
   // Creates a test form and use the 2nd field as the selected one.
   const autofill::FormData form_data = CreateTestFormDataWith3TextAreaFields();
diff --git a/components/history_strings.grdp b/components/history_strings.grdp
index caa7806..cb18b71 100644
--- a/components/history_strings.grdp
+++ b/components/history_strings.grdp
@@ -131,6 +131,12 @@
   <message name="IDS_HISTORY_EMBEDDINGS_PROMO_BODY" desc="On the history page, the body text of the promo for history embeddings." translateable="false">
     Enter a search query to search through history. Here is some more placeholder text about how to search History and what data is available. Here is some more text to make this placeholder longer. View more in <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1"&gt;</ph>Settings<ph name="END_LINK">&lt;/a&gt;</ph>.
   </message>
+  <message name="IDS_HISTORY_EMBEDDINGS_SHOW_BY_DATE" desc="On the history page, the label on the option to show history results grouped by date." translateable="false">
+    Show by date
+  </message>
+  <message name="IDS_HISTORY_EMBEDDINGS_SHOW_BY_GROUP" desc="On the history page, the label on the option to show history results grouped by history clusters." translateable="false">
+    Show by group
+  </message>
   <message name="IDS_HISTORY_EMBEDDINGS_SUGGESTION_1" desc="On the history page, the text inside of a suggestion chip that a user can click to update their search to show results from yesterday" translateable="false">
     Yesterday
   </message>
diff --git a/components/js_injection/DEPS b/components/js_injection/DEPS
index 74dd110e..7f796b6 100644
--- a/components/js_injection/DEPS
+++ b/components/js_injection/DEPS
@@ -4,3 +4,9 @@
   "+net",
   "+third_party/blink/public",
 ]
+
+specific_include_rules = {
+  'navigation_listener_browsertest.cc': [
+    "+content/shell/browser/shell.h",
+  ],
+}
\ No newline at end of file
diff --git a/components/js_injection/browser/BUILD.gn b/components/js_injection/browser/BUILD.gn
index 738d6010..1868603 100644
--- a/components/js_injection/browser/BUILD.gn
+++ b/components/js_injection/browser/BUILD.gn
@@ -8,6 +8,8 @@
     "js_communication_host.h",
     "js_to_browser_messaging.cc",
     "js_to_browser_messaging.h",
+    "navigation_web_message_sender.cc",
+    "navigation_web_message_sender.h",
     "web_message.cc",
     "web_message.h",
     "web_message_host.h",
@@ -25,3 +27,22 @@
     "//url",
   ]
 }
+
+source_set("browser_tests") {
+  testonly = true
+
+  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+
+  sources = [ "navigation_listener_browsertest.cc" ]
+
+  deps = [
+    ":browser",
+    "//base/test:test_support",
+    "//content/shell:content_shell_lib",
+    "//content/test:browsertest_support",
+    "//content/test:test_support",
+    "//net:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
diff --git a/components/js_injection/browser/js_communication_host.cc b/components/js_injection/browser/js_communication_host.cc
index dd24977..88e67ada 100644
--- a/components/js_injection/browser/js_communication_host.cc
+++ b/components/js_injection/browser/js_communication_host.cc
@@ -8,10 +8,12 @@
 #include "base/functional/function_ref.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/js_injection/browser/js_to_browser_messaging.h"
+#include "components/js_injection/browser/navigation_web_message_sender.h"
 #include "components/js_injection/browser/web_message_host.h"
 #include "components/js_injection/browser/web_message_host_factory.h"
 #include "components/js_injection/common/origin_matcher.h"
 #include "components/js_injection/common/origin_matcher_mojom_traits.h"
+#include "content/public/browser/page.h"
 #include "content/public/browser/web_contents.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
@@ -153,6 +155,23 @@
     }
   }
 
+  if (base::FeatureList::IsEnabled(features::kEnableNavigationListener) &&
+      js_object_name ==
+          NavigationWebMessageSender::kNavigationListenerObjectName) {
+    // This is the special navigationListener object that is registered to
+    // listen for navigation events instead of establishing a connection to
+    // the renderer. This shouldn't create an object in the renderer. Instead,
+    // create a NavigationWebMessageSender for the primary Page, so that
+    // navigation notifications for it will be sent.
+    // TODO(https://crbug.com/332809183): Guard this behind an origin trial
+    // check later on.
+    has_navigation_listener_ = true;
+    NavigationWebMessageSender::CreateForPage(web_contents()->GetPrimaryPage(),
+                                              factory.get());
+    NavigationWebMessageSender::GetForPage(web_contents()->GetPrimaryPage())
+        ->DispatchOptInMessage();
+  }
+
   js_objects_.push_back(std::make_unique<JsObject>(
       js_object_name, origin_matcher, std::move(factory)));
 
@@ -250,6 +269,21 @@
   std::vector<mojom::JsObjectPtr> js_objects;
   js_objects.reserve(js_objects_.size());
   for (const auto& js_object : js_objects_) {
+    if (base::FeatureList::IsEnabled(features::kEnableNavigationListener) &&
+        js_object->name ==
+            NavigationWebMessageSender::kNavigationListenerObjectName) {
+      // This is the special navigationListener object that is registered to
+      // listen for navigation events instead of establishing a connection to
+      // the renderer. Don't create an object in the renderer. The
+      // NavigationWebMessageSender for  `render_frame_host`'s Page should
+      // either already be created when the object is first registered (see
+      // `AddWebMessageHostFactory()`) or when the Page becomes the primary Page
+      // (see `PrimaryPageChanged()`).
+      // TODO(https://crbug.com/332809183): Guard this behind an origin trial
+      // check later on.
+      CHECK(has_navigation_listener_);
+      continue;
+    }
     mojo::PendingAssociatedRemote<mojom::JsToBrowserMessaging> pending_remote;
     js_to_browser_messagings_[render_frame_host->GetGlobalId()].emplace_back(
         std::make_unique<JsToBrowserMessaging>(
@@ -263,6 +297,29 @@
   configurator_remote->SetJsObjects(std::move(js_objects));
 }
 
+void JsCommunicationHost::PrimaryPageChanged(content::Page& page) {
+  // TODO(https://crbug.com/332809183): Guard this behind an origin trial check
+  // later on.
+  if (!base::FeatureList::IsEnabled(features::kEnableNavigationListener) ||
+      !has_navigation_listener_) {
+    return;
+  }
+  for (const auto& js_object : js_objects_) {
+    if (js_object->name ==
+        NavigationWebMessageSender::kNavigationListenerObjectName) {
+      // The active Page in the primary main frame just changed. Ensure that a
+      // NavigationWebMessageSender is created for the primary Page, so that
+      // navigation notifications for it will be sent correctly, including the
+      // navigation that committed the primary Page. Note that some Pages
+      // might not be primary even when navigations happen on them (e.g.
+      // prerendering Pages), but we won't send notifications for those pages,
+      // so there is no need to create the NavigationWebMessageSenders for
+      // them before they become the primary Page.
+      NavigationWebMessageSender::CreateForPage(page, js_object->factory.get());
+    }
+  }
+}
+
 void JsCommunicationHost::NotifyFrameForAddDocumentStartJavaScript(
     const DocumentStartJavaScript* script,
     content::RenderFrameHost* render_frame_host) {
diff --git a/components/js_injection/browser/js_communication_host.h b/components/js_injection/browser/js_communication_host.h
index 773a6fe..975757c 100644
--- a/components/js_injection/browser/js_communication_host.h
+++ b/components/js_injection/browser/js_communication_host.h
@@ -108,6 +108,7 @@
       content::RenderFrameHost* render_frame_host,
       content::RenderFrameHost::LifecycleState old_state,
       content::RenderFrameHost::LifecycleState new_state) override;
+  void PrimaryPageChanged(content::Page& page) override;
 
  private:
   void NotifyFrameForWebMessageListener(
@@ -127,6 +128,7 @@
   std::map<content::GlobalRenderFrameHostId,
            std::vector<std::unique_ptr<JsToBrowserMessaging>>>
       js_to_browser_messagings_;
+  bool has_navigation_listener_ = false;
 };
 
 }  // namespace js_injection
diff --git a/components/js_injection/browser/navigation_listener_browsertest.cc b/components/js_injection/browser/navigation_listener_browsertest.cc
new file mode 100644
index 0000000..73669fc
--- /dev/null
+++ b/components/js_injection/browser/navigation_listener_browsertest.cc
@@ -0,0 +1,731 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/json/json_writer.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/js_injection/browser/js_communication_host.h"
+#include "components/js_injection/browser/navigation_web_message_sender.h"
+#include "components/js_injection/browser/web_message.h"
+#include "components/js_injection/browser/web_message_host.h"
+#include "components/js_injection/browser/web_message_host_factory.h"
+#include "content/public/browser/back_forward_cache.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_features.h"
+#include "content/public/test/back_forward_cache_util.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "net/test/embedded_test_server/controllable_http_response.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace js_injection {
+
+using HostToken = base::UnguessableToken;
+// Listens to navigation messages and queues them per-WebMessageHost. The queued
+// messages can be read in sequence for each host. It's useful to queue the
+// messages per-host, since some messages can arrive interleaved with messages
+// intended for other hosts, e.g. PAGE_DELETED messages can fire a bit later
+// than the next page's NAVIGATION_COMPLETED message, if the RenderFrameHost
+// changes and the deletion of the previous RenderFrameHost happens after
+// the new RenderFrameHost's navigation committed. However, it's guaranteed
+// that the order within the same host stays consistent (NAVIGATION_COMPLETED &
+// PAGE_LOAD_END won't fire after PAGE_DELETED) and only PAGE_DELETED can be
+// fired for the old page after the new page's NAVIGATION_COMPLETED.
+class NavigationMessageListener {
+ public:
+  NavigationMessageListener() = default;
+  ~NavigationMessageListener() = default;
+
+  // Returns the index for the next message for `host` in its message queue.
+  size_t GetNextMessageIndexForHost(const HostToken& host) {
+    CHECK(message_queues_.contains(host));
+    return message_queues_[host].next_message_index;
+  }
+
+  // Returns true if there's at least 1 new queued message for `host`.
+  bool HasNextMessageForHost(const HostToken& host) {
+    return message_queues_[host].messages.size() >
+           GetNextMessageIndexForHost(host);
+  }
+
+  // Returns true if there's at least 1 new queued message for any host.
+  bool HasNextMessageForAnyHost() {
+    for (const auto& host_message_pair : message_queues_) {
+      if (HasNextMessageForHost(host_message_pair.first)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // Returns the first message in the queue for `host`.
+  WebMessage* NextMessageForHost(const HostToken& host) {
+    CHECK(HasNextMessageForHost(host));
+    size_t message_index = GetNextMessageIndexForHost(host);
+    message_queues_[host].next_message_index = message_index + 1;
+    return message_queues_[host].messages.at(message_index).get();
+  }
+
+  // Waits until there's a message waiting for `host`.
+  void WaitForNextMessageForHost(const HostToken& host) {
+    if (HasNextMessageForHost(host)) {
+      return;
+    }
+    message_queues_[host].message_waiter = std::make_unique<base::RunLoop>();
+    message_queues_[host].message_waiter->Run();
+    CHECK(HasNextMessageForHost(host));
+  }
+
+  // Waits until there's a message waiting in any host's queue. Useful when
+  // navigating to a new page and the WebMessageHost is not known yet.
+  void WaitForNextMessageForAnyHost() {
+    if (HasNextMessageForAnyHost()) {
+      return;
+    }
+
+    any_message_waiter_ = std::make_unique<base::RunLoop>();
+    any_message_waiter_->Run();
+  }
+
+  // The message `message` has been posted to the FakeWebMessageHost associated
+  // with `host`. Add the message to that host's queue.
+  void OnPostMessage(const HostToken& host,
+                     std::unique_ptr<WebMessage> message) {
+    message_queues_[host].messages.push_back(std::move(message));
+    // We received a new message for `host`, so we can unblock calls to
+    // `WaitForNextMessage*()` if needed.
+    if (message_queues_[host].message_waiter.get()) {
+      message_queues_[host].message_waiter->Quit();
+    }
+    if (any_message_waiter_.get()) {
+      any_message_waiter_->Quit();
+    }
+  }
+
+ private:
+  struct MessageQueue {
+    std::unique_ptr<base::RunLoop> message_waiter;
+    std::vector<std::unique_ptr<WebMessage>> messages;
+    int next_message_index = 0;
+  };
+  std::map<HostToken, MessageQueue> message_queues_;
+
+  std::unique_ptr<base::RunLoop> any_message_waiter_;
+};
+
+class FakeWebMessageHost : public WebMessageHost {
+ public:
+  explicit FakeWebMessageHost(NavigationMessageListener* listener)
+      : listener_(listener) {}
+  ~FakeWebMessageHost() override = default;
+
+  // WebMessageHost overrides:
+  void OnPostMessage(std::unique_ptr<WebMessage> message) override {
+    listener_->OnPostMessage(token(), std::move(message));
+  }
+
+  base::UnguessableToken token() const { return token_; }
+
+ private:
+  base::UnguessableToken token_ = base::UnguessableToken::Create();
+  raw_ptr<NavigationMessageListener> listener_;
+};
+
+class FakeWebMessageHostFactory : public WebMessageHostFactory {
+ public:
+  explicit FakeWebMessageHostFactory(NavigationMessageListener* listener)
+      : listener_(listener) {}
+  ~FakeWebMessageHostFactory() override = default;
+
+  // WebMessageHostFactory overrides:
+  std::unique_ptr<WebMessageHost> CreateHost(
+      const std::string& top_level_origin_string,
+      const std::string& origin_string,
+      bool is_main_frame,
+      WebMessageReplyProxy* proxy) override {
+    return std::make_unique<FakeWebMessageHost>(listener_);
+  }
+
+ private:
+  raw_ptr<NavigationMessageListener> listener_;
+};
+
+class NavigationListenerBrowserTest : public content::ContentBrowserTest {
+ public:
+  NavigationListenerBrowserTest() {
+    features_.InitAndEnableFeature(features::kEnableNavigationListener);
+  }
+  ~NavigationListenerBrowserTest() override = default;
+
+ protected:
+  // content::ContentBrowserTest overrides:
+  void SetUpOnMainThread() override {
+    content::ContentBrowserTest::SetUpOnMainThread();
+  }
+  void TearDownOnMainThread() override {
+    content::ContentBrowserTest::TearDownOnMainThread();
+  }
+
+  content::WebContents* web_contents() { return shell()->web_contents(); }
+
+  NavigationMessageListener& listener() { return listener_; }
+
+  void SetupNavigationListener() {
+    if (!js_communication_host_.get()) {
+      js_communication_host_ =
+          std::make_unique<JsCommunicationHost>(web_contents());
+    }
+    js_communication_host_->AddWebMessageHostFactory(
+        std::make_unique<FakeWebMessageHostFactory>(&listener()),
+        NavigationWebMessageSender::kNavigationListenerObjectName, {"*"});
+  }
+
+  HostToken GetCurrentHostToken() {
+    NavigationWebMessageSender* sender = NavigationWebMessageSender::GetForPage(
+        web_contents()->GetPrimaryPage());
+    return static_cast<FakeWebMessageHost*>(
+               sender->GetWebMessageHostForTesting())
+        ->token();
+  }
+
+  HostToken NavigateToFirstPage() {
+    NavigationWebMessageSender* sender0 =
+        NavigationWebMessageSender::GetForPage(
+            web_contents()->GetPrimaryPage());
+    EXPECT_TRUE(sender0);
+    HostToken host0 =
+        static_cast<FakeWebMessageHost*>(sender0->GetWebMessageHostForTesting())
+            ->token();
+    CheckNavigationMessage(listener().NextMessageForHost(host0),
+                           NavigationWebMessageSender::kOptedInMessage);
+    GURL navigation_url = embedded_test_server()->GetURL("/title1.html");
+    EXPECT_TRUE(content::NavigateToURL(shell(), navigation_url));
+    NavigationWebMessageSender* sender = NavigationWebMessageSender::GetForPage(
+        web_contents()->GetPrimaryPage());
+    EXPECT_TRUE(sender);
+    HostToken host =
+        static_cast<FakeWebMessageHost*>(sender->GetWebMessageHostForTesting())
+            ->token();
+    CheckNavigationMessage(listener().NextMessageForHost(host0),
+                           NavigationWebMessageSender::kPageDeletedMessage);
+    CheckNavigationCompletedMessage(listener().NextMessageForHost(host),
+                                    /*url=*/navigation_url,
+                                    /*is_same_document=*/false,
+                                    /*is_page_initiated=*/false,
+                                    /*is_error_page=*/false,
+                                    /*is_reload=*/false,
+                                    /*is_history=*/false,
+                                    /*committed=*/true,
+                                    /*status_code=*/200);
+    CheckNavigationMessage(listener().NextMessageForHost(host),
+                           NavigationWebMessageSender::kPageLoadEndMessage);
+    EXPECT_FALSE(listener().HasNextMessageForHost(host));
+    return host;
+  }
+
+  void CheckNavigationMessage(WebMessage* message, std::string type) {
+    base::Value::Dict expected_dict;
+    expected_dict.Set("type", type);
+    ASSERT_EQ(
+        NavigationWebMessageSender::CreateWebMessage(std::move(expected_dict))
+            ->message,
+        message->message);
+  }
+
+  void CheckNavigationCompletedMessage(WebMessage* message,
+                                       GURL& url,
+                                       bool is_same_document,
+                                       bool is_page_initiated,
+                                       bool is_error_page,
+                                       bool is_reload,
+                                       bool is_history,
+                                       bool committed,
+                                       int status_code) {
+    base::Value::Dict message_dict =
+        base::Value::Dict()
+            .Set("type",
+                 NavigationWebMessageSender::kNavigationCompletedMessage)
+            .Set("url", url.spec())
+            .Set("isSameDocument", is_same_document)
+            .Set("isPageInitiated", is_page_initiated)
+            .Set("isErrorPage", is_error_page)
+            .Set("isReload", is_reload)
+            .Set("isHistory", is_history)
+            .Set("committed", committed)
+            .Set("statusCode", status_code);
+    ASSERT_EQ(
+        NavigationWebMessageSender::CreateWebMessage(std::move(message_dict))
+            ->message,
+        message->message);
+  }
+
+ private:
+  std::unique_ptr<JsCommunicationHost> js_communication_host_;
+  NavigationMessageListener listener_;
+  base::test::ScopedFeatureList features_;
+};
+
+// Test that adding the special navigationListener will result in receiving
+// navigation messages for a variety of navigation cases:
+// 1) Regular navigation
+// 2) Reload
+// 3) Same-document navigation
+// 4) Same-document history navigation
+// 5) Failed navigation resulting in an error page.
+IN_PROC_BROWSER_TEST_F(NavigationListenerBrowserTest, Basic) {
+  // Setup the navigation listener.
+  SetupNavigationListener();
+  // A NavigationWebMessageSender & FakeWebMessageHost is immediately created
+  // for the initial empty document.
+  HostToken host0 = GetCurrentHostToken();
+  CheckNavigationMessage(listener().NextMessageForHost(host0),
+                         NavigationWebMessageSender::kOptedInMessage);
+
+  // Navigation #1: navigate to the first page.
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL navigation_url = embedded_test_server()->GetURL("/title1.html");
+  ASSERT_TRUE(content::NavigateToURL(shell(), navigation_url));
+
+  // A new NavigationWebMessageSender with a new FakeWebMessageHost has been
+  // created for the new page.
+  HostToken host1 = GetCurrentHostToken();
+
+  // Assert that the initial empty document's page got deleted, because we
+  // committed a new page.
+  CheckNavigationMessage(listener().NextMessageForHost(host0),
+                         NavigationWebMessageSender::kPageDeletedMessage);
+
+  // Assert that we got navigation messages for the first navigation.
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host1),
+                                  /*url=*/navigation_url,
+                                  /*is_same_document=*/false,
+                                  /*is_page_initiated=*/false,
+                                  /*is_error_page=*/false,
+                                  /*is_reload=*/false,
+                                  /*is_history=*/false,
+                                  /*committed=*/true,
+                                  /*status_code=*/200);
+  CheckNavigationMessage(listener().NextMessageForHost(host1),
+                         NavigationWebMessageSender::kPageLoadEndMessage);
+  ASSERT_FALSE(listener().HasNextMessageForHost(host1));
+
+  // Navigation #2: Reload the first page.
+  content::ReloadBlockUntilNavigationsComplete(shell(), 1);
+
+  // A new NavigationWebMessageSender with a new FakeWebMessageHost has been
+  // created for the new page.
+  HostToken host2 = GetCurrentHostToken();
+
+  // The previous page has been deleted.
+  listener().WaitForNextMessageForHost(host1);
+  CheckNavigationMessage(listener().NextMessageForHost(host1),
+                         NavigationWebMessageSender::kPageDeletedMessage);
+
+  // Navigation messages for the reload.
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host2),
+                                  /*url=*/navigation_url,
+                                  /*is_same_document=*/false,
+                                  /*is_page_initiated=*/false,
+                                  /*is_error_page=*/false,
+                                  /*is_reload=*/true,
+                                  /*is_history=*/false,
+                                  /*committed=*/true,
+                                  /*status_code=*/200);
+  CheckNavigationMessage(listener().NextMessageForHost(host2),
+                         NavigationWebMessageSender::kPageLoadEndMessage);
+  ASSERT_FALSE(listener().HasNextMessageForAnyHost());
+
+  // Navigation #3: Navigate same-document.
+  GURL navigation_url_foo = embedded_test_server()->GetURL("/title1.html#foo");
+  ASSERT_TRUE(content::NavigateToURL(shell(), navigation_url_foo));
+
+  // The previous `host2` is reused as the navigation stays on the same page.
+  // Also, no PAGE_LOAD_END since the navigation doesn't load a new page.
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host2),
+                                  /*url=*/navigation_url_foo,
+                                  /*is_same_document=*/true,
+                                  /*is_page_initiated=*/false,
+                                  /*is_error_page=*/false,
+                                  /*is_reload=*/false,
+                                  /*is_history=*/false,
+                                  /*committed=*/true,
+                                  /*status_code=*/200);
+  ASSERT_FALSE(listener().HasNextMessageForAnyHost());
+
+  // Navigation #4: Navigate history same-document.
+  ASSERT_TRUE(HistoryGoBack(web_contents()));
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host2),
+                                  /*url=*/navigation_url,
+                                  /*is_same_document=*/true,
+                                  /*is_page_initiated=*/false,
+                                  /*is_error_page=*/false,
+                                  /*is_reload=*/false,
+                                  /*is_history=*/true,
+                                  /*committed=*/true,
+                                  /*status_code=*/200);
+  ASSERT_FALSE(listener().HasNextMessageForAnyHost());
+
+  // Navigation #5: Navigate to error page.
+  GURL error_url = embedded_test_server()->GetURL("/not-found");
+  ASSERT_FALSE(content::NavigateToURL(shell(), error_url));
+
+  // A new NavigationWebMessageSender with a new FakeWebMessageHost has been
+  // created for the new error page.
+  HostToken host3 = GetCurrentHostToken();
+
+  if (!base::FeatureList::IsEnabled(features::kBackForwardCache)) {
+    // The previous page will be deleted, unless it gets into the back/forward
+    // cache.
+    listener().WaitForNextMessageForHost(host2);
+    CheckNavigationMessage(listener().NextMessageForHost(host2),
+                           NavigationWebMessageSender::kPageDeletedMessage);
+  }
+
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host3),
+                                  /*url=*/error_url,
+                                  /*is_same_document=*/false,
+                                  /*is_page_initiated=*/false,
+                                  /*is_error_page=*/true,
+                                  /*is_reload=*/false,
+                                  /*is_history=*/false,
+                                  /*committed=*/true,
+                                  /*status_code=*/404);
+  CheckNavigationMessage(listener().NextMessageForHost(host3),
+                         NavigationWebMessageSender::kPageLoadEndMessage);
+
+  // No further messages.
+  ASSERT_FALSE(listener().HasNextMessageForAnyHost());
+}
+
+// Test navigation messages when navigating away from a page that hasn't fired
+// the load event.
+IN_PROC_BROWSER_TEST_F(NavigationListenerBrowserTest, NoLoadEnd) {
+  std::string page_with_infinite_loading_image = "/page_with_loading_image";
+  std::string infinite_loading_image = "/image";
+  net::test_server::ControllableHttpResponse page_request(
+      embedded_test_server(), page_with_infinite_loading_image);
+  net::test_server::ControllableHttpResponse image_request(
+      embedded_test_server(), infinite_loading_image);
+  ASSERT_TRUE(embedded_test_server()->Start());
+  SetupNavigationListener();
+  HostToken host0 = GetCurrentHostToken();
+  CheckNavigationMessage(listener().NextMessageForHost(host0),
+                         NavigationWebMessageSender::kOptedInMessage);
+
+  // Navigation #1: navigate to the first page, which has an infinitely loading
+  // image.
+  GURL url_with_infinite_load =
+      embedded_test_server()->GetURL(page_with_infinite_loading_image);
+  GURL image_url = embedded_test_server()->GetURL(infinite_loading_image);
+  ASSERT_TRUE(web_contents()->GetController().LoadURL(
+      url_with_infinite_load, content::Referrer(), ui::PAGE_TRANSITION_TYPED,
+      std::string()));
+  // Send the response for the navigation, but not for the image request. This
+  // will cause the page to not get the load event, since the image is still
+  // loading. Also, don't finish sending the page's body, so that the page won't
+  // get into BFCache (otherwise the test might be flaky with a PAGE_DELETED
+  // call that might or might not pop up depending on whether the
+  // DOMContentLoaded event arrived before we navigate away again or not).
+  page_request.WaitForRequest();
+  page_request.Send(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Type: text/html; charset=utf-8\r\n"
+      "\r\n"
+      "<html><body>"
+      "<script> window.onload = () => { document.title = 'loaded'; } </script>"
+      "<img src='" +
+      image_url.spec() + "'/>");
+  image_request.WaitForRequest();
+
+  // Wait for the next two navigation messages, after which we know that a new
+  // NavigationWebMessageSender should already be created.
+  listener().WaitForNextMessageForAnyHost();
+  // The first message will indicate that the previous page has been deleted.
+  CheckNavigationMessage(listener().NextMessageForHost(host0),
+                         NavigationWebMessageSender::kPageDeletedMessage);
+
+  // The next messages will be for the navigation above.
+  HostToken host = GetCurrentHostToken();
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host),
+                                  /*url=*/url_with_infinite_load,
+                                  /*is_same_document=*/false,
+                                  /*is_page_initiated=*/false,
+                                  /*is_error_page=*/false,
+                                  /*is_reload=*/false,
+                                  /*is_history=*/false,
+                                  /*committed=*/true,
+                                  /*status_code=*/200);
+
+  // Assert that there's no PAGE_LOAD_END message and the load event didn't fire
+  // in the document.
+  ASSERT_FALSE(listener().HasNextMessageForHost(host));
+  ASSERT_NE("loaded",
+            content::EvalJs(web_contents(), "document.title").ExtractString());
+
+  // Navigation #2: navigate to the second page before the first page finished
+  // loading.
+  GURL navigation_url_2 = embedded_test_server()->GetURL("/title2.html");
+  ASSERT_TRUE(web_contents()->GetController().LoadURL(
+      navigation_url_2, content::Referrer(), ui::PAGE_TRANSITION_TYPED,
+      std::string()));
+
+  // The previous page will be deleted, as the document body never fully loaded.
+  listener().WaitForNextMessageForHost(host);
+  CheckNavigationMessage(listener().NextMessageForHost(host),
+                         NavigationWebMessageSender::kPageDeletedMessage);
+
+  // No PAGE_LOAD_END messages for the previous page.
+  ASSERT_FALSE(listener().HasNextMessageForHost(host));
+
+  // Wait for the next navigation message, after which we know that a new
+  // NavigationWebMessageSender should already be created.
+  listener().WaitForNextMessageForAnyHost();
+  HostToken host2 = GetCurrentHostToken();
+  ASSERT_NE(host, host2);
+
+  // Assert that we got navigation messages for the second navigation.
+  listener().WaitForNextMessageForHost(host2);
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host2),
+                                  /*url=*/navigation_url_2,
+                                  /*is_same_document=*/false,
+                                  /*is_page_initiated=*/false,
+                                  /*is_error_page=*/false,
+                                  /*is_reload=*/false,
+                                  /*is_history=*/false,
+                                  /*committed=*/true,
+                                  /*status_code=*/200);
+  // The PAGE_LOAD_END should be for the second page.
+  listener().WaitForNextMessageForHost(host2);
+  CheckNavigationMessage(listener().NextMessageForHost(host2),
+                         NavigationWebMessageSender::kPageLoadEndMessage);
+
+  // No other messages after this.
+  ASSERT_FALSE(listener().HasNextMessageForAnyHost());
+}
+
+// Test navigation messages when a new renderer-initiated same-document
+// navigation happens while a cross-document navigation is ongoing and hasn't
+// received its network response. Both navigations should commit.
+IN_PROC_BROWSER_TEST_F(NavigationListenerBrowserTest,
+                       NewRendererInitiatedSameDocNavDuringCrossDocNav) {
+  std::string page_with_delayed_response = "/page_with_delayed_response";
+  net::test_server::ControllableHttpResponse request_to_delay(
+      embedded_test_server(), page_with_delayed_response);
+  ASSERT_TRUE(embedded_test_server()->Start());
+  SetupNavigationListener();
+
+  // Navigation #1: Navigate to the first page. This is needed because doing
+  // same-document navigations from the initial empty document behaves slightly
+  // differently from normal same-document navigations, so we want to avoid
+  // doing it from the initial empty document.
+  HostToken host = NavigateToFirstPage();
+
+  // Navigation #2: Navigate to another page, which will have a slightly delayed
+  // response, so that we can start another navigation before this one commits.
+  GURL navigation_url_2 =
+      embedded_test_server()->GetURL(page_with_delayed_response);
+
+  ASSERT_TRUE(web_contents()->GetController().LoadURL(
+      navigation_url_2, content::Referrer(), ui::PAGE_TRANSITION_TYPED,
+      std::string()));
+
+  // Before the navigation above finishes, do a same-document navigation.
+  ASSERT_TRUE(content::ExecJs(web_contents(), "location.hash = '#foo';"));
+  listener().WaitForNextMessageForHost(host);
+
+  // Check that the same-document navigation committed successfully.
+  GURL navigation_url_foo = embedded_test_server()->GetURL("/title1.html#foo");
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host),
+                                  /*url=*/navigation_url_foo,
+                                  /*is_same_document=*/true,
+                                  /*is_page_initiated=*/true,
+                                  /*is_error_page=*/false,
+                                  /*is_reload=*/false,
+                                  /*is_history=*/false,
+                                  /*committed=*/true,
+                                  /*status_code=*/200);
+  ASSERT_FALSE(listener().HasNextMessageForHost(host));
+
+  // Send the response for the cross-document navigation after the same-document
+  // navigation finished.
+  request_to_delay.WaitForRequest();
+  request_to_delay.Send(
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Type: text/html; charset=utf-8\r\n"
+      "\r\n"
+      "<html><body></body></html>");
+  request_to_delay.Done();
+  ASSERT_TRUE(content::WaitForLoadStop(web_contents()));
+
+  // A new NavigationWebMessageSender has been created for the new page.
+  HostToken host2 = GetCurrentHostToken();
+
+  if (!base::FeatureList::IsEnabled(features::kBackForwardCache)) {
+    // The previous page will be deleted, unless it gets into the back/forward
+    // cache.
+    listener().WaitForNextMessageForHost(host);
+    CheckNavigationMessage(listener().NextMessageForHost(host),
+                           NavigationWebMessageSender::kPageDeletedMessage);
+  }
+
+  // Check that the cross-document navigation committed successfully.
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host2),
+                                  /*url=*/navigation_url_2,
+                                  /*is_same_document=*/false,
+                                  /*is_page_initiated=*/false,
+                                  /*is_error_page=*/false,
+                                  /*is_reload=*/false,
+                                  /*is_history=*/false,
+                                  /*committed=*/true,
+                                  /*status_code=*/200);
+  CheckNavigationMessage(listener().NextMessageForHost(host2),
+                         NavigationWebMessageSender::kPageLoadEndMessage);
+  ASSERT_FALSE(listener().HasNextMessageForAnyHost());
+}
+
+// Test navigation messages when a new browser-initiated same-document
+// navigation happens while a cross-document navigation is ongoing and hasn't
+// received its network response. Different than above, the newer same-document
+// navigation should cancel the first navigation here, since the newer
+// NavigationHandle will take the place of the first NavigationHandle.
+IN_PROC_BROWSER_TEST_F(NavigationListenerBrowserTest,
+                       NewBrowserInitiatedSameDocNavDuringCrossDocNav) {
+  std::string page_with_delayed_response = "/page_with_delayed_response";
+  net::test_server::ControllableHttpResponse request_to_delay(
+      embedded_test_server(), page_with_delayed_response);
+  ASSERT_TRUE(embedded_test_server()->Start());
+  SetupNavigationListener();
+
+  // Navigation #1: Navigate to the first page. This is needed because doing
+  // same-document navigations from the initial empty document behaves slightly
+  // differently from normal same-document navigations, so we want to avoid
+  // doing it from the initial empty document.
+  HostToken host = NavigateToFirstPage();
+
+  // Navigation #2: Navigate to another page, which will have a slightly delayed
+  // response, so that we can start another navigation before this one finishes.
+  GURL navigation_url_2 =
+      embedded_test_server()->GetURL(page_with_delayed_response);
+  ASSERT_TRUE(web_contents()->GetController().LoadURL(
+      navigation_url_2, content::Referrer(), ui::PAGE_TRANSITION_TYPED,
+      std::string()));
+  // Ensure that the network request has been received.
+  request_to_delay.WaitForRequest();
+
+  // Before the navigation above finishes, do a same-document navigation.
+  GURL navigation_url_foo = embedded_test_server()->GetURL("/title1.html#foo");
+  ASSERT_TRUE(web_contents()->GetController().LoadURL(
+      navigation_url_foo, content::Referrer(), ui::PAGE_TRANSITION_TYPED,
+      std::string()));
+  listener().WaitForNextMessageForHost(host);
+
+  // Check that the slow cross-document navigation got canceled, because the new
+  // same-document navigation created a new NavigationHandle, deleting the
+  // earlier navigation's NavigationHandle.
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host),
+                                  /*url=*/navigation_url_2,
+                                  /*is_same_document=*/false,
+                                  /*is_page_initiated=*/false,
+                                  /*is_error_page=*/false,
+                                  /*is_reload=*/false,
+                                  /*is_history=*/false,
+                                  /*committed=*/false,
+                                  /*status_code=*/200);
+
+  listener().WaitForNextMessageForHost(host);
+  // Check that the same-document navigation committed successfully.
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host),
+                                  /*url=*/navigation_url_foo,
+                                  /*is_same_document=*/true,
+                                  /*is_page_initiated=*/false,
+                                  /*is_error_page=*/false,
+                                  /*is_reload=*/false,
+                                  /*is_history=*/false,
+                                  /*committed=*/true,
+                                  /*status_code=*/200);
+
+  ASSERT_TRUE(content::WaitForLoadStop(web_contents()));
+  ASSERT_FALSE(listener().HasNextMessageForAnyHost());
+}
+
+// Test navigation messages when a new cross-document navigation happens while
+// an earlier cross-document navigation is ongoing and hasn't received its
+// network response. The newer navigation should cancel the first navigation
+// here, since the newer NavigationHandle will take the place of the first
+// NavigationHandle. This behavior will be the same regardless of whether the
+// newer navigation is browser- or renderer-initiated.
+IN_PROC_BROWSER_TEST_F(NavigationListenerBrowserTest,
+                       NewCrossDocNavDuringCrossDocNav) {
+  std::string page_with_delayed_response = "/page_with_delayed_response";
+  net::test_server::ControllableHttpResponse request_to_delay(
+      embedded_test_server(), page_with_delayed_response);
+  ASSERT_TRUE(embedded_test_server()->Start());
+  SetupNavigationListener();
+  HostToken host = GetCurrentHostToken();
+  CheckNavigationMessage(listener().NextMessageForHost(host),
+                         NavigationWebMessageSender::kOptedInMessage);
+
+  // Navigate to another page, which will have a slightly delayed response, so
+  // that we can start another navigation before this one finishes.
+  GURL navigation_url_2 =
+      embedded_test_server()->GetURL(page_with_delayed_response);
+  ASSERT_TRUE(web_contents()->GetController().LoadURL(
+      navigation_url_2, content::Referrer(), ui::PAGE_TRANSITION_TYPED,
+      std::string()));
+  // Ensure that the network request has been received.
+  request_to_delay.WaitForRequest();
+
+  // Before the navigation above finishes, do another cross-document navigation.
+  GURL navigation_url_3 = embedded_test_server()->GetURL("/title2.html");
+  ASSERT_TRUE(web_contents()->GetController().LoadURL(
+      navigation_url_3, content::Referrer(), ui::PAGE_TRANSITION_TYPED,
+      std::string()));
+  listener().WaitForNextMessageForHost(host);
+
+  // Check that the earlier cross-document navigation got canceled, because the
+  // new navigation created a new NavigationHandle, deleting the earlier
+  // navigation's NavigationHandle.
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host),
+                                  /*url=*/navigation_url_2,
+                                  /*is_same_document=*/false,
+                                  /*is_page_initiated=*/false,
+                                  /*is_error_page=*/false,
+                                  /*is_reload=*/false,
+                                  /*is_history=*/false,
+                                  /*committed=*/false,
+                                  /*status_code=*/200);
+
+  // Wait for the newer navigation to finish.
+  ASSERT_TRUE(content::WaitForLoadStop(web_contents()));
+
+  // A new NavigationWebMessageSender has been created for the new page.
+  HostToken host2 = GetCurrentHostToken();
+
+  // Check that the newer cross-document navigation committed successfully.
+  CheckNavigationMessage(listener().NextMessageForHost(host),
+                         NavigationWebMessageSender::kPageDeletedMessage);
+  CheckNavigationCompletedMessage(listener().NextMessageForHost(host2),
+                                  /*url=*/navigation_url_3,
+                                  /*is_same_document=*/false,
+                                  /*is_page_initiated=*/false,
+                                  /*is_error_page=*/false,
+                                  /*is_reload=*/false,
+                                  /*is_history=*/false,
+                                  /*committed=*/true,
+                                  /*status_code=*/200);
+  CheckNavigationMessage(listener().NextMessageForHost(host2),
+                         NavigationWebMessageSender::kPageLoadEndMessage);
+
+  ASSERT_FALSE(listener().HasNextMessageForAnyHost());
+}
+
+}  // namespace js_injection
diff --git a/components/js_injection/browser/navigation_web_message_sender.cc b/components/js_injection/browser/navigation_web_message_sender.cc
new file mode 100644
index 0000000..aefd263
--- /dev/null
+++ b/components/js_injection/browser/navigation_web_message_sender.cc
@@ -0,0 +1,145 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/js_injection/browser/navigation_web_message_sender.h"
+
+#include "base/json/json_writer.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/js_injection/browser/web_message.h"
+#include "components/js_injection/browser/web_message_host.h"
+#include "components/js_injection/browser/web_message_host_factory.h"
+#include "components/js_injection/browser/web_message_reply_proxy.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/page.h"
+#include "content/public/browser/web_contents.h"
+#include "net/http/http_response_headers.h"
+
+namespace features {
+BASE_FEATURE(kEnableNavigationListener,
+             "EnableNavigationListener",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+}  // namespace features
+
+namespace js_injection {
+
+// An empty WebMessageReplyProxy used as a placeholder as there is no
+// connection to the renderer. This object is 1:1 with the Page, so it
+// can be used by the app to identify which Page an injected navigation
+// message is associated with.
+class EmptyReplyProxy : public WebMessageReplyProxy {
+ public:
+  explicit EmptyReplyProxy(content::Page& page) : page_(&page) {}
+  EmptyReplyProxy(const EmptyReplyProxy&) = delete;
+  EmptyReplyProxy& operator=(const EmptyReplyProxy&) = delete;
+  ~EmptyReplyProxy() override = default;
+
+  // WebMessageReplyProxy:
+  void PostWebMessage(blink::WebMessagePayload message) override {
+    // Do nothing as there is no connection to the renderer.
+  }
+  content::Page& GetPage() override { return *page_; }
+
+ private:
+  raw_ptr<content::Page> page_;
+};
+
+const char16_t NavigationWebMessageSender::kNavigationListenerObjectName[] =
+    u"experimentalWebViewNavigationListener";
+
+const char NavigationWebMessageSender::kOptedInMessage[] =
+    "NAVIGATION_MESSAGE_OPTED_IN";
+const char NavigationWebMessageSender::kNavigationCompletedMessage[] =
+    "NAVIGATION_COMPLETED";
+const char NavigationWebMessageSender::kPageLoadEndMessage[] = "PAGE_LOAD_END";
+const char NavigationWebMessageSender::kPageDeletedMessage[] = "PAGE_DELETED";
+
+NavigationWebMessageSender::NavigationWebMessageSender(
+    content::Page& page,
+    WebMessageHostFactory* factory)
+    : content::PageUserData<NavigationWebMessageSender>(page),
+      content::WebContentsObserver(
+          content::WebContents::FromRenderFrameHost(&page.GetMainDocument())) {
+  CHECK(base::FeatureList::IsEnabled(features::kEnableNavigationListener));
+  CHECK(page.IsPrimary());
+  const std::string origin_string =
+      page.GetMainDocument().GetLastCommittedOrigin().Serialize();
+  reply_proxy_ = std::make_unique<EmptyReplyProxy>(page);
+  host_ = factory->CreateHost(origin_string, origin_string,
+                              /*is_main_frame=*/true, reply_proxy_.get());
+}
+
+NavigationWebMessageSender::~NavigationWebMessageSender() {
+  CHECK(!page().GetMainDocument().IsInLifecycleState(
+      content::RenderFrameHost::LifecycleState::kPendingCommit));
+  PostMessageWithType(kPageDeletedMessage);
+}
+
+void NavigationWebMessageSender::DispatchOptInMessage() {
+  CHECK(page().IsPrimary());
+  PostMessageWithType(kOptedInMessage);
+}
+
+void NavigationWebMessageSender::DidFinishLoad(
+    content::RenderFrameHost* render_frame_host,
+    const GURL& validated_url) {
+  if (!page().IsPrimary() || render_frame_host != &page().GetMainDocument()) {
+    // Only send the load notifications for the primary main frame.
+    return;
+  }
+  PostMessageWithType(kPageLoadEndMessage);
+}
+
+void NavigationWebMessageSender::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!page().IsPrimary() || !navigation_handle->IsInPrimaryMainFrame()) {
+    // Only send navigation notifications for primary pages, and only from the
+    // associated NavigationWebMessageSender. Note that since
+    // `IsInPrimaryMainFrame()` can also be true when the navigation didn't
+    // commit / create a new page, it means that the messages for those
+    // navigations will be fired on the sender of the current primary page.
+    return;
+  }
+  base::Value::Dict message_dict =
+      base::Value::Dict()
+          .Set("type", kNavigationCompletedMessage)
+          .Set("url", navigation_handle->GetURL().spec())
+          .Set("isSameDocument", navigation_handle->IsSameDocument())
+          .Set("isPageInitiated", navigation_handle->IsRendererInitiated())
+          .Set("isErrorPage", navigation_handle->IsErrorPage())
+          .Set("isReload",
+               navigation_handle->GetReloadType() != content::ReloadType::NONE)
+          .Set("isHistory", navigation_handle->IsHistory())
+          .Set("committed", navigation_handle->HasCommitted())
+          // Some navigations don't have HTTP responses. Default to 200 for
+          // those cases.
+          .Set("statusCode",
+               navigation_handle->GetResponseHeaders()
+                   ? navigation_handle->GetResponseHeaders()->response_code()
+                   : 200);
+  PostMessage(std::move(message_dict));
+}
+
+std::unique_ptr<WebMessage> NavigationWebMessageSender::CreateWebMessage(
+    base::Value::Dict message_dict) {
+  base::Value message(std::move(message_dict));
+  std::string json_message;
+  base::JSONWriter::Write(message, &json_message);
+  std::unique_ptr<WebMessage> web_message = std::make_unique<WebMessage>();
+  web_message->message = base::UTF8ToUTF16(json_message);
+  return web_message;
+}
+
+void NavigationWebMessageSender::PostMessageWithType(std::string_view type) {
+  base::Value::Dict message_dict;
+  message_dict.Set("type", type);
+  PostMessage(std::move(message_dict));
+}
+
+void NavigationWebMessageSender::PostMessage(base::Value::Dict message_dict) {
+  host_->OnPostMessage(CreateWebMessage(std::move(message_dict)));
+}
+
+PAGE_USER_DATA_KEY_IMPL(NavigationWebMessageSender);
+
+}  // namespace js_injection
diff --git a/components/js_injection/browser/navigation_web_message_sender.h b/components/js_injection/browser/navigation_web_message_sender.h
new file mode 100644
index 0000000..499b806
--- /dev/null
+++ b/components/js_injection/browser/navigation_web_message_sender.h
@@ -0,0 +1,107 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_JS_INJECTION_BROWSER_NAVIGATION_WEB_MESSAGE_SENDER_H_
+#define COMPONENTS_JS_INJECTION_BROWSER_NAVIGATION_WEB_MESSAGE_SENDER_H_
+
+#include <string>
+
+#include "base/values.h"
+#include "content/public/browser/page_user_data.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace content {
+class NavigationHandle;
+class Page;
+}  // namespace content
+
+namespace features {
+
+// Enable creation of this class for special navigation listeners.
+BASE_DECLARE_FEATURE(kEnableNavigationListener);
+
+}  // namespace features
+
+namespace js_injection {
+
+class EmptyReplyProxy;
+class WebMessageHost;
+class WebMessageHostFactory;
+struct WebMessage;
+
+// A special interceptor for "navigation listener" WebMessageListeners where
+// instead of establishing a connection with the renderer, a navigation observer
+// is created on the browser, to inject navigation-related notification
+// messages to the embedder.
+//
+// The NavigationWebMessageSender is 1:1 with Page, and navigation messages
+// related to a certain Page will be sent by the NavigationWebMessageSender
+// associated with that Page.
+// See https://crbug.com/332809183 on why this is needed.
+class NavigationWebMessageSender
+    : public content::PageUserData<NavigationWebMessageSender>,
+      public content::WebContentsObserver {
+ public:
+  static const char16_t kNavigationListenerObjectName[];
+
+  // === Various messages that can be dispatched to the client ===
+
+  // Only dispatched once globally, when the first NavigationWebMessageSender is
+  // created. This indicates to the client that the special navigation listeners
+  // are implemented (it might not be available in older versions).
+  static const char kOptedInMessage[];
+  // Indicates that a navigation has completed. The message will contain details
+  // like the URL, whether the navigation is same-document or not, etc. This is
+  // dispatched on `DidFinishNavigation()`.
+  static const char kNavigationCompletedMessage[];
+  // Indicates that the page has finished loading. This is dispatched on
+  // `DidFinishLoad()`.
+  static const char kPageLoadEndMessage[];
+  // Indicates that the page has been deleted. This is dispatched from the class
+  // destructor, since this is a PageUserData. If the page is BFCached, this
+  // will be when the page is evicted. Otherwise, it will be when the primary
+  // Page changed after a cross-document navigation away from this apge.
+  static const char kPageDeletedMessage[];
+
+  ~NavigationWebMessageSender() override;
+
+  void DispatchOptInMessage();
+
+ private:
+  friend class PageUserData<NavigationWebMessageSender>;
+  friend class NavigationListenerBrowserTest;
+  FRIEND_TEST_ALL_PREFIXES(NavigationListenerBrowserTest, Basic);
+  FRIEND_TEST_ALL_PREFIXES(NavigationListenerBrowserTest, NoLoadEnd);
+  FRIEND_TEST_ALL_PREFIXES(NavigationListenerBrowserTest,
+                           NewRendererInitiatedSameDocNavDuringCrossDocNav);
+  FRIEND_TEST_ALL_PREFIXES(NavigationListenerBrowserTest,
+                           NewBrowserInitiatedSameDocNavDuringCrossDocNav);
+  FRIEND_TEST_ALL_PREFIXES(NavigationListenerBrowserTest,
+                           NewCrossDocNavDuringCrossDocNav);
+
+  static std::unique_ptr<WebMessage> CreateWebMessage(
+      base::Value::Dict message_dict);
+
+  NavigationWebMessageSender(content::Page& page,
+                             WebMessageHostFactory* factory);
+
+  // content::WebContentsObserver implementations
+  void DidFinishLoad(content::RenderFrameHost* render_frame_host,
+                     const GURL& validated_url) override;
+  void DidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override;
+
+  void PostMessageWithType(std::string_view type);
+  void PostMessage(base::Value::Dict message_dict);
+
+  WebMessageHost* GetWebMessageHostForTesting() { return host_.get(); }
+
+  std::unique_ptr<EmptyReplyProxy> reply_proxy_;
+  std::unique_ptr<WebMessageHost> host_;
+  PAGE_USER_DATA_KEY_DECL();
+};
+
+}  // namespace js_injection
+
+#endif  // COMPONENTS_JS_INJECTION_BROWSER_NAVIGATION_WEB_MESSAGE_SENDER_H_
diff --git a/components/optimization_guide/core/model_execution/session_impl.cc b/components/optimization_guide/core/model_execution/session_impl.cc
index 92a55523..1abe998 100644
--- a/components/optimization_guide/core/model_execution/session_impl.cc
+++ b/components/optimization_guide/core/model_execution/session_impl.cc
@@ -531,6 +531,9 @@
 
 void SessionImpl::OnComplete(
     on_device_model::mojom::ResponseSummaryPtr summary) {
+  // Stop timer, just in case we didn't already via OnResponse().
+  on_device_state_->timer_for_first_response.Stop();
+
   base::TimeDelta time_to_completion =
       base::TimeTicks::Now() - on_device_state_->start;
   base::UmaHistogramMediumTimes(
@@ -651,24 +654,6 @@
 void SessionImpl::SendResponse(
     ResponseType response_type,
     const std::string& safety_check_text) {
-  on_device_state_->timer_for_first_response.Stop();
-
-  proto::OnDeviceModelServiceResponse* logged_response =
-      on_device_state_->MutableLoggedResponse();
-
-  logged_response->set_output_string(on_device_state_->current_response);
-
-  std::string redacted_response = on_device_state_->current_response;
-  auto redact_result =
-      on_device_state_->opts.adapter->Redact(*last_message_, redacted_response);
-  if (redact_result == RedactResult::kReject) {
-    logged_response->set_status(
-        proto::ON_DEVICE_MODEL_SERVICE_RESPONSE_STATUS_RETRACTED);
-    CancelPendingResponse(ExecuteModelResult::kContainedPII,
-                          ModelExecutionError::kFiltered);
-    return;
-  }
-
   const bool is_complete = response_type != ResponseType::kPartial;
   const bool is_unsupported_language =
       on_device_state_->opts.safety_cfg
@@ -676,7 +661,7 @@
               on_device_state_->current_safety_info);
   const bool is_unsafe = on_device_state_->opts.safety_cfg.IsUnsafeText(
       on_device_state_->current_safety_info);
-  if (is_unsafe || is_complete) {
+  if (is_unsafe || is_unsupported_language || is_complete) {
     on_device_state_->AddTextSafetyExecutionLogging(
       safety_check_text, on_device_state_->current_safety_info,
       is_unsafe);
@@ -697,6 +682,22 @@
     }
   }
 
+  proto::OnDeviceModelServiceResponse* logged_response =
+      on_device_state_->MutableLoggedResponse();
+
+  logged_response->set_output_string(on_device_state_->current_response);
+
+  std::string redacted_response = on_device_state_->current_response;
+  auto redact_result =
+      on_device_state_->opts.adapter->Redact(*last_message_, redacted_response);
+  if (redact_result == RedactResult::kReject) {
+    logged_response->set_status(
+        proto::ON_DEVICE_MODEL_SERVICE_RESPONSE_STATUS_RETRACTED);
+    CancelPendingResponse(ExecuteModelResult::kContainedPII,
+                          ModelExecutionError::kFiltered);
+    return;
+  }
+
   auto output = on_device_state_->opts.adapter->ConstructOutputMetadata(
       redacted_response);
   if (!output) {
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index b82843b..54c1c4d 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit b82843b98be70e1c4f8af58b22f418142f341954
+Subproject commit 54c1c4d2bfa106bef94a8026a4562243ac89dfbd
diff --git a/components/password_manager/core/browser/features/password_features.cc b/components/password_manager/core/browser/features/password_features.cc
index ebb8f206..b03c206 100644
--- a/components/password_manager/core/browser/features/password_features.cc
+++ b/components/password_manager/core/browser/features/password_features.cc
@@ -74,17 +74,21 @@
              base::FEATURE_DISABLED_BY_DEFAULT);
 #endif
 
-// TODO(crbug.com/330686628): Keep disabled for Android when enabling on Desktop
-// and iOS.
 BASE_FEATURE(kPasswordManagerEnableReceiverService,
              "PasswordManagerEnableReceiverService",
+#if BUILDFLAG(IS_ANDROID)
              base::FEATURE_DISABLED_BY_DEFAULT);
+#else
+             base::FEATURE_ENABLED_BY_DEFAULT);
+#endif  // BUILDFLAG(IS_ANDROID)
 
-// TODO(crbug.com/330686628): Keep disabled for Android when enabling on Desktop
-// and iOS.
 BASE_FEATURE(kPasswordManagerEnableSenderService,
              "PasswordManagerEnableSenderService",
+#if BUILDFLAG(IS_ANDROID)
              base::FEATURE_DISABLED_BY_DEFAULT);
+#else
+             base::FEATURE_ENABLED_BY_DEFAULT);
+#endif  // BUILDFLAG(IS_ANDROID)
 
 BASE_FEATURE(kPasswordManagerLogToTerminal,
              "PasswordManagerLogToTerminal",
@@ -106,7 +110,7 @@
 
 BASE_FEATURE(kSharedPasswordNotificationUI,
              "SharedPasswordNotificationUI",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kSkipUndecryptablePasswords,
              "SkipUndecryptablePasswords",
diff --git a/components/sync/engine/sync_scheduler_impl.cc b/components/sync/engine/sync_scheduler_impl.cc
index 6d2efe6..2533d161 100644
--- a/components/sync/engine/sync_scheduler_impl.cc
+++ b/components/sync/engine/sync_scheduler_impl.cc
@@ -56,6 +56,7 @@
     case THROTTLED:
     case TRANSIENT_ERROR:
     case PARTIAL_FAILURE:
+    case UNKNOWN_ERROR:
       return false;
     case NOT_MY_BIRTHDAY:
     case CLIENT_DATA_OBSOLETE:
@@ -67,11 +68,6 @@
       // waiting forever. So assert we would send something.
       DCHECK_NE(error.action, UNKNOWN_ACTION);
       return true;
-    case UNKNOWN_ERROR:
-      // TODO(crbug.com/40691256): This NOTREACHED is questionable because the
-      // sync server can cause it.
-      NOTREACHED();
-      return false;
     case CONFLICT:
     case INVALID_MESSAGE:
       NOTREACHED();
diff --git a/components/viz/test/test_gles2_interface.cc b/components/viz/test/test_gles2_interface.cc
index 2fe96da..81fc3a6 100644
--- a/components/viz/test/test_gles2_interface.cc
+++ b/components/viz/test/test_gles2_interface.cc
@@ -375,13 +375,6 @@
   }
 }
 
-GLuint TestGLES2Interface::CreateAndConsumeTextureCHROMIUM(
-    const GLbyte* mailbox) {
-  GLuint texture_id;
-  GenTextures(1, &texture_id);
-  return texture_id;
-}
-
 GLuint TestGLES2Interface::CreateAndTexStorage2DSharedImageCHROMIUM(
     const GLbyte* mailbox) {
   GLuint texture_id;
diff --git a/components/viz/test/test_gles2_interface.h b/components/viz/test/test_gles2_interface.h
index c5df5dc..d2e62c0 100644
--- a/components/viz/test/test_gles2_interface.h
+++ b/components/viz/test/test_gles2_interface.h
@@ -83,7 +83,6 @@
   void EndQueryEXT(GLenum target) override;
   void GetQueryObjectuivEXT(GLuint id, GLenum pname, GLuint* params) override;
 
-  GLuint CreateAndConsumeTextureCHROMIUM(const GLbyte* mailbox) override;
   GLuint CreateAndTexStorage2DSharedImageCHROMIUM(
       const GLbyte* mailbox) override;
 
diff --git a/content/browser/android/web_contents_observer_proxy.cc b/content/browser/android/web_contents_observer_proxy.cc
index 9968aa7..efef072e 100644
--- a/content/browser/android/web_contents_observer_proxy.cc
+++ b/content/browser/android/web_contents_observer_proxy.cc
@@ -13,6 +13,8 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/trace_event/trace_event.h"
 #include "content/browser/android/navigation_handle_proxy.h"
+#include "content/browser/media/session/media_session_android.h"
+#include "content/browser/media/session/media_session_impl.h"
 #include "content/browser/renderer_host/navigation_request.h"
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
@@ -334,4 +336,13 @@
   Java_WebContentsObserverProxy_onWebContentsLostFocus(env, java_observer_);
 }
 
+void WebContentsObserverProxy::MediaSessionCreated(MediaSession* session) {
+  JNIEnv* env = AttachCurrentThread();
+  Java_WebContentsObserverProxy_mediaSessionCreated(
+      env, java_observer_,
+      static_cast<MediaSessionImpl*>(session)
+          ->GetMediaSessionAndroid()
+          ->GetJavaObject());
+}
+
 }  // namespace content
diff --git a/content/browser/android/web_contents_observer_proxy.h b/content/browser/android/web_contents_observer_proxy.h
index df8d68e..069cfe1 100644
--- a/content/browser/android/web_contents_observer_proxy.h
+++ b/content/browser/android/web_contents_observer_proxy.h
@@ -16,6 +16,7 @@
 
 namespace content {
 
+class MediaSession;
 class WebContents;
 class RenderFrameHost;
 
@@ -79,6 +80,7 @@
   void VirtualKeyboardModeChanged(ui::mojom::VirtualKeyboardMode mode) override;
   void OnWebContentsFocused(RenderWidgetHost*) override;
   void OnWebContentsLostFocus(RenderWidgetHost*) override;
+  void MediaSessionCreated(MediaSession* media_session) override;
 
   base::android::ScopedJavaGlobalRef<jobject> java_observer_;
   GURL base_url_of_last_started_data_url_;
diff --git a/content/browser/devtools/network_service_devtools_observer.cc b/content/browser/devtools/network_service_devtools_observer.cc
index c4db6be..94be7c1 100644
--- a/content/browser/devtools/network_service_devtools_observer.cc
+++ b/content/browser/devtools/network_service_devtools_observer.cc
@@ -13,7 +13,6 @@
 #include "content/public/common/content_client.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "services/network/public/mojom/http_raw_headers.mojom.h"
-#include "services/network/public/mojom/shared_dictionary_error.mojom.h"
 #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
 
 namespace content {
@@ -348,98 +347,6 @@
       bundle_request_devtools_id);
 }
 
-namespace {
-
-protocol::String BuildSharedDictionaryError(
-    network::mojom::SharedDictionaryError write_error) {
-  using network::mojom::SharedDictionaryError;
-  namespace SharedDictionaryErrorEnum =
-      protocol::Audits::SharedDictionaryErrorEnum;
-  switch (write_error) {
-    case SharedDictionaryError::kUseErrorCrossOriginNoCorsRequest:
-      return SharedDictionaryErrorEnum::UseErrorCrossOriginNoCorsRequest;
-    case SharedDictionaryError::kUseErrorDictionaryLoadFailure:
-      return SharedDictionaryErrorEnum::UseErrorDictionaryLoadFailure;
-    case SharedDictionaryError::kUseErrorMatchingDictionaryNotUsed:
-      return SharedDictionaryErrorEnum::UseErrorMatchingDictionaryNotUsed;
-    case SharedDictionaryError::kUseErrorUnexpectedContentDictionaryHeader:
-      return SharedDictionaryErrorEnum::
-          UseErrorUnexpectedContentDictionaryHeader;
-    case SharedDictionaryError::kWriteErrorAlreadyRegistered:
-      NOTREACHED_NORETURN();
-    case SharedDictionaryError::kWriteErrorCossOriginNoCorsRequest:
-      return SharedDictionaryErrorEnum::WriteErrorCossOriginNoCorsRequest;
-    case SharedDictionaryError::kWriteErrorDisallowedBySettings:
-      return SharedDictionaryErrorEnum::WriteErrorDisallowedBySettings;
-    case SharedDictionaryError::kWriteErrorExpiredResponse:
-      return SharedDictionaryErrorEnum::WriteErrorExpiredResponse;
-    case SharedDictionaryError::kWriteErrorFeatureDisabled:
-      return SharedDictionaryErrorEnum::WriteErrorFeatureDisabled;
-    case SharedDictionaryError::kWriteErrorInsufficientResources:
-      return SharedDictionaryErrorEnum::WriteErrorInsufficientResources;
-    case SharedDictionaryError::kWriteErrorInvalidMatchField:
-      return SharedDictionaryErrorEnum::WriteErrorInvalidMatchField;
-    case SharedDictionaryError::kWriteErrorInvalidStructuredHeader:
-      return SharedDictionaryErrorEnum::WriteErrorInvalidStructuredHeader;
-    case SharedDictionaryError::kWriteErrorNavigationRequest:
-      return SharedDictionaryErrorEnum::WriteErrorNavigationRequest;
-    case SharedDictionaryError::kWriteErrorNoMatchField:
-      return SharedDictionaryErrorEnum::WriteErrorNoMatchField;
-    case SharedDictionaryError::kWriteErrorNonListMatchDestField:
-      return SharedDictionaryErrorEnum::WriteErrorNonListMatchDestField;
-    case SharedDictionaryError::kWriteErrorNonSecureContext:
-      return SharedDictionaryErrorEnum::WriteErrorNonSecureContext;
-    case SharedDictionaryError::kWriteErrorNonStringIdField:
-      return SharedDictionaryErrorEnum::WriteErrorNonStringIdField;
-    case SharedDictionaryError::kWriteErrorNonStringInMatchDestList:
-      return SharedDictionaryErrorEnum::WriteErrorNonStringInMatchDestList;
-    case SharedDictionaryError::kWriteErrorNonStringMatchField:
-      return SharedDictionaryErrorEnum::WriteErrorNonStringMatchField;
-    case SharedDictionaryError::kWriteErrorNonTokenTypeField:
-      return SharedDictionaryErrorEnum::WriteErrorNonTokenTypeField;
-    case SharedDictionaryError::kWriteErrorRequestAborted:
-      return SharedDictionaryErrorEnum::WriteErrorRequestAborted;
-    case SharedDictionaryError::kWriteErrorShuttingDown:
-      return SharedDictionaryErrorEnum::WriteErrorShuttingDown;
-    case SharedDictionaryError::kWriteErrorTooLongIdField:
-      return SharedDictionaryErrorEnum::WriteErrorTooLongIdField;
-    case SharedDictionaryError::kWriteErrorUnsupportedType:
-      return SharedDictionaryErrorEnum::WriteErrorUnsupportedType;
-  }
-}
-
-}  // namespace
-
-void NetworkServiceDevToolsObserver::OnSharedDictionaryError(
-    const std::string& devtool_request_id,
-    const GURL& url,
-    network::mojom::SharedDictionaryError error) {
-  RenderFrameHostImpl* rfhi = GetRenderFrameHostImplFrom(frame_tree_node_id_);
-  if (!rfhi) {
-    return;
-  }
-  auto affected_request = protocol::Audits::AffectedRequest::Create()
-                              .SetRequestId(devtool_request_id)
-                              .SetUrl(url.spec())
-                              .Build();
-  auto shared_dictionary_issue_details =
-      protocol::Audits::SharedDictionaryIssueDetails::Create()
-          .SetSharedDictionaryError(BuildSharedDictionaryError(error))
-          .SetRequest(std::move(affected_request))
-          .Build();
-  auto details = protocol::Audits::InspectorIssueDetails::Create()
-                     .SetSharedDictionaryIssueDetails(
-                         std::move(shared_dictionary_issue_details))
-                     .Build();
-  auto issue =
-      protocol::Audits::InspectorIssue::Create()
-          .SetCode(
-              protocol::Audits::InspectorIssueCodeEnum::SharedDictionaryIssue)
-          .SetDetails(std::move(details))
-          .Build();
-  devtools_instrumentation::ReportBrowserInitiatedIssue(rfhi, issue.get());
-}
-
 void NetworkServiceDevToolsObserver::Clone(
     mojo::PendingReceiver<network::mojom::DevToolsObserver> observer) {
   mojo::MakeSelfOwnedReceiver(
diff --git a/content/browser/devtools/network_service_devtools_observer.h b/content/browser/devtools/network_service_devtools_observer.h
index 7c51af0..857d1691 100644
--- a/content/browser/devtools/network_service_devtools_observer.h
+++ b/content/browser/devtools/network_service_devtools_observer.h
@@ -97,10 +97,6 @@
       const GURL& url,
       const std::string& error_message,
       const std::optional<std::string>& bundle_request_devtools_id) override;
-  void OnSharedDictionaryError(
-      const std::string& devtool_request_id,
-      const GURL& url,
-      network::mojom::SharedDictionaryError error) override;
   void Clone(mojo::PendingReceiver<network::mojom::DevToolsObserver> listener)
       override;
 
diff --git a/content/browser/interest_group/auction_runner_unittest.cc b/content/browser/interest_group/auction_runner_unittest.cc
index bc31ac33..b457d7a 100644
--- a/content/browser/interest_group/auction_runner_unittest.cc
+++ b/content/browser/interest_group/auction_runner_unittest.cc
@@ -36,6 +36,8 @@
 #include "base/time/time.h"
 #include "base/uuid.h"
 #include "build/build_config.h"
+#include "components/cbor/values.h"
+#include "components/cbor/writer.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/browser/fenced_frame/fenced_frame_reporter.h"
 #include "content/browser/interest_group/ad_auction_page_data.h"
@@ -93,6 +95,7 @@
 #include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom-shared.h"
 #include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h"
 #include "third_party/blink/public/mojom/private_aggregation/private_aggregation_host.mojom.h"
+#include "third_party/zlib/google/compression_utils.h"
 
 using auction_worklet::TestDevToolsAgentClient;
 using testing::HasSubstr;
@@ -21668,6 +21671,227 @@
   }
 }
 
+TEST_F(AuctionRunnerTest, MatcheReportingIdsInServerResponse) {
+  const struct {
+    std::string test_name;
+    std::string winner_name;
+    std::optional<std::string> response_buyer_reporting_id;
+    std::optional<std::string> response_buyer_and_seller_reporting_id;
+    std::vector<std::string> errors;
+    AuctionResult result;
+  } kTestCases[] = {
+      {
+          "Response no reportingId, winner no reportingId",
+          "NoReportingIds",
+          std::nullopt,
+          std::nullopt,
+          {},
+          AuctionResult::kSuccess,
+      },
+      {
+          "Response no reportingId, winner buyerReportingId",
+          "BuyerReportingId",
+          std::nullopt,
+          std::nullopt,
+          {},
+          AuctionResult::kSuccess,
+      },
+      {
+          "Response no reportingId, winner buyerAndSellerReportingId",
+          "BuyerAndSellerReportingId",
+          std::nullopt,
+          std::nullopt,
+          {},
+          AuctionResult::kSuccess,
+      },
+      {
+          "Response buyerReportingId, winner no reportingId",
+          "NoReportingIds",
+          "BuyerReportingId",
+          std::nullopt,
+          {"runAdAuction(): Couldn't reconstruct winning bid"},
+          AuctionResult::kInvalidServerResponse,
+      },
+      {
+          "Response buyerReportingId, winner buyerReportingId",
+          "BuyerReportingId",
+          "BuyerReportingId",
+          std::nullopt,
+          {},
+          AuctionResult::kSuccess,
+      },
+      {
+          // If server provided buyerReportingId to reporting, we should drop
+          // the bid because the server processed reporting with a different
+          // reportingId.
+          "Response buyerReportingId, winner buyerAndSellerReportingId",
+          "BuyerAndSellerReportingId",
+          "BuyerReportingId",
+          std::nullopt,
+          {"runAdAuction(): Couldn't reconstruct winning bid"},
+          AuctionResult::kInvalidServerResponse,
+      },
+      {
+          "Response misspelled buyerReportingId, winner buyerReportingId",
+          "BuyerReportingId",
+          "buyerreportingid",
+          std::nullopt,
+          {"runAdAuction(): Couldn't reconstruct winning bid"},
+          AuctionResult::kInvalidServerResponse,
+      },
+      {
+          "Response buyerAndSellerReportingId, winner no reportingId",
+          "NoReportingIds",
+          std::nullopt,
+          "BuyerAndSellerReportingId",
+          {"runAdAuction(): Couldn't reconstruct winning bid"},
+          AuctionResult::kInvalidServerResponse,
+      },
+      {
+          // If server provided buyerAndSellerReportingId to reporting, we
+          // should drop the bid because the server processed reporting with a
+          // different reportingId.
+          "Response buyerAndSellerReportingId, winner buyerReportingId",
+          "BuyerReportingId",
+          std::nullopt,
+          "BuyerAndSellerReportingId",
+          {"runAdAuction(): Couldn't reconstruct winning bid"},
+          AuctionResult::kInvalidServerResponse,
+      },
+      {
+          "Response buyerAndSellerReportingId, winner "
+          "buyerAndSellerReportingId",
+          "BuyerAndSellerReportingId",
+          std::nullopt,
+          "BuyerAndSellerReportingId",
+          {},
+          AuctionResult::kSuccess,
+      },
+      {
+          "Response misspelled buyerAndSellerReportingId, winner "
+          "buyerAndSellerReportingId",
+          "BuyerAndSellerReportingId",
+          std::nullopt,
+          "buyerandsellerreportingid",
+          {"runAdAuction(): Couldn't reconstruct winning bid"},
+          AuctionResult::kInvalidServerResponse,
+      },
+  };
+
+  for (const auto& test_case : kTestCases) {
+    SCOPED_TRACE(test_case.test_name);
+    base::HistogramTester hist;
+
+    cbor::Value::MapValue response_map;
+    response_map[cbor::Value("adRenderURL")] = cbor::Value("https://ad1.com");
+    response_map[cbor::Value("interestGroupName")] =
+        cbor::Value(test_case.winner_name);
+    response_map[cbor::Value("interestGroupOwner")] =
+        cbor::Value(kBidder1.Serialize());
+
+    if (test_case.response_buyer_reporting_id) {
+      response_map[cbor::Value("buyerReportingId")] =
+          cbor::Value(test_case.response_buyer_reporting_id.value());
+    }
+
+    if (test_case.response_buyer_and_seller_reporting_id) {
+      response_map[cbor::Value("buyerAndSellerReportingId")] =
+          cbor::Value(test_case.response_buyer_and_seller_reporting_id.value());
+    }
+
+    cbor::Value::MapValue bidding_groups_map;
+    cbor::Value::ArrayValue bidding_groups_array;
+    bidding_groups_array.emplace_back(0);
+    bidding_groups_array.emplace_back(1);
+    bidding_groups_array.emplace_back(2);
+    bidding_groups_map[cbor::Value(kBidder1.Serialize())] =
+        cbor::Value(std::move(bidding_groups_array));
+
+    response_map[cbor::Value("biddingGroups")] =
+        cbor::Value(std::move(bidding_groups_map));
+
+    std::optional<std::vector<uint8_t>> response_vector =
+        cbor::Writer::Write(cbor::Value(std::move(response_map)));
+    ASSERT_TRUE(response_vector);
+    std::string unframed_response;
+    ASSERT_TRUE(
+        compression::GzipCompress(response_vector.value(), &unframed_response));
+
+    uint32_t request_size = unframed_response.size();
+    std::string response = {0x02, static_cast<char>(request_size >> 24),
+                            static_cast<char>(request_size >> 16),
+                            static_cast<char>(request_size >> 8),
+                            static_cast<char>(request_size >> 0)};
+
+    response += unframed_response;
+
+    const base::Uuid request_id = base::Uuid::GenerateRandomV4();
+    server_response_request_id_ = request_id;
+
+    quiche::ObliviousHttpRequest::Context client_context =
+        CreateBiddingAndAuctionEncryptionContext();
+    std::string encrypted_response =
+        quiche::ObliviousHttpResponse::CreateServerObliviousResponse(
+            response, client_context,
+            kBiddingAndAuctionEncryptionResponseMediaType)
+            ->EncapsulateAndSerialize();
+
+    ad_auction_page_data_ = PageUserData<AdAuctionPageData>::GetOrCreateForPage(
+        web_contents()->GetPrimaryPage());
+
+    std::string witnessed_hash = crypto::SHA256HashString(encrypted_response);
+    ad_auction_page_data_->AddAuctionResultWitnessForOrigin(kSeller,
+                                                            witnessed_hash);
+
+    AdAuctionRequestContext context(
+        kSeller,
+        {{kBidder1,
+          {"NoReportingIds", "BuyerReportingId", "BuyerAndSellerReportingId"}}},
+        std::move(client_context), base::TimeTicks::Now());
+    ad_auction_page_data_->RegisterAdAuctionRequestContext(request_id,
+                                                           std::move(context));
+
+    std::vector<StorageInterestGroup> bidders;
+    StorageInterestGroup no_reporting_ids_group = MakeInterestGroup(
+        kBidder1, "NoReportingIds", kBidder1Url,
+        /*trusted_bidding_signals_url=*/std::nullopt,
+        /*trusted_bidding_signals_keys=*/{}, GURL("https://ad1.com"),
+        /*ad_component_urls=*/std::nullopt);
+    bidders.emplace_back(std::move(no_reporting_ids_group));
+
+    StorageInterestGroup buyer_reporting_id_group = MakeInterestGroup(
+        kBidder1, "BuyerReportingId", kBidder1Url,
+        /*trusted_bidding_signals_url=*/std::nullopt,
+        /*trusted_bidding_signals_keys=*/{}, GURL("https://ad1.com"),
+        /*ad_component_urls=*/std::nullopt);
+    buyer_reporting_id_group.interest_group.ads.value()[0].buyer_reporting_id =
+        "BuyerReportingId";
+    bidders.emplace_back(std::move(buyer_reporting_id_group));
+
+    StorageInterestGroup buyer_and_seller_reporting_id_group =
+        MakeInterestGroup(kBidder1, "BuyerAndSellerReportingId", kBidder1Url,
+                          /*trusted_bidding_signals_url=*/std::nullopt,
+                          /*trusted_bidding_signals_keys=*/{},
+                          GURL("https://ad1.com"),
+                          /*ad_component_urls=*/std::nullopt);
+    buyer_and_seller_reporting_id_group.interest_group.ads.value()[0]
+        .buyer_and_seller_reporting_id = "BuyerAndSellerReportingId";
+    bidders.emplace_back(std::move(buyer_and_seller_reporting_id_group));
+
+    StartAuction(kSellerUrl, std::move(bidders));
+
+    abortable_ad_auction_->ResolvedAuctionAdResponsePromise(
+        blink::mojom::AuctionAdConfigAuctionId::NewMainAuction(0),
+        mojo_base::BigBuffer(
+            base::as_bytes(base::make_span(encrypted_response))));
+
+    auction_run_loop_->Run();
+    EXPECT_THAT(result_.errors, testing::ElementsAreArray(test_case.errors));
+    hist.ExpectUniqueSample("Ads.InterestGroup.ServerAuction.Result",
+                            test_case.result, 1);
+  }
+}
+
 TEST_F(AuctionRunnerTest,
        TrustedBiddingSignalsAdSlotParamGroupingFeatureDisabled) {
   base::test::ScopedFeatureList debug_features;
diff --git a/content/browser/interest_group/bidding_and_auction_response.cc b/content/browser/interest_group/bidding_and_auction_response.cc
index 52e0d91..567564d 100644
--- a/content/browser/interest_group/bidding_and_auction_response.cc
+++ b/content/browser/interest_group/bidding_and_auction_response.cc
@@ -194,6 +194,16 @@
   if (maybe_ad_metadata) {
     output.ad_metadata = *maybe_ad_metadata;
   }
+  std::string* maybe_buyer_reporting_id =
+      input_dict->FindString("buyerReportingId");
+  if (maybe_buyer_reporting_id) {
+    output.buyer_reporting_id = *maybe_buyer_reporting_id;
+  }
+  std::string* maybe_buyer_and_seller_reporting_id =
+      input_dict->FindString("buyerAndSellerReportingId");
+  if (maybe_buyer_and_seller_reporting_id) {
+    output.buyer_and_seller_reporting_id = *maybe_buyer_and_seller_reporting_id;
+  }
 
   output.result = AuctionResult::kSuccess;
   return std::move(output);
diff --git a/content/browser/interest_group/bidding_and_auction_response.h b/content/browser/interest_group/bidding_and_auction_response.h
index a088a6d..7a7c21c 100644
--- a/content/browser/interest_group/bidding_and_auction_response.h
+++ b/content/browser/interest_group/bidding_and_auction_response.h
@@ -67,6 +67,8 @@
   std::optional<blink::AdCurrency> bid_currency;
   std::optional<url::Origin> top_level_seller;
   std::optional<std::string> ad_metadata;
+  std::optional<std::string> buyer_reporting_id;
+  std::optional<std::string> buyer_and_seller_reporting_id;
 
   std::optional<std::string> error;
   // The Bidding and Auction server uses the top_level_seller_reporting field
diff --git a/content/browser/interest_group/bidding_and_auction_response_unittest.cc b/content/browser/interest_group/bidding_and_auction_response_unittest.cc
index e4fc087..4fc4c04 100644
--- a/content/browser/interest_group/bidding_and_auction_response_unittest.cc
+++ b/content/browser/interest_group/bidding_and_auction_response_unittest.cc
@@ -134,6 +134,14 @@
                      testing::Eq(other.get().score)),
       testing::Field("bid", &BiddingAndAuctionResponse::bid,
                      testing::Eq(other.get().bid)),
+      testing::Field("ad_metadata", &BiddingAndAuctionResponse::ad_metadata,
+                     testing::Eq(other.get().ad_metadata)),
+      testing::Field("buyer_reporting_id",
+                     &BiddingAndAuctionResponse::buyer_reporting_id,
+                     testing::Eq(other.get().buyer_reporting_id)),
+      testing::Field("buyer_and_seller_reporting_id",
+                     &BiddingAndAuctionResponse::buyer_and_seller_reporting_id,
+                     testing::Eq(other.get().buyer_and_seller_reporting_id)),
       testing::Field("error", &BiddingAndAuctionResponse::error,
                      testing::Eq(other.get().error)),
   };
@@ -618,6 +626,31 @@
             return response;
           }(),
       },
+      {
+          base::Value(CreateValidResponseDict().Set("adMetadata", "data")),
+          []() {
+            BiddingAndAuctionResponse response = CreateExpectedValidResponse();
+            response.ad_metadata = "data";
+            return response;
+          }(),
+      },
+      {
+          base::Value(CreateValidResponseDict().Set("buyerReportingId", "foo")),
+          []() {
+            BiddingAndAuctionResponse response = CreateExpectedValidResponse();
+            response.buyer_reporting_id = "foo";
+            return response;
+          }(),
+      },
+      {
+          base::Value(CreateValidResponseDict().Set("buyerAndSellerReportingId",
+                                                    "bar")),
+          []() {
+            BiddingAndAuctionResponse response = CreateExpectedValidResponse();
+            response.buyer_and_seller_reporting_id = "bar";
+            return response;
+          }(),
+      },
   };
   for (const auto& test_case : kTestCases) {
     SCOPED_TRACE(test_case.input.DebugString());
diff --git a/content/browser/interest_group/interest_group_auction.cc b/content/browser/interest_group/interest_group_auction.cc
index 4e725270..e61588e 100644
--- a/content/browser/interest_group/interest_group_auction.cc
+++ b/content/browser/interest_group/interest_group_auction.cc
@@ -1389,6 +1389,8 @@
       double bid,
       const std::optional<blink::AdCurrency>& bid_currency,
       const std::optional<std::string>& ad_metadata,
+      const std::optional<std::string>& buyer_reporting_id,
+      const std::optional<std::string>& buyer_and_seller_reporting_id,
       blink::AdDescriptor ad_descriptor,
       std::vector<blink::AdDescriptor> ad_component_descriptors) {
     CHECK_EQ(1u, bid_states_.size());
@@ -1411,6 +1413,17 @@
       // Bid render url must match the interest group.
       return nullptr;
     }
+    // Reporting IDs used by the server (if any) must match the ad on device.
+    if (buyer_and_seller_reporting_id &&
+        matching_ad->buyer_and_seller_reporting_id !=
+            *buyer_and_seller_reporting_id) {
+      return nullptr;
+    }
+    if (buyer_reporting_id &&
+        matching_ad->buyer_reporting_id != *buyer_reporting_id) {
+      return nullptr;
+    }
+
     for (const auto& ad_component_descriptor : ad_component_descriptors) {
       const blink::InterestGroup::Ad* matching_ad_component = FindMatchingAd(
           *interest_group.ad_components, bid_state->kanon_keys, interest_group,
@@ -5237,7 +5250,8 @@
       buyer_helpers_[0]->TryToCreateBidFromServerResponse(
           auction_worklet::mojom::BidRole::kUnenforcedKAnon,
           saved_response_->bid.value_or(0.0001), saved_response_->bid_currency,
-          saved_response_->ad_metadata,
+          saved_response_->ad_metadata, saved_response_->buyer_reporting_id,
+          saved_response_->buyer_and_seller_reporting_id,
           /*ad_descriptor=*/
           blink::AdDescriptor(saved_response_->ad_render_url),
           /*ad_component_descriptors=*/std::move(ad_components));
diff --git a/content/browser/media/session/media_session_android.cc b/content/browser/media/session/media_session_android.cc
index 8c0a97c..5a8a20d 100644
--- a/content/browser/media/session/media_session_android.cc
+++ b/content/browser/media/session/media_session_android.cc
@@ -68,10 +68,11 @@
   if (!contents)
     return ScopedJavaLocalRef<jobject>();
 
-  MediaSessionImpl* session = MediaSessionImpl::Get(contents);
-  DCHECK(session);
-  return MediaSessionAndroid::JavaObjectGetter::GetJavaObject(
-      session->GetMediaSessionAndroid());
+  MediaSessionImpl* session =
+      static_cast<MediaSessionImpl*>(MediaSessionImpl::GetIfExists(contents));
+  return session ? MediaSessionAndroid::JavaObjectGetter::GetJavaObject(
+                       session->GetMediaSessionAndroid())
+                 : nullptr;
 }
 
 void MediaSessionAndroid::MediaSessionInfoChanged(
diff --git a/content/browser/media/session/media_session_android.h b/content/browser/media/session/media_session_android.h
index de687e27..e2f3a3d 100644
--- a/content/browser/media/session/media_session_android.h
+++ b/content/browser/media/session/media_session_android.h
@@ -71,6 +71,7 @@
       const base::android::JavaParamRef<jobject>& j_obj);
 
  private:
+  friend class WebContentsObserverProxy;
   base::android::ScopedJavaLocalRef<jobject> GetJavaObject();
 
   // The linked Java object. The strong reference is hold by Java WebContensImpl
diff --git a/content/browser/navigation_browsertest.cc b/content/browser/navigation_browsertest.cc
index f50bc8c..615adcb6 100644
--- a/content/browser/navigation_browsertest.cc
+++ b/content/browser/navigation_browsertest.cc
@@ -4282,9 +4282,18 @@
 // (and only) frame of that SiteInstance. Deleting it usually causes proxies in
 // the same SiteInstanceGroup to be deleted, meaning the OpenURL IPC may never
 // be received.
+//
+// Fails on linux-bfcache-rel. See crbug.com/336671248.
+#if BUILDFLAG(IS_LINUX)
+#define MAYBE_FormSubmissionInRemoteFrameSenderDeletedBeforeReceivingOpenURL \
+  DISABLED_FormSubmissionInRemoteFrameSenderDeletedBeforeReceivingOpenURL
+#else
+#define MAYBE_FormSubmissionInRemoteFrameSenderDeletedBeforeReceivingOpenURL \
+  FormSubmissionInRemoteFrameSenderDeletedBeforeReceivingOpenURL
+#endif
 IN_PROC_BROWSER_TEST_F(
     NavigationBrowserTest,
-    FormSubmissionInRemoteFrameSenderDeletedBeforeReceivingOpenURL) {
+    MAYBE_FormSubmissionInRemoteFrameSenderDeletedBeforeReceivingOpenURL) {
   // We crash a renderer in the OpenURL interceptor.
   content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
   content::IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index a66bdf5..798de31 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -6938,6 +6938,10 @@
   return NavigationTypeUtils::IsSameDocument(common_params_->navigation_type);
 }
 
+bool NavigationRequest::IsHistory() const {
+  return NavigationTypeUtils::IsHistory(common_params_->navigation_type);
+}
+
 bool NavigationRequest::IsRestore() const {
   return NavigationTypeUtils::IsRestore(common_params_->navigation_type);
 }
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index 08c2d39..4c912bc7 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -360,6 +360,7 @@
   net::Error GetNetErrorCode() override;
   RenderFrameHostImpl* GetRenderFrameHost() const override;
   bool IsSameDocument() const override;
+  bool IsHistory() const override;
   bool HasCommitted() const override;
   bool IsErrorPage() const override;
   bool HasSubframeNavigationEntryCommitted() override;
diff --git a/content/browser/web_package/signed_exchange_request_handler_browsertest.cc b/content/browser/web_package/signed_exchange_request_handler_browsertest.cc
index b60e74f..35a62e4e 100644
--- a/content/browser/web_package/signed_exchange_request_handler_browsertest.cc
+++ b/content/browser/web_package/signed_exchange_request_handler_browsertest.cc
@@ -262,13 +262,7 @@
     : public testing::WithParamInterface<bool>,
       public SignedExchangeRequestHandlerBrowserTestBase {
  public:
-  SignedExchangeRequestHandlerBrowserTest() {
-    // TODO(crbug.com/334954143) Fix the AcceptLanguage tests when turning on
-    // the ReduceAcceptLanguage feature.
-    scoped_feature_list_.InitWithFeatures(
-        {}, {network::features::kReduceAcceptLanguage});
-    use_prefetch_ = GetParam();
-  }
+  SignedExchangeRequestHandlerBrowserTest() { use_prefetch_ = GetParam(); }
 
   SignedExchangeRequestHandlerBrowserTest(
       const SignedExchangeRequestHandlerBrowserTest&) = delete;
@@ -335,7 +329,6 @@
   }
 
   bool use_prefetch_ = false;
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_P(SignedExchangeRequestHandlerBrowserTest, Simple) {
diff --git a/content/common/download/mhtml_file_writer.mojom b/content/common/download/mhtml_file_writer.mojom
index 344bac79..4055972 100644
--- a/content/common/download/mhtml_file_writer.mojom
+++ b/content/common/download/mhtml_file_writer.mojom
@@ -4,6 +4,7 @@
 
 module content.mojom;
 
+import "mojo/public/mojom/base/byte_string.mojom";
 import "mojo/public/mojom/base/file.mojom";
 import "mojo/public/mojom/base/time.mojom";
 
@@ -69,10 +70,10 @@
   // generated using SHA256HashString function from crypto/sha2.h and hashing
   // |salt + url.spec()|.
   // This array MUST be sorted and contain no duplicates.
-  array<string> digests_of_uris_to_skip;
+  array<mojo_base.mojom.ByteString> digests_of_uris_to_skip;
 
   // Salt used for |digests_of_uris_to_skip|.
-  string salt;
+  mojo_base.mojom.ByteString salt;
 
   // Destination handle to write MHTML data to.
   MhtmlOutputHandle output_handle;
@@ -88,5 +89,5 @@
   // thread serializing the frame.
   SerializeAsMHTML(SerializeAsMHTMLParams params)
     =>  (MhtmlSaveStatus status,
-        array<string> digests_of_uris_to_skip);
+        array<mojo_base.mojom.ByteString> digests_of_uris_to_skip);
 };
diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java
index e630c5b..ee5ac07 100644
--- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java
+++ b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java
@@ -14,6 +14,7 @@
 import org.chromium.content_public.browser.GlobalRenderFrameHostId;
 import org.chromium.content_public.browser.LifecycleState;
 import org.chromium.content_public.browser.LoadCommittedDetails;
+import org.chromium.content_public.browser.MediaSession;
 import org.chromium.content_public.browser.NavigationHandle;
 import org.chromium.content_public.browser.WebContentsObserver;
 import org.chromium.ui.base.WindowAndroid;
@@ -491,6 +492,17 @@
 
     @Override
     @CalledByNative
+    public void mediaSessionCreated(MediaSession mediaSession) {
+        handleObserverCall();
+        Iterator<WebContentsObserver> observersIterator = mObservers.iterator();
+        for (; observersIterator.hasNext(); ) {
+            observersIterator.next().mediaSessionCreated(mediaSession);
+        }
+        finishObserverCall();
+    }
+
+    @Override
+    @CalledByNative
     public void destroy() {
         // Super destruction semantics (removing the observer from the
         // Java-based WebContents) are quite different, so we explicitly avoid
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java b/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java
index 8b73886..3b9e62d5 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java
@@ -232,6 +232,9 @@
     /** Called when the top level WindowAndroid changes. */
     public void onTopLevelNativeWindowChanged(@Nullable WindowAndroid windowAndroid) {}
 
+    /** Called when a MediaSession is created for the WebContents. */
+    public void mediaSessionCreated(MediaSession mediaSession) {}
+
     /** Stop observing the web contents and clean up associated references. */
     public void destroy() {
         if (mWebContents == null) return;
diff --git a/content/public/browser/navigation_handle.h b/content/public/browser/navigation_handle.h
index 285ed9d..8825809 100644
--- a/content/public/browser/navigation_handle.h
+++ b/content/public/browser/navigation_handle.h
@@ -334,6 +334,11 @@
   // * same page history navigation
   virtual bool IsSameDocument() const = 0;
 
+  // Whether the navigation is a history traversal navigation, which navigates
+  // to a pre-existing NavigationEntry. Note that this will return false for
+  // reloads, and return true for session restore navigations.
+  virtual bool IsHistory() const = 0;
+
   // Whether the navigation has encountered a server redirect or not.
   virtual bool WasServerRedirect() = 0;
 
diff --git a/content/public/test/mock_navigation_handle.h b/content/public/test/mock_navigation_handle.h
index e71d26c..39158ce 100644
--- a/content/public/test/mock_navigation_handle.h
+++ b/content/public/test/mock_navigation_handle.h
@@ -129,6 +129,10 @@
     return render_frame_host_;
   }
   bool IsSameDocument() const override { return is_same_document_; }
+  bool IsHistory() const override {
+    NOTIMPLEMENTED();
+    return false;
+  }
   MOCK_METHOD0(WasServerRedirect, bool());
   const std::vector<GURL>& GetRedirectChain() override {
     return redirect_chain_;
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 d8ebb4d..2e3ed81 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -481,7 +481,6 @@
 # Graphite was disabled on older NVIDIA drivers, and these tests fail under
 # ganesh.
 crbug.com/332731568 [ win nvidia graphite-disabled ] Pixel_Video_HEVC [ Failure ]
-crbug.com/332731568 [ win nvidia graphite-disabled ] Pixel_WebGPUCanvasOneCopyCapture [ Failure ]
 
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
diff --git a/docs/asan.md b/docs/asan.md
index 36c78d72..9839aac 100644
--- a/docs/asan.md
+++ b/docs/asan.md
@@ -200,6 +200,31 @@
 is_debug=false
 ```
 
+Running ASan applications on Android requires additional device setup. Chromium
+testing scripts take care of this, so testing works as expected:
+```shell
+build/android/test_runner.py instrumentation --test-apk ContentShellTest \
+    --test_data content:content/test/data/android/device_files -v -v -v \
+    --tool=asan --release
+```
+
+If the above step fails or to run stuff without Chromium testing script (ex.
+ContentShell.apk, or any third party apk or binary), device setup is needed:
+```shell
+tools/android/asan/third_party/asan_device_setup.sh \
+    --lib third_party/android_toolchain/ndk/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/*/lib/linux
+# wait a few seconds for the device to reload
+```
+It only needs to be run once per device. It is safe to run it multiple times.
+Examine the output to ensure that setup was successful (you may need to run
+`adb disable-verity` and restart the device first). When this is done, the
+device will run ASan apks as well as normal apks without any further setup.
+
+To run command-line tools (i.e. binaries), prefix them with `asanwrapper`:
+```shell
+adb shell /system/bin/asanwrapper /path/to/binary
+```
+
 Use `build/android/asan_symbolize.py` to symbolize stack from `adb logcat`. It
 needs the `--output-directory` argument and takes care of translating the device
 path to the unstripped binary in the output directory.
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index fc3996b4..6cc6ae8 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1942,6 +1942,7 @@
   OS_DIAGNOSTICS_REPLYTOROUTINEINQUIRY = 1880,
   USERSCRIPTS_GETWORLDCONFIGURATIONS = 1881,
   USERSCRIPTS_RESETWORLDCONFIGURATION = 1882,
+  AUTOTESTPRIVATE_WAITFORLOGINANIMATIONEND = 1883,
   // Last entry: Add new entries above, then run:
   // tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/common/api/runtime.json b/extensions/common/api/runtime.json
index 0c60676..58034df 100644
--- a/extensions/common/api/runtime.json
+++ b/extensions/common/api/runtime.json
@@ -473,7 +473,6 @@
         "name": "sendMessage",
         "type": "function",
         "nocompile": true,
-        "allowAmbiguousOptionalArguments": true,
         "description": "Sends a single message to event listeners within your extension or a different extension/app. Similar to $(ref:runtime.connect) but only sends a single message, with an optional response. If sending to your extension, the $(ref:runtime.onMessage) event will be fired in every frame of your extension (except for the sender's frame), or $(ref:runtime.onMessageExternal), if a different extension. Note that extensions cannot send messages to content scripts using this method. To send messages to content scripts, use $(ref:tabs.sendMessage).",
         "parameters": [
           {"type": "string", "name": "extensionId", "optional": true, "description": "The ID of the extension to send the message to. If omitted, the message will be sent to your own extension/app. Required if sending messages from a web page for <a href=\"/docs/extensions/manifest/externally_connectable\">web messaging</a>."},
diff --git a/extensions/common/api/test.json b/extensions/common/api/test.json
index 5562b24..4b58da9 100644
--- a/extensions/common/api/test.json
+++ b/extensions/common/api/test.json
@@ -223,7 +223,6 @@
         "name": "checkDeepEq",
         "type": "function",
         "nocompile": true,
-        "allowAmbiguousOptionalArguments": true,
         "parameters": [
           // These need to be optional because they can be null.
           {"type": "any", "name": "expected", "optional": true},
@@ -234,7 +233,6 @@
         "name": "assertEq",
         "type": "function",
         "nocompile": true,
-        "allowAmbiguousOptionalArguments": true,
         "parameters": [
           // These need to be optional because they can be null.
           {"type": "any", "name": "expected", "optional": true},
@@ -246,7 +244,6 @@
         "name": "assertNe",
         "type": "function",
         "nocompile": true,
-        "allowAmbiguousOptionalArguments": true,
         "parameters": [
           // These need to be optional because they can be null.
           {"type": "any", "name": "expected", "optional": true},
diff --git a/gpu/GLES2/extensions/CHROMIUM/CHROMIUM_texture_mailbox.txt b/gpu/GLES2/extensions/CHROMIUM/CHROMIUM_texture_mailbox.txt
deleted file mode 100644
index 65c99ce2..0000000
--- a/gpu/GLES2/extensions/CHROMIUM/CHROMIUM_texture_mailbox.txt
+++ /dev/null
@@ -1,80 +0,0 @@
-Name
-
-    CHROMIUM_texture_mailbox
-
-Name Strings
-
-    GL_CHROMIUM_texture_mailbox
-
-Version
-
-    Last Modified Date: March 18, 2024
-
-Dependencies
-
-    OpenGL ES 2.0 is required.
-
-Overview
-
-    This extension defines a way of sharing texture image data between texture
-    objects in different contexts where the contexts would not normally share
-    texture resources. One new function is exported.
-    glCreateAndConsumeTextureCHROMIUM associates the texture object referenced
-    by a mailbox name to a texture name.
-
-New Procedures and Functions
-
-    GLuint glCreateAndConsumeTextureCHROMIUM (const GLbyte *mailbox)
-
-    Returns a new texture name pointing to the texture object associated with
-    the mailbox name. Does not alter the texture bindings or alter the currently
-    bound texture in any way. All the contexts that have consumed the texture
-    object, as well as produced it share the texture object, as if the contexts
-    were part of the share group. The texture object is deleted once all
-    contexts have deleted the texture name associated with the texture object,
-    and detached it from all framebuffer objects as well as texture unit
-    bindings. See Appendix C of the OpenGL ES 2.0 specification for details
-    relative to visibility in one context of changes to the shared texture
-    object in another context.
-
-    If glCreateAndConsumeTextureCHROMIUM generates an error, the new texture
-    name remains unbound.  It is treated in the same way as a new texture name
-    returned by GenTextures.
-
-    INVALID_OPERATION is generated if <mailbox> is not associated with a texture
-    object.
-
-New Tokens
-
-    The size of a mailbox name in bytes.
-
-        GL_MAILBOX_SIZE_CHROMIUM                             16
-
-Errors
-
-    None.
-
-New Tokens
-
-    None.
-
-New State
-
-    None.
-
-Revision History
-
-    2011-04-25   Documented the extension
-    2013-05-23   Major revision in Produce/Consume semantics, introducing
-                 sharing.
-    2014-06-02   Added glProduceTextureDirectCHROMIUM and
-                 glCreateAndConsumeTextureCHROMIUM definitions.
-    2016-08-03   Allow unbinding mailbox using glProduceTextureDirectCHROMIUM.
-    2017-10-20   Remove glConsumeTextureCHROMIUM and update
-                 glCreateAndConsumeTextureCHROMIUM definition.
-    2017-11-20   Remove glProduceTextureCHROMIUM.  Removed <target> arguments
-                 from glProduceTextureDirectCHROMIUM and
-                 glCreateAndConsumeTextureCHROMIUM, and updated definition.
-    2018-06-11   Merge glGenMailboxCHROMIUM into glProduceTextureDirectCHROMIUM.
-                 Update documentation to reflect reality.
-    2024-03-18   Removed no-longer-used glProduceTextureDirectCHROMIUM.
diff --git a/gpu/GLES2/gl2chromium_autogen.h b/gpu/GLES2/gl2chromium_autogen.h
index f4b0aee..a8b21338 100644
--- a/gpu/GLES2/gl2chromium_autogen.h
+++ b/gpu/GLES2/gl2chromium_autogen.h
@@ -325,8 +325,6 @@
 #define glDrawElementsInstancedBaseVertexBaseInstanceANGLE \
   GLES2_GET_FUN(DrawElementsInstancedBaseVertexBaseInstanceANGLE)
 #define glVertexAttribDivisorANGLE GLES2_GET_FUN(VertexAttribDivisorANGLE)
-#define glCreateAndConsumeTextureCHROMIUM \
-  GLES2_GET_FUN(CreateAndConsumeTextureCHROMIUM)
 #define glBindUniformLocationCHROMIUM GLES2_GET_FUN(BindUniformLocationCHROMIUM)
 #define glTraceBeginCHROMIUM GLES2_GET_FUN(TraceBeginCHROMIUM)
 #define glTraceEndCHROMIUM GLES2_GET_FUN(TraceEndCHROMIUM)
diff --git a/gpu/GLES2/gl2extchromium.h b/gpu/GLES2/gl2extchromium.h
index ee25811..2981d42 100644
--- a/gpu/GLES2/gl2extchromium.h
+++ b/gpu/GLES2/gl2extchromium.h
@@ -21,15 +21,6 @@
 #ifndef GL_MAILBOX_SIZE_CHROMIUM
 #define GL_MAILBOX_SIZE_CHROMIUM 16
 #endif
-#ifdef GL_GLEXT_PROTOTYPES
-GL_APICALL GLuint GL_APIENTRY
-glCreateAndConsumeTextureCHROMIUM(const GLbyte* mailbox);
-#endif
-typedef void (GL_APIENTRYP PFNGLGENMAILBOXCHROMIUMPROC) (GLbyte* mailbox);
-typedef void (GL_APIENTRYP PFNGLPRODUCETEXTUREDIRECTCHROMIUMPROC) (
-    GLuint texture, GLenum target, const GLbyte* mailbox);
-typedef GLuint(GL_APIENTRYP PFNGLCREATEANDCONSUMETEXTURECHROMIUMPROC)(
-    const GLbyte* mailbox);
 #endif  /* GL_CHROMIUM_texture_mailbox */
 
 /* GL_CHROMIUM_pixel_transfer_buffer_object */
diff --git a/gpu/command_buffer/build_gles2_cmd_buffer.py b/gpu/command_buffer/build_gles2_cmd_buffer.py
index 03cd0d16..d031797 100755
--- a/gpu/command_buffer/build_gles2_cmd_buffer.py
+++ b/gpu/command_buffer/build_gles2_cmd_buffer.py
@@ -1856,20 +1856,6 @@
     'unit_test': False,
     'es3': True,
   },
-  'CreateAndConsumeTextureCHROMIUM': {
-    'type': 'NoCommand',
-    'extension': "CHROMIUM_texture_mailbox",
-    'trace_level': 2,
-  },
-  'CreateAndConsumeTextureINTERNAL': {
-    'decoder_func': 'DoCreateAndConsumeTextureINTERNAL',
-    'internal': True,
-    'type': 'PUT',
-    'count': 16,  # GL_MAILBOX_SIZE_CHROMIUM
-    'impl_func': False,
-    'unit_test': False,
-    'trace_level': 2,
-  },
   'ClearStencil': {
     'type': 'StateSet',
     'state': 'ClearStencil',
diff --git a/gpu/command_buffer/client/gles2_c_lib_autogen.h b/gpu/command_buffer/client/gles2_c_lib_autogen.h
index a91c0ab..2f2146f 100644
--- a/gpu/command_buffer/client/gles2_c_lib_autogen.h
+++ b/gpu/command_buffer/client/gles2_c_lib_autogen.h
@@ -1534,9 +1534,6 @@
 void GL_APIENTRY GLES2VertexAttribDivisorANGLE(GLuint index, GLuint divisor) {
   gles2::GetGLContext()->VertexAttribDivisorANGLE(index, divisor);
 }
-GLuint GL_APIENTRY GLES2CreateAndConsumeTextureCHROMIUM(const GLbyte* mailbox) {
-  return gles2::GetGLContext()->CreateAndConsumeTextureCHROMIUM(mailbox);
-}
 void GL_APIENTRY GLES2BindUniformLocationCHROMIUM(GLuint program,
                                                   GLint location,
                                                   const char* name) {
@@ -3068,11 +3065,6 @@
         reinterpret_cast<GLES2FunctionPointer>(glVertexAttribDivisorANGLE),
     },
     {
-        "glCreateAndConsumeTextureCHROMIUM",
-        reinterpret_cast<GLES2FunctionPointer>(
-            glCreateAndConsumeTextureCHROMIUM),
-    },
-    {
         "glBindUniformLocationCHROMIUM",
         reinterpret_cast<GLES2FunctionPointer>(glBindUniformLocationCHROMIUM),
     },
diff --git a/gpu/command_buffer/client/gles2_cmd_helper_autogen.h b/gpu/command_buffer/client/gles2_cmd_helper_autogen.h
index 917c841..9a9ac80 100644
--- a/gpu/command_buffer/client/gles2_cmd_helper_autogen.h
+++ b/gpu/command_buffer/client/gles2_cmd_helper_autogen.h
@@ -2890,18 +2890,6 @@
   }
 }
 
-void CreateAndConsumeTextureINTERNALImmediate(GLuint texture,
-                                              const GLbyte* mailbox) {
-  const uint32_t size =
-      gles2::cmds::CreateAndConsumeTextureINTERNALImmediate::ComputeSize();
-  gles2::cmds::CreateAndConsumeTextureINTERNALImmediate* c =
-      GetImmediateCmdSpaceTotalSize<
-          gles2::cmds::CreateAndConsumeTextureINTERNALImmediate>(size);
-  if (c) {
-    c->Init(texture, mailbox);
-  }
-}
-
 void BindUniformLocationCHROMIUMBucket(GLuint program,
                                        GLint location,
                                        uint32_t name_bucket_id) {
diff --git a/gpu/command_buffer/client/gles2_implementation.cc b/gpu/command_buffer/client/gles2_implementation.cc
index 029a579..dc2341e3 100644
--- a/gpu/command_buffer/client/gles2_implementation.cc
+++ b/gpu/command_buffer/client/gles2_implementation.cc
@@ -6843,24 +6843,6 @@
   CheckGLError();
 }
 
-GLuint GLES2Implementation::CreateAndConsumeTextureCHROMIUM(
-    const GLbyte* data) {
-  GPU_CLIENT_SINGLE_THREAD_CHECK();
-  GPU_CLIENT_LOG("[" << GetLogPrefix() << "] glCreateAndConsumeTextureCHROMIUM("
-                     << static_cast<const void*>(data) << ")");
-  const Mailbox& mailbox = *reinterpret_cast<const Mailbox*>(data);
-  DCHECK(mailbox.Verify()) << "CreateAndConsumeTextureCHROMIUM was passed a "
-                              "mailbox that was not generated via Mailbox "
-                              "creation functions.";
-  GLuint client_id;
-  GetIdHandler(SharedIdNamespaces::kTextures)->MakeIds(this, 0, 1, &client_id);
-  helper_->CreateAndConsumeTextureINTERNALImmediate(client_id, data);
-  if (share_group_->bind_generates_resource())
-    helper_->CommandBufferHelper::OrderingBarrier();
-  CheckGLError();
-  return client_id;
-}
-
 GLuint GLES2Implementation::CreateAndTexStorage2DSharedImageCHROMIUM(
     const GLbyte* mailbox_data) {
   GPU_CLIENT_SINGLE_THREAD_CHECK();
diff --git a/gpu/command_buffer/client/gles2_implementation_autogen.h b/gpu/command_buffer/client/gles2_implementation_autogen.h
index 5f398894..5c378cf2 100644
--- a/gpu/command_buffer/client/gles2_implementation_autogen.h
+++ b/gpu/command_buffer/client/gles2_implementation_autogen.h
@@ -1082,8 +1082,6 @@
 
 void VertexAttribDivisorANGLE(GLuint index, GLuint divisor) override;
 
-GLuint CreateAndConsumeTextureCHROMIUM(const GLbyte* mailbox) override;
-
 void BindUniformLocationCHROMIUM(GLuint program,
                                  GLint location,
                                  const char* name) override;
diff --git a/gpu/command_buffer/client/gles2_implementation_unittest.cc b/gpu/command_buffer/client/gles2_implementation_unittest.cc
index 7eb0325..6f3351a 100644
--- a/gpu/command_buffer/client/gles2_implementation_unittest.cc
+++ b/gpu/command_buffer/client/gles2_implementation_unittest.cc
@@ -3586,20 +3586,6 @@
   EXPECT_TRUE(NoCommandsWritten());
 }
 
-TEST_F(GLES2ImplementationTest, CreateAndConsumeTextureCHROMIUM) {
-  struct Cmds {
-    cmds::CreateAndConsumeTextureINTERNALImmediate cmd;
-    GLbyte data[GL_MAILBOX_SIZE_CHROMIUM];
-  };
-
-  Mailbox mailbox = Mailbox::GenerateLegacyMailboxForTesting();
-  Cmds expected;
-  expected.cmd.Init(kTexturesStartId, mailbox.name);
-  GLuint id = gl_->CreateAndConsumeTextureCHROMIUM(mailbox.name);
-  EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)));
-  EXPECT_EQ(kTexturesStartId, id);
-}
-
 TEST_F(GLES2ImplementationTest, CreateAndTexStorage2DSharedImageCHROMIUM) {
   struct Cmds {
     cmds::CreateAndTexStorage2DSharedImageINTERNALImmediate cmd;
diff --git a/gpu/command_buffer/client/gles2_interface_autogen.h b/gpu/command_buffer/client/gles2_interface_autogen.h
index 7ab3b4dd..692e8a8 100644
--- a/gpu/command_buffer/client/gles2_interface_autogen.h
+++ b/gpu/command_buffer/client/gles2_interface_autogen.h
@@ -809,7 +809,6 @@
     GLint basevertex,
     GLuint baseinstance) = 0;
 virtual void VertexAttribDivisorANGLE(GLuint index, GLuint divisor) = 0;
-virtual GLuint CreateAndConsumeTextureCHROMIUM(const GLbyte* mailbox) = 0;
 virtual void BindUniformLocationCHROMIUM(GLuint program,
                                          GLint location,
                                          const char* name) = 0;
diff --git a/gpu/command_buffer/client/gles2_interface_stub_autogen.h b/gpu/command_buffer/client/gles2_interface_stub_autogen.h
index 6c68574..c56b947 100644
--- a/gpu/command_buffer/client/gles2_interface_stub_autogen.h
+++ b/gpu/command_buffer/client/gles2_interface_stub_autogen.h
@@ -785,7 +785,6 @@
     GLint basevertex,
     GLuint baseinstance) override;
 void VertexAttribDivisorANGLE(GLuint index, GLuint divisor) override;
-GLuint CreateAndConsumeTextureCHROMIUM(const GLbyte* mailbox) override;
 void BindUniformLocationCHROMIUM(GLuint program,
                                  GLint location,
                                  const char* name) override;
diff --git a/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h b/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h
index fe37e8e8..c41935a 100644
--- a/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h
+++ b/gpu/command_buffer/client/gles2_interface_stub_impl_autogen.h
@@ -1047,10 +1047,6 @@
     GLuint /* baseinstance */) {}
 void GLES2InterfaceStub::VertexAttribDivisorANGLE(GLuint /* index */,
                                                   GLuint /* divisor */) {}
-GLuint GLES2InterfaceStub::CreateAndConsumeTextureCHROMIUM(
-    const GLbyte* /* mailbox */) {
-  return 0;
-}
 void GLES2InterfaceStub::BindUniformLocationCHROMIUM(GLuint /* program */,
                                                      GLint /* location */,
                                                      const char* /* name */) {}
diff --git a/gpu/command_buffer/client/gles2_trace_implementation_autogen.h b/gpu/command_buffer/client/gles2_trace_implementation_autogen.h
index 302775d..8249579 100644
--- a/gpu/command_buffer/client/gles2_trace_implementation_autogen.h
+++ b/gpu/command_buffer/client/gles2_trace_implementation_autogen.h
@@ -785,7 +785,6 @@
     GLint basevertex,
     GLuint baseinstance) override;
 void VertexAttribDivisorANGLE(GLuint index, GLuint divisor) override;
-GLuint CreateAndConsumeTextureCHROMIUM(const GLbyte* mailbox) override;
 void BindUniformLocationCHROMIUM(GLuint program,
                                  GLint location,
                                  const char* name) override;
diff --git a/gpu/command_buffer/client/gles2_trace_implementation_impl_autogen.h b/gpu/command_buffer/client/gles2_trace_implementation_impl_autogen.h
index 08a0f4d..78fe5ee 100644
--- a/gpu/command_buffer/client/gles2_trace_implementation_impl_autogen.h
+++ b/gpu/command_buffer/client/gles2_trace_implementation_impl_autogen.h
@@ -2195,13 +2195,6 @@
   gl_->VertexAttribDivisorANGLE(index, divisor);
 }
 
-GLuint GLES2TraceImplementation::CreateAndConsumeTextureCHROMIUM(
-    const GLbyte* mailbox) {
-  TRACE_EVENT_BINARY_EFFICIENT0("gpu",
-                                "GLES2Trace::CreateAndConsumeTextureCHROMIUM");
-  return gl_->CreateAndConsumeTextureCHROMIUM(mailbox);
-}
-
 void GLES2TraceImplementation::BindUniformLocationCHROMIUM(GLuint program,
                                                            GLint location,
                                                            const char* name) {
diff --git a/gpu/command_buffer/client/shared_image_interface.h b/gpu/command_buffer/client/shared_image_interface.h
index 5ffd7593..dcf6718 100644
--- a/gpu/command_buffer/client/shared_image_interface.h
+++ b/gpu/command_buffer/client/shared_image_interface.h
@@ -115,8 +115,7 @@
   // ClientSharedImage struct contains a mailbox that can be imported into said
   // APIs using their corresponding shared image functions (e.g.
   // GLES2Interface::CreateAndTexStorage2DSharedImageCHROMIUM or
-  // RasterInterface::CopySharedImage) or (deprecated) mailbox functions (e.g.
-  // GLES2Interface::CreateAndConsumeTextureCHROMIUM).
+  // RasterInterface::CopySharedImage).
   // The |SharedImageInterface| keeps ownership of the image until
   // |DestroySharedImage| is called or the interface itself is destroyed (e.g.
   // the GPU channel is lost).
@@ -219,8 +218,7 @@
   // Returns a mailbox that can be imported into said APIs using their
   // corresponding shared image functions (e.g.
   // GLES2Interface::CreateAndTexStorage2DSharedImageCHROMIUM or
-  // RasterInterface::CopySharedImage) or (deprecated) mailbox functions (e.g.
-  // GLES2Interface::CreateAndConsumeTextureCHROMIUM).
+  // RasterInterface::CopySharedImage).
   // The |SharedImageInterface| keeps ownership of the image until
   // |DestroySharedImage| is called or the interface itself is destroyed (e.g.
   // the GPU channel is lost).
@@ -302,8 +300,7 @@
   // Creates a swap chain.
   // Returns shared images for front and back buffers of a DXGI Swap Chain that
   // can be imported into GL command buffer using shared image functions (e.g.
-  // GLES2Interface::CreateAndTexStorage2DSharedImageCHROMIUM) or (deprecated)
-  // mailbox functions (e.g. GLES2Interface::CreateAndConsumeTextureCHROMIUM).
+  // GLES2Interface::CreateAndTexStorage2DSharedImageCHROMIUM).
   virtual SwapChainSharedImages CreateSwapChain(
       viz::SharedImageFormat format,
       const gfx::Size& size,
diff --git a/gpu/command_buffer/common/gles2_cmd_format_autogen.h b/gpu/command_buffer/common/gles2_cmd_format_autogen.h
index bff4777..253f7bb 100644
--- a/gpu/command_buffer/common/gles2_cmd_format_autogen.h
+++ b/gpu/command_buffer/common/gles2_cmd_format_autogen.h
@@ -14408,47 +14408,6 @@
 static_assert(offsetof(VertexAttribDivisorANGLE, divisor) == 8,
               "offset of VertexAttribDivisorANGLE divisor should be 8");
 
-struct CreateAndConsumeTextureINTERNALImmediate {
-  typedef CreateAndConsumeTextureINTERNALImmediate ValueType;
-  static const CommandId kCmdId = kCreateAndConsumeTextureINTERNALImmediate;
-  static const cmd::ArgFlags kArgFlags = cmd::kAtLeastN;
-  static const uint8_t cmd_flags = CMD_FLAG_SET_TRACE_LEVEL(2);
-
-  static uint32_t ComputeDataSize() {
-    return static_cast<uint32_t>(sizeof(GLbyte) * 16);
-  }
-
-  static uint32_t ComputeSize() {
-    return static_cast<uint32_t>(sizeof(ValueType) + ComputeDataSize());
-  }
-
-  void SetHeader() { header.SetCmdByTotalSize<ValueType>(ComputeSize()); }
-
-  void Init(GLuint _texture, const GLbyte* _mailbox) {
-    SetHeader();
-    texture = _texture;
-    memcpy(ImmediateDataAddress(this), _mailbox, ComputeDataSize());
-  }
-
-  void* Set(void* cmd, GLuint _texture, const GLbyte* _mailbox) {
-    static_cast<ValueType*>(cmd)->Init(_texture, _mailbox);
-    const uint32_t size = ComputeSize();
-    return NextImmediateCmdAddressTotalSize<ValueType>(cmd, size);
-  }
-
-  gpu::CommandHeader header;
-  uint32_t texture;
-};
-
-static_assert(sizeof(CreateAndConsumeTextureINTERNALImmediate) == 8,
-              "size of CreateAndConsumeTextureINTERNALImmediate should be 8");
-static_assert(
-    offsetof(CreateAndConsumeTextureINTERNALImmediate, header) == 0,
-    "offset of CreateAndConsumeTextureINTERNALImmediate header should be 0");
-static_assert(
-    offsetof(CreateAndConsumeTextureINTERNALImmediate, texture) == 4,
-    "offset of CreateAndConsumeTextureINTERNALImmediate texture should be 4");
-
 struct BindUniformLocationCHROMIUMBucket {
   typedef BindUniformLocationCHROMIUMBucket ValueType;
   static const CommandId kCmdId = kBindUniformLocationCHROMIUMBucket;
diff --git a/gpu/command_buffer/common/gles2_cmd_format_test_autogen.h b/gpu/command_buffer/common/gles2_cmd_format_test_autogen.h
index df555cb..a4189ea 100644
--- a/gpu/command_buffer/common/gles2_cmd_format_test_autogen.h
+++ b/gpu/command_buffer/common/gles2_cmd_format_test_autogen.h
@@ -4714,39 +4714,6 @@
   CheckBytesWrittenMatchesExpectedSize(next_cmd, sizeof(cmd));
 }
 
-TEST_F(GLES2FormatTest, CreateAndConsumeTextureINTERNALImmediate) {
-  const int kSomeBaseValueToTestWith = 51;
-  static GLbyte data[] = {
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 0),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 1),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 2),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 3),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 4),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 5),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 6),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 7),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 8),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 9),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 10),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 11),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 12),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 13),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 14),
-      static_cast<GLbyte>(kSomeBaseValueToTestWith + 15),
-  };
-  cmds::CreateAndConsumeTextureINTERNALImmediate& cmd =
-      *GetBufferAs<cmds::CreateAndConsumeTextureINTERNALImmediate>();
-  void* next_cmd = cmd.Set(&cmd, static_cast<GLuint>(11), data);
-  EXPECT_EQ(static_cast<uint32_t>(
-                cmds::CreateAndConsumeTextureINTERNALImmediate::kCmdId),
-            cmd.header.command);
-  EXPECT_EQ(sizeof(cmd) + RoundSizeToMultipleOfEntries(sizeof(data)),
-            cmd.header.size * 4u);
-  EXPECT_EQ(static_cast<GLuint>(11), cmd.texture);
-  CheckBytesWrittenMatchesExpectedSize(
-      next_cmd, sizeof(cmd) + RoundSizeToMultipleOfEntries(sizeof(data)));
-}
-
 TEST_F(GLES2FormatTest, BindUniformLocationCHROMIUMBucket) {
   cmds::BindUniformLocationCHROMIUMBucket& cmd =
       *GetBufferAs<cmds::BindUniformLocationCHROMIUMBucket>();
diff --git a/gpu/command_buffer/common/gles2_cmd_ids_autogen.h b/gpu/command_buffer/common/gles2_cmd_ids_autogen.h
index c15d14d4..7ed5a80 100644
--- a/gpu/command_buffer/common/gles2_cmd_ids_autogen.h
+++ b/gpu/command_buffer/common/gles2_cmd_ids_autogen.h
@@ -298,65 +298,64 @@
   OP(DrawElementsInstancedANGLE)                               /* 539 */ \
   OP(DrawElementsInstancedBaseVertexBaseInstanceANGLE)         /* 540 */ \
   OP(VertexAttribDivisorANGLE)                                 /* 541 */ \
-  OP(CreateAndConsumeTextureINTERNALImmediate)                 /* 542 */ \
-  OP(BindUniformLocationCHROMIUMBucket)                        /* 543 */ \
-  OP(TraceBeginCHROMIUM)                                       /* 544 */ \
-  OP(TraceEndCHROMIUM)                                         /* 545 */ \
-  OP(DiscardFramebufferEXTImmediate)                           /* 546 */ \
-  OP(LoseContextCHROMIUM)                                      /* 547 */ \
-  OP(DrawBuffersEXTImmediate)                                  /* 548 */ \
-  OP(DiscardBackbufferCHROMIUM)                                /* 549 */ \
-  OP(FlushDriverCachesCHROMIUM)                                /* 550 */ \
-  OP(SetActiveURLCHROMIUM)                                     /* 551 */ \
-  OP(ContextVisibilityHintCHROMIUM)                            /* 552 */ \
-  OP(BlendBarrierKHR)                                          /* 553 */ \
-  OP(BindFragDataLocationIndexedEXTBucket)                     /* 554 */ \
-  OP(BindFragDataLocationEXTBucket)                            /* 555 */ \
-  OP(GetFragDataIndexEXT)                                      /* 556 */ \
-  OP(InitializeDiscardableTextureCHROMIUM)                     /* 557 */ \
-  OP(UnlockDiscardableTextureCHROMIUM)                         /* 558 */ \
-  OP(LockDiscardableTextureCHROMIUM)                           /* 559 */ \
-  OP(WindowRectanglesEXTImmediate)                             /* 560 */ \
-  OP(CreateGpuFenceINTERNAL)                                   /* 561 */ \
-  OP(WaitGpuFenceCHROMIUM)                                     /* 562 */ \
-  OP(DestroyGpuFenceCHROMIUM)                                  /* 563 */ \
-  OP(SetReadbackBufferShadowAllocationINTERNAL)                /* 564 */ \
-  OP(FramebufferTextureMultiviewOVR)                           /* 565 */ \
-  OP(MaxShaderCompilerThreadsKHR)                              /* 566 */ \
-  OP(CreateAndTexStorage2DSharedImageINTERNALImmediate)        /* 567 */ \
-  OP(BeginSharedImageAccessDirectCHROMIUM)                     /* 568 */ \
-  OP(EndSharedImageAccessDirectCHROMIUM)                       /* 569 */ \
-  OP(ConvertRGBAToYUVAMailboxesINTERNALImmediate)              /* 570 */ \
-  OP(ConvertYUVAMailboxesToRGBINTERNALImmediate)               /* 571 */ \
-  OP(ConvertYUVAMailboxesToTextureINTERNALImmediate)           /* 572 */ \
-  OP(CopySharedImageINTERNALImmediate)                         /* 573 */ \
-  OP(CopySharedImageToTextureINTERNALImmediate)                /* 574 */ \
-  OP(ReadbackARGBImagePixelsINTERNAL)                          /* 575 */ \
-  OP(WritePixelsYUVINTERNAL)                                   /* 576 */ \
-  OP(EnableiOES)                                               /* 577 */ \
-  OP(DisableiOES)                                              /* 578 */ \
-  OP(BlendEquationiOES)                                        /* 579 */ \
-  OP(BlendEquationSeparateiOES)                                /* 580 */ \
-  OP(BlendFunciOES)                                            /* 581 */ \
-  OP(BlendFuncSeparateiOES)                                    /* 582 */ \
-  OP(ColorMaskiOES)                                            /* 583 */ \
-  OP(IsEnablediOES)                                            /* 584 */ \
-  OP(ProvokingVertexANGLE)                                     /* 585 */ \
-  OP(FramebufferMemorylessPixelLocalStorageANGLE)              /* 586 */ \
-  OP(FramebufferTexturePixelLocalStorageANGLE)                 /* 587 */ \
-  OP(FramebufferPixelLocalClearValuefvANGLEImmediate)          /* 588 */ \
-  OP(FramebufferPixelLocalClearValueivANGLEImmediate)          /* 589 */ \
-  OP(FramebufferPixelLocalClearValueuivANGLEImmediate)         /* 590 */ \
-  OP(BeginPixelLocalStorageANGLEImmediate)                     /* 591 */ \
-  OP(EndPixelLocalStorageANGLEImmediate)                       /* 592 */ \
-  OP(PixelLocalStorageBarrierANGLE)                            /* 593 */ \
-  OP(FramebufferPixelLocalStorageInterruptANGLE)               /* 594 */ \
-  OP(FramebufferPixelLocalStorageRestoreANGLE)                 /* 595 */ \
-  OP(GetFramebufferPixelLocalStorageParameterfvANGLE)          /* 596 */ \
-  OP(GetFramebufferPixelLocalStorageParameterivANGLE)          /* 597 */ \
-  OP(ClipControlEXT)                                           /* 598 */ \
-  OP(PolygonModeANGLE)                                         /* 599 */ \
-  OP(PolygonOffsetClampEXT)                                    /* 600 */
+  OP(BindUniformLocationCHROMIUMBucket)                        /* 542 */ \
+  OP(TraceBeginCHROMIUM)                                       /* 543 */ \
+  OP(TraceEndCHROMIUM)                                         /* 544 */ \
+  OP(DiscardFramebufferEXTImmediate)                           /* 545 */ \
+  OP(LoseContextCHROMIUM)                                      /* 546 */ \
+  OP(DrawBuffersEXTImmediate)                                  /* 547 */ \
+  OP(DiscardBackbufferCHROMIUM)                                /* 548 */ \
+  OP(FlushDriverCachesCHROMIUM)                                /* 549 */ \
+  OP(SetActiveURLCHROMIUM)                                     /* 550 */ \
+  OP(ContextVisibilityHintCHROMIUM)                            /* 551 */ \
+  OP(BlendBarrierKHR)                                          /* 552 */ \
+  OP(BindFragDataLocationIndexedEXTBucket)                     /* 553 */ \
+  OP(BindFragDataLocationEXTBucket)                            /* 554 */ \
+  OP(GetFragDataIndexEXT)                                      /* 555 */ \
+  OP(InitializeDiscardableTextureCHROMIUM)                     /* 556 */ \
+  OP(UnlockDiscardableTextureCHROMIUM)                         /* 557 */ \
+  OP(LockDiscardableTextureCHROMIUM)                           /* 558 */ \
+  OP(WindowRectanglesEXTImmediate)                             /* 559 */ \
+  OP(CreateGpuFenceINTERNAL)                                   /* 560 */ \
+  OP(WaitGpuFenceCHROMIUM)                                     /* 561 */ \
+  OP(DestroyGpuFenceCHROMIUM)                                  /* 562 */ \
+  OP(SetReadbackBufferShadowAllocationINTERNAL)                /* 563 */ \
+  OP(FramebufferTextureMultiviewOVR)                           /* 564 */ \
+  OP(MaxShaderCompilerThreadsKHR)                              /* 565 */ \
+  OP(CreateAndTexStorage2DSharedImageINTERNALImmediate)        /* 566 */ \
+  OP(BeginSharedImageAccessDirectCHROMIUM)                     /* 567 */ \
+  OP(EndSharedImageAccessDirectCHROMIUM)                       /* 568 */ \
+  OP(ConvertRGBAToYUVAMailboxesINTERNALImmediate)              /* 569 */ \
+  OP(ConvertYUVAMailboxesToRGBINTERNALImmediate)               /* 570 */ \
+  OP(ConvertYUVAMailboxesToTextureINTERNALImmediate)           /* 571 */ \
+  OP(CopySharedImageINTERNALImmediate)                         /* 572 */ \
+  OP(CopySharedImageToTextureINTERNALImmediate)                /* 573 */ \
+  OP(ReadbackARGBImagePixelsINTERNAL)                          /* 574 */ \
+  OP(WritePixelsYUVINTERNAL)                                   /* 575 */ \
+  OP(EnableiOES)                                               /* 576 */ \
+  OP(DisableiOES)                                              /* 577 */ \
+  OP(BlendEquationiOES)                                        /* 578 */ \
+  OP(BlendEquationSeparateiOES)                                /* 579 */ \
+  OP(BlendFunciOES)                                            /* 580 */ \
+  OP(BlendFuncSeparateiOES)                                    /* 581 */ \
+  OP(ColorMaskiOES)                                            /* 582 */ \
+  OP(IsEnablediOES)                                            /* 583 */ \
+  OP(ProvokingVertexANGLE)                                     /* 584 */ \
+  OP(FramebufferMemorylessPixelLocalStorageANGLE)              /* 585 */ \
+  OP(FramebufferTexturePixelLocalStorageANGLE)                 /* 586 */ \
+  OP(FramebufferPixelLocalClearValuefvANGLEImmediate)          /* 587 */ \
+  OP(FramebufferPixelLocalClearValueivANGLEImmediate)          /* 588 */ \
+  OP(FramebufferPixelLocalClearValueuivANGLEImmediate)         /* 589 */ \
+  OP(BeginPixelLocalStorageANGLEImmediate)                     /* 590 */ \
+  OP(EndPixelLocalStorageANGLEImmediate)                       /* 591 */ \
+  OP(PixelLocalStorageBarrierANGLE)                            /* 592 */ \
+  OP(FramebufferPixelLocalStorageInterruptANGLE)               /* 593 */ \
+  OP(FramebufferPixelLocalStorageRestoreANGLE)                 /* 594 */ \
+  OP(GetFramebufferPixelLocalStorageParameterfvANGLE)          /* 595 */ \
+  OP(GetFramebufferPixelLocalStorageParameterivANGLE)          /* 596 */ \
+  OP(ClipControlEXT)                                           /* 597 */ \
+  OP(PolygonModeANGLE)                                         /* 598 */ \
+  OP(PolygonOffsetClampEXT)                                    /* 599 */
 
 enum CommandId {
   kOneBeforeStartPoint =
diff --git a/gpu/command_buffer/gles2_cmd_buffer_functions.txt b/gpu/command_buffer/gles2_cmd_buffer_functions.txt
index 571e63b..d007d88 100644
--- a/gpu/command_buffer/gles2_cmd_buffer_functions.txt
+++ b/gpu/command_buffer/gles2_cmd_buffer_functions.txt
@@ -319,8 +319,6 @@
 GL_APICALL void         GL_APIENTRY glDrawElementsInstancedANGLE (GLenumDrawMode mode, GLsizei count, GLenumIndexType type, const void* indices, GLsizei primcount);
 GL_APICALL void         GL_APIENTRY glDrawElementsInstancedBaseVertexBaseInstanceANGLE (GLenumDrawMode mode, GLsizei count, GLenumIndexType type, const void* indices, GLsizei primcount, GLint basevertex, GLuint baseinstance);
 GL_APICALL void         GL_APIENTRY glVertexAttribDivisorANGLE (GLuint index, GLuint divisor);
-GL_APICALL GLuint       GL_APIENTRY glCreateAndConsumeTextureCHROMIUM (const GLbyte* mailbox);
-GL_APICALL void         GL_APIENTRY glCreateAndConsumeTextureINTERNAL (GLuint texture, const GLbyte* mailbox);
 GL_APICALL void         GL_APIENTRY glBindUniformLocationCHROMIUM (GLidProgram program, GLint location, const char* name);
 GL_APICALL void         GL_APIENTRY glTraceBeginCHROMIUM (const char* category_name, const char* trace_name);
 GL_APICALL void         GL_APIENTRY glTraceEndCHROMIUM (void);
diff --git a/gpu/command_buffer/service/dawn_context_provider.cc b/gpu/command_buffer/service/dawn_context_provider.cc
index 0fc773d..b3a44166 100644
--- a/gpu/command_buffer/service/dawn_context_provider.cc
+++ b/gpu/command_buffer/service/dawn_context_provider.cc
@@ -326,6 +326,13 @@
   }
   adapter_options.nextInChain = &toggles_desc;
 
+#if BUILDFLAG(IS_ANDROID)
+  if (adapter_options.backendType == wgpu::BackendType::Vulkan) {
+    features.push_back(wgpu::FeatureName::StaticSamplers);
+    features.push_back(wgpu::FeatureName::YCbCrVulkanSamplers);
+  }
+#endif
+
 #if BUILDFLAG(IS_WIN)
   if (adapter_options.backendType == wgpu::BackendType::D3D11) {
     features.push_back(wgpu::FeatureName::D3D11MultithreadProtected);
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 2fa0edf..bfa2618e 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -1092,8 +1092,6 @@
                                    GLsizei width,
                                    GLsizei height);
 
-  void DoCreateAndConsumeTextureINTERNAL(GLuint client_id,
-                                         const volatile GLbyte* key);
   void DoCreateAndTexStorage2DSharedImageINTERNAL(
       GLuint client_id,
       const volatile GLbyte* mailbox);
@@ -16982,48 +16980,6 @@
                  ContextState::k3D, "glTexStorage3D");
 }
 
-void GLES2DecoderImpl::DoCreateAndConsumeTextureINTERNAL(
-    GLuint client_id,
-    const volatile GLbyte* data) {
-  TRACE_EVENT2("gpu", "GLES2DecoderImpl::DoCreateAndConsumeTextureINTERNAL",
-      "context", logger_.GetLogPrefix(),
-      "mailbox[0]", static_cast<unsigned char>(data[0]));
-  Mailbox mailbox =
-      Mailbox::FromVolatile(*reinterpret_cast<const volatile Mailbox*>(data));
-  DLOG_IF(ERROR, !mailbox.Verify()) << "CreateAndConsumeTextureCHROMIUM was "
-                                       "passed a mailbox that was not "
-                                       "generated by GenMailboxCHROMIUM.";
-  if (!client_id) {
-    LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION,
-                       "glCreateAndConsumeTextureCHROMIUM",
-                       "invalid client id");
-    return;
-  }
-
-  TextureRef* texture_ref = GetTexture(client_id);
-  if (texture_ref) {
-    // No need to create texture here, the client_id already has an associated
-    // texture.
-    LOCAL_SET_GL_ERROR(
-        GL_INVALID_OPERATION,
-        "glCreateAndConsumeTextureCHROMIUM", "client id already in use");
-    return;
-  }
-  Texture* texture =
-      Texture::CheckedCast(group_->mailbox_manager()->ConsumeTexture(mailbox));
-  if (!texture) {
-    // Create texture to handle invalid mailbox (see http://crbug.com/472465).
-    bool result = GenTexturesHelper(1, &client_id);
-    DCHECK(result);
-    LOCAL_SET_GL_ERROR(
-        GL_INVALID_OPERATION,
-        "glCreateAndConsumeTextureCHROMIUM", "invalid mailbox name");
-    return;
-  }
-
-  texture_ref = texture_manager()->Consume(client_id, texture);
-}
-
 void GLES2DecoderImpl::DoCreateAndTexStorage2DSharedImageINTERNAL(
     GLuint client_id,
     const volatile GLbyte* mailbox_data) {
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h b/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h
index 5ff501a..02033f9a 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h
@@ -4971,29 +4971,6 @@
   return error::kNoError;
 }
 
-error::Error GLES2DecoderImpl::HandleCreateAndConsumeTextureINTERNALImmediate(
-    uint32_t immediate_data_size,
-    const volatile void* cmd_data) {
-  const volatile gles2::cmds::CreateAndConsumeTextureINTERNALImmediate& c =
-      *static_cast<const volatile gles2::cmds::
-                       CreateAndConsumeTextureINTERNALImmediate*>(cmd_data);
-  GLuint texture = static_cast<GLuint>(c.texture);
-  uint32_t mailbox_size;
-  if (!GLES2Util::ComputeDataSize<GLbyte, 16>(1, &mailbox_size)) {
-    return error::kOutOfBounds;
-  }
-  if (mailbox_size > immediate_data_size) {
-    return error::kOutOfBounds;
-  }
-  volatile const GLbyte* mailbox = GetImmediateDataAs<volatile const GLbyte*>(
-      c, mailbox_size, immediate_data_size);
-  if (mailbox == nullptr) {
-    return error::kOutOfBounds;
-  }
-  DoCreateAndConsumeTextureINTERNAL(texture, mailbox);
-  return error::kNoError;
-}
-
 error::Error GLES2DecoderImpl::HandleTraceEndCHROMIUM(
     uint32_t immediate_data_size,
     const volatile void* cmd_data) {
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doer_prototypes.h b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doer_prototypes.h
index f3662f9..590ff4b 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doer_prototypes.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doer_prototypes.h
@@ -894,8 +894,6 @@
     GLint basevertices,
     GLuint baseinstances);
 error::Error DoVertexAttribDivisorANGLE(GLuint index, GLuint divisor);
-error::Error DoCreateAndConsumeTextureINTERNAL(GLuint texture_client_id,
-                                               const volatile GLbyte* mailbox);
 error::Error DoBindUniformLocationCHROMIUM(GLuint program,
                                            GLint location,
                                            const char* name);
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
index 3a189b05..64effb9 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
@@ -4779,36 +4779,6 @@
   return error::kNoError;
 }
 
-error::Error GLES2DecoderPassthroughImpl::DoCreateAndConsumeTextureINTERNAL(
-    GLuint texture_client_id,
-    const volatile GLbyte* mailbox) {
-  if (!texture_client_id ||
-      resources_->texture_id_map.HasClientID(texture_client_id)) {
-    return error::kInvalidArguments;
-  }
-
-  const Mailbox& mb = Mailbox::FromVolatile(
-      *reinterpret_cast<const volatile Mailbox*>(mailbox));
-  scoped_refptr<TexturePassthrough> texture = TexturePassthrough::CheckedCast(
-      group_->mailbox_manager()->ConsumeTexture(mb));
-  if (texture == nullptr) {
-    // Create texture to handle invalid mailbox (see http://crbug.com/472465 and
-    // http://crbug.com/851878).
-    DoGenTextures(1, &texture_client_id);
-    InsertError(GL_INVALID_OPERATION, "Invalid mailbox name.");
-    return error::kNoError;
-  }
-
-  // Update id mappings
-  resources_->texture_id_map.RemoveClientID(texture_client_id);
-  resources_->texture_id_map.SetIDMapping(texture_client_id,
-                                          texture->service_id());
-  resources_->texture_object_map.RemoveClientID(texture_client_id);
-  resources_->texture_object_map.SetIDMapping(texture_client_id, texture);
-
-  return error::kNoError;
-}
-
 error::Error GLES2DecoderPassthroughImpl::DoBindUniformLocationCHROMIUM(
     GLuint program,
     GLint location,
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers_autogen.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers_autogen.cc
index a56053b..0af99e5 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers_autogen.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_handlers_autogen.cc
@@ -4247,33 +4247,6 @@
   return error::kNoError;
 }
 
-error::Error
-GLES2DecoderPassthroughImpl::HandleCreateAndConsumeTextureINTERNALImmediate(
-    uint32_t immediate_data_size,
-    const volatile void* cmd_data) {
-  const volatile gles2::cmds::CreateAndConsumeTextureINTERNALImmediate& c =
-      *static_cast<const volatile gles2::cmds::
-                       CreateAndConsumeTextureINTERNALImmediate*>(cmd_data);
-  GLuint texture = static_cast<GLuint>(c.texture);
-  uint32_t mailbox_size;
-  if (!GLES2Util::ComputeDataSize<GLbyte, 16>(1, &mailbox_size)) {
-    return error::kOutOfBounds;
-  }
-  if (mailbox_size > immediate_data_size) {
-    return error::kOutOfBounds;
-  }
-  volatile const GLbyte* mailbox = GetImmediateDataAs<volatile const GLbyte*>(
-      c, mailbox_size, immediate_data_size);
-  if (mailbox == nullptr) {
-    return error::kOutOfBounds;
-  }
-  error::Error error = DoCreateAndConsumeTextureINTERNAL(texture, mailbox);
-  if (error != error::kNoError) {
-    return error;
-  }
-  return error::kNoError;
-}
-
 error::Error GLES2DecoderPassthroughImpl::HandleTraceEndCHROMIUM(
     uint32_t immediate_data_size,
     const volatile void* cmd_data) {
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
index 29451758..3c5e7c0 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
@@ -2914,57 +2914,6 @@
   EXPECT_EQ(GL_INVALID_ENUM, GetGLError());
 }
 
-TEST_P(GLES2DecoderTest, CreateAndConsumeTextureCHROMIUMInvalidMailbox) {
-  // Attempt to consume the mailbox when no texture has been produced with it.
-  Mailbox mailbox = Mailbox::GenerateLegacyMailboxForTesting();
-  GLuint new_texture_id = kNewClientId;
-
-  EXPECT_CALL(*gl_, GenTextures(1, _))
-      .WillOnce(SetArgPointee<1>(kNewServiceId))
-      .RetiresOnSaturation();
-  EXPECT_CALL(*gl_, ActiveTexture(GL_TEXTURE1)).Times(1).RetiresOnSaturation();
-
-  auto& texture_cmd = *GetImmediateAs<cmds::ActiveTexture>();
-  texture_cmd.Init(GL_TEXTURE1);
-  EXPECT_EQ(error::kNoError, ExecuteCmd(texture_cmd));
-
-  auto& consume_cmd =
-      *GetImmediateAs<cmds::CreateAndConsumeTextureINTERNALImmediate>();
-  consume_cmd.Init(new_texture_id, mailbox.name);
-  EXPECT_EQ(error::kNoError,
-            ExecuteImmediateCmd(consume_cmd, sizeof(mailbox.name)));
-
-  // CreateAndConsumeTexture should fail if the mailbox isn't associated with a
-  // texture.
-  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
-
-  // Make sure the new client_id is associated with a texture ref even though
-  // CreateAndConsumeTexture failed.
-  TextureRef* texture_ref =
-      group().texture_manager()->GetTexture(new_texture_id);
-  ASSERT_TRUE(texture_ref != nullptr);
-  Texture* texture = texture_ref->texture();
-  // New texture should be unbound to a target.
-  EXPECT_TRUE(texture->target() == GL_NONE);
-  // New texture should have a valid service_id.
-  EXPECT_EQ(kNewServiceId, texture->service_id());
-}
-
-TEST_P(GLES2DecoderTest, CreateAndConsumeTextureCHROMIUMInvalidTexture) {
-  Mailbox mailbox = Mailbox::GenerateLegacyMailboxForTesting();
-
-  // Attempt to consume the mailbox with an invalid texture id.
-  GLuint new_texture_id = 0;
-  auto& consume_cmd =
-      *GetImmediateAs<cmds::CreateAndConsumeTextureINTERNALImmediate>();
-  consume_cmd.Init(new_texture_id, mailbox.name);
-  EXPECT_EQ(error::kNoError,
-            ExecuteImmediateCmd(consume_cmd, sizeof(mailbox.name)));
-
-  // CreateAndConsumeTexture should fail.
-  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
-}
-
 TEST_P(GLES2DecoderTest, CreateAndTexStorage2DSharedImageCHROMIUM) {
   MemoryTypeTracker memory_tracker(memory_tracker_.get());
   Mailbox mailbox = Mailbox::GenerateForSharedImage();
diff --git a/infra/config/generated/builders/build/ios-build-perf-developer/properties.json b/infra/config/generated/builders/build/ios-build-perf-developer/properties.json
index 0696d0c..77fdc1d 100644
--- a/infra/config/generated/builders/build/ios-build-perf-developer/properties.json
+++ b/infra/config/generated/builders/build/ios-build-perf-developer/properties.json
@@ -46,7 +46,7 @@
   },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
-    "jobs": 800,
+    "jobs": 640,
     "metrics_project": "chromium-reclient-metrics",
     "scandeps_server": true
   },
diff --git a/infra/config/generated/builders/build/mac-build-perf-developer/properties.json b/infra/config/generated/builders/build/mac-build-perf-developer/properties.json
index 177e8fc..14c88f1 100644
--- a/infra/config/generated/builders/build/mac-build-perf-developer/properties.json
+++ b/infra/config/generated/builders/build/mac-build-perf-developer/properties.json
@@ -43,7 +43,7 @@
   },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
-    "jobs": 800,
+    "jobs": 640,
     "metrics_project": "chromium-reclient-metrics",
     "scandeps_server": true
   },
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index 5b26a49..577a352 100644
--- a/infra/config/generated/testing/variants.pyl
+++ b/infra/config/generated/testing/variants.pyl
@@ -267,32 +267,32 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 126.0.6436.0',
+    'description': 'Run with ash-chrome version 126.0.6437.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v126.0.6436.0',
-          'revision': 'version:126.0.6436.0',
+          'location': 'lacros_version_skew_tests_v126.0.6437.0',
+          'revision': 'version:126.0.6437.0',
         },
       ],
     },
   },
   'LACROS_VERSION_SKEW_DEV': {
     'identifier': 'Lacros version skew testing ash dev',
-    'description': 'Run with ash-chrome version 125.0.6422.5',
+    'description': 'Run with ash-chrome version 125.0.6422.10',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v125.0.6422.5',
-          'revision': 'version:125.0.6422.5',
+          'location': 'lacros_version_skew_tests_v125.0.6422.10',
+          'revision': 'version:125.0.6422.10',
         },
       ],
     },
diff --git a/infra/config/subprojects/build/build.star b/infra/config/subprojects/build/build.star
index 830e35ee..99af560 100644
--- a/infra/config/subprojects/build/build.star
+++ b/infra/config/subprojects/build/build.star
@@ -628,7 +628,7 @@
         category = "mac",
         short_name = "dev",
     ),
-    reclient_jobs = 800,
+    reclient_jobs = 640,
 )
 
 developer_build_perf_builder(
@@ -665,7 +665,7 @@
         category = "ios",
         short_name = "dev",
     ),
-    reclient_jobs = 800,
+    reclient_jobs = 640,
     xcode = xcode.xcode_default,
 )
 
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index c265bc3..acd8c43 100644
--- a/infra/config/targets/lacros-version-skew-variants.json
+++ b/infra/config/targets/lacros-version-skew-variants.json
@@ -1,32 +1,32 @@
 {
   "LACROS_VERSION_SKEW_CANARY": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 126.0.6436.0",
+    "description": "Run with ash-chrome version 126.0.6437.0",
     "identifier": "Lacros version skew testing ash canary",
     "swarming": {
       "cipd_packages": [
         {
           "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-          "location": "lacros_version_skew_tests_v126.0.6436.0",
-          "revision": "version:126.0.6436.0"
+          "location": "lacros_version_skew_tests_v126.0.6437.0",
+          "revision": "version:126.0.6437.0"
         }
       ]
     }
   },
   "LACROS_VERSION_SKEW_DEV": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 125.0.6422.5",
+    "description": "Run with ash-chrome version 125.0.6422.10",
     "identifier": "Lacros version skew testing ash dev",
     "swarming": {
       "cipd_packages": [
         {
           "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-          "location": "lacros_version_skew_tests_v125.0.6422.5",
-          "revision": "version:125.0.6422.5"
+          "location": "lacros_version_skew_tests_v125.0.6422.10",
+          "revision": "version:125.0.6422.10"
         }
       ]
     }
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 5446f0e..9f09336 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -2000,6 +2000,7 @@
     "test/test_data_directory.h",
     "test/test_doh_server.cc",
     "test/test_doh_server.h",
+    "test/test_with_task_environment.cc",
     "test/test_with_task_environment.h",
     "test/url_request/ssl_certificate_error_job.cc",
     "test/url_request/ssl_certificate_error_job.h",
diff --git a/net/quic/quic_session_pool_test_base.cc b/net/quic/quic_session_pool_test_base.cc
index 18de3795..00e4522 100644
--- a/net/quic/quic_session_pool_test_base.cc
+++ b/net/quic/quic_session_pool_test_base.cc
@@ -153,6 +153,8 @@
                     /*use_priority_header=*/false),
       http_server_properties_(std::make_unique<HttpServerProperties>()),
       cert_verifier_(std::make_unique<MockCertVerifier>()),
+      net_log_(NetLogWithSource::Make(NetLog::Get(),
+                                      NetLogSourceType::QUIC_SESSION_POOL)),
       failed_on_default_network_callback_(base::BindRepeating(
           &QuicSessionPoolTestBase::OnFailedOnDefaultNetwork,
           base::Unretained(this))),
diff --git a/net/test/test_with_task_environment.cc b/net/test/test_with_task_environment.cc
new file mode 100644
index 0000000..52ee679a
--- /dev/null
+++ b/net/test/test_with_task_environment.cc
@@ -0,0 +1,75 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/test/test_with_task_environment.h"
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "net/log/file_net_log_observer.h"
+#include "net/log/net_log_util.h"
+
+namespace net {
+
+WithTaskEnvironment::WithTaskEnvironment(
+    base::test::TaskEnvironment::TimeSource time_source)
+    : task_environment_(base::test::TaskEnvironment::MainThreadType::IO,
+                        time_source) {
+  MaybeStartNetLog();
+}
+
+WithTaskEnvironment::~WithTaskEnvironment() {
+  if (file_net_log_observer_) {
+    base::RunLoop run_loop;
+    file_net_log_observer_->StopObserving(/*polled_data=*/nullptr,
+                                          run_loop.QuitClosure());
+    run_loop.Run();
+  }
+}
+
+void WithTaskEnvironment::MaybeStartNetLog() {
+  // TODO(crbug.com/336167322): Move network::switches::kLogNetLog so that we
+  // can use the switch here.
+  constexpr const char kLogNetLogSwitch[] = "log-net-log";
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
+  if (!command_line->HasSwitch(kLogNetLogSwitch)) {
+    return;
+  }
+
+  base::FilePath log_file_path =
+      command_line->GetSwitchValuePath(kLogNetLogSwitch);
+  if (log_file_path.empty()) {
+    return;
+  }
+
+  base::File file = base::File(
+      log_file_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+  if (!file.IsValid()) {
+    return;
+  }
+
+  auto constants = std::make_unique<base::Value::Dict>(GetNetConstants());
+  base::Value::Dict client_info;
+  client_info.Set("name", "net_unittests");
+  base::CommandLine::StringType command_line_string =
+      command_line->GetCommandLineString();
+#if BUILDFLAG(IS_WIN)
+  client_info.Set("command_line", base::WideToUTF8(command_line_string));
+#else
+  client_info.Set("command_line", command_line_string);
+#endif
+  constants->Set("clientInfo", std::move(client_info));
+
+  file_net_log_observer_ = FileNetLogObserver::CreateUnboundedPreExisting(
+      std::move(file), NetLogCaptureMode::kEverything, std::move(constants));
+  file_net_log_observer_->StartObserving(NetLog::Get());
+}
+
+}  // namespace net
diff --git a/net/test/test_with_task_environment.h b/net/test/test_with_task_environment.h
index 6ab45fa..4fc24a8 100644
--- a/net/test/test_with_task_environment.h
+++ b/net/test/test_with_task_environment.h
@@ -5,6 +5,8 @@
 #ifndef NET_TEST_TEST_WITH_TASK_ENVIRONMENT_H_
 #define NET_TEST_TEST_WITH_TASK_ENVIRONMENT_H_
 
+#include <memory>
+
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -15,6 +17,8 @@
 
 namespace net {
 
+class FileNetLogObserver;
+
 // Inherit from this class if a TaskEnvironment is needed in a test.
 // Use in class hierachies where inheritance from ::testing::Test at the same
 // time is not desirable or possible (for example, when inheriting from
@@ -29,9 +33,9 @@
   // to mock time.
   explicit WithTaskEnvironment(
       base::test::TaskEnvironment::TimeSource time_source =
-          base::test::TaskEnvironment::TimeSource::DEFAULT)
-      : task_environment_(base::test::TaskEnvironment::MainThreadType::IO,
-                          time_source) {}
+          base::test::TaskEnvironment::TimeSource::DEFAULT);
+
+  ~WithTaskEnvironment();
 
   [[nodiscard]] bool MainThreadIsIdle() const {
     return task_environment_.MainThreadIsIdle();
@@ -65,7 +69,10 @@
   }
 
  private:
+  void MaybeStartNetLog();
+
   base::test::TaskEnvironment task_environment_;
+  std::unique_ptr<FileNetLogObserver> file_net_log_observer_;
 };
 
 // Inherit from this class instead of ::testing::Test directly if a
diff --git a/services/network/cors/cors_url_loader.cc b/services/network/cors/cors_url_loader.cc
index 3c33ac15..b75a876 100644
--- a/services/network/cors/cors_url_loader.cc
+++ b/services/network/cors/cors_url_loader.cc
@@ -973,14 +973,8 @@
 
 void CorsURLLoader::MaybeReportSharedDictionaryErrorToDevTools(
     mojom::SharedDictionaryError error) {
-  // No need to send AlreadyRegistered error to DevTools.
-  if (error == mojom::SharedDictionaryError::kWriteErrorAlreadyRegistered) {
-    return;
-  }
-  if (devtools_observer_ && request_.devtools_request_id) {
-    devtools_observer_->OnSharedDictionaryError(*request_.devtools_request_id,
-                                                request_.url, error);
-  }
+  // TODO(crbug.com/333756098): Send `error` to the browser process via
+  // `devtools_observer_`.
 }
 
 std::optional<URLLoaderCompletionStatus> CorsURLLoader::ConvertPreflightResult(
diff --git a/services/network/cors/preflight_controller_unittest.cc b/services/network/cors/preflight_controller_unittest.cc
index 3db89e7..8bc0dff 100644
--- a/services/network/cors/preflight_controller_unittest.cc
+++ b/services/network/cors/preflight_controller_unittest.cc
@@ -31,7 +31,6 @@
 #include "services/network/public/mojom/http_raw_headers.mojom.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "services/network/public/mojom/network_service.mojom.h"
-#include "services/network/public/mojom/shared_dictionary_error.mojom.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "services/network/test/client_security_state_builder.h"
@@ -395,11 +394,6 @@
       const std::string& error_message,
       const std::optional<std::string>& bundle_request_devtools_id) override {}
 
-  void OnSharedDictionaryError(
-      const std::string& devtool_request_id,
-      const GURL& url,
-      network::mojom::SharedDictionaryError error) override {}
-
   void OnCorsError(const std::optional<std::string>& devtool_request_id,
                    const std::optional<::url::Origin>& initiator_origin,
                    mojom::ClientSecurityStatePtr client_security_state,
diff --git a/services/network/public/mojom/devtools_observer.mojom b/services/network/public/mojom/devtools_observer.mojom
index ce9f42b..b1c5d8d963 100644
--- a/services/network/public/mojom/devtools_observer.mojom
+++ b/services/network/public/mojom/devtools_observer.mojom
@@ -19,7 +19,6 @@
 import "services/network/public/mojom/referrer_policy.mojom";
 import "services/network/public/mojom/request_priority.mojom";
 import "services/network/public/mojom/service_worker_router_info.mojom";
-import "services/network/public/mojom/shared_dictionary_error.mojom";
 import "services/network/public/mojom/trust_tokens.mojom";
 import "services/network/public/mojom/ip_endpoint.mojom";
 import "services/network/public/mojom/url_loader_completion_status.mojom";
@@ -197,14 +196,6 @@
     string error_message,
     string? bundle_request_devtools_id);
 
-  // Called when an error occurs while handling a response with
-  // "Use-As-Dictionary" header, or fetching request with a registered
-  // dictionary.
-  OnSharedDictionaryError(
-    string devtool_request_id,
-    url.mojom.Url url,
-    SharedDictionaryError error);
-
   // Used by the NetworkService to create a copy of this observer.
   // (e.g. when creating an observer for URLLoader from URLLoaderFactory's
   // observer).
diff --git a/services/network/test/mock_devtools_observer.h b/services/network/test/mock_devtools_observer.h
index 0a143ea..630b7a65 100644
--- a/services/network/test/mock_devtools_observer.h
+++ b/services/network/test/mock_devtools_observer.h
@@ -17,7 +17,6 @@
 #include "services/network/public/mojom/devtools_observer.mojom.h"
 #include "services/network/public/mojom/http_raw_headers.mojom-forward.h"
 #include "services/network/public/mojom/ip_address_space.mojom-forward.h"
-#include "services/network/public/mojom/shared_dictionary_error.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace network {
@@ -109,13 +108,6 @@
                const std::optional<std::string>& bundle_request_devtools_id),
               (override));
 
-  MOCK_METHOD(void,
-              OnSharedDictionaryError,
-              (const std::string& devtool_request_id,
-               const GURL& url,
-               network::mojom::SharedDictionaryError error),
-              (override));
-
   void OnCorsError(const std::optional<std::string>& devtool_request_id,
                    const std::optional<::url::Origin>& initiator_origin,
                    mojom::ClientSecurityStatePtr client_security_state,
diff --git a/services/webnn/tflite/graph_builder.cc b/services/webnn/tflite/graph_builder.cc
index 832144a..9024ba6 100644
--- a/services/webnn/tflite/graph_builder.cc
+++ b/services/webnn/tflite/graph_builder.cc
@@ -370,7 +370,10 @@
       break;
     }
     case mojom::Operation::Tag::kReshape: {
-      ASSIGN_OR_RETURN(operator_offset, SerializeReshape(*op.get_reshape()));
+      const mojom::Reshape& reshape = *op.get_reshape();
+      ASSIGN_OR_RETURN(operator_offset,
+                       SerializeReshape(reshape.input_operand_id,
+                                        reshape.output_operand_id));
       break;
     }
     case mojom::Operation::Tag::kSigmoid:
@@ -978,8 +981,13 @@
           MojoOperandTypeToTFLite(GetOperand(op.output_operand_id).data_type));
     case mojom::ElementWiseUnary::Kind::kLogicalNot:
       return SerializeLogicalNot(op);
-    case mojom::ElementWiseUnary::Kind::kTan:
     case mojom::ElementWiseUnary::Kind::kIdentity:
+      // Implement WebNN identity operation with TFLite reshape operator, the
+      // output shape is the same as input.
+      // TODO(crbug.com/336399247): Skip identity implementation with
+      // redirecting output tensor to input.
+      return SerializeReshape(op.input_operand_id, op.output_operand_id);
+    case mojom::ElementWiseUnary::Kind::kTan:
     case mojom::ElementWiseUnary::Kind::kErf:
     case mojom::ElementWiseUnary::Kind::kReciprocal:
       return base::unexpected(
@@ -1490,11 +1498,12 @@
                                   builtin_options_type, builtin_options);
 }
 
-auto GraphBuilder::SerializeReshape(const mojom::Reshape& reshape)
+auto GraphBuilder::SerializeReshape(uint64_t input_operand_id,
+                                    uint64_t output_operand_id)
     -> base::expected<OperatorOffset, std::string> {
   // Get the shape of the output tensor, such that this operator can reshape the
   // input to it.
-  const mojom::Operand& output_operand = GetOperand(reshape.output_operand_id);
+  const mojom::Operand& output_operand = GetOperand(output_operand_id);
   ASSIGN_OR_RETURN(std::vector<int32_t> signed_output_dimensions,
                    ToSignedDimensions(output_operand.dimensions));
 
@@ -1504,9 +1513,8 @@
           std::move(signed_output_dimensions)));
 
   return SerializeUnaryOperation(
-      ::tflite::BuiltinOperator_RESHAPE, reshape.input_operand_id,
-      reshape.output_operand_id, ::tflite::BuiltinOptions_ReshapeOptions,
-      reshape_options.Union());
+      ::tflite::BuiltinOperator_RESHAPE, input_operand_id, output_operand_id,
+      ::tflite::BuiltinOptions_ReshapeOptions, reshape_options.Union());
 }
 
 auto GraphBuilder::SerializeSigmoid(const mojom::Sigmoid& sigmoid)
diff --git a/services/webnn/tflite/graph_builder.h b/services/webnn/tflite/graph_builder.h
index 963b14e..d0b041f 100644
--- a/services/webnn/tflite/graph_builder.h
+++ b/services/webnn/tflite/graph_builder.h
@@ -180,7 +180,8 @@
   base::expected<OperatorOffset, std::string> SerializeResample2d(
       const mojom::Resample2d& resample2d);
   base::expected<OperatorOffset, std::string> SerializeReshape(
-      const mojom::Reshape& reshape);
+      uint64_t input_operand_id,
+      uint64_t output_operand_id);
   OperatorOffset SerializeSigmoid(const mojom::Sigmoid& sigmoid);
   base::expected<OperatorOffset, std::string> SerializeSlice(
       const mojom::Slice& slice);
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 68f92d5..39be295 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5487,9 +5487,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6436.0",
+        "description": "Run with ash-chrome version 126.0.6437.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5499,8 +5499,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6436.0",
-              "revision": "version:126.0.6436.0"
+              "location": "lacros_version_skew_tests_v126.0.6437.0",
+              "revision": "version:126.0.6437.0"
             }
           ],
           "dimensions": {
@@ -5517,9 +5517,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6422.5",
+        "description": "Run with ash-chrome version 125.0.6422.10",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5529,8 +5529,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6422.5",
-              "revision": "version:125.0.6422.5"
+              "location": "lacros_version_skew_tests_v125.0.6422.10",
+              "revision": "version:125.0.6422.10"
             }
           ],
           "dimensions": {
@@ -5643,9 +5643,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6436.0",
+        "description": "Run with ash-chrome version 126.0.6437.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5655,8 +5655,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6436.0",
-              "revision": "version:126.0.6436.0"
+              "location": "lacros_version_skew_tests_v126.0.6437.0",
+              "revision": "version:126.0.6437.0"
             }
           ],
           "dimensions": {
@@ -5673,9 +5673,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6422.5",
+        "description": "Run with ash-chrome version 125.0.6422.10",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5685,8 +5685,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6422.5",
-              "revision": "version:125.0.6422.5"
+              "location": "lacros_version_skew_tests_v125.0.6422.10",
+              "revision": "version:125.0.6422.10"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index dd5bc73..c791f28 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -19663,9 +19663,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6436.0",
+        "description": "Run with ash-chrome version 126.0.6437.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19675,8 +19675,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6436.0",
-              "revision": "version:126.0.6436.0"
+              "location": "lacros_version_skew_tests_v126.0.6437.0",
+              "revision": "version:126.0.6437.0"
             }
           ],
           "dimensions": {
@@ -19693,9 +19693,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6422.5",
+        "description": "Run with ash-chrome version 125.0.6422.10",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19705,8 +19705,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6422.5",
-              "revision": "version:125.0.6422.5"
+              "location": "lacros_version_skew_tests_v125.0.6422.10",
+              "revision": "version:125.0.6422.10"
             }
           ],
           "dimensions": {
@@ -19819,9 +19819,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6436.0",
+        "description": "Run with ash-chrome version 126.0.6437.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19831,8 +19831,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6436.0",
-              "revision": "version:126.0.6436.0"
+              "location": "lacros_version_skew_tests_v126.0.6437.0",
+              "revision": "version:126.0.6437.0"
             }
           ],
           "dimensions": {
@@ -19849,9 +19849,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6422.5",
+        "description": "Run with ash-chrome version 125.0.6422.10",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19861,8 +19861,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6422.5",
-              "revision": "version:125.0.6422.5"
+              "location": "lacros_version_skew_tests_v125.0.6422.10",
+              "revision": "version:125.0.6422.10"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 1f47e5c..7c702c84 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -41837,9 +41837,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6436.0",
+        "description": "Run with ash-chrome version 126.0.6437.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41848,8 +41848,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6436.0",
-              "revision": "version:126.0.6436.0"
+              "location": "lacros_version_skew_tests_v126.0.6437.0",
+              "revision": "version:126.0.6437.0"
             }
           ],
           "dimensions": {
@@ -41866,9 +41866,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6422.5",
+        "description": "Run with ash-chrome version 125.0.6422.10",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41877,8 +41877,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6422.5",
-              "revision": "version:125.0.6422.5"
+              "location": "lacros_version_skew_tests_v125.0.6422.10",
+              "revision": "version:125.0.6422.10"
             }
           ],
           "dimensions": {
@@ -41987,9 +41987,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6436.0",
+        "description": "Run with ash-chrome version 126.0.6437.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41998,8 +41998,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6436.0",
-              "revision": "version:126.0.6436.0"
+              "location": "lacros_version_skew_tests_v126.0.6437.0",
+              "revision": "version:126.0.6437.0"
             }
           ],
           "dimensions": {
@@ -42016,9 +42016,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6422.5",
+        "description": "Run with ash-chrome version 125.0.6422.10",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -42027,8 +42027,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6422.5",
-              "revision": "version:125.0.6422.5"
+              "location": "lacros_version_skew_tests_v125.0.6422.10",
+              "revision": "version:125.0.6422.10"
             }
           ],
           "dimensions": {
@@ -43336,9 +43336,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6436.0",
+        "description": "Run with ash-chrome version 126.0.6437.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43348,8 +43348,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6436.0",
-              "revision": "version:126.0.6436.0"
+              "location": "lacros_version_skew_tests_v126.0.6437.0",
+              "revision": "version:126.0.6437.0"
             }
           ],
           "dimensions": {
@@ -43366,9 +43366,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6422.5",
+        "description": "Run with ash-chrome version 125.0.6422.10",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43378,8 +43378,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6422.5",
-              "revision": "version:125.0.6422.5"
+              "location": "lacros_version_skew_tests_v125.0.6422.10",
+              "revision": "version:125.0.6422.10"
             }
           ],
           "dimensions": {
@@ -43492,9 +43492,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6436.0",
+        "description": "Run with ash-chrome version 126.0.6437.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43504,8 +43504,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6436.0",
-              "revision": "version:126.0.6436.0"
+              "location": "lacros_version_skew_tests_v126.0.6437.0",
+              "revision": "version:126.0.6437.0"
             }
           ],
           "dimensions": {
@@ -43522,9 +43522,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6422.5",
+        "description": "Run with ash-chrome version 125.0.6422.10",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43534,8 +43534,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6422.5",
-              "revision": "version:125.0.6422.5"
+              "location": "lacros_version_skew_tests_v125.0.6422.10",
+              "revision": "version:125.0.6422.10"
             }
           ],
           "dimensions": {
@@ -44817,9 +44817,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6436.0",
+        "description": "Run with ash-chrome version 126.0.6437.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44828,8 +44828,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6436.0",
-              "revision": "version:126.0.6436.0"
+              "location": "lacros_version_skew_tests_v126.0.6437.0",
+              "revision": "version:126.0.6437.0"
             }
           ],
           "dimensions": {
@@ -44846,9 +44846,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6422.5",
+        "description": "Run with ash-chrome version 125.0.6422.10",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44857,8 +44857,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6422.5",
-              "revision": "version:125.0.6422.5"
+              "location": "lacros_version_skew_tests_v125.0.6422.10",
+              "revision": "version:125.0.6422.10"
             }
           ],
           "dimensions": {
@@ -44967,9 +44967,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6436.0",
+        "description": "Run with ash-chrome version 126.0.6437.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44978,8 +44978,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6436.0",
-              "revision": "version:126.0.6436.0"
+              "location": "lacros_version_skew_tests_v126.0.6437.0",
+              "revision": "version:126.0.6437.0"
             }
           ],
           "dimensions": {
@@ -44996,9 +44996,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 125.0.6422.5",
+        "description": "Run with ash-chrome version 125.0.6422.10",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -45007,8 +45007,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6422.5",
-              "revision": "version:125.0.6422.5"
+              "location": "lacros_version_skew_tests_v125.0.6422.10",
+              "revision": "version:125.0.6422.10"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 65ac870..b0c11d4 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -15763,12 +15763,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 126.0.6436.0",
+        "description": "Run with ash-chrome version 126.0.6437.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15778,8 +15778,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6436.0",
-              "revision": "version:126.0.6436.0"
+              "location": "lacros_version_skew_tests_v126.0.6437.0",
+              "revision": "version:126.0.6437.0"
             }
           ],
           "dimensions": {
@@ -15796,12 +15796,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 125.0.6422.5",
+        "description": "Run with ash-chrome version 125.0.6422.10",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15811,8 +15811,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6422.5",
-              "revision": "version:125.0.6422.5"
+              "location": "lacros_version_skew_tests_v125.0.6422.10",
+              "revision": "version:125.0.6422.10"
             }
           ],
           "dimensions": {
@@ -15939,12 +15939,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 126.0.6436.0",
+        "description": "Run with ash-chrome version 126.0.6437.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15954,8 +15954,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6436.0",
-              "revision": "version:126.0.6436.0"
+              "location": "lacros_version_skew_tests_v126.0.6437.0",
+              "revision": "version:126.0.6437.0"
             }
           ],
           "dimensions": {
@@ -15972,12 +15972,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 125.0.6422.5",
+        "description": "Run with ash-chrome version 125.0.6422.10",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15987,8 +15987,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v125.0.6422.5",
-              "revision": "version:125.0.6422.5"
+              "location": "lacros_version_skew_tests_v125.0.6422.10",
+              "revision": "version:125.0.6422.10"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 5b26a49..577a352 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -267,32 +267,32 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 126.0.6436.0',
+    'description': 'Run with ash-chrome version 126.0.6437.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6436.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6437.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v126.0.6436.0',
-          'revision': 'version:126.0.6436.0',
+          'location': 'lacros_version_skew_tests_v126.0.6437.0',
+          'revision': 'version:126.0.6437.0',
         },
       ],
     },
   },
   'LACROS_VERSION_SKEW_DEV': {
     'identifier': 'Lacros version skew testing ash dev',
-    'description': 'Run with ash-chrome version 125.0.6422.5',
+    'description': 'Run with ash-chrome version 125.0.6422.10',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.5/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v125.0.6422.10/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v125.0.6422.5',
-          'revision': 'version:125.0.6422.5',
+          'location': 'lacros_version_skew_tests_v125.0.6422.10',
+          'revision': 'version:125.0.6422.10',
         },
       ],
     },
diff --git a/testing/libfuzzer/fuzzers/command_buffer_lpm_fuzzer/cmd_buf_lpm_fuzz.cc b/testing/libfuzzer/fuzzers/command_buffer_lpm_fuzzer/cmd_buf_lpm_fuzz.cc
index 7234c297..d12ebbc7 100644
--- a/testing/libfuzzer/fuzzers/command_buffer_lpm_fuzzer/cmd_buf_lpm_fuzz.cc
+++ b/testing/libfuzzer/fuzzers/command_buffer_lpm_fuzzer/cmd_buf_lpm_fuzz.cc
@@ -141,13 +141,12 @@
 
   VLOG(3) << "Wire protocol setup";
   wire_channel_ = webgpu()->GetAPIChannel();
-  dawn_procs_ = std::make_unique<DawnProcTable>(wire_channel_->GetProcs());
-  dawnProcSetProcs(dawn_procs_.get());
+  dawnProcSetProcs(&dawn::wire::client::GetProcs());
   dawn_wire_services_ =
       static_cast<webgpu::DawnWireServices*>(wire_channel_.get());
   dawn_wire_serializer_ = dawn_wire_services_->serializer();
   wire_descriptor_ = std::make_unique<dawn::wire::WireServerDescriptor>();
-  wire_descriptor_->procs = dawn_procs_.get();
+  wire_descriptor_->procs = &dawn::wire::client::GetProcs();
   wire_descriptor_->serializer = dawn_wire_serializer_.get();
   wire_server_ = std::make_unique<dawn::wire::WireServer>(*wire_descriptor_);
   dawn_instance_ = std::make_unique<dawn::native::Instance>();
diff --git a/testing/libfuzzer/fuzzers/command_buffer_lpm_fuzzer/cmd_buf_lpm_fuzz.h b/testing/libfuzzer/fuzzers/command_buffer_lpm_fuzzer/cmd_buf_lpm_fuzz.h
index b46b9f7..772cc8fb 100644
--- a/testing/libfuzzer/fuzzers/command_buffer_lpm_fuzzer/cmd_buf_lpm_fuzz.h
+++ b/testing/libfuzzer/fuzzers/command_buffer_lpm_fuzzer/cmd_buf_lpm_fuzz.h
@@ -147,7 +147,6 @@
   std::unique_ptr<dawn::wire::WireServer> wire_server_;
   scoped_refptr<webgpu::APIChannel> wire_channel_;
   std::unique_ptr<dawn::native::Instance> dawn_instance_;
-  std::unique_ptr<DawnProcTable> dawn_procs_;
   std::unique_ptr<dawn::wire::WireServerDescriptor> wire_descriptor_;
   scoped_refptr<Buffer> buffer_;
   int32_t command_buffer_id_ = -1;
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index bd174455..db554ce3 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -14567,7 +14567,19 @@
             ],
             "experiments": [
                 {
-                    "name": "Enable",
+                    "name": "preload-on-make-contents",
+                    "params": {
+                        "preload-mode": "preload-on-make-contents"
+                    },
+                    "enable_features": [
+                        "PreloadTopChromeWebUI"
+                    ]
+                },
+                {
+                    "name": "preload-on-warmup",
+                    "params": {
+                        "preload-mode": "preload-on-warmup"
+                    },
                     "enable_features": [
                         "PreloadTopChromeWebUI"
                     ]
@@ -16082,26 +16094,6 @@
             ]
         }
     ],
-    "ReduceAcceptLanguage": [
-        {
-            "platforms": [
-                "android",
-                "chromeos",
-                "chromeos_lacros",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "ReduceAcceptLanguage"
-                    ]
-                }
-            ]
-        }
-    ],
     "ReduceIPAddressChangeNotification": [
         {
             "platforms": [
diff --git a/third_party/angle b/third_party/angle
index 94179be..c74d263 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 94179bed1c110a6904b2c3794048214b4af9ea86
+Subproject commit c74d2634e6e094865e2ad1c43e37f00910c119be
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 5eaf7ff..f711447d 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -2285,10 +2285,6 @@
              "SystemColorChooser",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kTextCodecCJKEnabled,
-             "TextCodecCJKEnabled",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enables third party script regex matching for detecting technologies.
 BASE_FEATURE(kThirdPartyScriptDetection,
              "ThirdPartyScriptDetection",
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 06de0ad..14c024b 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -1487,10 +1487,6 @@
 
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kTargetBlankImpliesNoOpener);
 
-// Use TextCodecCJK for encoding/decoding CJK except for Big5.
-// If the flag is disabled TextCodecICU would be used instead.
-BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kTextCodecCJKEnabled);
-
 // If enabled, regex match on script source to detect third party technologies.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kThirdPartyScriptDetection);
 
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index 5f32db9..1b41a9d 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -741,32 +741,6 @@
       NoRegisterOsSourceHeader
       NoRegisterOsTriggerHeader
 
-  type SharedDictionaryError extends string
-    enum
-      UseErrorCrossOriginNoCorsRequest
-      UseErrorDictionaryLoadFailure
-      UseErrorMatchingDictionaryNotUsed
-      UseErrorUnexpectedContentDictionaryHeader
-      WriteErrorCossOriginNoCorsRequest
-      WriteErrorDisallowedBySettings
-      WriteErrorExpiredResponse
-      WriteErrorFeatureDisabled
-      WriteErrorInsufficientResources
-      WriteErrorInvalidMatchField
-      WriteErrorInvalidStructuredHeader
-      WriteErrorNavigationRequest
-      WriteErrorNoMatchField
-      WriteErrorNonListMatchDestField
-      WriteErrorNonSecureContext
-      WriteErrorNonStringIdField
-      WriteErrorNonStringInMatchDestList
-      WriteErrorNonStringMatchField
-      WriteErrorNonTokenTypeField
-      WriteErrorRequestAborted
-      WriteErrorShuttingDown
-      WriteErrorTooLongIdField
-      WriteErrorUnsupportedType
-
   # Details for issues around "Attribution Reporting API" usage.
   # Explainer: https://github.com/WICG/attribution-reporting-api
   type AttributionReportingIssueDetails extends object
@@ -793,11 +767,6 @@
       string url
       optional SourceCodeLocation location
 
-  type SharedDictionaryIssueDetails extends object
-    properties
-      SharedDictionaryError sharedDictionaryError
-      AffectedRequest request
-
   type GenericIssueErrorType extends string
     enum
       CrossOriginPortalPostMessageError
@@ -1003,7 +972,6 @@
       StylesheetLoadingIssue
       FederatedAuthUserInfoRequestIssue
       PropertyRuleIssue
-      SharedDictionaryIssue
 
   # This struct holds a list of optional fields with additional information
   # specific to the kind of issue. When adding a new issue code, please also
@@ -1030,7 +998,6 @@
       optional StylesheetLoadingIssueDetails stylesheetLoadingIssueDetails
       optional PropertyRuleIssueDetails propertyRuleIssueDetails
       optional FederatedAuthUserInfoRequestIssueDetails federatedAuthUserInfoRequestIssueDetails
-      optional SharedDictionaryIssueDetails sharedDictionaryIssueDetails
 
   # A unique id for a DevTools inspector issue. Allows other entities (e.g.
   # exceptions, CDP message, console messages, etc.) to reference an issue.
@@ -3132,6 +3099,20 @@
       # NodeIds of top layer elements
       array of NodeId nodeIds
 
+  # Returns the NodeId of the matched element according to certain relations.
+  experimental command getElementByRelation
+    parameters
+      # Id of the node from which to query the relation.
+      NodeId nodeId
+      # Type of relation to get.
+      enum relation
+        # Get the popover target for a given element. In this case, this given
+        # element can only be an HTMLFormControlElement (<input>, <button>).
+        PopoverTarget
+    returns
+      # NodeId of the element matching the queried relation.
+      NodeId nodeId
+
   # Re-does the last undone action.
   experimental command redo
 
diff --git a/third_party/blink/renderer/core/dom/observable.cc b/third_party/blink/renderer/core/dom/observable.cc
index 94175c1..0b8e683 100644
--- a/third_party/blink/renderer/core/dom/observable.cc
+++ b/third_party/blink/renderer/core/dom/observable.cc
@@ -1388,7 +1388,7 @@
 
       ScriptState::Scope scope(script_state_);
       v8::TryCatch try_catch(script_state_->GetIsolate());
-      v8::Maybe<bool> matches = predicate_->Invoke(nullptr, value);
+      v8::Maybe<bool> matches = predicate_->Invoke(nullptr, value, idx_++);
       if (try_catch.HasCaught()) {
         subscriber_->error(
             script_state_,
@@ -1416,6 +1416,7 @@
     }
 
    private:
+    uint64_t idx_ = 0;
     Member<Subscriber> subscriber_;
     Member<ScriptState> script_state_;
     Member<V8Predicate> predicate_;
diff --git a/third_party/blink/renderer/core/dom/observable.idl b/third_party/blink/renderer/core/dom/observable.idl
index 29c4642..0c8964d 100644
--- a/third_party/blink/renderer/core/dom/observable.idl
+++ b/third_party/blink/renderer/core/dom/observable.idl
@@ -13,7 +13,9 @@
 // Differs from Mapper only in return type, since this callback is exclusively
 // used to visit each element in a sequence, not transform it.
 callback Visitor = undefined (any element, unsigned long long index);
-callback Predicate = boolean (any value);
+// This matches the predicate in the ECMAScript Iterator helpers proposal, i.e.,
+// including the `index` parameter.
+callback Predicate = boolean (any value, unsigned long long index);
 
 callback ObservableInspectorAbortHandler = undefined (any value);
 
diff --git a/third_party/blink/renderer/core/editing/frame_selection.cc b/third_party/blink/renderer/core/editing/frame_selection.cc
index 989a9cd..739615a 100644
--- a/third_party/blink/renderer/core/editing/frame_selection.cc
+++ b/third_party/blink/renderer/core/editing/frame_selection.cc
@@ -358,11 +358,31 @@
   NotifyAccessibilityForSelectionChange();
   NotifyCompositorForSelectionChange();
   NotifyEventHandlerForSelectionChange();
-  // The task source should be kDOMManipulation, but the spec doesn't say
-  // anything about this.
-  frame_->DomWindow()->EnqueueDocumentEvent(
-      *Event::Create(event_type_names::kSelectionchange),
-      TaskType::kMiscPlatformAPI);
+
+  // Dispatch selectionchange events per element based on the new spec:
+  // https://w3c.github.io/selection-api/#selectionchange-event
+  if (RuntimeEnabledFeatures::DispatchSelectionchangeEventPerElementEnabled()) {
+    TextControlElement* text_control =
+        EnclosingTextControl(GetSelectionInDOMTree().Anchor());
+    if (text_control) {
+      text_control->EnqueueEvent(
+          *Event::CreateBubble(event_type_names::kSelectionchange),
+          TaskType::kMiscPlatformAPI);
+    } else {
+      frame_->DomWindow()->EnqueueDocumentEvent(
+          *Event::Create(event_type_names::kSelectionchange),
+          TaskType::kMiscPlatformAPI);
+    }
+  }
+  // When DispatchSelectionchangeEventPerElement is disabled, fall back to old
+  // path.
+  else {
+    // The task source should be kDOMManipulation, but the spec doesn't say
+    // anything about this.
+    frame_->DomWindow()->EnqueueDocumentEvent(
+        *Event::Create(event_type_names::kSelectionchange),
+        TaskType::kMiscPlatformAPI);
+  }
 }
 
 void FrameSelection::SetSelectionForAccessibility(
diff --git a/third_party/blink/renderer/core/html/forms/text_control_element.cc b/third_party/blink/renderer/core/html/forms/text_control_element.cc
index b4610a0..2ec6102c 100644
--- a/third_party/blink/renderer/core/html/forms/text_control_element.cc
+++ b/third_party/blink/renderer/core/html/forms/text_control_element.cc
@@ -508,11 +508,19 @@
 
   // TODO(crbug.com/927646): The focused element should always be connected, but
   // we fail to ensure so in some cases. Fix it.
-  if (ShouldApplySelectionCache() || !isConnected())
+  if (ShouldApplySelectionCache() || !isConnected()) {
+    if (did_change) {
+      ScheduleSelectionchangeEvent();
+    }
     return did_change;
+  }
 
-  if (!frame || !inner_editor)
+  if (!frame || !inner_editor) {
+    if (did_change) {
+      ScheduleSelectionchangeEvent();
+    }
     return did_change;
+  }
 
   Position start_position = PositionForIndex(inner_editor, start);
   Position end_position =
@@ -811,6 +819,13 @@
   GetDocument().EnqueueAnimationFrameEvent(event);
 }
 
+void TextControlElement::ScheduleSelectionchangeEvent() {
+  if (RuntimeEnabledFeatures::DispatchSelectionchangeEventPerElementEnabled()) {
+    EnqueueEvent(*Event::CreateBubble(event_type_names::kSelectionchange),
+                 TaskType::kMiscPlatformAPI);
+  }
+}
+
 void TextControlElement::ParseAttribute(
     const AttributeModificationParams& params) {
   if (params.name == html_names::kPlaceholderAttr) {
diff --git a/third_party/blink/renderer/core/html/forms/text_control_element.h b/third_party/blink/renderer/core/html/forms/text_control_element.h
index 2e2e871..ff3e45b 100644
--- a/third_party/blink/renderer/core/html/forms/text_control_element.h
+++ b/third_party/blink/renderer/core/html/forms/text_control_element.h
@@ -234,6 +234,7 @@
                          mojom::blink::FocusType,
                          InputDeviceCapabilities* source_capabilities) final;
   void ScheduleSelectEvent();
+  void ScheduleSelectionchangeEvent();
   void DisabledOrReadonlyAttributeChanged(const QualifiedName&);
 
   // Called in dispatchFocusEvent(), after placeholder process, before calling
diff --git a/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc b/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc
index eff7ada..8a6f30fb 100644
--- a/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc
@@ -97,17 +97,6 @@
   return value->GetAsDouble();
 }
 
-String AnimationDisplayName(const Animation& animation) {
-  if (!animation.id().empty())
-    return animation.id();
-  else if (auto* css_animation = DynamicTo<CSSAnimation>(animation))
-    return css_animation->animationName();
-  else if (auto* css_transition = DynamicTo<CSSTransition>(animation))
-    return css_transition->transitionProperty();
-  else
-    return animation.id();
-}
-
 }  // namespace
 
 InspectorAnimationAgent::InspectorAnimationAgent(
@@ -123,6 +112,19 @@
   DCHECK(css_agent);
 }
 
+String InspectorAnimationAgent::AnimationDisplayName(
+    const Animation& animation) {
+  if (!animation.id().empty()) {
+    return animation.id();
+  } else if (auto* css_animation = DynamicTo<CSSAnimation>(animation)) {
+    return css_animation->animationName();
+  } else if (auto* css_transition = DynamicTo<CSSTransition>(animation)) {
+    return css_transition->transitionProperty();
+  } else {
+    return "";
+  }
+}
+
 void InspectorAnimationAgent::Restore() {
   if (enabled_.Get()) {
     instrumenting_agents_->AddInspectorAnimationAgent(this);
diff --git a/third_party/blink/renderer/core/inspector/inspector_animation_agent.h b/third_party/blink/renderer/core/inspector/inspector_animation_agent.h
index e30d32f..efb0271f 100644
--- a/third_party/blink/renderer/core/inspector/inspector_animation_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_animation_agent.h
@@ -68,6 +68,7 @@
   protocol::Response AssertAnimation(const String& id,
                                      blink::Animation*& result);
 
+  static String AnimationDisplayName(const Animation& animation);
   void Trace(Visitor*) const override;
 
  private:
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
index 29d9b61..dfa39c09 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
@@ -1704,6 +1704,30 @@
   return protocol::Response::Success();
 }
 
+protocol::Response InspectorDOMAgent::getElementByRelation(
+    int node_id,
+    const String& relation,
+    int* related_element_id) {
+  *related_element_id = 0;
+  Node* node = nullptr;
+  protocol::Response response = AssertNode(node_id, node);
+  if (!response.IsSuccess()) {
+    return response;
+  }
+
+  Element* element = nullptr;
+  if (relation == protocol::DOM::GetElementByRelation::RelationEnum::PopoverTarget) {
+      if (auto* invoker = DynamicTo<HTMLFormControlElement>(node)) {
+        element = invoker->popoverTargetElement().popover;
+      }
+  }
+
+  if (element) {
+    *related_element_id = PushNodePathToFrontend(element);
+  }
+  return protocol::Response::Success();
+}
+
 // static
 const HeapVector<Member<Element>>
 InspectorDOMAgent::GetContainerQueryingDescendants(Element* container) {
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.h b/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
index 4533b69..4bd0b0d 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
@@ -268,6 +268,10 @@
   static const HeapVector<Member<Element>> GetContainerQueryingDescendants(
       Element* container);
 
+  protocol::Response getElementByRelation(int node_id,
+                                          const String& relation,
+                                          int* out_node_id) override;
+
   bool Enabled() const;
   IncludeWhitespaceEnum IncludeWhitespace() const;
   void ReleaseDanglingNodes();
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
index 14c482b..501e7f8 100644
--- a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
@@ -26,6 +26,7 @@
 #include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
 #include "third_party/blink/renderer/core/html/parser/html_document_parser.h"
 #include "third_party/blink/renderer/core/inspector/identifiers_factory.h"
+#include "third_party/blink/renderer/core/inspector/inspector_animation_agent.h"
 #include "third_party/blink/renderer/core/inspector/inspector_network_agent.h"
 #include "third_party/blink/renderer/core/inspector/inspector_page_agent.h"
 #include "third_party/blink/renderer/core/layout/hit_test_location.h"
@@ -1581,6 +1582,8 @@
   dict.Add("id", String::Number(animation.SequenceNumber()));
   dict.Add("state", animation.PlayStateString());
   if (const AnimationEffect* effect = animation.effect()) {
+    dict.Add("displayName",
+             InspectorAnimationAgent::AnimationDisplayName(animation));
     dict.Add("name", animation.id());
     if (auto* frame_effect = DynamicTo<KeyframeEffect>(effect)) {
       if (Element* target = frame_effect->EffectTarget())
diff --git a/third_party/blink/renderer/core/layout/inline/ruby_utils.cc b/third_party/blink/renderer/core/layout/inline/ruby_utils.cc
index b0d9a49..f390747 100644
--- a/third_party/blink/renderer/core/layout/inline/ruby_utils.cc
+++ b/third_party/blink/renderer/core/layout/inline/ruby_utils.cc
@@ -72,6 +72,58 @@
   return std::make_tuple(over + over_diff, under - under_diff);
 }
 
+FontHeight ComputeEmHeight(const LogicalLineItem& line_item) {
+  if (const auto& shape_result_view = line_item.shape_result) {
+    const ComputedStyle* style = line_item.Style();
+    const SimpleFontData* primary_font_data = style->GetFont().PrimaryFont();
+    if (!primary_font_data) {
+      return FontHeight();
+    }
+    const auto font_baseline = style->GetFontBaseline();
+    const FontHeight primary_height =
+        primary_font_data->GetFontMetrics().GetFloatFontHeight(font_baseline);
+    FontHeight result_height;
+    // We don't use ShapeResultView::FallbackFonts() because we can't know if
+    // the primary font is actually used with FallbackFonts().
+    HeapVector<ShapeResult::RunFontData> run_fonts;
+    ClearCollectionScope clear_scope(&run_fonts);
+    shape_result_view->GetRunFontData(&run_fonts);
+    for (const auto& run_font : run_fonts) {
+      const SimpleFontData* font_data = run_font.font_data_;
+      if (!font_data) {
+        continue;
+      }
+      result_height.Unite(
+          font_data->NormalizedTypoAscentAndDescent(font_baseline));
+    }
+    result_height.ascent = std::min(LayoutUnit(result_height.ascent.Ceil()),
+                                    primary_height.ascent);
+    result_height.descent = std::min(LayoutUnit(result_height.descent.Ceil()),
+                                     primary_height.descent);
+    result_height.Move(line_item.rect.offset.block_offset +
+                       primary_height.ascent);
+    return result_height;
+  }
+  if (const auto& layout_result = line_item.layout_result) {
+    const auto& fragment = layout_result->GetPhysicalFragment();
+    const auto& style = fragment.Style();
+    LogicalSize logical_size =
+        LogicalFragment(style.GetWritingDirection(), fragment).Size();
+    const LayoutBox* box = DynamicTo<LayoutBox>(line_item.GetLayoutObject());
+    if (logical_size.inline_size && box && box->IsAtomicInlineLevel()) {
+      LogicalRect overflow =
+          WritingModeConverter(
+              {ToLineWritingMode(style.GetWritingMode()), style.Direction()},
+              fragment.Size())
+              .ToLogical(box->ScrollableOverflowRect());
+      // Assume 0 is the baseline.  BlockOffset() is always negative.
+      return FontHeight(-overflow.offset.block_offset - line_item.BlockOffset(),
+                        overflow.BlockEndOffset() + line_item.BlockOffset());
+    }
+  }
+  return FontHeight();
+}
+
 }  // anonymous namespace
 
 RubyItemIndexes ParseRubyInInlineItems(const HeapVector<InlineItem>& items,
@@ -394,6 +446,7 @@
     const ComputedStyle& line_style,
     std::optional<FontHeight> annotation_metrics) {
   // Min/max position of content and annotations, ignoring line-height.
+  // They are distance from the line box top.
   const LayoutUnit line_over;
   LayoutUnit content_over = line_over + line_box_metrics.ascent;
   LayoutUnit content_under = content_over;
@@ -404,6 +457,8 @@
   const LayoutUnit line_under = line_over + line_box_metrics.LineHeight();
   bool has_over_emphasis = false;
   bool has_under_emphasis = false;
+  // TODO(crbug.com/324111880): This loop can be replaced with
+  // ComputeLogicalLineEmHeight() after enabling RubyLineBreakable flag.
   for (const LogicalLineItem& item : logical_line) {
     if (!item.HasInFlowFragment())
       continue;
@@ -418,6 +473,7 @@
             item_over, item_under, *style, *item.shape_result);
       }
     } else {
+      const LayoutBox* box = DynamicTo<LayoutBox>(item.GetLayoutObject());
       const auto* fragment = item.GetPhysicalFragment();
       if (fragment && fragment->IsRubyColumn()) {
         DCHECK(!RuntimeEnabledFeatures::RubyLineBreakableEnabled());
@@ -449,6 +505,10 @@
           else if (overflow > LayoutUnit())
             has_under_annotation = true;
         }
+      } else if (RuntimeEnabledFeatures::RubyAnnotationSpaceFixEnabled() &&
+                 fragment && box && box->IsAtomicInlineLevel() &&
+                 !box->IsInitialLetterBox()) {
+        item_under = ComputeEmHeight(item).LineHeight();
       } else if (item.IsInlineBox()) {
         continue;
       }
@@ -943,58 +1003,6 @@
 
 namespace {
 
-FontHeight ComputeEmHeight(const LogicalLineItem& line_item) {
-  if (const auto& shape_result_view = line_item.shape_result) {
-    const ComputedStyle* style = line_item.Style();
-    const SimpleFontData* primary_font_data = style->GetFont().PrimaryFont();
-    if (!primary_font_data) {
-      return FontHeight();
-    }
-    const auto font_baseline = style->GetFontBaseline();
-    const FontHeight primary_height =
-        primary_font_data->GetFontMetrics().GetFloatFontHeight(font_baseline);
-    FontHeight result_height;
-    // We don't use ShapeResultView::FallbackFonts() because we can't know if
-    // the primary font is actually used with FallbackFonts().
-    HeapVector<ShapeResult::RunFontData> run_fonts;
-    ClearCollectionScope clear_scope(&run_fonts);
-    shape_result_view->GetRunFontData(&run_fonts);
-    for (const auto& run_font : run_fonts) {
-      const SimpleFontData* font_data = run_font.font_data_;
-      if (!font_data) {
-        continue;
-      }
-      result_height.Unite(
-          font_data->NormalizedTypoAscentAndDescent(font_baseline));
-    }
-    result_height.ascent = std::min(LayoutUnit(result_height.ascent.Ceil()),
-                                    primary_height.ascent);
-    result_height.descent = std::min(LayoutUnit(result_height.descent.Ceil()),
-                                     primary_height.descent);
-    result_height.Move(line_item.rect.offset.block_offset +
-                       primary_height.ascent);
-    return result_height;
-  }
-  if (const auto& layout_result = line_item.layout_result) {
-    const auto& fragment = layout_result->GetPhysicalFragment();
-    const auto& style = fragment.Style();
-    LogicalSize logical_size =
-        LogicalFragment(style.GetWritingDirection(), fragment).Size();
-    const LayoutBox* box = DynamicTo<LayoutBox>(line_item.GetLayoutObject());
-    if (logical_size.inline_size && box && box->IsAtomicInlineLevel()) {
-      LogicalRect overflow =
-          WritingModeConverter(
-              {ToLineWritingMode(style.GetWritingMode()), style.Direction()},
-              fragment.Size())
-              .ToLogical(box->ScrollableOverflowRect());
-      // Assume 0 is the baseline.  BlockOffset() is always negative.
-      return FontHeight(-overflow.offset.block_offset - line_item.BlockOffset(),
-                        overflow.BlockEndOffset() + line_item.BlockOffset());
-    }
-  }
-  return FontHeight();
-}
-
 FontHeight ComputeLogicalLineEmHeight(const LogicalLineItems& line_items) {
   FontHeight height;
   for (const auto& item : line_items) {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 6677dde..e3402cf 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1337,6 +1337,12 @@
       base_feature: "DispatchHiddenVisibilityTransitions",
     },
     {
+      // Dispatch selectionchange event per element according to the new spec:
+      // https://w3c.github.io/selection-api/#selectionchange-event
+      name: "DispatchSelectionchangeEventPerElement",
+      status: "experimental",
+    },
+    {
       // Allowing elements with display:contents to have focus.
       // See https://crbug.com/1366037
       name: "DisplayContentsFocusable",
@@ -3345,6 +3351,11 @@
       status: "stable",
     },
     {
+      // crbug.com/336592423
+      name: "RubyAnnotationSpaceFix",
+      status: "stable",
+    },
+    {
       // crbug.com/324111880
       name: "RubyLineBreakable",
     },
diff --git a/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc b/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc
index 33ce43f..80ddb28 100644
--- a/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc
+++ b/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc
@@ -83,8 +83,6 @@
   // apart; ICU treats these names as synonyms.
   registrar("ISO-8859-8-I", "ISO-8859-8-I");
 
-  const bool is_text_codec_cjk_enabled =
-      base::FeatureList::IsEnabled(blink::features::kTextCodecCJKEnabled);
   int32_t num_encodings = ucnv_countAvailable();
   for (int32_t i = 0; i < num_encodings; ++i) {
     const char* name = ucnv_getAvailableName(i);
@@ -117,7 +115,7 @@
     }
 #endif
     // Avoid codecs supported by `TextCodecCJK`.
-    if (is_text_codec_cjk_enabled && TextCodecCJK::IsSupported(standard_name)) {
+    if (TextCodecCJK::IsSupported(standard_name)) {
       continue;
     }
 
@@ -150,8 +148,7 @@
 
     // Avoid registering codecs registered by
     // `TextCodecCJK::RegisterEncodingNames`.
-    if (!is_text_codec_cjk_enabled ||
-        !TextCodecCJK::IsSupported(standard_name)) {
+    if (!TextCodecCJK::IsSupported(standard_name)) {
       registrar(standard_name, standard_name);
     }
 
@@ -272,8 +269,6 @@
   // See comment above in registerEncodingNames.
   registrar("ISO-8859-8-I", Create, nullptr);
 
-  const bool is_text_codec_cjk_enabled =
-      base::FeatureList::IsEnabled(blink::features::kTextCodecCJKEnabled);
   int32_t num_encodings = ucnv_countAvailable();
   for (int32_t i = 0; i < num_encodings; ++i) {
     const char* name = ucnv_getAvailableName(i);
@@ -295,7 +290,7 @@
     }
 #endif
     // Avoid codecs supported by `TextCodecCJK`.
-    if (is_text_codec_cjk_enabled && TextCodecCJK::IsSupported(standard_name)) {
+    if (TextCodecCJK::IsSupported(standard_name)) {
       continue;
     }
     registrar(standard_name, Create, nullptr);
diff --git a/third_party/blink/renderer/platform/wtf/text/text_encoding_registry.cc b/third_party/blink/renderer/platform/wtf/text/text_encoding_registry.cc
index 4911a43..78808a1e 100644
--- a/third_party/blink/renderer/platform/wtf/text/text_encoding_registry.cc
+++ b/third_party/blink/renderer/platform/wtf/text/text_encoding_registry.cc
@@ -210,10 +210,8 @@
   TextCodecReplacement::RegisterEncodingNames(AddToTextEncodingNameMap);
   TextCodecReplacement::RegisterCodecs(AddToTextCodecMap);
 
-  if (base::FeatureList::IsEnabled(blink::features::kTextCodecCJKEnabled)) {
-    TextCodecCJK::RegisterEncodingNames(AddToTextEncodingNameMap);
-    TextCodecCJK::RegisterCodecs(AddToTextCodecMap);
-  }
+  TextCodecCJK::RegisterEncodingNames(AddToTextEncodingNameMap);
+  TextCodecCJK::RegisterCodecs(AddToTextCodecMap);
 
   TextCodecICU::RegisterEncodingNames(AddToTextEncodingNameMap);
   TextCodecICU::RegisterCodecs(AddToTextCodecMap);
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index f5f5250d..9a8dcbf 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3848,8 +3848,10 @@
 crbug.com/851363 http/tests/devtools/sxg/sxg-prefetch.js [ Failure Pass ]
 
 # Prefetching inspector protocol test is flaky
-crbug.com/1347616 virtual/prefetch/http/tests/inspector-protocol/prefetch/request-will-be-sent.https.js [ Failure Pass ]
-crbug.com/1347616 virtual/prefetch-reusable/http/tests/inspector-protocol/prefetch/request-will-be-sent.https.js [ Failure Pass ]
+crbug.com/40854799 virtual/prefetch/http/tests/inspector-protocol/prefetch/request-will-be-sent.https.js [ Failure Pass ]
+crbug.com/40854799 virtual/prefetch-reusable/http/tests/inspector-protocol/prefetch/request-will-be-sent.https.js [ Failure Pass ]
+crbug.com/40854799 virtual/prefetch/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https.js [ Failure Pass ]
+crbug.com/40854799 virtual/prefetch-reusable/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https.js [ Failure Pass ]
 
 # Sheriff 2018-03-22
 crbug.com/824848 external/wpt/html/semantics/links/following-hyperlinks/activation-behavior.window.html [ Failure Pass ]
@@ -5551,6 +5553,7 @@
 crbug.com/324536287 external/wpt/soft-navigation-heuristics/interaction-with-paint-before-back.tentative.html [ Failure Pass ]
 
 # Suppress http/tests/inspector-protocol/network/navigate-iframe-in2out.js test cluster
+crbug.com/1413112 [ Mac10.15 ] virtual/reduce-accept-language/http/tests/inspector-protocol/network/navigate-iframe-in2out.js [ Failure ]
 crbug.com/1413726 [ Mac ] http/tests/inspector-protocol/network/navigate-iframe-in2out.js [ Failure ]
 
 # Sheriff 2022-10-07
@@ -7197,8 +7200,6 @@
 crbug.com/330761492 external/wpt/html/editing/the-hidden-attribute/beforematch-scroll-to-text-fragment.html [ Failure Pass ]
 
 # RubyLineBreakable
-crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-break/ruby-000.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-break/ruby-002.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-contain/contain-layout-005.html [ Crash Failure ]
 crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-contain/contain-paint-008.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-contain/contain-paint-ignored-cases-ruby-containing-block-001.html [ Crash ]
@@ -7300,3 +7301,6 @@
 # Gardener 2024-04-16
 crbug.com/335003887 [ Mac14 ] virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/gpu/arg_min_max.https.any.worker.html [ Failure Pass ]
 crbug.com/335003887 [ Win11 ] virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/gpu/arg_min_max.https.any.worker.html [ Failure Pass ]
+
+# Gardener 2024-04-24
+crbug.com/336691003 [ Linux ] external/wpt/long-animation-frame/tentative/loaf-source-location-inline-classic-script.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestLists/MacOld.txt b/third_party/blink/web_tests/TestLists/MacOld.txt
index dc90897..4619dac 100644
--- a/third_party/blink/web_tests/TestLists/MacOld.txt
+++ b/third_party/blink/web_tests/TestLists/MacOld.txt
@@ -81,6 +81,7 @@
 virtual/oopr-canvas2d/fast/canvas/OffscreenCanvas-2d-drawImage.html
 virtual/produce-compile-hints/external/wpt/fetch/api/basic/scheme-blob.sub.any.html
 virtual/produce-compile-hints/external/wpt/fetch/api/basic/scheme-blob.sub.any.worker.html
+virtual/reduce-accept-language/http/tests/inspector-protocol/network/navigate-iframe-in2out.js
 virtual/scalefactor200/fast/hidpi/static/validation-bubble-appearance-hidpi.html
 virtual/stable/webexposed/global-interface-listing-platform-specific.html
 virtual/text-antialias/basic/001.html
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index d629ada0..10443fe 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -82,6 +82,19 @@
     "expires": "Jul 1, 2024"
   },
   {
+    "prefix": "reduce-accept-language",
+    "platforms": ["Linux", "Mac", "Win"],
+    "bases": ["http/tests/navigation/language",
+              "http/tests/inspector-protocol/network",
+              "http/tests/serviceworker/reduce-accept-language/fetch-event-headers.html",
+              "navigator_language/reduce_accept_language"],
+    "exclusive_tests": ["http/tests/serviceworker/reduce-accept-language/fetch-event-headers.html",
+                        "navigator_language/reduce_accept_language"],
+    "args": ["--enable-features=ReduceAcceptLanguage",
+             "--disable-threaded-compositing", "--disable-threaded-animation"],
+    "expires": "Jul 1, 2024"
+  },
+  {
     "prefix": "gpu",
     "platforms": ["Linux", "Mac", "Win", "Fuchsia"],
     "bases": [ "fast/canvas",
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/generic/wildcard-host-part.sub.window.js b/third_party/blink/web_tests/external/wpt/content-security-policy/generic/wildcard-host-part.sub.window.js
new file mode 100644
index 0000000..d210cc6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/generic/wildcard-host-part.sub.window.js
@@ -0,0 +1,27 @@
+setup(_ => {
+  const meta = document.createElement("meta");
+  meta.httpEquiv = "content-security-policy";
+  meta.content = "img-src http://*:{{ports[http][0]}}";
+  document.head.appendChild(meta);
+});
+
+async_test((t) => {
+  const img = document.createElement("img");
+  img.onerror = t.step_func_done();
+  img.onload = t.unreached_func("`data:` image should have been blocked.");
+  img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
+}, "Host wildcard doesn't affect scheme matching.");
+
+async_test((t) => {
+  const img = document.createElement("img");
+  img.onload = t.step_func_done();
+  img.onerror = t.unreached_func("Image from www2 host should have loaded.");
+  img.src = "http://{{domains[www1]}}:{{ports[http][0]}}/content-security-policy/support/pass.png";
+}, "Host wildcard allows arbitrary hosts (www1).");
+
+async_test((t) => {
+  const img = document.createElement("img");
+  img.onload = t.step_func_done();
+  img.onerror = t.unreached_func("Image from www2 host should have loaded.");
+  img.src = "http://{{domains[www2]}}:{{ports[http][0]}}/content-security-policy/support/pass.png";
+}, "Host wildcard allows arbitrary hosts (www2).");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/ruby-002.html b/third_party/blink/web_tests/external/wpt/css/css-break/ruby-002.html
index d17cc56..2c4f6aae 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-break/ruby-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/ruby-002.html
@@ -30,15 +30,13 @@
 <div style="position:relative; width:100px; height:100px; background:red;">
   <div style="columns:2; column-fill:auto; column-gap:0; height:175px; orphans:1; widows:1;">
     <ruby>
-      <div class="main"></div><rt><div class="annotation"></div></rt>
-    </ruby><ruby class="under">
-      <div class="main"></div><rt><div class="annotation"></div></rt>
+      <div class="main"></div><rt><div class="annotation"></div></rt></ruby
+    ><ruby class="under"><div class="main"></div><rt><div class="annotation"></div></rt>
     </ruby>
     <br>
     <ruby>
-      <div class="main"></div><rt><div class="annotation"></div></rt>
-    </ruby><ruby class="under">
-      <div class="main"></div><rt><div class="annotation"></div></rt>
+      <div class="main"></div><rt><div class="annotation"></div></rt></ruby
+    ><ruby class="under"><div class="main"></div><rt><div class="annotation"></div></rt>
     </ruby>
     <br>
   </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ruby/line-spacing.html b/third_party/blink/web_tests/external/wpt/css/css-ruby/line-spacing.html
index 9d3c6f2..4854e984c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ruby/line-spacing.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-ruby/line-spacing.html
@@ -149,5 +149,16 @@
   assert_greater_than_equal(thirdLine.top - rubyLine.top,
                             rubyLine.top - firstLine.top + RUBY_EMPHASIS_SIZE);
 }, 'Don\'t Consume half-leading of the next line with text-emphasis');
+
+// crbug.com/336592423
+test(() => {
+  const {container, ruby, rt} = renderRuby(
+      '<div style="line-height:1;">' +
+      '<span style="display:inline-block; width:1em; height:4em; vertical-align:top"></span><br>' +
+      '<ruby>base<rt>annotation</rt></ruby></div>');
+  const firstLine = container.querySelector('span').getBoundingClientRect();
+  const rtBox = rt.getBoundingClientRect();
+  assert_greater_than_equal(rtBox.top, firstLine.bottom);
+}, 'An atomic-inline should not overlap with an annotation in the next line');
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-filter.any.js b/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-filter.any.js
index 8a49bcf..3c1a7d7 100644
--- a/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-filter.any.js
+++ b/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-filter.any.js
@@ -103,3 +103,15 @@
       ['source teardown', 'source abort event', 'filter observable complete']);
 }, "filter(): Upon source completion, source Observable teardown sequence " +
    "happens after downstream filter complete() is called");
+
+test(() => {
+  const source = new Observable(subscriber => {
+    subscriber.next('value1');
+    subscriber.next('value2');
+    subscriber.next('value3');
+  });
+
+  const indices = [];
+  source.filter((value, index) => indices.push(index)).subscribe();
+  assert_array_equals(indices, [0, 1, 2]);
+}, "filter(): Index is passed correctly to predicate");
diff --git a/third_party/blink/web_tests/external/wpt/selection/onselectionchange-on-distinct-text-controls-expected.txt b/third_party/blink/web_tests/external/wpt/selection/onselectionchange-on-distinct-text-controls-expected.txt
index 0ed464df..7727922e 100644
--- a/third_party/blink/web_tests/external/wpt/selection/onselectionchange-on-distinct-text-controls-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/selection/onselectionchange-on-distinct-text-controls-expected.txt
@@ -1,7 +1,7 @@
 This is a testharness.js-based test.
 [FAIL] selectionchange event on each input element fires independently
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 2
 [FAIL] selectionchange event on each textarea element fires independently
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 2
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/selection/textcontrols/selectionchange-expected.txt b/third_party/blink/web_tests/external/wpt/selection/textcontrols/selectionchange-expected.txt
index 3cc6254..658ebec3b 100644
--- a/third_party/blink/web_tests/external/wpt/selection/textcontrols/selectionchange-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/selection/textcontrols/selectionchange-expected.txt
@@ -1,92 +1,28 @@
 This is a testharness.js-based test.
-Found 44 FAIL, 0 TIMEOUT, 0 NOTRUN.
-[FAIL] Modifying selectionStart value of the input element
-  assert_equals: expected 1 but got 0
-[FAIL] Modifying selectionEnd value of the input element
-  assert_equals: expected 1 but got 0
-[FAIL] Calling setSelectionRange() on the input element
-  assert_equals: expected 1 but got 0
+Found 12 FAIL, 0 TIMEOUT, 0 NOTRUN.
 [FAIL] Calling select() on the input element
-  assert_equals: expected 1 but got 0
-[FAIL] Calling setRangeText() on the input element
-  assert_equals: expected 1 but got 0
-[FAIL] Setting the same selectionStart value twice on the input element
-  assert_equals: expected 1 but got 0
-[FAIL] Setting the same selectionEnd value twice on the input element
-  assert_equals: expected 1 but got 0
-[FAIL] Setting the same selection range twice on the input element
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 2
 [FAIL] Calling select() twice on the input element
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 2
 [FAIL] Calling setRangeText() after select() on the input element
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 2
 [FAIL] Calling setRangeText() repeatedly on the input element
-  assert_equals: expected 1 but got 0
-[FAIL] Modifying selectionStart value of the disconnected input element
-  assert_equals: expected 1 but got 0
-[FAIL] Modifying selectionEnd value of the disconnected input element
-  assert_equals: expected 1 but got 0
-[FAIL] Calling setSelectionRange() on the disconnected input element
-  assert_equals: expected 1 but got 0
-[FAIL] Calling select() on the disconnected input element
-  assert_equals: expected 1 but got 0
-[FAIL] Calling setRangeText() on the disconnected input element
-  assert_equals: expected 1 but got 0
-[FAIL] Setting the same selectionStart value twice on the disconnected input element
-  assert_equals: expected 1 but got 0
-[FAIL] Setting the same selectionEnd value twice on the disconnected input element
-  assert_equals: expected 1 but got 0
-[FAIL] Setting the same selection range twice on the disconnected input element
-  assert_equals: expected 1 but got 0
-[FAIL] Calling select() twice on the disconnected input element
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 5
 [FAIL] Calling setRangeText() after select() on the disconnected input element
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 2
 [FAIL] Calling setRangeText() repeatedly on the disconnected input element
-  assert_equals: expected 1 but got 0
-[FAIL] Modifying selectionStart value of the textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Modifying selectionEnd value of the textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Calling setSelectionRange() on the textarea element
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 4
 [FAIL] Calling select() on the textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Calling setRangeText() on the textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Setting the same selectionStart value twice on the textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Setting the same selectionEnd value twice on the textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Setting the same selection range twice on the textarea element
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 2
 [FAIL] Calling select() twice on the textarea element
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 2
 [FAIL] Calling setRangeText() after select() on the textarea element
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 2
 [FAIL] Calling setRangeText() repeatedly on the textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Modifying selectionStart value of the disconnected textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Modifying selectionEnd value of the disconnected textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Calling setSelectionRange() on the disconnected textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Calling select() on the disconnected textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Calling setRangeText() on the disconnected textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Setting the same selectionStart value twice on the disconnected textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Setting the same selectionEnd value twice on the disconnected textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Setting the same selection range twice on the disconnected textarea element
-  assert_equals: expected 1 but got 0
-[FAIL] Calling select() twice on the disconnected textarea element
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 5
 [FAIL] Calling setRangeText() after select() on the disconnected textarea element
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 2
 [FAIL] Calling setRangeText() repeatedly on the disconnected textarea element
-  assert_equals: expected 1 but got 0
+  assert_equals: expected 1 but got 4
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/fast/events/touch/gesture/focus-selectionchange-on-tap-expected.txt b/third_party/blink/web_tests/fast/events/touch/gesture/focus-selectionchange-on-tap-expected.txt
index fcaa07b4..6880c47 100644
--- a/third_party/blink/web_tests/fast/events/touch/gesture/focus-selectionchange-on-tap-expected.txt
+++ b/third_party/blink/web_tests/fast/events/touch/gesture/focus-selectionchange-on-tap-expected.txt
@@ -24,7 +24,7 @@
 Received mouseup on target
 Received click on target
 PASS tapHandled is false
-Received selectionchange on #document anchor=BODY[2]
+Received selectionchange on target anchor=BODY[2]
 PASS isFocused(target) is true
 
 
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-and-redirect-cross-site-when-document-alive.https-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-and-redirect-cross-site-when-document-alive.https-expected.txt
index 99bfc0e1..c211689c 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-and-redirect-cross-site-when-document-alive.https-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-and-redirect-cross-site-when-document-alive.https-expected.txt
@@ -12,7 +12,6 @@
     request : {
         headers : {
             Accept : */*
-            Accept-Language : en-us
             Referer : https://127.0.0.1:8443/inspector-protocol/resources/inspector-protocol-page.html
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
@@ -73,7 +72,6 @@
     request : {
         headers : {
             Accept : */*
-            Accept-Language : en-us
             Referer : https://127.0.0.1:8443/inspector-protocol/resources/inspector-protocol-page.html
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-and-redirect-when-document-alive.https-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-and-redirect-when-document-alive.https-expected.txt
index 6cd14fe..94fe849 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-and-redirect-when-document-alive.https-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-and-redirect-when-document-alive.https-expected.txt
@@ -12,7 +12,6 @@
     request : {
         headers : {
             Accept : */*
-            Accept-Language : en-us
             Referer : https://127.0.0.1:8443/inspector-protocol/resources/inspector-protocol-page.html
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
@@ -73,7 +72,6 @@
     request : {
         headers : {
             Accept : */*
-            Accept-Language : en-us
             Referer : https://127.0.0.1:8443/inspector-protocol/resources/inspector-protocol-page.html
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-post-when-document-alive.https-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-post-when-document-alive.https-expected.txt
index f330ed4..533d1dff 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-post-when-document-alive.https-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-post-when-document-alive.https-expected.txt
@@ -13,7 +13,6 @@
         hasPostData : true
         headers : {
             Accept : */*
-            Accept-Language : en-us
             Content-Type : text/plain;charset=UTF-8
             Referer : https://127.0.0.1:8443/inspector-protocol/resources/inspector-protocol-page.html
             User-Agent : <string>
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-when-document-alive.https-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-when-document-alive.https-expected.txt
index d8c5972e..80ee8aa 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-when-document-alive.https-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch-later/activate-when-document-alive.https-expected.txt
@@ -12,7 +12,6 @@
     request : {
         headers : {
             Accept : */*
-            Accept-Language : en-us
             Referer : https://127.0.0.1:8443/inspector-protocol/resources/inspector-protocol-page.html
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/redirect-headers-override-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/redirect-headers-override-expected.txt
index 3fbf33f..028c9e37 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/redirect-headers-override-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/redirect-headers-override-expected.txt
@@ -1,7 +1,6 @@
 Tests overridden headers don't stick across redirects
 Redirected request headers:{
     Accept : */*
-    Accept-Language : en-us
     Referer : http://127.0.0.1:8000/inspector-protocol/resources/inspector-protocol-page.html
     User-Agent : <string>
 }
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/redirect-headers-override3-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/redirect-headers-override3-expected.txt
index ec51fcc..b99ca883 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/redirect-headers-override3-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fetch/redirect-headers-override3-expected.txt
@@ -1,13 +1,11 @@
 Tests overridden headers don't stick across redirects
 Redirected request 1 headers: {
     Accept : */*
-    Accept-Language : en-us
     Referer : http://127.0.0.1:8000/inspector-protocol/resources/inspector-protocol-page.html
     User-Agent : <string>
 }
 Redirected request 2 headers: {
     Accept : */*
-    Accept-Language : en-us
     Referer : http://127.0.0.1:8000/inspector-protocol/resources/inspector-protocol-page.html
     User-Agent : <string>
 }
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/network/webbundle-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/network/webbundle-expected.txt
index c520fc19..daf1246 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/network/webbundle-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/network/webbundle-expected.txt
@@ -11,7 +11,6 @@
         redirectHasExtraInfo : false
         request : {
             headers : {
-                Accept-Language : en-us
                 Upgrade-Insecure-Requests : 1
                 User-Agent : <string>
                 sec-ch-ua : "content_shell";v="999"
@@ -44,7 +43,6 @@
         redirectHasExtraInfo : false
         request : {
             headers : {
-                Accept-Language : en-us
                 Referer : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
                 User-Agent : <string>
                 sec-ch-ua : "content_shell";v="999"
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https-expected.txt
index a2c2e16..e561da12 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https-expected.txt
@@ -10,7 +10,6 @@
     redirectHasExtraInfo : false
     request : {
         headers : {
-            Accept-Language : en-us
             Upgrade-Insecure-Requests : 1
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
@@ -36,7 +35,7 @@
     headers : {
         Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
         Accept-Encoding : gzip, deflate, br, zstd
-        Accept-Language : en-us
+        Accept-Language : en-us,en
         Connection : keep-alive
         Host : 127.0.0.1:8443
         Sec-Fetch-Dest : document
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https-expected.txt
index 4b2b188..4e05392 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https-expected.txt
@@ -10,7 +10,6 @@
     redirectHasExtraInfo : false
     request : {
         headers : {
-            Accept-Language : en-us
             Upgrade-Insecure-Requests : 1
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
@@ -36,7 +35,7 @@
     headers : {
         Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
         Accept-Encoding : gzip, deflate, br, zstd
-        Accept-Language : en-us
+        Accept-Language : en-us,en
         Connection : keep-alive
         Host : 127.0.0.1:8443
         Sec-Fetch-Dest : document
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/resources/animation.html b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/animation.html
new file mode 100644
index 0000000..57061f6
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/resources/animation.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Simple animation</title>
+    <style>
+      @keyframes simple-animation {
+        from {
+          color: red;
+          transform: translateX(0%);
+        }
+        to {
+          color: yellow;
+          transform: translateX(100%);
+        }
+      }
+
+      div {
+        width: 200px;
+        height: 200px;
+        background-color: red;
+        animation-name: simple-animation;
+        animation-duration: 4s;
+      }
+    </style>
+  </head>
+  <body>
+    <div>hello world. This Animation has an unsupported css property!</div>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/animation-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/animation-expected.txt
new file mode 100644
index 0000000..b12b9b4
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/animation-expected.txt
@@ -0,0 +1,25 @@
+Tests the data of an Animation event
+Recording started
+Tracing complete
+Object: {
+	args: {
+		data: {
+			displayName: simple-animation
+			id: string
+			name: 
+			nodeId: number
+			nodeName: string
+			state: running
+		}
+	}
+	cat: string
+	id2: {
+		local: string
+	}
+	name: Animation
+	ph: b
+	pid: number
+	tid: number
+	ts: number
+}
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/animation.js b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/animation.js
new file mode 100644
index 0000000..17a0fb24
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/animation.js
@@ -0,0 +1,27 @@
+(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
+    const {session, dp} = await testRunner.startBlank(
+        'Tests the data of an Animation event');
+
+    const TracingHelper =
+        await testRunner.loadScript('../resources/tracing-test.js');
+    const tracingHelper = new TracingHelper(testRunner, session);
+
+    await dp.Page.enable();
+    await dp.Animation.enable();
+
+    await tracingHelper.startTracing('blink.animations,devtools.timeline,benchmark,rail');
+
+    dp.Page.navigate(
+        {url: 'http://127.0.0.1:8000/inspector-protocol/resources/animation.html'});
+
+    // Wait for animation.
+    await dp.Animation.onceAnimationStarted();
+
+    const events = await tracingHelper.stopTracing(/blink\.animations|devtools\.timeline|benchmark|rail/);
+    const animationEvents = events.filter(event => event.name && event.name === 'Animation' && event.ph !== 'n').sort((a, b) => a.ts - b.ts);
+    for (const event of animationEvents) {
+        tracingHelper.logEventShape(event, [], ['name', 'data', 'ph', 'state', 'compositeFailed', 'unsupportedProperties', 'displayName']);
+    }
+
+    testRunner.completeTest();
+  });
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/rendering-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/rendering-expected.txt
index d12bf3d..86ba1ce1 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/rendering-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/rendering-expected.txt
@@ -24,6 +24,7 @@
 Object: {
 	args: {
 		data: {
+			displayName: string
 			id: string
 			name: string
 			nodeId: number
diff --git a/third_party/blink/web_tests/http/tests/navigation/language/accept-language-header-expected.txt b/third_party/blink/web_tests/http/tests/navigation/language/accept-language-header-expected.txt
index f2669528..080b8300 100644
--- a/third_party/blink/web_tests/http/tests/navigation/language/accept-language-header-expected.txt
+++ b/third_party/blink/web_tests/http/tests/navigation/language/accept-language-header-expected.txt
@@ -1,4 +1,4 @@
 Tests the default Accept-Language header
 
-HTTP Accept-Language header should be: en-us
+HTTP Accept-Language header should be: en-us,en
 
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/fetch-event-headers.html b/third_party/blink/web_tests/http/tests/serviceworker/fetch-event-headers.html
index 8b74a41..9e853a7 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/fetch-event-headers.html
+++ b/third_party/blink/web_tests/http/tests/serviceworker/fetch-event-headers.html
@@ -31,8 +31,8 @@
           header_names.sort();
           assert_array_equals(
             header_names,
-            ["accept", "accept-language", "sec-ch-ua", "sec-ch-ua-mobile",
-            "sec-ch-ua-platform", "upgrade-insecure-requests", "user-agent"],
+            ["accept", "sec-ch-ua", "sec-ch-ua-mobile", "sec-ch-ua-platform",
+             "upgrade-insecure-requests", "user-agent"],
             'event.request has the expected headers.');
 
           return service_worker_unregister_and_done(t, scope);
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/dom-getElementByRelation-expected.txt b/third_party/blink/web_tests/inspector-protocol/dom/dom-getElementByRelation-expected.txt
new file mode 100644
index 0000000..641791f
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/dom/dom-getElementByRelation-expected.txt
@@ -0,0 +1,10 @@
+Tests DOM.getElementByRelation command.
+Node Id from query selector and getElementByRelation should be the same:
+true
+non-existent target id should be zero: 
+0
+non-existent target id should be zero: 
+0
+Node Id from query selector and getElementByRelation should be the same:
+true
+
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/dom-getElementByRelation.js b/third_party/blink/web_tests/inspector-protocol/dom/dom-getElementByRelation.js
new file mode 100644
index 0000000..d647866
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/dom/dom-getElementByRelation.js
@@ -0,0 +1,34 @@
+(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
+  const {dp} = await testRunner.startURL('resources/dom-get-element-by-relation.html', 'Tests DOM.getElementByRelation command.');
+
+  await dp.DOM.enable();
+  const getDocumentResponse = await dp.DOM.getDocument();
+  const popoverOpener1 = (await dp.DOM.querySelector({nodeId: getDocumentResponse.result.root.nodeId, selector: '.popover-opener-1' })).result.nodeId;
+  const popoverTarget1ById = (await dp.DOM.querySelector({nodeId: getDocumentResponse.result.root.nodeId, selector: '#my-popover-1' })).result.nodeId;
+  const popoverTarget1 = await dp.DOM.getElementByRelation({nodeId: popoverOpener1, relation: 'PopoverTarget'});
+  testRunner.log('Node Id from query selector and getElementByRelation should be the same:');
+  testRunner.log(popoverTarget1ById === popoverTarget1.result.nodeId);
+
+  const popoverOpener2 = (await dp.DOM.querySelector({nodeId: getDocumentResponse.result.root.nodeId, selector: '.popover-opener-2' })).result.nodeId;
+  const emptyTarget = await dp.DOM.getElementByRelation({nodeId: popoverOpener2, relation: 'PopoverTarget'});
+  testRunner.log('non-existent target id should be zero: ');
+  testRunner.log(emptyTarget.result.nodeId);
+
+  const popoverOpener3 = (await dp.DOM.querySelector({nodeId: getDocumentResponse.result.root.nodeId, selector: '.popover-opener-3' })).result.nodeId;
+  const targetByNonFormControlEl = await dp.DOM.getElementByRelation({nodeId: popoverOpener3, relation: 'PopoverTarget'});
+  testRunner.log('non-existent target id should be zero: ');
+  testRunner.log(targetByNonFormControlEl.result.nodeId);
+
+  // Verify that it works with popover target set via JavaScript API.
+  await dp.Runtime.evaluate({ expression: `
+    const popoverOpener2 = document.querySelector('.popover-opener-2');
+    const myPopover2 = document.getElementById('my-popover-2');
+    popoverOpener2.popoverTargetElement = myPopover2;
+  `});
+  const popoverTarget2 = await dp.DOM.getElementByRelation({nodeId: popoverOpener2, relation: 'PopoverTarget'});
+  const popoverTarget2ById = (await dp.DOM.querySelector({nodeId: getDocumentResponse.result.root.nodeId, selector: '#my-popover-2' })).result.nodeId;
+  testRunner.log('Node Id from query selector and getElementByRelation should be the same:');
+  testRunner.log(popoverTarget2ById === popoverTarget2.result.nodeId);
+
+  testRunner.completeTest();
+})
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/resources/dom-get-element-by-relation.html b/third_party/blink/web_tests/inspector-protocol/dom/resources/dom-get-element-by-relation.html
new file mode 100644
index 0000000..041dd0d
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/dom/resources/dom-get-element-by-relation.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <button class="popover-opener-1" popovertarget="my-popover-1">Open Popover</button>
+    <button class="popover-opener-2">Open Popover</button>
+    <div class="popover-opener-3" popovertarget="my-popover-2">Open Popover</div>
+    <div popover id="my-popover-1">popover 1</div>
+    <div popover id="my-popover-2">popover 2</div>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/navigator_language/navigator_language.html b/third_party/blink/web_tests/navigator_language/navigator_language.html
index 7795600c..49e2d5f 100644
--- a/third_party/blink/web_tests/navigator_language/navigator_language.html
+++ b/third_party/blink/web_tests/navigator_language/navigator_language.html
@@ -91,10 +91,11 @@
         testRunner.setAcceptLanguages(data.accept_languages);
         await languageChangePromise;
 
-        // ReduceAcceptLanguage experiment turns on, only returns one language.
-        assert_equals(navigator.languages.length, 1);
+        assert_equals(navigator.languages.length, data.languages.length);
         assert_equals(navigator.language, data.languages[0]);
-        assert_equals(navigator.languages[0], data.languages[0]);
+        for (var j = 0; j < navigator.languages.length; ++j) {
+            assert_equals(navigator.languages[j], data.languages[j]);
+        }
     }
 }, "Test that navigator.languages reflects the accept languages value.");
 
diff --git a/third_party/blink/web_tests/navigator_language/reduce_accept_language/navigator_language.html b/third_party/blink/web_tests/navigator_language/reduce_accept_language/navigator_language.html
new file mode 100644
index 0000000..a8f6fd2
--- /dev/null
+++ b/third_party/blink/web_tests/navigator_language/reduce_accept_language/navigator_language.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+
+async function languageChange() {
+    return new Promise(resolve => window.onlanguagechange = resolve);
+}
+
+async function languageChangeEvent() {
+    return new Promise(resolve => window.addEventListener('languagechange', resolve));
+}
+
+promise_test(async () => {
+    var testValues = [
+        { accept_languages: 'foo', languages: ['foo'] },
+        { accept_languages: '', languages: ['en-US'] },
+        { accept_languages: 'foo,bar', languages: [ 'foo' ] },
+        { accept_languages: '  foo , bar ', languages: [ 'foo' ] },
+        { accept_languages: '  foo ; bar ', languages: [ 'foo ; bar' ] },
+        { accept_languages: '_foo_', languages: ['_foo_'] },
+        { accept_languages: 'en_', languages: ['en-'] },
+        { accept_languages: 'en__', languages: ['en-_'] },
+        { accept_languages: 'en_US, fr_FR', languages: ['en-US'] },
+        { accept_languages: 'en_US_CA', languages: ['en-US_CA'] },
+    ];
+
+    for (var i = 0; i < testValues.length; ++i) {
+        var data = testValues[i];
+
+        var languageChangePromise = languageChange();
+        testRunner.setAcceptLanguages(data.accept_languages);
+        await languageChangePromise;
+
+        assert_equals(navigator.languages.length, 1);
+        assert_equals(navigator.language, data.languages[0]);
+        // Verify usage count metric records.
+        var ReduceAcceptLanguage = 4386; // From UseCounter.h
+        assert_true(internals.isUseCounted(document, ReduceAcceptLanguage));
+        for (var j = 0; j < navigator.languages.length; ++j) {
+            console.log(j);
+            assert_equals(data.languages[j], navigator.languages[j]);
+        }
+    }
+}, "Test that navigator.languages reflects the accept languages value.");
+
+
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/platform/fuchsia/navigator_language/navigator_language-expected.txt b/third_party/blink/web_tests/platform/fuchsia/navigator_language/navigator_language-expected.txt
deleted file mode 100644
index f31d363a..0000000
--- a/third_party/blink/web_tests/platform/fuchsia/navigator_language/navigator_language-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Test that navigator.languages reflects the accept languages value.
-  assert_equals: expected 1 but got 2
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/elementwise_unary.https.any-expected.txt b/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/elementwise_unary.https.any-expected.txt
index 5a2642b..d3f05b4 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/elementwise_unary.https.any-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/elementwise_unary.https.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 27 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 20 FAIL, 0 TIMEOUT, 0 NOTRUN.
 [FAIL] erf float32 0D scalar
   promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kErf is not implemented."
 [FAIL] erf float32 1D constant tensor
@@ -14,20 +14,6 @@
   promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kErf is not implemented."
 [FAIL] erf float32 5D tensor
   promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kErf is not implemented."
-[FAIL] identity float32 0D scalar
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
-[FAIL] identity float32 1D constant tensor
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
-[FAIL] identity float32 1D tensor
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
-[FAIL] identity float32 2D tensor
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
-[FAIL] identity float32 3D tensor
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
-[FAIL] identity float32 4D tensor
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
-[FAIL] identity float32 5D tensor
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
 [FAIL] reciprocal float32 0D scalar
   promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kReciprocal is not implemented."
 [FAIL] reciprocal float32 1D constant tensor
diff --git a/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/elementwise_unary.https.any.worker-expected.txt b/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/elementwise_unary.https.any.worker-expected.txt
index 5a2642b..d3f05b4 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/elementwise_unary.https.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/elementwise_unary.https.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 27 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 20 FAIL, 0 TIMEOUT, 0 NOTRUN.
 [FAIL] erf float32 0D scalar
   promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kErf is not implemented."
 [FAIL] erf float32 1D constant tensor
@@ -14,20 +14,6 @@
   promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kErf is not implemented."
 [FAIL] erf float32 5D tensor
   promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kErf is not implemented."
-[FAIL] identity float32 0D scalar
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
-[FAIL] identity float32 1D constant tensor
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
-[FAIL] identity float32 1D tensor
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
-[FAIL] identity float32 2D tensor
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
-[FAIL] identity float32 3D tensor
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
-[FAIL] identity float32 4D tensor
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
-[FAIL] identity float32 5D tensor
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kIdentity is not implemented."
 [FAIL] reciprocal float32 0D scalar
   promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': kReciprocal is not implemented."
 [FAIL] reciprocal float32 1D constant tensor
diff --git a/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-and-redirect-cross-site-when-document-alive.https-expected.txt b/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-and-redirect-cross-site-when-document-alive.https-expected.txt
index 7335246..07f3efc1 100644
--- a/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-and-redirect-cross-site-when-document-alive.https-expected.txt
+++ b/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-and-redirect-cross-site-when-document-alive.https-expected.txt
@@ -12,7 +12,6 @@
     request : {
         headers : {
             Accept : */*
-            Accept-Language : en-us
             Referer : https://127.0.0.1:8443/inspector-protocol/resources/inspector-protocol-page.html
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
@@ -73,7 +72,6 @@
     request : {
         headers : {
             Accept : */*
-            Accept-Language : en-us
             Referer : https://127.0.0.1:8443/inspector-protocol/resources/inspector-protocol-page.html
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
diff --git a/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-and-redirect-when-document-alive.https-expected.txt b/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-and-redirect-when-document-alive.https-expected.txt
index a150fd759..4b908dc 100644
--- a/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-and-redirect-when-document-alive.https-expected.txt
+++ b/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-and-redirect-when-document-alive.https-expected.txt
@@ -12,7 +12,6 @@
     request : {
         headers : {
             Accept : */*
-            Accept-Language : en-us
             Referer : https://127.0.0.1:8443/inspector-protocol/resources/inspector-protocol-page.html
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
@@ -73,7 +72,6 @@
     request : {
         headers : {
             Accept : */*
-            Accept-Language : en-us
             Referer : https://127.0.0.1:8443/inspector-protocol/resources/inspector-protocol-page.html
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
diff --git a/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-post-when-document-alive.https-expected.txt b/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-post-when-document-alive.https-expected.txt
index 3f87ef3d..9ff7efb3 100644
--- a/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-post-when-document-alive.https-expected.txt
+++ b/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-post-when-document-alive.https-expected.txt
@@ -13,7 +13,6 @@
         hasPostData : true
         headers : {
             Accept : */*
-            Accept-Language : en-us
             Content-Type : text/plain;charset=UTF-8
             Referer : https://127.0.0.1:8443/inspector-protocol/resources/inspector-protocol-page.html
             User-Agent : <string>
diff --git a/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-when-document-alive.https-expected.txt b/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-when-document-alive.https-expected.txt
index af931a71..3cfc133 100644
--- a/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-when-document-alive.https-expected.txt
+++ b/third_party/blink/web_tests/platform/win11-arm64/http/tests/inspector-protocol/fetch-later/activate-when-document-alive.https-expected.txt
@@ -12,7 +12,6 @@
     request : {
         headers : {
             Accept : */*
-            Accept-Language : en-us
             Referer : https://127.0.0.1:8443/inspector-protocol/resources/inspector-protocol-page.html
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
diff --git a/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch-reusable/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https-expected.txt b/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch-reusable/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https-expected.txt
index 3b1171f..c076ecc5 100644
--- a/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch-reusable/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https-expected.txt
+++ b/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch-reusable/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https-expected.txt
@@ -10,7 +10,6 @@
     redirectHasExtraInfo : false
     request : {
         headers : {
-            Accept-Language : en-us
             Upgrade-Insecure-Requests : 1
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
@@ -36,7 +35,7 @@
     headers : {
         Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
         Accept-Encoding : gzip, deflate, br, zstd
-        Accept-Language : en-us
+        Accept-Language : en-us,en
         Connection : keep-alive
         Host : 127.0.0.1:8443
         Sec-Fetch-Dest : document
diff --git a/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch-reusable/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https-expected.txt b/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch-reusable/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https-expected.txt
index 6f5fb71..205c1ed 100644
--- a/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch-reusable/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https-expected.txt
+++ b/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch-reusable/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https-expected.txt
@@ -35,7 +35,7 @@
     headers : {
         Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
         Accept-Encoding : gzip, deflate, br, zstd
-        Accept-Language : en-us
+        Accept-Language : en-us,en
         Connection : keep-alive
         Host : 127.0.0.1:8443
         Sec-Fetch-Dest : document
@@ -157,7 +157,7 @@
     headers : {
         Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
         Accept-Encoding : gzip, deflate, br, zstd
-        Accept-Language : en-us
+        Accept-Language : en-us,en
         Connection : keep-alive
         Host : 127.0.0.1:8443
         Purpose : prefetch
@@ -266,7 +266,7 @@
     headers : {
         Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
         Accept-Encoding : gzip, deflate, br, zstd
-        Accept-Language : en-us
+        Accept-Language : en-us,en
         Connection : keep-alive
         Host : 127.0.0.1:8443
         Purpose : prefetch
diff --git a/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https-expected.txt b/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https-expected.txt
index 3b1171f..c076ecc5 100644
--- a/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https-expected.txt
+++ b/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect-cross-site.https-expected.txt
@@ -10,7 +10,6 @@
     redirectHasExtraInfo : false
     request : {
         headers : {
-            Accept-Language : en-us
             Upgrade-Insecure-Requests : 1
             User-Agent : <string>
             sec-ch-ua : "content_shell";v="999"
@@ -36,7 +35,7 @@
     headers : {
         Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
         Accept-Encoding : gzip, deflate, br, zstd
-        Accept-Language : en-us
+        Accept-Language : en-us,en
         Connection : keep-alive
         Host : 127.0.0.1:8443
         Sec-Fetch-Dest : document
diff --git a/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https-expected.txt b/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https-expected.txt
index 6f5fb71..205c1ed 100644
--- a/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https-expected.txt
+++ b/third_party/blink/web_tests/platform/win11-arm64/virtual/prefetch/http/tests/inspector-protocol/prefetch/request-will-be-sent-redirect.https-expected.txt
@@ -35,7 +35,7 @@
     headers : {
         Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
         Accept-Encoding : gzip, deflate, br, zstd
-        Accept-Language : en-us
+        Accept-Language : en-us,en
         Connection : keep-alive
         Host : 127.0.0.1:8443
         Sec-Fetch-Dest : document
@@ -157,7 +157,7 @@
     headers : {
         Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
         Accept-Encoding : gzip, deflate, br, zstd
-        Accept-Language : en-us
+        Accept-Language : en-us,en
         Connection : keep-alive
         Host : 127.0.0.1:8443
         Purpose : prefetch
@@ -266,7 +266,7 @@
     headers : {
         Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
         Accept-Encoding : gzip, deflate, br, zstd
-        Accept-Language : en-us
+        Accept-Language : en-us,en
         Connection : keep-alive
         Host : 127.0.0.1:8443
         Purpose : prefetch
diff --git a/third_party/blink/web_tests/virtual/reduce-accept-language/README.md b/third_party/blink/web_tests/virtual/reduce-accept-language/README.md
new file mode 100644
index 0000000..7634f1e5
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/reduce-accept-language/README.md
@@ -0,0 +1 @@
+# This suite runs tests with --enable-features=ReduceAcceptLanguage
diff --git a/third_party/blink/web_tests/virtual/reduce-accept-language/http/tests/inspector-protocol/network/webbundle-expected.txt b/third_party/blink/web_tests/virtual/reduce-accept-language/http/tests/inspector-protocol/network/webbundle-expected.txt
new file mode 100644
index 0000000..c520fc19
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/reduce-accept-language/http/tests/inspector-protocol/network/webbundle-expected.txt
@@ -0,0 +1,113 @@
+Verifies that webbundle events are triggered
+requestWillBeSent[
+    [0] : {
+        documentURL : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
+        frameId : <string>
+        hasUserGesture : false
+        initiator : {
+            type : other
+        }
+        loaderId : <string>
+        redirectHasExtraInfo : false
+        request : {
+            headers : {
+                Accept-Language : en-us
+                Upgrade-Insecure-Requests : 1
+                User-Agent : <string>
+                sec-ch-ua : "content_shell";v="999"
+                sec-ch-ua-mobile : ?0
+                sec-ch-ua-platform : "Unknown"
+            }
+            initialPriority : VeryHigh
+            isSameSite : true
+            method : GET
+            mixedContentType : none
+            referrerPolicy : strict-origin-when-cross-origin
+            url : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
+        }
+        requestId : <string>
+        timestamp : <number>
+        type : Document
+        wallTime : <number>
+    }
+    [1] : {
+        documentURL : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
+        frameId : <string>
+        hasUserGesture : false
+        initiator : {
+            columnNumber : 11
+            lineNumber : 13
+            type : parser
+            url : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
+        }
+        loaderId : <string>
+        redirectHasExtraInfo : false
+        request : {
+            headers : {
+                Accept-Language : en-us
+                Referer : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
+                User-Agent : <string>
+                sec-ch-ua : "content_shell";v="999"
+                sec-ch-ua-mobile : ?0
+                sec-ch-ua-platform : "Unknown"
+            }
+            initialPriority : High
+            isSameSite : true
+            method : GET
+            mixedContentType : none
+            referrerPolicy : strict-origin-when-cross-origin
+            url : http://127.0.0.1:8000/inspector-protocol/network/resources/webbundle.php
+        }
+        requestId : <string>
+        timestamp : <number>
+        type : Other
+        wallTime : <number>
+    }
+    [2] : {
+        documentURL : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
+        frameId : <string>
+        hasUserGesture : false
+        initiator : {
+            columnNumber : 78
+            lineNumber : 14
+            type : parser
+            url : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
+        }
+        loaderId : <string>
+        redirectHasExtraInfo : false
+        request : {
+            headers : {
+                Referer : http://127.0.0.1:8000/
+                User-Agent : <string>
+            }
+            initialPriority : High
+            isSameSite : false
+            method : GET
+            mixedContentType : none
+            referrerPolicy : strict-origin-when-cross-origin
+            url : uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720
+        }
+        requestId : <string>
+        timestamp : <number>
+        type : Script
+        wallTime : <number>
+    }
+]
+webBundleMetadataReceived{
+    requestId : <string>
+    urls : [
+        [0] : uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720
+        [1] : uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae
+    ]
+}
+webBundleInnerResponse{
+    bundleRequestId : <string>
+    innerRequestId : <string>
+    innerRequestURL : uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720
+}
+webBundleMetadataReceived.urls: uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720,uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae
+webBundleInnerResponse.innerRequestURL: uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720
+bundle request ID from webBundleMetadataReceived matches ID from requestWillBeSent
+inner request ID from webBundleInnerResponse matches ID from requestWillBeSent
+inner request ID from webBundleInnerResponse matches ID from webBundleMetadataReceived
+
diff --git a/third_party/blink/web_tests/virtual/reduce-accept-language/http/tests/navigation/language/accept-language-header-expected.txt b/third_party/blink/web_tests/virtual/reduce-accept-language/http/tests/navigation/language/accept-language-header-expected.txt
new file mode 100644
index 0000000..f2669528
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/reduce-accept-language/http/tests/navigation/language/accept-language-header-expected.txt
@@ -0,0 +1,4 @@
+Tests the default Accept-Language header
+
+HTTP Accept-Language header should be: en-us
+
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/language/accept-language-header-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/navigation/language/accept-language-header-expected.txt
deleted file mode 100644
index 080b8300..0000000
--- a/third_party/blink/web_tests/virtual/stable/http/tests/navigation/language/accept-language-header-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Tests the default Accept-Language header
-
-HTTP Accept-Language header should be: en-us,en
-
diff --git a/third_party/catapult b/third_party/catapult
index 221ecf7..4821643 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit 221ecf70737a2ee01ca0f9aae6133b1a05dc5f09
+Subproject commit 48216439d9883995da2edbf29ffa82740666c5c3
diff --git a/third_party/dawn b/third_party/dawn
index 48de84d..48ca92d 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 48de84dfe6458204fe463d1249e336680302ff71
+Subproject commit 48ca92d67dde7192a4871aa4f17a73af7c204e94
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index 5f2fd89..1a8fe4d 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit 5f2fd897ad3e517576bef2eab8e9250d43aaf800
+Subproject commit 1a8fe4df13a1bdd4845fb4845f412214a76e56aa
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 19d8873..e6256af 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 19d8873c294f5859d5dfcf6a4a57966761f48694
+Subproject commit e6256afd3f8f181453984b407fe63c50de9559f5
diff --git a/third_party/skia b/third_party/skia
index cdede8e..9e23cc5 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit cdede8e2e18b68d4412ac09d7d583034d22f3162
+Subproject commit 9e23cc57008740ab890bd9add274af8a2bdd7882
diff --git a/third_party/webrtc b/third_party/webrtc
index fffd489..0a8703b 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit fffd489d2e5be9f48d3d46ab753baa9b020f3652
+Subproject commit 0a8703b5c117ea8a6bd5eb7fa6d350681126cb20
diff --git a/tools/android/BUILD.gn b/tools/android/BUILD.gn
index 2e5d39d..bc8a00d1 100644
--- a/tools/android/BUILD.gn
+++ b/tools/android/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//base/allocator/partition_allocator/partition_alloc.gni")
+import("//build/config/sanitizers/sanitizers.gni")
 
 # Intermediate target grouping the android tools needed to run native
 # unittests and instrumentation test apks.
@@ -18,6 +19,9 @@
     "//tools/perf:run_benchmark_wrapper",
     "//tools/perf/clear_system_cache",
   ]
+  if (is_asan) {
+    deps += [ "//tools/android/asan/third_party:asan_device_setup" ]
+  }
   if (use_full_mte) {
     deps += [ "//tools/android/mte:mte_device_setup" ]
   }
diff --git a/tools/android/asan/third_party/BUILD.gn b/tools/android/asan/third_party/BUILD.gn
new file mode 100644
index 0000000..f3d96603
--- /dev/null
+++ b/tools/android/asan/third_party/BUILD.gn
@@ -0,0 +1,54 @@
+# Copyright 2019 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/config.gni")
+import("//build/config/clang/clang.gni")
+import("//build/util/generate_wrapper.gni")
+
+generate_wrapper("asan_device_setup") {
+  testonly = true
+  executable = "with_asan.py"
+  wrapper_script = "$root_out_dir/bin/run_with_asan"
+
+  _lib_archs = []
+
+  if (target_cpu == "arm" || target_cpu == "arm64") {
+    _lib_archs += [
+      "arm",
+      "aarch64",
+    ]
+  } else if (target_cpu == "x86") {
+    _lib_archs += [ "i686" ]
+  } else {
+    assert(false, "No ASAN library available for $target_cpu")
+  }
+
+  _adb_path = "${public_android_sdk_root}/platform-tools/adb"
+  _lib_dir = "${clang_base_path}/lib/clang/${clang_version}/lib/linux"
+  _lib_paths = []
+  foreach(_lib_arch, _lib_archs) {
+    _lib_paths += [ "${_lib_dir}/libclang_rt.asan-${_lib_arch}-android.so" ]
+  }
+  data = [
+    "asan_device_setup.sh",
+    "with_asan.py",
+    _adb_path,
+  ]
+  data += _lib_paths
+
+  data_deps = [
+    "//build/android:devil_chromium_py",
+    "//third_party/catapult/devil",
+  ]
+
+  _rebased_adb_path = rebase_path(_adb_path, root_build_dir)
+  _rebased_lib_dir_path = rebase_path(_lib_dir, root_build_dir)
+
+  executable_args = [
+    "--adb",
+    "@WrappedPath(${_rebased_adb_path})",
+    "--lib",
+    "@WrappedPath(${_rebased_lib_dir_path})",
+  ]
+}
diff --git a/tools/android/asan/third_party/README.chromium b/tools/android/asan/third_party/README.chromium
new file mode 100644
index 0000000..7bcd2fce
--- /dev/null
+++ b/tools/android/asan/third_party/README.chromium
@@ -0,0 +1,8 @@
+Name: asan_device_setup.sh
+License: Apache 2.0
+Version: fbb9132e71a2
+URL: https://reviews.llvm.org/source/llvm-github/browse/main/compiler-rt/lib/asan/scripts/asan_device_setup
+Security Critical: no
+Shipped: no
+
+asan_device_setup.sh is a verbatim copy of asan_device_setup in the LLVM trunk.
diff --git a/tools/android/asan/third_party/asan_device_setup.sh b/tools/android/asan/third_party/asan_device_setup.sh
new file mode 100755
index 0000000..95f9d35f
--- /dev/null
+++ b/tools/android/asan/third_party/asan_device_setup.sh
@@ -0,0 +1,466 @@
+#!/bin/bash
+#===- lib/asan/scripts/asan_device_setup -----------------------------------===#
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+# Prepare Android device to run ASan applications.
+#
+#===------------------------------------------------------------------------===#
+
+set -e
+
+HERE="$(cd "$(dirname "$0")" && pwd)"
+
+revert=no
+extra_options=
+device=
+lib=
+use_su=0
+
+function usage {
+    echo "usage: $0 [--revert] [--device device-id] [--lib path] [--extra-options options]"
+    echo "  --revert: Uninstall ASan from the device."
+    echo "  --lib: Path to ASan runtime library."
+    echo "  --extra-options: Extra ASAN_OPTIONS."
+    echo "  --device: Install to the given device. Use 'adb devices' to find"
+    echo "            device-id."
+    echo "  --use-su: Use 'su -c' prefix for every adb command instead of using"
+    echo "            'adb root' once."
+    echo
+    exit 1
+}
+
+function adb_push {
+  if [ $use_su -eq 0 ]; then
+    $ADB push "$1" "$2"
+  else
+    local FILENAME=$(basename $1)
+    $ADB push "$1" "/data/local/tmp/$FILENAME"
+    $ADB shell su -c "rm \\\"$2/$FILENAME\\\"" >&/dev/null
+    $ADB shell su -c "cat \\\"/data/local/tmp/$FILENAME\\\" > \\\"$2/$FILENAME\\\""
+    $ADB shell su -c "rm \\\"/data/local/tmp/$FILENAME\\\""
+  fi
+}
+
+function adb_remount {
+  if [ $use_su -eq 0 ]; then
+    $ADB remount
+  else
+    local STORAGE=`$ADB shell mount | grep /system | cut -d ' ' -f1`
+    if [ "$STORAGE" != "" ]; then
+      echo Remounting $STORAGE at /system
+      $ADB shell su -c "mount -o rw,remount $STORAGE /system"
+    else
+      echo Failed to get storage device name for "/system" mount point
+    fi
+  fi
+}
+
+function adb_shell {
+  if [ $use_su -eq 0 ]; then
+    $ADB shell $@
+  else
+    $ADB shell su -c "$*"
+  fi
+}
+
+function adb_root {
+  if [ $use_su -eq 0 ]; then
+    $ADB root
+  fi
+}
+
+function adb_wait_for_device {
+  $ADB wait-for-device
+}
+
+function adb_pull {
+  if [ $use_su -eq 0 ]; then
+    $ADB pull "$1" "$2"
+  else
+    local FILENAME=$(basename $1)
+    $ADB shell rm "/data/local/tmp/$FILENAME" >&/dev/null
+    $ADB shell su -c "[ -f \\\"$1\\\" ] && cat \\\"$1\\\" > \\\"/data/local/tmp/$FILENAME\\\" && chown root.shell \\\"/data/local/tmp/$FILENAME\\\" && chmod 755 \\\"/data/local/tmp/$FILENAME\\\"" &&
+    $ADB pull "/data/local/tmp/$FILENAME" "$2" >&/dev/null && $ADB shell "rm \"/data/local/tmp/$FILENAME\""
+  fi
+}
+
+function get_device_arch { # OUT OUT64
+    local _outvar=$1
+    local _outvar64=$2
+    local _ABI=$(adb_shell getprop ro.product.cpu.abi)
+    local _ARCH=
+    local _ARCH64=
+    if [[ $_ABI == x86* ]]; then
+        _ARCH=i686
+    elif [[ $_ABI == armeabi* ]]; then
+        _ARCH=arm
+    elif [[ $_ABI == arm64-v8a* ]]; then
+        _ARCH=arm
+        _ARCH64=aarch64
+    else
+        echo "Unrecognized device ABI: $_ABI"
+        exit 1
+    fi
+    eval $_outvar=\$_ARCH
+    eval $_outvar64=\$_ARCH64
+}
+
+while [[ $# > 0 ]]; do
+  case $1 in
+    --revert)
+      revert=yes
+      ;;
+    --extra-options)
+      shift
+      if [[ $# == 0 ]]; then
+        echo "--extra-options requires an argument."
+        exit 1
+      fi
+      extra_options="$1"
+      ;;
+    --lib)
+      shift
+      if [[ $# == 0 ]]; then
+        echo "--lib requires an argument."
+        exit 1
+      fi
+      lib="$1"
+      ;;
+    --device)
+      shift
+      if [[ $# == 0 ]]; then
+        echo "--device requires an argument."
+        exit 1
+      fi
+      device="$1"
+      ;;
+    --use-su)
+      use_su=1
+      ;;
+    *)
+      usage
+      ;;
+  esac
+  shift
+done
+
+ADB=${ADB:-adb}
+if [[ x$device != x ]]; then
+    ADB="$ADB -s $device"
+fi
+
+if [ $use_su -eq 1 ]; then
+  # Test if 'su' is present on the device
+  SU_TEST_OUT=`$ADB shell su -c "echo foo" 2>&1 | sed 's/\r$//'`
+  if [ $? != 0 -o "$SU_TEST_OUT" != "foo" ]; then
+    echo "ERROR: Cannot use 'su -c':"
+    echo "$ adb shell su -c \"echo foo\""
+    echo $SU_TEST_OUT
+    echo "Check that 'su' binary is correctly installed on the device or omit"
+    echo "            --use-su flag"
+    exit 1
+  fi
+fi
+
+echo '>> Remounting /system rw'
+adb_wait_for_device
+adb_root
+adb_wait_for_device
+adb_remount
+adb_wait_for_device
+
+get_device_arch ARCH ARCH64
+echo "Target architecture: $ARCH"
+ASAN_RT="libclang_rt.asan-$ARCH-android.so"
+if [[ -n $ARCH64 ]]; then
+  echo "Target architecture: $ARCH64"
+  ASAN_RT64="libclang_rt.asan-$ARCH64-android.so"
+fi
+
+RELEASE=$(adb_shell getprop ro.build.version.release)
+PRE_L=0
+if echo "$RELEASE" | grep '^4\.' >&/dev/null; then
+    PRE_L=1
+fi
+ANDROID_O=0
+if echo "$RELEASE" | grep '^8\.0\.' >&/dev/null; then
+    # 8.0.x is for Android O
+    ANDROID_O=1
+fi
+
+if [[ x$revert == xyes ]]; then
+    echo '>> Uninstalling ASan'
+
+    if ! adb_shell ls -l /system/bin/app_process | grep -o '\->.*app_process' >&/dev/null; then
+      echo '>> Pre-L device detected.'
+      adb_shell mv /system/bin/app_process.real /system/bin/app_process
+      adb_shell rm /system/bin/asanwrapper
+    elif ! adb_shell ls -l /system/bin/app_process64.real | grep -o 'No such file or directory' >&/dev/null; then
+      # 64-bit installation.
+      adb_shell mv /system/bin/app_process32.real /system/bin/app_process32
+      adb_shell mv /system/bin/app_process64.real /system/bin/app_process64
+      adb_shell rm /system/bin/asanwrapper
+      adb_shell rm /system/bin/asanwrapper64
+    else
+      # 32-bit installation.
+      adb_shell rm /system/bin/app_process.wrap
+      adb_shell rm /system/bin/asanwrapper
+      adb_shell rm /system/bin/app_process
+      adb_shell ln -s /system/bin/app_process32 /system/bin/app_process
+    fi
+
+    if [[ ANDROID_O -eq 1 ]]; then
+      adb_shell mv /system/etc/ld.config.txt.saved /system/etc/ld.config.txt
+    fi
+
+    echo '>> Restarting shell'
+    adb_shell stop
+    adb_shell start
+
+    # Remove the library on the last step to give a chance to the 'su' binary to
+    # be executed without problem.
+    adb_shell rm /system/lib/$ASAN_RT
+
+    echo '>> Done'
+    exit 0
+fi
+
+if [[ -d "$lib" ]]; then
+    ASAN_RT_PATH="$lib"
+elif [[ -f "$lib" && "$lib" == *"$ASAN_RT" ]]; then
+    ASAN_RT_PATH=$(dirname "$lib")
+elif [[ -f "$HERE/$ASAN_RT" ]]; then
+    ASAN_RT_PATH="$HERE"
+elif [[ $(basename "$HERE") == "bin" ]]; then
+    # We could be in the toolchain's base directory.
+    # Consider ../lib, ../lib/asan, ../lib/linux,
+    # ../lib/clang/$VERSION/lib/linux, and ../lib64/clang/$VERSION/lib/linux.
+    P=$(ls "$HERE"/../lib/"$ASAN_RT" \
+           "$HERE"/../lib/asan/"$ASAN_RT" \
+           "$HERE"/../lib/linux/"$ASAN_RT" \
+           "$HERE"/../lib/clang/*/lib/linux/"$ASAN_RT" \
+           "$HERE"/../lib64/clang/*/lib/linux/"$ASAN_RT" 2>/dev/null | sort | tail -1)
+    if [[ -n "$P" ]]; then
+        ASAN_RT_PATH="$(dirname "$P")"
+    fi
+fi
+
+if [[ -z "$ASAN_RT_PATH" || ! -f "$ASAN_RT_PATH/$ASAN_RT" ]]; then
+    echo ">> ASan runtime library not found"
+    exit 1
+fi
+
+if [[ -n "$ASAN_RT64" ]]; then
+  if [[ -z "$ASAN_RT_PATH" || ! -f "$ASAN_RT_PATH/$ASAN_RT64" ]]; then
+    echo ">> ASan runtime library not found"
+    exit 1
+  fi
+fi
+
+TMPDIRBASE=$(mktemp -d)
+TMPDIROLD="$TMPDIRBASE/old"
+TMPDIR="$TMPDIRBASE/new"
+mkdir "$TMPDIROLD"
+
+if ! adb_shell ls -l /system/bin/app_process | grep -o '\->.*app_process' >&/dev/null; then
+
+    if adb_pull /system/bin/app_process.real /dev/null >&/dev/null; then
+        echo '>> Old-style ASan installation detected. Reverting.'
+        adb_shell mv /system/bin/app_process.real /system/bin/app_process
+    fi
+
+    echo '>> Pre-L device detected. Setting up app_process symlink.'
+    adb_shell mv /system/bin/app_process /system/bin/app_process32
+    adb_shell ln -s /system/bin/app_process32 /system/bin/app_process
+fi
+
+echo '>> Copying files from the device'
+if [[ -n "$ASAN_RT64" ]]; then
+  adb_pull /system/lib/"$ASAN_RT" "$TMPDIROLD" || true
+  adb_pull /system/lib64/"$ASAN_RT64" "$TMPDIROLD" || true
+  adb_pull /system/bin/app_process32 "$TMPDIROLD" || true
+  adb_pull /system/bin/app_process32.real "$TMPDIROLD" || true
+  adb_pull /system/bin/app_process64 "$TMPDIROLD" || true
+  adb_pull /system/bin/app_process64.real "$TMPDIROLD" || true
+  adb_pull /system/bin/asanwrapper "$TMPDIROLD" || true
+  adb_pull /system/bin/asanwrapper64 "$TMPDIROLD" || true
+else
+  adb_pull /system/lib/"$ASAN_RT" "$TMPDIROLD" || true
+  adb_pull /system/bin/app_process32 "$TMPDIROLD" || true
+  adb_pull /system/bin/app_process.wrap "$TMPDIROLD" || true
+  adb_pull /system/bin/asanwrapper "$TMPDIROLD" || true
+fi
+cp -r "$TMPDIROLD" "$TMPDIR"
+
+if [[ -f "$TMPDIR/app_process.wrap" || -f "$TMPDIR/app_process64.real" ]]; then
+    echo ">> Previous installation detected"
+else
+    echo ">> New installation"
+fi
+
+echo '>> Generating wrappers'
+
+cp "$ASAN_RT_PATH/$ASAN_RT" "$TMPDIR/"
+if [[ -n "$ASAN_RT64" ]]; then
+  cp "$ASAN_RT_PATH/$ASAN_RT64" "$TMPDIR/"
+fi
+
+ASAN_OPTIONS=start_deactivated=1
+
+# The name of a symlink to libclang_rt.asan-$ARCH-android.so used in LD_PRELOAD.
+# The idea is to have the same name in lib and lib64 to keep it from falling
+# apart when a 64-bit process spawns a 32-bit one, inheriting the environment.
+ASAN_RT_SYMLINK=symlink-to-libclang_rt.asan
+
+function generate_zygote_wrapper { # from, to
+  local _from=$1
+  local _to=$2
+  if [[ PRE_L -eq 0 ]]; then
+    # LD_PRELOAD parsing is broken in N if it starts with ":". Luckily, it is
+    # unset in the system environment since L.
+    local _ld_preload=$ASAN_RT_SYMLINK
+  else
+    local _ld_preload=\$LD_PRELOAD:$ASAN_RT_SYMLINK
+  fi
+  cat <<EOF >"$TMPDIR/$_from"
+#!/system/bin/sh-from-zygote
+ASAN_OPTIONS=$ASAN_OPTIONS \\
+ASAN_ACTIVATION_OPTIONS=include_if_exists=/data/local/tmp/asan.options.%b \\
+LD_PRELOAD=$_ld_preload \\
+exec $_to "\$@"
+
+EOF
+}
+
+# On Android-L not allowing user segv handler breaks some applications.
+# Since ~May 2017 this is the default setting; included for compatibility with
+# older library versions.
+if [[ PRE_L -eq 0 ]]; then
+    ASAN_OPTIONS="$ASAN_OPTIONS,allow_user_segv_handler=1"
+fi
+
+if [[ x$extra_options != x ]] ; then
+    ASAN_OPTIONS="$ASAN_OPTIONS,$extra_options"
+fi
+
+# Zygote wrapper.
+if [[ -f "$TMPDIR/app_process64" ]]; then
+  # A 64-bit device.
+  if [[ ! -f "$TMPDIR/app_process64.real" ]]; then
+    # New installation.
+    mv "$TMPDIR/app_process32" "$TMPDIR/app_process32.real"
+    mv "$TMPDIR/app_process64" "$TMPDIR/app_process64.real"
+  fi
+  generate_zygote_wrapper "app_process32" "/system/bin/app_process32.real"
+  generate_zygote_wrapper "app_process64" "/system/bin/app_process64.real"
+else
+  # A 32-bit device.
+  generate_zygote_wrapper "app_process.wrap" "/system/bin/app_process32"
+fi
+
+# General command-line tool wrapper (use for anything that's not started as
+# zygote).
+cat <<EOF >"$TMPDIR/asanwrapper"
+#!/system/bin/sh
+LD_PRELOAD=$ASAN_RT_SYMLINK \\
+exec \$@
+
+EOF
+
+if [[ -n "$ASAN_RT64" ]]; then
+  cat <<EOF >"$TMPDIR/asanwrapper64"
+#!/system/bin/sh
+LD_PRELOAD=$ASAN_RT_SYMLINK \\
+exec \$@
+
+EOF
+fi
+
+function install { # from, to, chmod, chcon
+  local _from=$1
+  local _to=$2
+  local _mode=$3
+  local _context=$4
+  local _basename="$(basename "$_from")"
+  echo "Installing $_to/$_basename $_mode $_context"
+  adb_push "$_from" "$_to/$_basename"
+  adb_shell chown root.shell "$_to/$_basename"
+  if [[ -n "$_mode" ]]; then
+    adb_shell chmod "$_mode" "$_to/$_basename"
+  fi
+  if [[ -n "$_context" ]]; then
+    adb_shell chcon "$_context" "$_to/$_basename"
+  fi
+}
+
+if ! ( cd "$TMPDIRBASE" && diff -qr old/ new/ ) ; then
+    # Make SELinux happy by keeping app_process wrapper and the shell
+    # it runs on in zygote domain.
+    ENFORCING=0
+    if adb_shell getenforce | grep Enforcing >/dev/null; then
+        # Sometimes shell is not allowed to change file contexts.
+        # Temporarily switch to permissive.
+        ENFORCING=1
+        adb_shell setenforce 0
+    fi
+
+    if [[ PRE_L -eq 1 ]]; then
+        CTX=u:object_r:system_file:s0
+    else
+        CTX=u:object_r:zygote_exec:s0
+    fi
+
+    echo '>> Pushing files to the device'
+
+    if [[ -n "$ASAN_RT64" ]]; then
+      install "$TMPDIR/$ASAN_RT" /system/lib 644
+      install "$TMPDIR/$ASAN_RT64" /system/lib64 644
+      install "$TMPDIR/app_process32" /system/bin 755 $CTX
+      install "$TMPDIR/app_process32.real" /system/bin 755 $CTX
+      install "$TMPDIR/app_process64" /system/bin 755 $CTX
+      install "$TMPDIR/app_process64.real" /system/bin 755 $CTX
+      install "$TMPDIR/asanwrapper" /system/bin 755
+      install "$TMPDIR/asanwrapper64" /system/bin 755
+
+      adb_shell rm -f /system/lib/$ASAN_RT_SYMLINK
+      adb_shell ln -s $ASAN_RT /system/lib/$ASAN_RT_SYMLINK
+      adb_shell rm -f /system/lib64/$ASAN_RT_SYMLINK
+      adb_shell ln -s $ASAN_RT64 /system/lib64/$ASAN_RT_SYMLINK
+    else
+      install "$TMPDIR/$ASAN_RT" /system/lib 644
+      install "$TMPDIR/app_process32" /system/bin 755 $CTX
+      install "$TMPDIR/app_process.wrap" /system/bin 755 $CTX
+      install "$TMPDIR/asanwrapper" /system/bin 755 $CTX
+
+      adb_shell rm -f /system/lib/$ASAN_RT_SYMLINK
+      adb_shell ln -s $ASAN_RT /system/lib/$ASAN_RT_SYMLINK
+
+      adb_shell rm /system/bin/app_process
+      adb_shell ln -s /system/bin/app_process.wrap /system/bin/app_process
+    fi
+
+    adb_shell cp /system/bin/sh /system/bin/sh-from-zygote
+    adb_shell chcon $CTX /system/bin/sh-from-zygote
+
+    if [[ ANDROID_O -eq 1 ]]; then
+      # For Android O, the linker namespace is temporarily disabled.
+      adb_shell mv /system/etc/ld.config.txt /system/etc/ld.config.txt.saved
+    fi
+
+    if [ $ENFORCING == 1 ]; then
+        adb_shell setenforce 1
+    fi
+
+    echo '>> Restarting shell (asynchronous)'
+    adb_shell stop
+    adb_shell start
+
+    echo '>> Please wait until the device restarts'
+else
+    echo '>> Device is up to date'
+fi
+
+rm -r "$TMPDIRBASE"
diff --git a/tools/android/asan/third_party/with_asan.py b/tools/android/asan/third_party/with_asan.py
new file mode 100755
index 0000000..481993e
--- /dev/null
+++ b/tools/android/asan/third_party/with_asan.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env vpython3
+# Copyright 2019 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+import argparse
+import contextlib
+import logging
+import os
+import subprocess
+import sys
+
+_SRC_ROOT = os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '..', '..', '..', '..'))
+
+sys.path.append(os.path.join(_SRC_ROOT, 'third_party', 'catapult', 'devil'))
+from devil import base_error
+from devil.android import device_utils
+from devil.android.sdk import adb_wrapper
+from devil.android.sdk import version_codes
+from devil.utils import logging_common
+
+sys.path.append(os.path.join(_SRC_ROOT, 'build', 'android'))
+import devil_chromium
+
+_SCRIPT_PATH = os.path.abspath(
+    os.path.join(
+        os.path.dirname(__file__),
+        'asan_device_setup.sh'))
+
+
+@contextlib.contextmanager
+def _LogDevicesOnFailure(msg):
+  try:
+    yield
+  except base_error.BaseError:
+    logging.exception(msg)
+    logging.error('Devices visible to adb:')
+    for entry in adb_wrapper.AdbWrapper.Devices(desired_state=None,
+                                                long_list=True):
+      logging.error('  %s: %s',
+                    entry[0].GetDeviceSerial(),
+                    ' '.join(entry[1:]))
+    raise
+
+
+@contextlib.contextmanager
+def Asan(args):
+  env = os.environ.copy()
+  env['ADB'] = args.adb
+
+  try:
+    with _LogDevicesOnFailure('Failed to set up the device.'):
+      device = device_utils.DeviceUtils.HealthyDevices(
+          device_arg=args.device)[0]
+      disable_verity = device.build_version_sdk >= version_codes.MARSHMALLOW
+      if disable_verity:
+        device.EnableRoot()
+        # TODO(crbug.com/790202): Stop logging output after diagnosing
+        # issues on android-asan.
+        verity_output = device.adb.DisableVerity()
+        if verity_output:
+          logging.info('disable-verity output:')
+          for line in verity_output.splitlines():
+            logging.info('  %s', line)
+        device.Reboot()
+      # Call EnableRoot prior to asan_device_setup.sh to ensure it doesn't
+      # get tripped up by the root timeout.
+      device.EnableRoot()
+      setup_cmd = [_SCRIPT_PATH, '--lib', args.lib]
+      if args.device:
+        setup_cmd += ['--device', args.device]
+      subprocess.check_call(setup_cmd, env=env)
+      yield
+  finally:
+    with _LogDevicesOnFailure('Failed to tear down the device.'):
+      device.EnableRoot()
+      teardown_cmd = [_SCRIPT_PATH, '--revert']
+      if args.device:
+        teardown_cmd += ['--device', args.device]
+      subprocess.check_call(teardown_cmd, env=env)
+      if disable_verity:
+        # TODO(crbug.com/790202): Stop logging output after diagnosing
+        # issues on android-asan.
+        verity_output = device.adb.EnableVerity()
+        if verity_output:
+          logging.info('enable-verity output:')
+          for line in verity_output.splitlines():
+            logging.info('  %s', line)
+        device.Reboot()
+
+
+def main(raw_args):
+  parser = argparse.ArgumentParser()
+  logging_common.AddLoggingArguments(parser)
+  parser.add_argument(
+      '--adb', type=os.path.realpath, required=True,
+      help='Path to adb binary.')
+  parser.add_argument(
+      '--device',
+      help='Device serial.')
+  parser.add_argument(
+      '--lib', type=os.path.realpath, required=True,
+      help='Path to asan library.')
+  parser.add_argument(
+      'command', nargs='*',
+      help='Command to run with ASAN installed.')
+  args = parser.parse_args()
+
+  # TODO(crbug.com/790202): Remove this after diagnosing issues
+  # with android-asan.
+  if not args.quiet:
+    args.verbose += 1
+
+  logging_common.InitializeLogging(args)
+  devil_chromium.Initialize(adb_path=args.adb)
+
+  with Asan(args):
+    if args.command:
+      return subprocess.call(args.command)
+
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/tools/json_schema_compiler/idl_schema.py b/tools/json_schema_compiler/idl_schema.py
index 7597716..d4394b6 100755
--- a/tools/json_schema_compiler/idl_schema.py
+++ b/tools/json_schema_compiler/idl_schema.py
@@ -226,8 +226,7 @@
     if self.node.GetProperty('deprecated'):
       properties['deprecated'] = self.node.GetProperty('deprecated')
 
-    for property_name in ['allowAmbiguousOptionalArguments',
-                          'nodoc', 'nocompile', 'nodart',
+    for property_name in ['nodoc', 'nocompile', 'nodart',
                           'serializableFunction']:
       if self.node.GetProperty(property_name):
         properties[property_name] = True
diff --git a/tools/metrics/histograms/metadata/ash/enums.xml b/tools/metrics/histograms/metadata/ash/enums.xml
index 018c7c2..e71fcf6 100644
--- a/tools/metrics/histograms/metadata/ash/enums.xml
+++ b/tools/metrics/histograms/metadata/ash/enums.xml
@@ -1675,6 +1675,8 @@
   <int value="0" label="Unknown"/>
   <int value="1" label="User inserts or copies a result."/>
   <int value="2" label="User abandons the session."/>
+  <int value="3" label="User selects an action to open another window."/>
+  <int value="4" label="User selects an action related to text format."/>
 </enum>
 
 <enum name="PinAutosubmitBackfillEvent">
diff --git a/tools/metrics/histograms/metadata/auto/histograms.xml b/tools/metrics/histograms/metadata/auto/histograms.xml
index 75b0e9a..55819a5 100644
--- a/tools/metrics/histograms/metadata/auto/histograms.xml
+++ b/tools/metrics/histograms/metadata/auto/histograms.xml
@@ -29,7 +29,7 @@
 
 <histogram
     name="AutoScreenBrightness.AdapterDecisionAtUserChange.BrightnessChange.Cause"
-    enum="AutoScreenBrightnessBrightnessChangeCause" expires_after="2024-04-30">
+    enum="AutoScreenBrightnessBrightnessChangeCause" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -41,7 +41,7 @@
 
 <histogram base="true"
     name="AutoScreenBrightness.AdapterDecisionAtUserChange.ModelIteration"
-    units="count" expires_after="2024-04-30">
+    units="count" expires_after="2024-09-25">
 <!-- Name completed by histogram_suffixes name="AdapterDecision" -->
 
   <owner>thanhdng@chromium.org</owner>
@@ -56,7 +56,7 @@
 <histogram
     name="AutoScreenBrightness.AdapterDecisionAtUserChange.NoBrightnessChange.Cause"
     enum="AutoScreenBrightnessNoBrightnessChangeCause"
-    expires_after="2024-04-30">
+    expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -68,7 +68,7 @@
 
 <histogram
     name="AutoScreenBrightness.AdapterDecisionAtUserChange.Unknown.AlsStd"
-    units="count" expires_after="2024-04-30">
+    units="count" expires_after="2024-09-25">
 <!-- Name completed by histogram_suffixes name="AdapterDecision" -->
 
   <owner>thanhdng@chromium.org</owner>
@@ -83,7 +83,7 @@
 
 <histogram
     name="AutoScreenBrightness.AdapterDecisionAtUserChange.{AlsBrightnessDirection}AlsDelta"
-    units="count" expires_after="2024-04-30">
+    units="count" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -100,7 +100,7 @@
 
 <histogram
     name="AutoScreenBrightness.AdapterDecisionAtUserChange.{AlsBrightnessDirection}AlsStd"
-    units="count" expires_after="2024-04-30">
+    units="count" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -124,14 +124,14 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.BrightnessChange.Cause"
-    enum="AutoScreenBrightnessBrightnessChangeCause" expires_after="2024-04-30">
+    enum="AutoScreenBrightnessBrightnessChangeCause" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>Reason for the model to change brightness. Chrome OS only.</summary>
 </histogram>
 
 <histogram name="AutoScreenBrightness.BrightnessChange.ElapsedTime" units="ms"
-    expires_after="2024-04-30">
+    expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -140,7 +140,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.BrightnessChange.ModelIteration"
-    units="count" expires_after="2024-04-30">
+    units="count" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -151,7 +151,7 @@
 
 <histogram name="AutoScreenBrightness.BrightnessMonitorStatus"
     enum="AutoScreenBrightnessBrightnessMonitorStatus"
-    expires_after="2024-04-30">
+    expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -161,7 +161,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.Atlas" units="count"
-    expires_after="2024-04-30">
+    expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -177,7 +177,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.Eve" units="count"
-    expires_after="2024-04-30">
+    expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -192,7 +192,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.Kohaku" units="count"
-    expires_after="2024-05-12">
+    expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -208,7 +208,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.NoAls" units="count"
-    expires_after="2024-04-30">
+    expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -223,7 +223,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.Nocturne"
-    units="count" expires_after="2024-04-30">
+    units="count" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -239,7 +239,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.SupportedAls"
-    units="count" expires_after="2024-04-30">
+    units="count" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -254,7 +254,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.UnsupportedAls"
-    units="count" expires_after="2024-04-30">
+    units="count" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -269,7 +269,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DataError"
-    enum="AutoScreenBrightnessDataError" expires_after="2024-04-30">
+    enum="AutoScreenBrightnessDataError" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -280,7 +280,7 @@
 
 <histogram
     name="AutoScreenBrightness.ElapsedTimeBetweenModelAndUserAdjustments"
-    units="ms" expires_after="2024-04-30">
+    units="ms" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -290,14 +290,14 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.GlobalCurveResetOnInitialization"
-    enum="Boolean" expires_after="2024-05-05">
+    enum="Boolean" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>Whether the global curve is reset at initialization.</summary>
 </histogram>
 
 <histogram name="AutoScreenBrightness.InvalidCurveReason"
-    enum="AutoScreenBrightnessInvalidCurveReason" expires_after="2024-04-30">
+    enum="AutoScreenBrightnessInvalidCurveReason" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -307,7 +307,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.MissingAlsWhenBrightnessChanged"
-    enum="BooleanError" expires_after="2024-07-01">
+    enum="BooleanError" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -329,7 +329,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelIterationCountAtInitialization"
-    units="count" expires_after="2024-04-30">
+    units="count" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>napper@chromium.org</owner>
   <owner>tby@chromium.org</owner>
@@ -339,7 +339,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelLoadingStatus"
-    enum="AutoScreenBrightnessModelLoadingStatus" expires_after="2024-04-30">
+    enum="AutoScreenBrightnessModelLoadingStatus" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>napper@chromium.org</owner>
   <owner>tby@chromium.org</owner>
@@ -348,7 +348,7 @@
 
 <histogram name="AutoScreenBrightness.ModelTraining.BrightnessChange"
     enum="AutoScreenBrightnessBoundedBrightnessChange"
-    expires_after="2024-04-30">
+    expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -358,7 +358,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelTraining.BrightnessOutlier"
-    enum="Boolean" expires_after="2024-04-30">
+    enum="Boolean" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -368,7 +368,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelTraining.Inaccuracy.NoUpdate"
-    units="%" expires_after="2024-04-30">
+    units="%" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>napper@chromium.org</owner>
   <owner>tby@chromium.org</owner>
@@ -380,7 +380,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelTraining.Inaccuracy.Update"
-    units="%" expires_after="2024-04-30">
+    units="%" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>napper@chromium.org</owner>
   <owner>tby@chromium.org</owner>
@@ -392,7 +392,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelTraining.ModelUserConsistent"
-    enum="Boolean" expires_after="2024-04-30">
+    enum="Boolean" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -402,7 +402,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.NewCurveSaved.Duration" units="ms"
-    expires_after="2024-04-30">
+    expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -413,7 +413,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.NewCurveSaved.Success"
-    enum="BooleanSuccess" expires_after="2024-04-30">
+    enum="BooleanSuccess" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -423,7 +423,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.Opposite.UserModelBrightnessAdjustments"
-    units="count" expires_after="2024-04-30">
+    units="count" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -435,7 +435,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ParameterError"
-    enum="AutoScreenBrightnessParameterError" expires_after="2024-04-30">
+    enum="AutoScreenBrightnessParameterError" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -445,7 +445,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.PersonalCurveValid" enum="BooleanValid"
-    expires_after="2024-04-30">
+    expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -454,7 +454,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.Same.UserModelBrightnessAdjustments"
-    units="count" expires_after="2024-04-30">
+    units="count" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -466,7 +466,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.TrainingCompleteDuration.NewCurve"
-    units="ms" expires_after="2024-04-30">
+    units="ms" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -476,7 +476,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.TrainingCompleteDuration.NoNewCurve"
-    units="ms" expires_after="2024-04-30">
+    units="ms" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
@@ -486,7 +486,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.UserAdjustmentEffect"
-    enum="AutoScreenBrightnessUserAdjustmentEffect" expires_after="2024-05-05">
+    enum="AutoScreenBrightnessUserAdjustmentEffect" expires_after="2024-09-25">
   <owner>thanhdng@chromium.org</owner>
   <owner>tby@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/dev/enums.xml b/tools/metrics/histograms/metadata/dev/enums.xml
index e8cafe3..c58e69c 100644
--- a/tools/metrics/histograms/metadata/dev/enums.xml
+++ b/tools/metrics/histograms/metadata/dev/enums.xml
@@ -873,17 +873,6 @@
   <int value="8" label="SchemeBlob"/>
 </enum>
 
-<enum name="DevToolsElementsSidebarTab">
-  <int value="0" label="Other sidebar pane"/>
-  <int value="1" label="Elements - Styles"/>
-  <int value="2" label="Elements - Computed"/>
-  <int value="3" label="Elements - Layout"/>
-  <int value="4" label="Elements - Event Listeners"/>
-  <int value="5" label="Elements - DOM Breakpoints"/>
-  <int value="6" label="Elements - Properties"/>
-  <int value="7" label="Elements - Accessibility"/>
-</enum>
-
 <enum name="DevtoolsExperiments">
   <int value="0" label="applyCustomStylesheet"/>
   <int value="1" label="captureNodeCreationStacks"/>
diff --git a/tools/metrics/histograms/metadata/dev/histograms.xml b/tools/metrics/histograms/metadata/dev/histograms.xml
index 464be12..d088550 100644
--- a/tools/metrics/histograms/metadata/dev/histograms.xml
+++ b/tools/metrics/histograms/metadata/dev/histograms.xml
@@ -178,15 +178,6 @@
   </summary>
 </histogram>
 
-<histogram name="DevTools.Elements.SidebarTabShown"
-    enum="DevToolsElementsSidebarTab" expires_after="2024-05-05">
-  <owner>bmeurer@google.com</owner>
-  <owner>changhaohan@chromium.org</owner>
-  <summary>
-    Specified DevTools sidebar tab was shown in the Elements panel.
-  </summary>
-</histogram>
-
 <histogram name="DevTools.ExperimentDisabled" enum="DevtoolsExperiments"
     expires_after="2024-09-01">
   <owner>yangguo@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/extensions/enums.xml b/tools/metrics/histograms/metadata/extensions/enums.xml
index 1830ad99..2626243 100644
--- a/tools/metrics/histograms/metadata/extensions/enums.xml
+++ b/tools/metrics/histograms/metadata/extensions/enums.xml
@@ -2735,6 +2735,7 @@
   <int value="1880" label="OS_DIAGNOSTICS_REPLYTOROUTINEINQUIRY"/>
   <int value="1881" label="USERSCRIPTS_GETWORLDCONFIGURATIONS"/>
   <int value="1882" label="USERSCRIPTS_RESETWORLDCONFIGURATION"/>
+  <int value="1883" label="AUTOTESTPRIVATE_WAITFORLOGINANIMATIONEND"/>
 </enum>
 
 <enum name="ExtensionInstallationCrxInstallError">
diff --git a/tools/metrics/histograms/metadata/file/histograms.xml b/tools/metrics/histograms/metadata/file/histograms.xml
index 6e26f05..2584aec5 100644
--- a/tools/metrics/histograms/metadata/file/histograms.xml
+++ b/tools/metrics/histograms/metadata/file/histograms.xml
@@ -717,7 +717,7 @@
 </histogram>
 
 <histogram name="FileBrowser.GoogleDrive.BulkPinning.Syncing.Error"
-    enum="GoogleDrive.BulkPinning.Stage" expires_after="2024-06-01">
+    enum="GoogleDrive.BulkPinning.Stage" expires_after="2025-04-01">
   <owner>simmonsjosh@google.com</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/webapps/histograms.xml b/tools/metrics/histograms/metadata/webapps/histograms.xml
index 49bfffd..d075265 100644
--- a/tools/metrics/histograms/metadata/webapps/histograms.xml
+++ b/tools/metrics/histograms/metadata/webapps/histograms.xml
@@ -1264,48 +1264,6 @@
   </summary>
 </histogram>
 
-<histogram
-    name="WebApp.Preinstalled.WindowExperiment.{UserGroup}.{DisplayModeChange}"
-    enum="DefaultAppName" expires_after="2024-05-05">
-  <owner>glenrob@chromium.org</owner>
-  <owner>desktop-pwas-team@google.com</owner>
-  <summary>
-    Records that a preinstalled app's display mode is changed by the user.
-    Recorded when the display mode changes for a user in the experiment group,
-    which is new ChromeOS users.
-  </summary>
-  <token key="UserGroup">
-    <variant name="Control" summary="Control user group."/>
-    <variant name="Tab" summary="Tab user group."/>
-    <variant name="Window" summary="Window user group."/>
-  </token>
-  <token key="DisplayModeChange">
-    <variant name="ChangedToTab" summary="Changed to open in tab."/>
-    <variant name="ChangedToWindow" summary="Changed to open in window."/>
-  </token>
-</histogram>
-
-<histogram
-    name="WebApp.Preinstalled.WindowExperiment.{UserGroup}.{LinkCapturingChange}"
-    enum="DefaultAppName" expires_after="2024-10-20">
-  <owner>glenrob@chromium.org</owner>
-  <owner>desktop-pwas-team@google.com</owner>
-  <summary>
-    Records that a preinstalled app's link capturing setting is changed by the
-    user. Recorded when the link capturing mode changes for a user in the
-    experiment group, which is new ChromeOS users.
-  </summary>
-  <token key="UserGroup">
-    <variant name="Control" summary="Control user group."/>
-    <variant name="Tab" summary="Tab user group."/>
-    <variant name="Window" summary="Window user group."/>
-  </token>
-  <token key="LinkCapturingChange">
-    <variant name="LinkCapturingDisabled" summary="Link capturing disabled."/>
-    <variant name="LinkCapturingEnabled" summary="Link capturing enabled."/>
-  </token>
-</histogram>
-
 <histogram name="WebApp.ProtocolHandlers.Registration.Result"
     enum="BooleanSuccess" expires_after="2025-09-10">
   <owner>dibyapal@chromium.org</owner>
diff --git a/tools/metrics/structured/sync/structured.xml b/tools/metrics/structured/sync/structured.xml
index a8af2b63..44a3632 100644
--- a/tools/metrics/structured/sync/structured.xml
+++ b/tools/metrics/structured/sync/structured.xml
@@ -2287,6 +2287,105 @@
     </metric>
   </event>
 
+  <enum name="PickerSessionOutcome">
+    <variant value="0">UNKNOWN</variant>
+    <variant value="1">INSERTED_OR_COPIED</variant>
+    <variant value="2">ABANDONED</variant>
+    <variant value="3">REDIRECTED</variant>
+    <variant value="4">FORMAT</variant>
+  </enum>
+
+  <enum name="PickerAction">
+    <variant value="0">UNKNOWN</variant>
+    <variant value="1">OPEN_EDITOR_WRITE</variant>
+    <variant value="2">OPEN_EDITOR_REWRITE</variant>
+    <variant value="3">OPEN_LINKS</variant>
+    <variant value="4">OPEN_EXPRESSIONS</variant>
+    <variant value="5">OPEN_CLIPBOARD</variant>
+    <variant value="6">OPEN_DRIVE_FILES</variant>
+    <variant value="7">OPEN_LOCAL_FILES</variant>
+    <variant value="8">OPEN_DATES_TIMES</variant>
+    <variant value="9">OPEN_UNITS_MATHS</variant>
+    <variant value="10">TRANSFORM_UPPER_CASE</variant>
+    <variant value="11">TRANSFORM_LOWER_CASE</variant>
+    <variant value="12">TRANSFORM_SENTENCE_CASE</variant>
+    <variant value="13">TRANSFORM_TITLE_CASE</variant>
+    <variant value="14">CAPS_ON</variant>
+    <variant value="15">CAPS_OFF</variant>
+  </enum>
+
+  <enum name="PickerResultSource">
+    <variant value="0">UNKNOWN</variant>
+    <variant value="1">OMNIBOX</variant>
+    <variant value="2">EMOJI</variant>
+    <variant value="3">CLIPBOARD</variant>
+    <variant value="4">DRIVE_FILES</variant>
+    <variant value="5">LOCAL_FILES</variant>
+    <variant value="6">DATES_TIMES</variant>
+    <variant value="7">UNITS_MATHS</variant>
+    <variant value="8">CASE_TRANSFORM</variant>
+    <variant value="9">TENOR</variant>
+  </enum>
+
+  <enum name="PickerResultType">
+    <variant value="0">UNKNOWN</variant>
+    <variant value="1">TEXT</variant>
+    <variant value="2">EMOJI</variant>
+    <variant value="3">SYMBOL</variant>
+    <variant value="4">EMOTICON</variant>
+    <variant value="5">CLIPBOARD_FILE</variant>
+    <variant value="6">CLIPBOARD_TEXT</variant>
+    <variant value="7">CLIPBOARD_IMAGE</variant>
+    <variant value="8">CLIPBOARD_HTML</variant>
+    <variant value="9">GIF</variant>
+    <variant value="10">LINK</variant>
+    <variant value="11">LOCAL_FILE</variant>
+    <variant value="12">DRIVE_FILE</variant>
+  </enum>
+
+  <event name="Picker.FinishSession">
+    <summary>
+      Recorded when user closes the picker window and finishes a session.
+    </summary>
+    <metric name="Outcome" type="PickerSessionOutcome">
+      <summary>
+        Outcome of the session.
+      </summary>
+    </metric>
+    <metric name="Action" type="PickerAction">
+      <summary>
+        Action the user takes during a session. The user can only take at most
+        1 action during a session. UNKNOWN if the user didn't take any action.
+      </summary>
+    </metric>
+    <metric name="ResultSource" type="PickerResultSource">
+      <summary>
+        Source of the inserted result. UNKNOWN if no result is inserted.
+      </summary>
+    </metric>
+    <metric name="ResultType" type="PickerResultType">
+      <summary>
+        Type of the inserted result. UNKNOWN if no result is inserted.
+      </summary>
+    </metric>
+    <metric name="TotalEdits" type="int">
+      <summary>
+        Total number of edits of the query text during the session.
+      </summary>
+    </metric>
+    <metric name="FinalQuerySize" type="int">
+      <summary>
+        Number of UTF-16 code units of the query text when the session
+        finishes.
+      </summary>
+    </metric>
+    <metric name="ResultIndex" type="int">
+      <summary>
+        Index of the inserted result. -1 no result is inserted.
+      </summary>
+    </metric>
+  </event>
+
   <event name="UserLogin">
     <summary>
       An event to signify a user is using the system.
diff --git a/ui/accessibility/accessibility_features.cc b/ui/accessibility/accessibility_features.cc
index f047efc..90766950 100644
--- a/ui/accessibility/accessibility_features.cc
+++ b/ui/accessibility/accessibility_features.cc
@@ -321,7 +321,7 @@
 BASE_FEATURE(kReadAnythingReadAloudAutomaticWordHighlighting,
              "ReadAnythingReadAloudAutomaticWordHighlighting",
              base::FEATURE_DISABLED_BY_DEFAULT);
-bool IsReadAnythingAutomaticWordHighlightingEnabled() {
+bool IsReadAnythingReadAloudAutomaticWordHighlightingEnabled() {
   return base::FeatureList::IsEnabled(::features::kReadAnythingReadAloud) &&
          base::FeatureList::IsEnabled(
              ::features::kReadAnythingReadAloudAutomaticWordHighlighting);
diff --git a/ui/color/color_mixer.cc b/ui/color/color_mixer.cc
index d0ee807..3d335b2e 100644
--- a/ui/color/color_mixer.cc
+++ b/ui/color/color_mixer.cc
@@ -46,13 +46,23 @@
 }
 
 SkColor ColorMixer::GetResultColor(ColorId id) const {
-  const SkColor color = GetInputColor(id);
   const auto i = recipes_.find(id);
+  const bool recipe_in_mixer = i != recipes_.end();
+
+  // GetInputColor() can be expensive if the resulting ColorMixer is not in the
+  // cache, so avoid calling it if we can. Most recipes are invariant so this
+  // can save significant time.
+  const SkColor input_color = (recipe_in_mixer && i->second.Invariant())
+                                  ? gfx::kPlaceholderColor
+                                  : GetInputColor(id);
+
+  if (!recipe_in_mixer) {
+    return input_color;
+  }
+
   const ColorMixer* const mixer =
       input_mixer_getter_ ? input_mixer_getter_.Run() : nullptr;
-  return (i == recipes_.end())
-             ? color
-             : i->second.GenerateResult(color, *(mixer ? mixer : this));
+  return i->second.GenerateResult(input_color, *(mixer ? mixer : this));
 }
 
 std::set<ColorId> ColorMixer::GetDefinedColorIds() const {
diff --git a/ui/color/color_recipe.cc b/ui/color/color_recipe.cc
index 1ab7cc1..e76a1289 100644
--- a/ui/color/color_recipe.cc
+++ b/ui/color/color_recipe.cc
@@ -29,6 +29,12 @@
 ColorRecipe::~ColorRecipe() = default;
 
 ColorRecipe& ColorRecipe::operator+=(const ColorTransform& transform) {
+  if (transform.invariant()) {
+    // If a transform is invariant, previous transform results will be
+    // discarded when the output color is computed. So they can be safely
+    // dropped.
+    transforms_.clear();
+  }
   transforms_.push_back(transform);
   return *this;
 }
@@ -43,6 +49,10 @@
   return output_color;
 }
 
+bool ColorRecipe::Invariant() const {
+  return !transforms_.empty() && transforms_.front().invariant();
+}
+
 ColorRecipe operator+(ColorRecipe recipe, const ColorTransform& transform) {
   recipe += transform;
   return recipe;
diff --git a/ui/color/color_recipe.h b/ui/color/color_recipe.h
index dc205e4c..b748c8f7 100644
--- a/ui/color/color_recipe.h
+++ b/ui/color/color_recipe.h
@@ -37,6 +37,9 @@
   // is passed to each transform, since it might need to request other colors.
   SkColor GenerateResult(SkColor input, const ColorMixer& mixer) const;
 
+  // Returns true if this recipe is invariant to input color.
+  bool Invariant() const;
+
  private:
   std::list<ColorTransform> transforms_;
 };
diff --git a/ui/color/color_transform.cc b/ui/color/color_transform.cc
index 0864052a9..e0e067b 100644
--- a/ui/color/color_transform.cc
+++ b/ui/color/color_transform.cc
@@ -18,7 +18,7 @@
 ColorTransform::ColorTransform(Callback callback)
     : callback_(std::move(callback)) {}
 
-ColorTransform::ColorTransform(SkColor color) {
+ColorTransform::ColorTransform(SkColor color) : invariant_(true) {
   const auto generator = [](SkColor color, SkColor input_color,
                             const ColorMixer& mixer) {
     DVLOG(2) << "ColorTransform From Color:"
@@ -30,7 +30,7 @@
   callback_ = base::BindRepeating(generator, color);
 }
 
-ColorTransform::ColorTransform(ColorId id) {
+ColorTransform::ColorTransform(ColorId id) : invariant_(true) {
   const auto generator = [](ColorId id, SkColor input_color,
                             const ColorMixer& mixer) {
     SkColor result_color = mixer.GetResultColor(id);
diff --git a/ui/color/color_transform.h b/ui/color/color_transform.h
index e341e69c..bd634d9 100644
--- a/ui/color/color_transform.h
+++ b/ui/color/color_transform.h
@@ -19,7 +19,8 @@
 class ColorMixer;
 
 // Callback is a function which transforms an |input| color, optionally using a
-// |mixer| (to obtain other colors).
+// |mixer| (to obtain other colors). Do not depend on the callback running
+// except if it's necessary for the final color.
 using Callback =
     base::RepeatingCallback<SkColor(SkColor input, const ColorMixer& mixer)>;
 
@@ -37,10 +38,15 @@
   ColorTransform& operator=(const ColorTransform&);
   ~ColorTransform();
 
+  // Returns true if the result of this transform will return the same result
+  // regardless of other transforms within the same ColorRecipe.
+  bool invariant() const { return invariant_; }
+
   SkColor Run(SkColor input_color, const ColorMixer& mixer) const;
 
  private:
   Callback callback_;
+  bool invariant_ = false;
 };
 
 // Functions to create common transforms:
diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc
index a4b3004..7ea221a2 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -15,6 +15,7 @@
 
 #include "base/auto_reset.h"
 #include "base/containers/contains.h"
+#include "base/debug/stack_trace.h"
 #include "base/functional/bind.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/notreached.h"
@@ -1326,10 +1327,6 @@
 void WaylandWindow::RequestState(PlatformWindowDelegate::State state,
                                  int64_t serial,
                                  bool force) {
-  // State should NOT be requested during the ongoing request handling.
-  CHECK(!requesting_state_) << "Detected re-enterancy of state request.";
-  base::AutoReset<bool> setter(&requesting_state_, true);
-
   LOG_IF(WARNING, in_flight_requests_.size() > 100u)
       << "The queue of configures is longer than 100!";
 
@@ -1511,6 +1508,14 @@
 }
 
 void WaylandWindow::MaybeApplyLatestStateRequest(bool force) {
+  // Calling `MaybeApplyLatestStateRequest` re-entrantly is hard to reason about
+  // and also can lead to memory corruption during accesses to
+  // `in_flight_requests_`.
+  CHECK(!applying_state_)
+      << "MaybeApplyLatestStateRequest called re-entrantly.";
+  auto setter =
+      std::make_optional<base::AutoReset<bool>>(&applying_state_, true);
+
   if (in_flight_requests_.empty()) {
     return;
   }
@@ -1552,6 +1557,11 @@
   // bounds.
   latest.viz_seq = delegate()->OnStateUpdate(old, latest.state);
 
+  // `ProcessSequencePoint` may re-entrantly call
+  // `MaybeApplyLatestStateRequest`. This is safe as long as we do not access
+  // references to `in_flight_requests_` after here.
+  setter.reset();
+
   // If we have state requests which don't require synchronization to latch, or
   // if no frames will be produced, ack them immediately. Using -2 (or any
   // negative number that isn't -1) will cause all requests with viz_seq==-1 to
diff --git a/ui/ozone/platform/wayland/host/wayland_window.h b/ui/ozone/platform/wayland/host/wayland_window.h
index 930f364b..32da5476a 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_window.h
@@ -734,9 +734,10 @@
   bool disable_null_target_dcheck_for_test_ = false;
 #endif
 
-  // Set to true when the state is in the process of request. This is used to
-  // check there is no re-enterancy.
-  bool requesting_state_ = false;
+  // Set to true when we are already in the process of applying a state.
+  // This is used to detect re-entrancy which is hard to reason about and
+  // also will cause memory corruption with the current implementation.
+  bool applying_state_ = false;
 
   scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
 
diff --git a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
index dc16193..809f6e4 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_drag_controller.cc
@@ -179,7 +179,7 @@
   SetDraggedWindow(nullptr, {});
 
   DCHECK(state_ == State::kAttaching || state_ == State::kDropped ||
-         state_ == State::kCancelled);
+         state_ == State::kCancelled) << "Drag state: " << int(state_);
   if (state_ == State::kAttaching) {
     state_ = State::kAttached;
     return false;
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc
index cea4aa7..7aff1158 100644
--- a/ui/views/controls/textfield/textfield.cc
+++ b/ui/views/controls/textfield/textfield.cc
@@ -638,7 +638,8 @@
   return GetInsets().top() + GetRenderText()->GetBaseline();
 }
 
-gfx::Size Textfield::CalculatePreferredSize() const {
+gfx::Size Textfield::CalculatePreferredSize(
+    const SizeBounds& available_size) const {
   DCHECK_GE(default_width_in_chars_, minimum_width_in_chars_);
   return gfx::Size(
       CharsToDips(default_width_in_chars_),
diff --git a/ui/views/controls/textfield/textfield.h b/ui/views/controls/textfield/textfield.h
index 48d6df0b..45f356c 100644
--- a/ui/views/controls/textfield/textfield.h
+++ b/ui/views/controls/textfield/textfield.h
@@ -335,7 +335,8 @@
 
   // View overrides:
   int GetBaseline() const override;
-  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size CalculatePreferredSize(
+      const SizeBounds& available_size) const override;
   gfx::Size GetMinimumSize() const override;
   void SetBorder(std::unique_ptr<Border> b) override;
   ui::Cursor GetCursor(const ui::MouseEvent& event) override;
diff --git a/ui/webui/resources/cr_components/history_embeddings/filter_chips.html b/ui/webui/resources/cr_components/history_embeddings/filter_chips.html
index ccdf550..963a3d8 100644
--- a/ui/webui/resources/cr_components/history_embeddings/filter_chips.html
+++ b/ui/webui/resources/cr_components/history_embeddings/filter_chips.html
@@ -1,4 +1,4 @@
-<style>
+<style include="md-select">
 :host {
   display: flex;
   align-items: center;
@@ -24,13 +24,16 @@
 }
 </style>
 
-<cr-chip id="byGroupChip" selected$="[[showResultsByGroup]]"
-    on-click="onByGroupClick_">
-  <iron-icon id="byGroupChipIcon"
-      icon="[[getByGroupIcon_(showResultsByGroup)]]">
-  </iron-icon>
-  $i18n{historyClustersTabLabel}
-</cr-chip>
+<select id="showByGroupSelectMenu" class="md-select"
+    value="[[showResultsByGroup]]"
+    on-change="onShowByGroupSelectMenuChanged_">
+  <option value="false">
+    $i18n{historyEmbeddingsShowByDate}
+  </option>
+  <option value="true">
+    $i18n{historyEmbeddingsShowByGroup}
+  </option>
+</select>
 
 <hr></hr>
 
diff --git a/ui/webui/resources/cr_components/history_embeddings/filter_chips.ts b/ui/webui/resources/cr_components/history_embeddings/filter_chips.ts
index 8a80344..2194ff7 100644
--- a/ui/webui/resources/cr_components/history_embeddings/filter_chips.ts
+++ b/ui/webui/resources/cr_components/history_embeddings/filter_chips.ts
@@ -4,12 +4,9 @@
 
 import '//resources/cr_elements/cr_chip/cr_chip.js';
 import '//resources/cr_elements/cr_shared_vars.css.js';
-import '//resources/cr_elements/icons.html.js';
-import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
-import './icons.html.js';
+import '//resources/cr_elements/md_select.css.js';
 
 import {loadTimeData} from '//resources/js/load_time_data.js';
-import type {IronIconElement} from '//resources/polymer/v3_0/iron-icon/iron-icon.js';
 import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import type {DomRepeatEvent} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -49,8 +46,7 @@
 
 export interface HistoryEmbeddingsFilterChips {
   $: {
-    byGroupChip: HTMLElement,
-    byGroupChipIcon: IronIconElement,
+    showByGroupSelectMenu: HTMLSelectElement,
   };
 }
 export class HistoryEmbeddingsFilterChips extends PolymerElement {
@@ -97,8 +93,8 @@
     return this.selectedSuggestion === suggestion;
   }
 
-  private onByGroupClick_() {
-    this.showResultsByGroup = !this.showResultsByGroup;
+  private onShowByGroupSelectMenuChanged_() {
+    this.showResultsByGroup = this.$.showByGroupSelectMenu.value === 'true';
   }
 
   private onTimeRangeStartChanged_() {
diff --git a/ui/webui/resources/cr_components/managed_footnote/managed_footnote.css b/ui/webui/resources/cr_components/managed_footnote/managed_footnote.css
index 1cd7fc0e..1095a67e 100644
--- a/ui/webui/resources/cr_components/managed_footnote/managed_footnote.css
+++ b/ui/webui/resources/cr_components/managed_footnote/managed_footnote.css
@@ -27,7 +27,7 @@
   color: var(--cr-link-color);
 }
 
-iron-icon {
+cr-icon {
   align-self: flex-start;
   flex-shrink: 0;
   height: 20px;
diff --git a/ui/webui/resources/cr_components/managed_footnote/managed_footnote.html.ts b/ui/webui/resources/cr_components/managed_footnote/managed_footnote.html.ts
index 9e1fcdb..b50dc68 100644
--- a/ui/webui/resources/cr_components/managed_footnote/managed_footnote.html.ts
+++ b/ui/webui/resources/cr_components/managed_footnote/managed_footnote.html.ts
@@ -9,7 +9,7 @@
 export function getHtml(this: ManagedFootnoteElement) {
   return html`${
       this.isManaged_ ? html`
-  <iron-icon .icon="${this.managedByIcon_}"></iron-icon>
+  <cr-icon .icon="${this.managedByIcon_}"></cr-icon>
   <div id="content" .innerHTML="${this.getManagementString_()}"></div>
 ` :
                         ''}`;
diff --git a/ui/webui/resources/cr_components/managed_footnote/managed_footnote.ts b/ui/webui/resources/cr_components/managed_footnote/managed_footnote.ts
index 86d9072..2d0c4d4 100644
--- a/ui/webui/resources/cr_components/managed_footnote/managed_footnote.ts
+++ b/ui/webui/resources/cr_components/managed_footnote/managed_footnote.ts
@@ -11,8 +11,8 @@
  * becomes visible.
  */
 
-import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
-import '//resources/cr_elements/icons.html.js';
+import '//resources/cr_elements/cr_icon/cr_icon.js';
+import '//resources/cr_elements/icons_lit.html.js';
 
 import {I18nMixinLit} from '//resources/cr_elements/i18n_mixin_lit.js';
 import {WebUiListenerMixinLit} from '//resources/cr_elements/web_ui_listener_mixin_lit.js';
diff --git a/v8 b/v8
index f1f9e58..08a022d 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit f1f9e588bb78fa34cb3f94e5a5a258011311c1d9
+Subproject commit 08a022d7017fd62a01493e29d7d4a54651ef1237