diff --git a/DEPS b/DEPS
index 5109fac..2048115 100644
--- a/DEPS
+++ b/DEPS
@@ -219,7 +219,7 @@
   #
   # CQ_INCLUDE_TRYBOTS=luci.chrome.try:lacros-amd64-generic-chrome-skylab
   # CQ_INCLUDE_TRYBOTS=luci.chrome.try:lacros-arm-generic-chrome-skylab
-  'lacros_sdk_version': '14844.0.0',
+  'lacros_sdk_version': '14945.0.0',
 
   # Generate location tag metadata to include in tests result data uploaded
   # to ResultDB. This isn't needed on some configs and the tool that generates
@@ -280,15 +280,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': '9d82f9e4b8c33b429b1c8a5b2a1d5e9636faad93',
+  'skia_revision': 'b2e4416a6bfcec9f773d1ab25df1ef2ca7f23322',
   # 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': 'e9a100d0e5eae77791468c1da1c0805dc5442401',
+  'v8_revision': 'd19c461a2d5bf8601092d273be198ec3743d78e9',
   # 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': 'fd9301c1d8dd11ace845d7cfb5322dffcfa36502',
+  'angle_revision': 'da984303fda121cced56dfa07c6890d3b29dc502',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -351,7 +351,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': '0f408470862fe81fbd949d8058803ae759fcadc9',
+  'catapult_revision': '6f2de7bf2ddaa1cdd03241a5de5333f07d33756e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -359,7 +359,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '727a1ed2058b3d1ccaff7f1ebe47d627504009d8',
+  'devtools_frontend_revision': '4667152d3b10df7fac3e496157963c6541ae78be',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -395,7 +395,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '776f6d176269432bdf681fc710967f9fe3f29089',
+  'dawn_revision': 'c0d493ffa1e3720bba898597fc2af6121a5b01b8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -419,11 +419,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libavif
   # and whatever else without interference from each other.
-  'libavif_revision': '1dedea43710c55a1bf1c675126cb6cb546c7b2a7',
+  'libavif_revision': '1d0cab42f6d1b70b0d66084c8170e716c8b88a3b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
-  'nearby_revision': '75aa59d57e1d34331c67fc84ba00a159ce2fc370',
+  'nearby_revision': '7e081c663af2762216cf6f85a2d95bfc84de1407',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -929,7 +929,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'qYbZhGFI6Byx-h1-gMAwav_sOAyRgupup2LcOewkUwYC',
+          'version': 'hIxw66nyAM74HK6z6GErDelxqqlhFqYsf6MI0X8BRfsC',
       },
     ],
     'condition': 'checkout_android',
@@ -1157,7 +1157,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9af90cb59e23b16860bf32e1fd5d39865166e3ab',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '78c53d11a01f8439b37010289e8bbdb139942d57',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1415,7 +1415,7 @@
     Var('chromium_git') + '/chromium/deps/libjpeg_turbo.git' + '@' + '22f1a22c99e9dde8cd3c72ead333f425c5a7aa77',
 
   'src/third_party/liblouis/src': {
-      'url': Var('chromium_git') + '/external/liblouis-github.git' + '@' + 'c05f3bfb0990434bd12bf6697d16ed943f2203c2',
+      'url': Var('chromium_git') + '/external/liblouis-github.git' + '@' + '9700847afb92cb35969bdfcbbfbbb74b9c7b3376',
       'condition': 'checkout_linux',
   },
 
@@ -1554,7 +1554,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '63cdebc57fc66b8782b36f38b46637879210b1b6',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'd86457ab539f83842f0e6c891c459bd764267b8d',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1685,7 +1685,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@955b22833ec40a749c3d718087cf16ed89332d1c',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@8e318438088194b433410d57c62933eb1770d8f7',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1721,10 +1721,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'e58ed2132aa47ac110a4cce1763abfa34f4fa34e',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '85f975b4f0f556927b678a5f2d45af989edf8e40',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '6c0f3bab40dead9e205a95ef64ad8f912c694a1d',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '8967672f6da421ced7f23d3a54b9bfdd457f683b',
+    Var('webrtc_git') + '/src.git' + '@' + '00c614272a1b67224b8371a7704a434f3ec5f61e',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1797,7 +1797,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@a88639320c5c182d969ee008702d7d2773a1c66d',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@a77b2070bdfca0846f6ed9b8936aa1811e5f1734',
     'condition': 'checkout_src_internal',
   },
 
@@ -1838,7 +1838,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'SZYZYegb8RTMDDlPu9C_mbjJtX09YslogU-BFuMyq4kC',
+        'version': '98xvPATCOcbtIz0jlu96tGnt7myEq1jcr3y9A5RiKTYC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
index 1be226a..772f2ed 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/services/JsSandboxServiceTest.java
@@ -11,6 +11,7 @@
 import com.google.common.util.concurrent.ListenableFuture;
 
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -20,6 +21,7 @@
 import org.chromium.android_webview.js_sandbox.client.JsSandbox;
 import org.chromium.android_webview.test.AwJUnit4ClassRunner;
 import org.chromium.base.ContextUtils;
+import org.chromium.base.test.util.DisabledTest;
 
 import java.nio.charset.StandardCharsets;
 import java.util.Vector;
@@ -182,6 +184,8 @@
         ListenableFuture<JsSandbox> JsSandboxFuture =
                 JsSandbox.newConnectedInstanceForTestingAsync(context);
         JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+        Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.ISOLATE_TERMINATION));
+
         JsIsolate jsIsolate = jsSandbox.createIsolate();
         ListenableFuture<String> resultFuture = jsIsolate.evaluateJavascriptAsync(code);
         boolean isOfCorrectType = false;
@@ -206,6 +210,8 @@
         ListenableFuture<JsSandbox> JsSandboxFuture =
                 JsSandbox.newConnectedInstanceForTestingAsync(context);
         JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+        Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.ISOLATE_TERMINATION));
+
         JsIsolate jsIsolate = jsSandbox.createIsolate();
         Vector<ListenableFuture<String>> resultFutures = new Vector<ListenableFuture<String>>();
         for (int i = 0; i < num_of_evaluations; i++) {
@@ -228,6 +234,21 @@
 
     @Test
     @MediumTest
+    @DisabledTest(
+            message =
+                    "Enable it back once we have a WebView version to see if the feature is actually supported in that version")
+    public void
+    testFeatureDetection() throws Throwable {
+        Context context = ContextUtils.getApplicationContext();
+        ListenableFuture<JsSandbox> JsSandboxFuture =
+                JsSandbox.newConnectedInstanceForTestingAsync(ContextUtils.getApplicationContext());
+        try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS)) {
+            Assert.assertFalse(jsSandbox.isFeatureSupported(JsSandbox.ISOLATE_TERMINATION));
+        }
+    }
+
+    @Test
+    @MediumTest
     public void testSimpleArrayBuffer() throws Throwable {
         final String provideString = "Hello World";
         final byte[] bytes = provideString.getBytes(StandardCharsets.US_ASCII);
@@ -243,6 +264,9 @@
                 JsSandbox.newConnectedInstanceForTestingAsync(context);
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
                 JsIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROMISE_RETURN));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROVIDE_CONSUME_ARRAY_BUFFER));
+
             boolean provideNamedDataReturn = jsIsolate.provideNamedData("id-1", bytes);
             Assert.assertTrue(provideNamedDataReturn);
             ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavascriptAsync(code);
@@ -269,6 +293,10 @@
                 JsSandbox.newConnectedInstanceForTestingAsync(context);
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
                 JsIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROMISE_RETURN));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROVIDE_CONSUME_ARRAY_BUFFER));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.WASM_COMPILATION));
+
             boolean provideNamedDataReturn = jsIsolate.provideNamedData("id-1", bytes);
             Assert.assertTrue(provideNamedDataReturn);
             ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavascriptAsync(code);
@@ -288,6 +316,8 @@
                 JsSandbox.newConnectedInstanceForTestingAsync(context);
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
                 JsIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROMISE_RETURN));
+
             ListenableFuture<String> resultFuture = jsIsolate.evaluateJavascriptAsync(code);
             String result = resultFuture.get(5, TimeUnit.SECONDS);
 
@@ -311,6 +341,8 @@
                 JsSandbox.newConnectedInstanceForTestingAsync(context);
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
                 JsIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROMISE_RETURN));
+
             ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavascriptAsync(code1);
             ListenableFuture<String> resultFuture2 = jsIsolate.evaluateJavascriptAsync(code2);
             String result = resultFuture1.get(5, TimeUnit.SECONDS);
@@ -344,6 +376,9 @@
                 JsSandbox.newConnectedInstanceForTestingAsync(context);
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
                 JsIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROMISE_RETURN));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROVIDE_CONSUME_ARRAY_BUFFER));
+
             jsIsolate.provideNamedData("id-1", bytes);
             jsIsolate.provideNamedData("id-2", bytes);
             jsIsolate.provideNamedData("id-3", bytes);
@@ -373,6 +408,9 @@
                 JsSandbox.newConnectedInstanceForTestingAsync(context);
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
                 JsIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROMISE_RETURN));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROVIDE_CONSUME_ARRAY_BUFFER));
+
             ListenableFuture<String> resultFuture = jsIsolate.evaluateJavascriptAsync(code);
             try {
                 String result = resultFuture.get(5, TimeUnit.SECONDS);
@@ -407,4 +445,48 @@
             }
         }
     }
+
+    @Test
+    @MediumTest
+    public void testMultipleSandboxesCannotCoexist() throws Throwable {
+        Context context = ContextUtils.getApplicationContext();
+        final String contains = "already bound";
+        ListenableFuture<JsSandbox> JsSandboxFuture1 =
+                JsSandbox.newConnectedInstanceForTestingAsync(context);
+        try (JsSandbox jsSandbox1 = JsSandboxFuture1.get(5, TimeUnit.SECONDS)) {
+            ListenableFuture<JsSandbox> JsSandboxFuture2 =
+                    JsSandbox.newConnectedInstanceForTestingAsync(context);
+            try {
+                JsSandbox jsSandbox2 = JsSandboxFuture2.get(5, TimeUnit.SECONDS);
+                Assert.fail("Should have thrown.");
+            } catch (ExecutionException e) {
+                if (!(e.getCause() instanceof RuntimeException)) {
+                    throw e;
+                }
+                Assert.assertTrue(e.getCause().getMessage().contains(contains));
+            }
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testSandboxCanBeCreatedAfterClosed() throws Throwable {
+        final String code = "\"PASS\"";
+        final String expected = "PASS";
+        Context context = ContextUtils.getApplicationContext();
+
+        ListenableFuture<JsSandbox> JsSandboxFuture1 =
+                JsSandbox.newConnectedInstanceForTestingAsync(context);
+        JsSandbox jsSandbox1 = JsSandboxFuture1.get(5, TimeUnit.SECONDS);
+        jsSandbox1.close();
+        ListenableFuture<JsSandbox> JsSandboxFuture2 =
+                JsSandbox.newConnectedInstanceForTestingAsync(context);
+        try (JsSandbox jsSandbox2 = JsSandboxFuture2.get(5, TimeUnit.SECONDS);
+                JsIsolate jsIsolate = jsSandbox2.createIsolate()) {
+            ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavascriptAsync(code);
+            String result = resultFuture1.get(5, TimeUnit.SECONDS);
+
+            Assert.assertEquals(expected, result);
+        }
+    }
 }
diff --git a/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/client/JsIsolate.java b/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/client/JsIsolate.java
index 5c72256..367a2a0 100644
--- a/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/client/JsIsolate.java
+++ b/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/client/JsIsolate.java
@@ -10,6 +10,7 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RequiresFeature;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
 
 import com.google.common.util.concurrent.ListenableFuture;
@@ -132,7 +133,11 @@
         mGuard.close();
     }
 
-    public boolean provideNamedData(@NonNull String name, @NonNull byte[] inputBytes) {
+    @RequiresFeature(name = JsSandbox.PROVIDE_CONSUME_ARRAY_BUFFER,
+            enforcement =
+                    "org.chromium.android_webview.js_sandbox.client.JsSandbox#isFeatureSupported")
+    public boolean
+    provideNamedData(@NonNull String name, @NonNull byte[] inputBytes) {
         if (mJsIsolateStub == null) {
             throw new IllegalStateException("Calling provideNamedData() after closing the Isolate");
         }
diff --git a/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/client/JsSandbox.java b/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/client/JsSandbox.java
index e928788..e749651 100644
--- a/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/client/JsSandbox.java
+++ b/android_webview/js_sandbox/java/src/org/chromium/android_webview/js_sandbox/client/JsSandbox.java
@@ -14,6 +14,8 @@
 import android.webkit.WebView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.StringDef;
 import androidx.annotation.VisibleForTesting;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
 import androidx.core.content.ContextCompat;
@@ -23,9 +25,14 @@
 import org.chromium.android_webview.js_sandbox.common.IJsSandboxIsolate;
 import org.chromium.android_webview.js_sandbox.common.IJsSandboxService;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.annotation.concurrent.GuardedBy;
 
@@ -38,6 +45,7 @@
     private static final String TAG = "JsSandbox";
     private static final String JS_SANDBOX_SERVICE_NAME =
             "org.chromium.android_webview.js_sandbox.service.JsSandboxService0";
+    private static AtomicBoolean sIsReadyToConnect = new AtomicBoolean(true);
     private final Object mLock = new Object();
     private CloseGuardHelper mGuard = CloseGuardHelper.create();
 
@@ -49,6 +57,56 @@
     @GuardedBy("mLock")
     private HashSet<JsIsolate> mActiveIsolateSet = new HashSet<JsIsolate>();
 
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @StringDef(value =
+                       {
+                               ISOLATE_TERMINATION,
+                               PROMISE_RETURN,
+                               PROVIDE_CONSUME_ARRAY_BUFFER,
+                               WASM_COMPILATION,
+                       })
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.PARAMETER, ElementType.METHOD})
+    public @interface JsSandboxFeature {}
+
+    /**
+     * Feature for {@link #isFeatureSupported(String)}.
+     * <p>This features provides additional behaviour to {@link JsIsolate#close()}. When this
+     * feature is present, {@link JsIsolate#close()} will terminate the currently running JS
+     * evaluation and close the isolate. If it is absent, {@link JsIsolate#close()} will close the
+     * isolate after all pending evaluations are run.
+     */
+    public static final String ISOLATE_TERMINATION = "ISOLATE_TERMINATION";
+
+    /**
+     * Feature for {@link #isFeatureSupported(String)}.
+     * <p>This features provides additional behaviour to {@link
+     * JsIsolate#evaluateJavascriptAsync(String)} ()}. When this feature is present, we support the
+     * JS to return promises, and {@link JsIsolate#evaluateJavascriptAsync(String)} waits for the
+     * promise to resolve and returns the resolved value of the promise.
+     */
+    public static final String PROMISE_RETURN = "PROMISE_RETURN";
+
+    /**
+     * Feature for {@link #isFeatureSupported(String)}.
+     * This feature covers {@link JsIsolate#provideNamedData(String, byte[])}
+     * <p>This also covers the JS API android.consumeNamedDataAsArrayBuffer(string).
+     */
+    public static final String PROVIDE_CONSUME_ARRAY_BUFFER = "PROVIDE_CONSUME_ARRAY_BUFFER";
+
+    /**
+     * Feature for {@link #isFeatureSupported(String)}.
+     * <p>This features provides additional behaviour to {@link
+     * JsIsolate#evaluateJavascriptAsync(String)} ()}. When this feature is present, we support
+     * using the JS API WebAssembly.compile(ArrayBuffer).
+     */
+    public static final String WASM_COMPILATION = "WASM_COMPILATION";
+
+    private HashSet<String> mClientSideFeatureSet;
+
     static class ConnectionSetup implements ServiceConnection {
         private CallbackToFutureAdapter.Completer<JsSandbox> mCompleter;
         private JsSandbox mJsSandbox;
@@ -86,6 +144,7 @@
                 mJsSandbox.doClose(new SandboxDeadException());
             } else {
                 mContext.unbindService(this);
+                sIsReadyToConnect.set(true);
             }
             if (mCompleter != null) {
                 mCompleter.setException(e);
@@ -132,21 +191,26 @@
         intent.setComponent(compName);
         return CallbackToFutureAdapter.getFuture(completer -> {
             ConnectionSetup connectionSetup = new ConnectionSetup(context, completer);
-            try {
-                boolean isBinding = context.bindService(intent, connectionSetup, flag);
-                if (isBinding) {
-                    Executor mainExecutor;
-                    mainExecutor = ContextCompat.getMainExecutor(context);
-                    completer.addCancellationListener(
-                            () -> context.unbindService(connectionSetup), mainExecutor);
-                } else {
+            if (sIsReadyToConnect.compareAndSet(true, false)) {
+                try {
+                    boolean isBinding = context.bindService(intent, connectionSetup, flag);
+                    if (isBinding) {
+                        Executor mainExecutor;
+                        mainExecutor = ContextCompat.getMainExecutor(context);
+                        completer.addCancellationListener(
+                                () -> { context.unbindService(connectionSetup); }, mainExecutor);
+                    } else {
+                        context.unbindService(connectionSetup);
+                        completer.setException(
+                                new RuntimeException("bindService() returned false " + intent));
+                    }
+                } catch (SecurityException e) {
                     context.unbindService(connectionSetup);
-                    completer.setException(
-                            new RuntimeException("bindService() returned false " + intent));
+                    completer.setException(e);
                 }
-            } catch (SecurityException e) {
-                context.unbindService(connectionSetup);
-                completer.setException(e);
+            } else {
+                completer.setException(
+                        new IllegalStateException("Binding to already bound service"));
             }
 
             // Debug string.
@@ -183,22 +247,39 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void populateClientFeatureSet() {
+        mClientSideFeatureSet = new HashSet<String>();
+        List<String> features;
+        try {
+            features = mJsSandboxService.getSupportedFeatures();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+
+        if (features.contains(IJsSandboxService.ISOLATE_TERMINATION)) {
+            mClientSideFeatureSet.add(ISOLATE_TERMINATION);
+        }
+        if (features.contains(IJsSandboxService.WASM_FROM_ARRAY_BUFFER)) {
+            mClientSideFeatureSet.add(PROMISE_RETURN);
+            mClientSideFeatureSet.add(PROVIDE_CONSUME_ARRAY_BUFFER);
+            mClientSideFeatureSet.add(WASM_COMPILATION);
+        }
+    }
+
     /**
-     * Quick temporary feature detection interface for testing the IPC.
-     * TODO(crbug.com/1297672): make parameterized and cached.
+     * Feature detection interface.
      */
-    public boolean isIsolateTerminationSupported() {
+    public boolean isFeatureSupported(@NonNull @JsSandboxFeature String feature) {
         synchronized (mLock) {
             if (mJsSandboxService == null) {
                 throw new IllegalStateException(
                         "Attempting to check features on a service that isn't connected");
             }
-            try {
-                List<String> features = mJsSandboxService.getSupportedFeatures();
-                return features.contains(IJsSandboxService.ISOLATE_TERMINATION);
-            } catch (RemoteException e) {
-                throw new RuntimeException(e);
+            if (mClientSideFeatureSet == null) {
+                populateClientFeatureSet();
             }
+            return mClientSideFeatureSet.contains(feature);
         }
     }
 
@@ -222,6 +303,10 @@
             }
             cancelPendingEvaluationsLocked(cancelPendingWith);
             mConnection.mContext.unbindService(mConnection);
+            // Currently we consider that we are ready for a new connection once we unbind. This
+            // might not be true if the process is not immediately killed by ActivityManager once it
+            // is unbound.
+            sIsReadyToConnect.set(true);
             mJsSandboxService = null;
         }
     }
diff --git a/android_webview/js_sandbox/javatests/src/org/chromium/webview_js_sandbox_test/test/WebViewJsSandboxTest.java b/android_webview/js_sandbox/javatests/src/org/chromium/webview_js_sandbox_test/test/WebViewJsSandboxTest.java
index ffa7100..3ef8ff75 100644
--- a/android_webview/js_sandbox/javatests/src/org/chromium/webview_js_sandbox_test/test/WebViewJsSandboxTest.java
+++ b/android_webview/js_sandbox/javatests/src/org/chromium/webview_js_sandbox_test/test/WebViewJsSandboxTest.java
@@ -11,6 +11,7 @@
 import com.google.common.util.concurrent.ListenableFuture;
 
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -20,6 +21,7 @@
 import org.chromium.android_webview.js_sandbox.client.JsSandbox;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.base.test.util.DisabledTest;
 
 import java.nio.charset.StandardCharsets;
 import java.util.Vector;
@@ -175,6 +177,8 @@
 
         ListenableFuture<JsSandbox> JsSandboxFuture = JsSandbox.newConnectedInstanceAsync(context);
         JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+        Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.ISOLATE_TERMINATION));
+
         JsIsolate jsIsolate = jsSandbox.createIsolate();
         ListenableFuture<String> resultFuture = jsIsolate.evaluateJavascriptAsync(code);
         boolean isOfCorrectType = false;
@@ -198,6 +202,8 @@
 
         ListenableFuture<JsSandbox> JsSandboxFuture = JsSandbox.newConnectedInstanceAsync(context);
         JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
+        Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.ISOLATE_TERMINATION));
+
         JsIsolate jsIsolate = jsSandbox.createIsolate();
         Vector<ListenableFuture<String>> resultFutures = new Vector<ListenableFuture<String>>();
         for (int i = 0; i < num_of_evaluations; i++) {
@@ -220,11 +226,16 @@
 
     @Test
     @MediumTest
-    public void testFeatureDetection() throws Throwable {
+    @DisabledTest(
+            message =
+                    "Enable it back once we have a WebView version to see if the feature is actually supported in that version")
+    public void
+    testFeatureDetection() throws Throwable {
+        Context context = ContextUtils.getApplicationContext();
         ListenableFuture<JsSandbox> JsSandboxFuture =
                 JsSandbox.newConnectedInstanceAsync(ContextUtils.getApplicationContext());
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS)) {
-            Assert.assertTrue(jsSandbox.isIsolateTerminationSupported());
+            Assert.assertFalse(jsSandbox.isFeatureSupported(JsSandbox.ISOLATE_TERMINATION));
         }
     }
 
@@ -244,6 +255,9 @@
         ListenableFuture<JsSandbox> JsSandboxFuture = JsSandbox.newConnectedInstanceAsync(context);
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
                 JsIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROMISE_RETURN));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROVIDE_CONSUME_ARRAY_BUFFER));
+
             boolean provideNamedDataReturn = jsIsolate.provideNamedData("id-1", bytes);
             Assert.assertTrue(provideNamedDataReturn);
             ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavascriptAsync(code);
@@ -269,6 +283,10 @@
         ListenableFuture<JsSandbox> JsSandboxFuture = JsSandbox.newConnectedInstanceAsync(context);
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
                 JsIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROMISE_RETURN));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROVIDE_CONSUME_ARRAY_BUFFER));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.WASM_COMPILATION));
+
             boolean provideNamedDataReturn = jsIsolate.provideNamedData("id-1", bytes);
             Assert.assertTrue(provideNamedDataReturn);
             ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavascriptAsync(code);
@@ -287,6 +305,8 @@
         ListenableFuture<JsSandbox> JsSandboxFuture = JsSandbox.newConnectedInstanceAsync(context);
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
                 JsIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROMISE_RETURN));
+
             ListenableFuture<String> resultFuture = jsIsolate.evaluateJavascriptAsync(code);
             String result = resultFuture.get(5, TimeUnit.SECONDS);
 
@@ -309,6 +329,8 @@
         ListenableFuture<JsSandbox> JsSandboxFuture = JsSandbox.newConnectedInstanceAsync(context);
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
                 JsIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROMISE_RETURN));
+
             ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavascriptAsync(code1);
             ListenableFuture<String> resultFuture2 = jsIsolate.evaluateJavascriptAsync(code2);
             String result = resultFuture1.get(5, TimeUnit.SECONDS);
@@ -341,6 +363,9 @@
         ListenableFuture<JsSandbox> JsSandboxFuture = JsSandbox.newConnectedInstanceAsync(context);
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
                 JsIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROMISE_RETURN));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROVIDE_CONSUME_ARRAY_BUFFER));
+
             jsIsolate.provideNamedData("id-1", bytes);
             jsIsolate.provideNamedData("id-2", bytes);
             jsIsolate.provideNamedData("id-3", bytes);
@@ -369,6 +394,9 @@
         ListenableFuture<JsSandbox> JsSandboxFuture = JsSandbox.newConnectedInstanceAsync(context);
         try (JsSandbox jsSandbox = JsSandboxFuture.get(5, TimeUnit.SECONDS);
                 JsIsolate jsIsolate = jsSandbox.createIsolate()) {
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROMISE_RETURN));
+            Assume.assumeTrue(jsSandbox.isFeatureSupported(JsSandbox.PROVIDE_CONSUME_ARRAY_BUFFER));
+
             ListenableFuture<String> resultFuture = jsIsolate.evaluateJavascriptAsync(code);
             try {
                 String result = resultFuture.get(5, TimeUnit.SECONDS);
@@ -402,4 +430,45 @@
             }
         }
     }
+
+    @Test
+    @MediumTest
+    public void testMultipleSandboxesCannotCoexist() throws Throwable {
+        Context context = ContextUtils.getApplicationContext();
+        final String contains = "already bound";
+        ListenableFuture<JsSandbox> JsSandboxFuture1 = JsSandbox.newConnectedInstanceAsync(context);
+        try (JsSandbox jsSandbox1 = JsSandboxFuture1.get(5, TimeUnit.SECONDS)) {
+            ListenableFuture<JsSandbox> JsSandboxFuture2 =
+                    JsSandbox.newConnectedInstanceAsync(context);
+            try {
+                JsSandbox jsSandbox2 = JsSandboxFuture2.get(5, TimeUnit.SECONDS);
+                Assert.fail("Should have thrown.");
+            } catch (ExecutionException e) {
+                if (!(e.getCause() instanceof RuntimeException)) {
+                    throw e;
+                }
+                Assert.assertTrue(e.getCause().getMessage().contains(contains));
+            }
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testSandboxCanBeCreatedAfterClosed() throws Throwable {
+        final String code = "\"PASS\"";
+        final String expected = "PASS";
+        Context context = ContextUtils.getApplicationContext();
+
+        ListenableFuture<JsSandbox> JsSandboxFuture1 = JsSandbox.newConnectedInstanceAsync(context);
+        JsSandbox jsSandbox1 = JsSandboxFuture1.get(5, TimeUnit.SECONDS);
+        jsSandbox1.close();
+        ListenableFuture<JsSandbox> JsSandboxFuture2 = JsSandbox.newConnectedInstanceAsync(context);
+        try (JsSandbox jsSandbox2 = JsSandboxFuture2.get(5, TimeUnit.SECONDS);
+                JsIsolate jsIsolate = jsSandbox2.createIsolate()) {
+            ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavascriptAsync(code);
+            String result = resultFuture1.get(5, TimeUnit.SECONDS);
+
+            Assert.assertEquals(expected, result);
+        }
+    }
 }
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 7d90e8a9..caf60f23 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -693,6 +693,12 @@
 const base::Feature kFastPairSavedDevices{"FastPairSavedDevices",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables the "Saved Devices" Fast Pair strict interpretation of opt-in status,
+// meaning that a user's preferences determine if retroactive pairing and
+// subsequent pairing scenarios are enabled.
+const base::Feature kFastPairSavedDevicesStrictOptIn{
+    "FastPairSavedDevicesStrictOptIn", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables experimental UI features in Files app.
 const base::Feature kFilesAppExperimental{"FilesAppExperimental",
                                           base::FEATURE_DISABLED_BY_DEFAULT};
@@ -1857,6 +1863,10 @@
   return base::FeatureList::IsEnabled(kFastPairSavedDevices);
 }
 
+bool IsFastPairSavedDevicesStrictOptInEnabled() {
+  return base::FeatureList::IsEnabled(kFastPairSavedDevicesStrictOptIn);
+}
+
 bool IsFileManagerFuseBoxEnabled() {
   return base::FeatureList::IsEnabled(kFuseBox);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 35c5536..1c9e76f 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -293,6 +293,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kFastPairSavedDevices;
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kFastPairSavedDevicesStrictOptIn;
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kFilesAppExperimental;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kFilesArchivemount2;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kFilesExtractArchive;
@@ -697,6 +699,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFastPairSoftwareScanningEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFastPairSubsequentPairingUXEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFastPairSavedDevicesEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFastPairSavedDevicesStrictOptInEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFileManagerFuseBoxEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFileManagerFuseBoxDebugEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsFileManagerSwaEnabled();
diff --git a/ash/login/login_screen_test_api.cc b/ash/login/login_screen_test_api.cc
index 8ed762f..81e5e61 100644
--- a/ash/login/login_screen_test_api.cc
+++ b/ash/login/login_screen_test_api.cc
@@ -281,8 +281,7 @@
   LockScreen::TestApi lock_screen_test(LockScreen::Get());
   LockContentsView::TestApi test_api(lock_screen_test.contents_view());
   return test_api.kiosk_default_message() &&
-         test_api.kiosk_default_message()->GetWidget() &&
-         test_api.kiosk_default_message()->GetWidget()->IsVisible();
+         test_api.kiosk_default_message()->GetVisible();
 }
 
 // static
diff --git a/ash/login/ui/kiosk_app_default_message.cc b/ash/login/ui/kiosk_app_default_message.cc
index 10876588..0ddb91b 100644
--- a/ash/login/ui/kiosk_app_default_message.cc
+++ b/ash/login/ui/kiosk_app_default_message.cc
@@ -36,20 +36,13 @@
 }  // namespace
 
 KioskAppDefaultMessage::KioskAppDefaultMessage()
-    : BubbleDialogDelegateView(nullptr, views::BubbleBorder::NONE),
+    : LoginBaseBubbleView(/*anchor_view=*/nullptr),
       background_animator_(
           /* Don't pass the Shelf so the translucent color is always used. */
           nullptr,
           Shell::Get()->wallpaper_controller()) {
   auto* layout_provider = views::LayoutProvider::Get();
-  set_close_on_deactivate(false);
-  set_margins(gfx::Insets(layout_provider->GetDistanceMetric(
-      views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_CONTROL)));
-  SetShowCloseButton(false);
-  SetButtons(ui::DIALOG_BUTTON_NONE);
-  // Bubbles that use transparent colors should not paint their ClientViews to a
-  // layer as doing so could result in visual artifacts.
-  SetPaintClientToLayer(false);
+  set_persistent(true);
   background_animator_.Init(ShelfBackgroundType::kDefaultBg);
   background_animator_observation_.Observe(&background_animator_);
 
@@ -74,27 +67,31 @@
   title_->SetMultiLine(true);
   TrayPopupUtils::SetLabelFontList(title_,
                                    TrayPopupUtils::FontStyle::kSmallTitle);
-
-  // TODO(crbug.com/1334979): We should refactor KioskAppDefaultMessage so that
-  // it's own by LockContentsView.
-  views::DialogDelegate::CreateDialogWidget(
-      this, nullptr /* context */,
-      Shell::GetContainer(
-          ash::Shell::GetRootWindowForNewWindows(),
-          kShellWindowId_LockScreenRelatedContainersContainer) /* parent */);
-
-  GetBubbleFrameView()->SetCornerRadius(
-      views::LayoutProvider::Get()->GetCornerRadiusMetric(
-          views::Emphasis::kHigh));
-  GetBubbleFrameView()->SetBackgroundColor(
-      AshColorProvider::Get()->GetBaseLayerColor(
-          AshColorProvider::BaseLayerType::kTransparent90));
 }
 
 KioskAppDefaultMessage::~KioskAppDefaultMessage() = default;
 
+gfx::Size KioskAppDefaultMessage::CalculatePreferredSize() const {
+  auto* layout_provider = views::LayoutProvider::Get();
+
+  // width = left_margin + icon_width + component_distance + title_width +
+  // right_margin
+  int width = icon_->CalculatePreferredSize().width() +
+              layout_provider->GetDistanceMetric(
+                  views::DISTANCE_RELATED_CONTROL_HORIZONTAL) +
+              title_->CalculatePreferredSize().width() +
+              2 * layout_provider->GetDistanceMetric(
+                      views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_CONTROL);
+  // height = upper_margin + icon_height + lower_margin
+  int height =
+      kIconSize + 2 * layout_provider->GetDistanceMetric(
+                          views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_CONTROL);
+
+  return gfx::Size(width, height);
+}
+
 void KioskAppDefaultMessage::OnThemeChanged() {
-  views::View::OnThemeChanged();
+  LoginBaseBubbleView::OnThemeChanged();
   auto* color_provider = AshColorProvider::Get();
 
   SkColor icon_color = color_provider->GetContentLayerColor(
@@ -107,4 +104,10 @@
   title_->SetEnabledColor(label_color);
 }
 
-}  // namespace ash
\ No newline at end of file
+gfx::Point KioskAppDefaultMessage::CalculatePosition() {
+  return gfx::Point(parent()->GetLocalBounds().width() / 2,
+                    parent()->GetLocalBounds().height() / 2) -
+         gfx::Vector2d(width() / 2, height());
+}
+
+}  // namespace ash
diff --git a/ash/login/ui/kiosk_app_default_message.h b/ash/login/ui/kiosk_app_default_message.h
index c5fa6a79..45bf9e5 100644
--- a/ash/login/ui/kiosk_app_default_message.h
+++ b/ash/login/ui/kiosk_app_default_message.h
@@ -6,10 +6,10 @@
 #define ASH_LOGIN_UI_KIOSK_APP_DEFAULT_MESSAGE_H_
 
 #include "ash/ash_export.h"
+#include "ash/login/ui/login_base_bubble_view.h"
 #include "ash/shelf/shelf_background_animator.h"
 #include "ash/shelf/shelf_background_animator_observer.h"
 #include "base/scoped_observation.h"
-#include "ui/views/bubble/bubble_dialog_delegate_view.h"
 
 namespace views {
 class ImageView;
@@ -22,17 +22,22 @@
 // KioskAppDefaultMessage is owned by itself and would be destroyed when its
 // widget got destroyed, which happened when the widget's window got destroyed.
 class ASH_EXPORT KioskAppDefaultMessage
-    : public views::BubbleDialogDelegateView,
+    : public LoginBaseBubbleView,
       public ShelfBackgroundAnimatorObserver {
  public:
   KioskAppDefaultMessage();
+
   KioskAppDefaultMessage(const KioskAppDefaultMessage&) = delete;
   KioskAppDefaultMessage& operator=(const KioskAppDefaultMessage&) = delete;
   ~KioskAppDefaultMessage() override;
 
   // views::View:
+  gfx::Size CalculatePreferredSize() const override;
   void OnThemeChanged() override;
 
+  // LoginBaseBubbleView
+  gfx::Point CalculatePosition() override;
+
  private:
   views::ImageView* icon_ = nullptr;
   views::Label* title_ = nullptr;
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 0c41a316..edb181f 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -2642,15 +2642,11 @@
     return;
 
   if (!kiosk_default_message_) {
-    // KioskAppDefaultMessage is owned by itself and would be destroyed when
-    // its widget got destroyed, which happened when the widget's window got
-    // destroyed.
-    kiosk_default_message_ = new KioskAppDefaultMessage();
+    kiosk_default_message_ =
+        AddChildView(std::make_unique<KioskAppDefaultMessage>());
   }
-  if (has_kiosk_apps_)
-    kiosk_default_message_->GetWidget()->Hide();
-  else
-    kiosk_default_message_->GetWidget()->Show();
+
+  kiosk_default_message_->SetVisible(!has_kiosk_apps_);
 }
 
 void LockContentsView::SetKioskLicenseModeForTesting(
diff --git a/ash/login/ui/lock_contents_view_unittest.cc b/ash/login/ui/lock_contents_view_unittest.cc
index 2db64c1..b8962f35 100644
--- a/ash/login/ui/lock_contents_view_unittest.cc
+++ b/ash/login/ui/lock_contents_view_unittest.cc
@@ -3361,7 +3361,7 @@
   SetNumberOfKioskApps(1);
 
   EXPECT_TRUE(test_api.kiosk_default_message());
-  EXPECT_FALSE(test_api.kiosk_default_message()->GetWidget()->IsVisible());
+  EXPECT_FALSE(test_api.kiosk_default_message()->GetVisible());
 }
 
 // Checks default message hidden if device is not with kiosk license and has
@@ -3407,7 +3407,7 @@
   SetNumberOfKioskApps(0);
 
   EXPECT_TRUE(test_api.kiosk_default_message());
-  EXPECT_TRUE(test_api.kiosk_default_message()->GetWidget()->IsVisible());
+  EXPECT_TRUE(test_api.kiosk_default_message()->GetVisible());
 }
 
 // Checks default message appeared if device is with kiosk license, no
@@ -3431,7 +3431,7 @@
   SetNumberOfKioskApps(0);
 
   EXPECT_TRUE(test_api.kiosk_default_message());
-  EXPECT_TRUE(test_api.kiosk_default_message()->GetWidget()->IsVisible());
+  EXPECT_TRUE(test_api.kiosk_default_message()->GetVisible());
 }
 
 // Checks default message appeared if device is with kiosk license and no
@@ -3456,12 +3456,12 @@
   SetNumberOfKioskApps(0);
 
   EXPECT_TRUE(test_api.kiosk_default_message());
-  EXPECT_TRUE(test_api.kiosk_default_message()->GetWidget()->IsVisible());
+  EXPECT_TRUE(test_api.kiosk_default_message()->GetVisible());
 
   SetNumberOfKioskApps(1);
 
   EXPECT_TRUE(test_api.kiosk_default_message());
-  EXPECT_FALSE(test_api.kiosk_default_message()->GetWidget()->IsVisible());
+  EXPECT_FALSE(test_api.kiosk_default_message()->GetVisible());
 }
 
 }  // namespace ash
diff --git a/ash/login/ui/login_base_bubble_view.h b/ash/login/ui/login_base_bubble_view.h
index 5f5755d..e543cfb3 100644
--- a/ash/login/ui/login_base_bubble_view.h
+++ b/ash/login/ui/login_base_bubble_view.h
@@ -94,7 +94,7 @@
   void ScheduleAnimation(bool visible);
 
   // Determine the position of the bubble prior to showing.
-  gfx::Point CalculatePosition();
+  virtual gfx::Point CalculatePosition();
 
   views::View* anchor_view_;
 
diff --git a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
index c2b52c12..2debd24 100644
--- a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
+++ b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
@@ -311,7 +311,8 @@
   // to notify a device is found, we can accurately reflect a user's status
   // in the moment. This is flagged on whether the user has the Fast Pair
   // Saved Devices flag enabled.
-  if (features::IsFastPairSavedDevicesEnabled()) {
+  if (features::IsFastPairSavedDevicesEnabled() &&
+      features::IsFastPairSavedDevicesStrictOptInEnabled()) {
     FastPairRepository::Get()->CheckOptInStatus(
         base::BindOnce(&RetroactivePairingDetectorImpl::OnCheckOptInStatus,
                        weak_ptr_factory_.GetWeakPtr(), model_id, ble_address,
diff --git a/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc b/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc
index 0d9153b..543ace1d 100644
--- a/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc
+++ b/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc
@@ -438,7 +438,16 @@
 TEST_F(RetroactivePairingDetectorTest, MessageStream_Ble_ModelId_FlagEnabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
+
+  // Strict interpretation of opt-in status while opted in means that we
+  // expect to be notified of retroactive pairing when the MessageStream
+  // connects after pairing is completed. This test is for the scenario
+  // when we receive both the model id and the BLE address bytes over the
+  // Message Stream.
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
   base::RunLoop().RunUntilIdle();
@@ -466,7 +475,90 @@
 TEST_F(RetroactivePairingDetectorTest, MessageStream_Ble_ModelId_FlagDisabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+
+  // Without the SavedDevices or StrictOptIn flags, we expect that opt-in
+  // status being opted out does not matter and should not impact whether or
+  // not we detect a retroactive pairing scenario. We expect to still receive
+  // the model id and BLE bytes once the Message Stream connects, and be
+  // notified of retroactive pairing found.
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  SetMessageStream(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_fast_pair_handshake_->InvokeCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(retroactive_pair_found_);
+  EXPECT_EQ(retroactive_device_->ble_address, kBleAddress);
+  EXPECT_EQ(retroactive_device_->metadata_id, kModelId);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       MessageStream_Ble_ModelId_StrictFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+
+  // With the SavedDevices flag but without the StrictOptIn flag, we expect that
+  // opt-in status being opted out does not matter and should not impact whether
+  // or not we detect a retroactive pairing scenario. We expect to still receive
+  // the model id and BLE bytes once the Message Stream connects, and be
+  // notified of retroactive pairing found.
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  SetMessageStream(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_fast_pair_handshake_->InvokeCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(retroactive_pair_found_);
+  EXPECT_EQ(retroactive_device_->ble_address, kBleAddress);
+  EXPECT_EQ(retroactive_device_->metadata_id, kModelId);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       MessageStream_Ble_ModelId_SavedFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{features::kFastPairSavedDevices});
+
+  // With the StrictOptIn flag but without the SavedDevices flag, we expect that
+  // opt-in status being opted out does not matter and should not impact whether
+  // or not we detect a retroactive pairing scenario. We expect to still receive
+  // the model id and BLE bytes once the Message Stream connects, and be
+  // notified of retroactive pairing found.
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
   base::RunLoop().RunUntilIdle();
@@ -539,7 +631,17 @@
        MessageStream_GetMessageStream_Ble_ModelId_FlagEnabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
+
+  // Strict interpretation of opt-in status while opted in means that we
+  // expect to be notified of retroactive pairing when the Message Stream
+  // connects after pairing is completed. This test is for the scenario
+  // when we receive both the model id and the BLE address bytes over the
+  // Message Stream. The case where we are not notified when opted out is
+  // tested in Notify_OptedOut_* tests below.
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
   base::RunLoop().RunUntilIdle();
@@ -564,7 +666,76 @@
        MessageStream_GetMessageStream_Ble_ModelId_FlagDisabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+
+  // With SavedDevices and StrictOptIn flags disabled, the user's opt-in status
+  // of opted out should not impact the notification of a retroactive pairing
+  // found.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  AddMessageStream(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+  fake_fast_pair_handshake_->InvokeCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(retroactive_pair_found_);
+  EXPECT_EQ(retroactive_device_->ble_address, kBleAddress);
+  EXPECT_EQ(retroactive_device_->metadata_id, kModelId);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       MessageStream_GetMessageStream_Ble_ModelId_StrictFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+
+  // With only one flag enabled and the other disabled, the user's opt-in status
+  // of opted out should not impact the notification of a retroactive pairing
+  // found.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  AddMessageStream(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+  fake_fast_pair_handshake_->InvokeCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(retroactive_pair_found_);
+  EXPECT_EQ(retroactive_device_->ble_address, kBleAddress);
+  EXPECT_EQ(retroactive_device_->metadata_id, kModelId);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       MessageStream_GetMessageStream_Ble_ModelId_SavedFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+
+  // With only one flag enabled and the other disabled, the user's opt-in status
+  // of opted out should not impact the notification of a retroactive pairing
+  // found.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{features::kFastPairSavedDevices});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
   base::RunLoop().RunUntilIdle();
@@ -589,7 +760,10 @@
        EnableScenarioIfLoggedInLater_FlagEnabled) {
   Login(user_manager::UserType::USER_TYPE_GUEST);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
   EXPECT_FALSE(retroactive_pair_found_);
@@ -615,7 +789,66 @@
        EnableScenarioIfLoggedInLater_FlagDisabled) {
   Login(user_manager::UserType::USER_TYPE_GUEST);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  CreateRetroactivePairingDetector();
+  base::RunLoop().RunUntilIdle();
+
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  AddMessageStream(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+  fake_fast_pair_handshake_->InvokeCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(retroactive_pair_found_);
+  EXPECT_EQ(retroactive_device_->ble_address, kBleAddress);
+  EXPECT_EQ(retroactive_device_->metadata_id, kModelId);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       EnableScenarioIfLoggedInLater_StrictFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_GUEST);
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  CreateRetroactivePairingDetector();
+  base::RunLoop().RunUntilIdle();
+
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  AddMessageStream(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+  fake_fast_pair_handshake_->InvokeCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(retroactive_pair_found_);
+  EXPECT_EQ(retroactive_device_->ble_address, kBleAddress);
+  EXPECT_EQ(retroactive_device_->metadata_id, kModelId);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       EnableScenarioIfLoggedInLater_SavedFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_GUEST);
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{features::kFastPairSavedDevices});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
   EXPECT_FALSE(retroactive_pair_found_);
@@ -672,7 +905,17 @@
        MessageStream_GetMessageStream_ModelId_FlagEnabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
+
+  // With both SavedDevices and StrictOptIn enabled, we expect to be notified
+  // when the Message Stream is connected before we are notified that the
+  // pairing is complete, and retrieving the model id and BLE address
+  // after the fact by parsing the previous messages received if the user
+  // is opted in to saving devices to their account. The case where we are not
+  // notified when opted out is tested in Notify_OptedOut_* tests below.
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
   base::RunLoop().RunUntilIdle();
@@ -693,7 +936,74 @@
        MessageStream_GetMessageStream_ModelId_FlagDisabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+
+  // With both SavedDevices and StrictOptIn disabled, we expect to be notified
+  // when the Message Stream is connected before we are notified that the
+  // pairing is complete, and retrieving the model id and BLE address
+  // after the fact by parsing the previous messages received even if the
+  // user is opted out of saving devices to their account.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  AddMessageStream(kModelIdBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       MessageStream_GetMessageStream_ModelId_StrictFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+
+  // With the SavedDevices flag enabled but the StrictOptIn disabled, we
+  // expect to not consider the user's status of opted out and still be
+  // notified when a MessageStream is connected before we are notified of
+  // pairing, and successfully parse a model id and BLE address to confirm
+  // retroactive pairing has been found.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  AddMessageStream(kModelIdBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       MessageStream_GetMessageStream_ModelId_SavedFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+
+  // With the StrictOptIn flag enabled but the SavedDevices disabled, we
+  // expect to not consider the user's status of opted out and still be
+  // notified when a MessageStream is connected before we are notified of
+  // pairing, and successfully parse a model id and BLE address to confirm
+  // retroactive pairing has been found.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{features::kFastPairSavedDevices});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
   base::RunLoop().RunUntilIdle();
@@ -714,7 +1024,14 @@
        MessageStream_Observer_Ble_ModelId_FlagEnabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
+
+  // This test is for the scenario where the Message Stream receives messages
+  // for the BLE address and model id after it is connected and paired, and
+  // the detector should observe these messages and notify us of the device.
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
   base::RunLoop().RunUntilIdle();
@@ -767,7 +1084,14 @@
        MessageStream_Observer_ModelId_FlagEnabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+
+  // With the SavedDevices and StrictOptIn flags enabled, we do not expect
+  // to be notified if we only receive the model id (no BLE address) even if
+  // the user is opted in.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
   base::RunLoop().RunUntilIdle();
@@ -793,7 +1117,87 @@
        MessageStream_Observer_ModelId_FlagDisabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+
+  // With the SavedDevices and StrictOptIn flags disabled, we do not expect
+  // to be notified if we only receive the model id (no BLE address) even if
+  // the user is opted out. Opt-in status shouldn't matter for notifying with
+  // the flags disabled, but here, since we don't have the information we need
+  // from the device for retroactive pairing, then we expect to not be
+  // notified.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       MessageStream_Observer_ModelId_StrictFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+
+  // With only the StrictOptIn flag disabled, we do not expect
+  // to be notified if we only receive the model id (no BLE address) even if
+  // the user is opted out. Opt-in status shouldn't matter for notifying with
+  // the flags disabled, but here, since we don't have the information we need
+  // from the device for retroactive pairing, then we expect to not be
+  // notified.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       MessageStream_Observer_ModelId_SavedFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+
+  // With only the SavedDevices flag disabled, we do not expect
+  // to be notified if we only receive the model id (no BLE address) even if
+  // the user is opted out. Opt-in status shouldn't matter for notifying with
+  // the flags disabled, but here, since we don't have the information we need
+  // from the device for retroactive pairing, then we expect to not be
+  // notified.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{features::kFastPairSavedDevices});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
   base::RunLoop().RunUntilIdle();
@@ -821,7 +1225,14 @@
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+
+  // This test is verifying that we are notified when the MessageStream
+  // is destroyed, and properly remove the MessageStream. The opt-in status
+  // and flags set should not impact this behavior.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
   base::RunLoop().RunUntilIdle();
   CreateRetroactivePairingDetector();
 
@@ -847,7 +1258,78 @@
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+
+  // This test is verifying that we are notified when the MessageStream
+  // is destroyed, and properly remove the MessageStream. The opt-in status
+  // and flags set should not impact this behavior.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  SetMessageStream(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  message_stream_.reset();
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       MessageStreamRemovedOnDestroyed_StrictFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::test::ScopedFeatureList feature_list;
+
+  // This test is verifying that we are notified when the MessageStream
+  // is destroyed, and properly remove the MessageStream. The opt-in status
+  // and flags set should not impact this behavior.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  SetMessageStream(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  message_stream_.reset();
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       MessageStreamRemovedOnDestroyed_SavedFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::test::ScopedFeatureList feature_list;
+
+  // This test is verifying that we are notified when the Message Stream
+  // is destroyed, and properly remove the Message Stream. The opt-in status
+  // and flags set should not impact this behavior.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{features::kFastPairSavedDevices});
   base::RunLoop().RunUntilIdle();
   CreateRetroactivePairingDetector();
 
@@ -871,7 +1353,14 @@
        MessageStreamRemovedOnDisconnect_FlagEnabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+
+  // This test is verifying that we are notified when the Message Stream
+  // is destroyed, and properly remove the Message Stream. The opt-in status
+  // and flags set should not impact this behavior.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
   base::RunLoop().RunUntilIdle();
@@ -904,7 +1393,92 @@
        MessageStreamRemovedOnDisconnect_FlagDisabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+
+  // This test is verifying that we are notified when the Message Stream
+  // is destroyed, and properly remove the Message Stream. The opt-in status
+  // and flags set should not impact this behavior.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  fake_socket_->SetErrorReason(
+      device::BluetoothSocket::ErrorReason::kDisconnected);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  message_stream_ =
+      std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       MessageStreamRemovedOnDisconnect_StrictFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+
+  // This test is verifying that we are notified when the Message Stream
+  // is destroyed, and properly remove the Message Stream. The opt-in status
+  // and flags set should not impact this behavior.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  fake_socket_->SetErrorReason(
+      device::BluetoothSocket::ErrorReason::kDisconnected);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  message_stream_ =
+      std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest,
+       MessageStreamRemovedOnDisconnect_SavedFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+
+  // This test is verifying that we are notified when the Message Stream
+  // is destroyed, and properly remove the Message Stream. The opt-in status
+  // and flags set should not impact this behavior.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{features::kFastPairSavedDevices});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
   base::RunLoop().RunUntilIdle();
@@ -936,7 +1510,14 @@
 TEST_F(RetroactivePairingDetectorTest, DontNotify_OptedOut_FlagEnabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+
+  // If the SavedDevices and StrictOptIn flags are enabled and the user is
+  // opted out, we expect not to be notified for retroactive pairing even if
+  // a potential one is found.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
   base::RunLoop().RunUntilIdle();
@@ -961,7 +1542,81 @@
 TEST_F(RetroactivePairingDetectorTest, Notify_OptedOut_FlagDisabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+
+  // If the SavedDevices and StrictOptIn flags are disabled, we expect to be
+  // notified when a retroactive pairing is found even if the user is opted out.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  fake_fast_pair_handshake_->InvokeCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest, Notify_OptedOut_StrictFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+
+  // If the SavedDevices flag is enabled but the StrictOptin flag is disabled,
+  // then we expect to be notified even if the user is opted out of saving
+  // devices to their account.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  fake_fast_pair_handshake_->InvokeCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest, Notify_OptedOut_SavedFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+
+  // If the SavedDevices flag is disabled but the StrictOptin flag is enabled,
+  // then we expect to be notified even if the user is opted out of saving
+  // devices to their account.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{features::kFastPairSavedDevices});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
   base::RunLoop().RunUntilIdle();
@@ -989,7 +1644,76 @@
 TEST_F(RetroactivePairingDetectorTest, Notify_OptedIn_FlagDisabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  fake_fast_pair_handshake_->InvokeCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest, Notify_OptedIn_StrictFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+
+  // When the strict interpretation is disabled, we expect to be notified about
+  // a retroactive pairing regardless of opt-in status.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
+  base::RunLoop().RunUntilIdle();
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  fake_socket_->SetIOBufferFromBytes(kModelIdBleAddressBytes);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  fake_fast_pair_handshake_->InvokeCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(retroactive_pair_found_);
+}
+
+TEST_F(RetroactivePairingDetectorTest, Notify_OptedIn_SavedFlagDisabled) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+
+  // When only the SavedDevices flag is disabled, we expect to be notified about
+  // a retroactive pairing regardless of opt-in status.
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{features::kFastPairSavedDevices});
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
   base::RunLoop().RunUntilIdle();
@@ -1018,7 +1742,12 @@
        DontNotify_OptedOut_OptedIn_FlagEnabled) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
+
+  // Simulate user is opted out.
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
   base::RunLoop().RunUntilIdle();
@@ -1039,6 +1768,9 @@
 
   EXPECT_FALSE(retroactive_pair_found_);
 
+  // Simulate user is opted in. Now we would expect to be notified of a
+  // retroactive pairing scenario when the flags are enabled for a
+  // strict interpretation of the opt in status.
   fast_pair_repository_.SetOptInStatus(
       nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
 
diff --git a/ash/quick_pair/ui/fast_pair/fast_pair_presenter_impl.cc b/ash/quick_pair/ui/fast_pair/fast_pair_presenter_impl.cc
index 4cd6c869..287ab81 100644
--- a/ash/quick_pair/ui/fast_pair/fast_pair_presenter_impl.cc
+++ b/ash/quick_pair/ui/fast_pair/fast_pair_presenter_impl.cc
@@ -138,9 +138,10 @@
   // Check if the user is opted in to saving devices to their account. If the
   // user is not opted in, we will show the guest notification which does not
   // mention saving devices to the user account. This is flagged depending if
-  // the Fast Pair Saved Devices is enabled.
-  if (features::IsFastPairSavedDevicesEnabled()) {
-    QP_LOG(INFO) << __func__ << ": Saved Devices Flag enabled";
+  // the Fast Pair Saved Devices is enabled and we are using a strict
+  // interpretation of the opt-in status.
+  if (features::IsFastPairSavedDevicesEnabled() &&
+      features::IsFastPairSavedDevicesStrictOptInEnabled()) {
     FastPairRepository::Get()->CheckOptInStatus(
         base::BindOnce(&FastPairPresenterImpl::OnCheckOptInStatus,
                        weak_pointer_factory_.GetWeakPtr(), device,
diff --git a/ash/quick_pair/ui/fast_pair/fast_pair_presenter_impl_unittest.cc b/ash/quick_pair/ui/fast_pair/fast_pair_presenter_impl_unittest.cc
index fb711a4..b6ba4a6 100644
--- a/ash/quick_pair/ui/fast_pair/fast_pair_presenter_impl_unittest.cc
+++ b/ash/quick_pair/ui/fast_pair/fast_pair_presenter_impl_unittest.cc
@@ -190,7 +190,10 @@
 
 TEST_F(FastPairPresenterImplTest, ShowDiscovery_User_OptedIn_FlagEnabled) {
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
   EXPECT_FALSE(test_message_center_.FindVisibleNotificationById(
       kFastPairDiscoveryUserNotificationId));
 
@@ -212,7 +215,35 @@
 
 TEST_F(FastPairPresenterImplTest, ShowDiscovery_User_OptedIn_FlagDisabled) {
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+  EXPECT_FALSE(test_message_center_.FindVisibleNotificationById(
+      kFastPairDiscoveryUserNotificationId));
+
+  ON_CALL(*browser_delegate_, GetIdentityManager())
+      .WillByDefault(testing::Return(identity_manager_));
+
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  repository_->SetOptInStatus(nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
+  base::RunLoop().RunUntilIdle();
+  fast_pair_presenter_->ShowDiscovery(
+      device_,
+      base::BindRepeating(&FastPairPresenterImplTest::OnDiscoveryAction,
+                          weak_pointer_factory_.GetWeakPtr(), device_));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(test_message_center_.FindVisibleNotificationById(
+      kFastPairDiscoveryUserNotificationId));
+}
+
+TEST_F(FastPairPresenterImplTest,
+       ShowDiscovery_User_OptedIn_StrictFlagDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
   EXPECT_FALSE(test_message_center_.FindVisibleNotificationById(
       kFastPairDiscoveryUserNotificationId));
 
@@ -234,7 +265,10 @@
 
 TEST_F(FastPairPresenterImplTest, ShowDiscovery_User_OptedOut_FlagEnabled) {
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
   EXPECT_FALSE(test_message_center_.FindVisibleNotificationById(
       kFastPairDiscoveryGuestNotificationId));
 
@@ -256,7 +290,35 @@
 
 TEST_F(FastPairPresenterImplTest, ShowDiscovery_User_OptedOut_FlagDisabled) {
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+  EXPECT_FALSE(test_message_center_.FindVisibleNotificationById(
+      kFastPairDiscoveryUserNotificationId));
+
+  ON_CALL(*browser_delegate_, GetIdentityManager())
+      .WillByDefault(testing::Return(identity_manager_));
+
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  repository_->SetOptInStatus(nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
+  base::RunLoop().RunUntilIdle();
+  fast_pair_presenter_->ShowDiscovery(
+      device_,
+      base::BindRepeating(&FastPairPresenterImplTest::OnDiscoveryAction,
+                          weak_pointer_factory_.GetWeakPtr(), device_));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(test_message_center_.FindVisibleNotificationById(
+      kFastPairDiscoveryUserNotificationId));
+}
+
+TEST_F(FastPairPresenterImplTest,
+       ShowDiscovery_User_OptedOut_StrictFlagDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
   EXPECT_FALSE(test_message_center_.FindVisibleNotificationById(
       kFastPairDiscoveryUserNotificationId));
 
@@ -297,7 +359,10 @@
 
 TEST_F(FastPairPresenterImplTest, RemoveNotifications) {
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
   ON_CALL(*browser_delegate_, GetIdentityManager())
       .WillByDefault(testing::Return(identity_manager_));
   Login(user_manager::UserType::USER_TYPE_REGULAR);
@@ -327,7 +392,36 @@
 
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+  repository_->SetOptInStatus(nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  fast_pair_presenter_->ShowDiscovery(
+      device_,
+      base::BindRepeating(&FastPairPresenterImplTest::OnDiscoveryAction,
+                          weak_pointer_factory_.GetWeakPtr(), device_));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(test_message_center_.FindVisibleNotificationById(
+      kFastPairDiscoveryUserNotificationId));
+}
+
+TEST_F(FastPairPresenterImplTest,
+       ShowDiscovery_NoDeviceMetadata_StrictFlagDisabled) {
+  EXPECT_FALSE(test_message_center_.FindVisibleNotificationById(
+      kFastPairDiscoveryUserNotificationId));
+
+  ON_CALL(*browser_delegate_, GetIdentityManager())
+      .WillByDefault(testing::Return(identity_manager_));
+  repository_->ClearFakeMetadata(kValidModelId);
+
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
   repository_->SetOptInStatus(nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
   base::RunLoop().RunUntilIdle();
   fast_pair_presenter_->ShowDiscovery(
@@ -350,7 +444,10 @@
 
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
   repository_->SetOptInStatus(nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
   base::RunLoop().RunUntilIdle();
   fast_pair_presenter_->ShowDiscovery(
@@ -378,7 +475,10 @@
 
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
   repository_->SetOptInStatus(nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
   base::RunLoop().RunUntilIdle();
   fast_pair_presenter_->ShowDiscovery(
@@ -406,7 +506,40 @@
 
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+  repository_->SetOptInStatus(nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  fast_pair_presenter_->ShowDiscovery(
+      device_,
+      base::BindRepeating(&FastPairPresenterImplTest::OnDiscoveryAction,
+                          weak_pointer_factory_.GetWeakPtr(), device_));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(test_message_center_.FindVisibleNotificationById(
+      kFastPairDiscoveryUserNotificationId));
+}
+
+TEST_F(FastPairPresenterImplTest, ShowDiscovery_V1Device_StrictFlagDisabled) {
+  EXPECT_FALSE(test_message_center_.FindVisibleNotificationById(
+      kFastPairDiscoveryUserNotificationId));
+
+  ON_CALL(*browser_delegate_, GetIdentityManager())
+      .WillByDefault(testing::Return(identity_manager_));
+  repository_->ClearFakeMetadata(kValidModelId);
+
+  nearby::fastpair::Device metadata;
+  repository_->SetFakeMetadata(kValidModelId, metadata);
+  device_ = base::MakeRefCounted<Device>(kValidModelId, kTestAddress,
+                                         Protocol::kFastPairInitial);
+
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
   repository_->SetOptInStatus(nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
   base::RunLoop().RunUntilIdle();
   fast_pair_presenter_->ShowDiscovery(
@@ -424,7 +557,10 @@
   EXPECT_FALSE(test_message_center_.FindVisibleNotificationById(
       kFastPairDiscoveryGuestNotificationId));
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, true);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices,
+                            features::kFastPairSavedDevicesStrictOptIn},
+      /*disabled_features=*/{});
 
   ON_CALL(*browser_delegate_, GetIdentityManager())
       .WillByDefault(testing::Return(nullptr));
@@ -447,7 +583,35 @@
   EXPECT_FALSE(test_message_center_.FindVisibleNotificationById(
       kFastPairDiscoveryGuestNotificationId));
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
+
+  ON_CALL(*browser_delegate_, GetIdentityManager())
+      .WillByDefault(testing::Return(nullptr));
+
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  repository_->SetOptInStatus(nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::RunLoop().RunUntilIdle();
+  fast_pair_presenter_->ShowDiscovery(
+      device_,
+      base::BindRepeating(&FastPairPresenterImplTest::OnDiscoveryAction,
+                          weak_pointer_factory_.GetWeakPtr(), device_));
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(test_message_center_.FindVisibleNotificationById(
+      kFastPairDiscoveryGuestNotificationId));
+}
+
+TEST_F(FastPairPresenterImplTest,
+       ShowDiscovery_Regular_NoIdentityManager_StrictFlagDisabled) {
+  EXPECT_FALSE(test_message_center_.FindVisibleNotificationById(
+      kFastPairDiscoveryGuestNotificationId));
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
 
   ON_CALL(*browser_delegate_, GetIdentityManager())
       .WillByDefault(testing::Return(nullptr));
@@ -470,7 +634,10 @@
       .WillByDefault(testing::Return(identity_manager_));
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
   base::RunLoop().RunUntilIdle();
   fast_pair_presenter_->ShowDiscovery(
       device_,
@@ -492,7 +659,10 @@
       .WillByDefault(testing::Return(identity_manager_));
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
   base::RunLoop().RunUntilIdle();
   fast_pair_presenter_->ShowDiscovery(
       device_,
@@ -514,7 +684,10 @@
       .WillByDefault(testing::Return(identity_manager_));
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
   base::RunLoop().RunUntilIdle();
   fast_pair_presenter_->ShowDiscovery(
       device_,
@@ -536,7 +709,10 @@
       .WillByDefault(testing::Return(identity_manager_));
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
   base::RunLoop().RunUntilIdle();
   fast_pair_presenter_->ShowDiscovery(
       device_,
@@ -581,7 +757,10 @@
 
   Login(user_manager::UserType::USER_TYPE_KIOSK_APP);
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatureState(features::kFastPairSavedDevices, false);
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{},
+      /*disabled_features=*/{features::kFastPairSavedDevices,
+                             features::kFastPairSavedDevicesStrictOptIn});
   repository_->SetOptInStatus(nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
   base::RunLoop().RunUntilIdle();
   fast_pair_presenter_->ShowDiscovery(
diff --git a/ash/rgb_keyboard/rgb_keyboard_manager.cc b/ash/rgb_keyboard/rgb_keyboard_manager.cc
index 41b57e9..a5ae665 100644
--- a/ash/rgb_keyboard/rgb_keyboard_manager.cc
+++ b/ash/rgb_keyboard/rgb_keyboard_manager.cc
@@ -37,6 +37,7 @@
 }
 
 RgbKeyboardManager::~RgbKeyboardManager() {
+  RgbkbdClient::Get()->RemoveObserver(this);
   ime_controller_ptr_->RemoveObserver(this);
 
   DCHECK_EQ(g_instance, this);
@@ -109,10 +110,6 @@
   capabilities_ = capability;
 }
 
-void RgbKeyboardManager::OnShutdown() {
-  RgbkbdClient::Get()->RemoveObserver(this);
-}
-
 void RgbKeyboardManager::OnGetRgbKeyboardCapabilities(
     absl::optional<rgbkbd::RgbKeyboardCapabilities> reply) {
   if (!reply.has_value()) {
diff --git a/ash/rgb_keyboard/rgb_keyboard_manager.h b/ash/rgb_keyboard/rgb_keyboard_manager.h
index 4105c52..fd16e384 100644
--- a/ash/rgb_keyboard/rgb_keyboard_manager.h
+++ b/ash/rgb_keyboard/rgb_keyboard_manager.h
@@ -48,7 +48,6 @@
   // RgbkbdClient::Observer:
   void OnCapabilityUpdatedForTesting(
       rgbkbd::RgbKeyboardCapabilities capability) override;
-  void OnShutdown() override;
 
   void FetchRgbKeyboardSupport();
 
diff --git a/ash/rgb_keyboard/rgb_keyboard_manager_unittest.cc b/ash/rgb_keyboard/rgb_keyboard_manager_unittest.cc
index f3598de6..a96ac57 100644
--- a/ash/rgb_keyboard/rgb_keyboard_manager_unittest.cc
+++ b/ash/rgb_keyboard/rgb_keyboard_manager_unittest.cc
@@ -40,8 +40,10 @@
   RgbKeyboardManagerTest(const RgbKeyboardManagerTest&) = delete;
   RgbKeyboardManagerTest& operator=(const RgbKeyboardManagerTest&) = delete;
   ~RgbKeyboardManagerTest() override {
-    // Destroy the global instance.
+    // Ordering for deletion is Manger -> Client -> IME Controller
+    manager_.reset();
     RgbkbdClient::Shutdown();
+    ime_controller_.reset();
   };
 
  protected:
diff --git a/ash/system/network/network_list_view_controller_impl.cc b/ash/system/network/network_list_view_controller_impl.cc
index acf407a..8ae9212 100644
--- a/ash/system/network/network_list_view_controller_impl.cc
+++ b/ash/system/network/network_list_view_controller_impl.cc
@@ -102,6 +102,14 @@
   return false;
 }
 
+bool IsCellularSimLocked() {
+  const DeviceStateProperties* cellular_device =
+      Shell::Get()->system_tray_model()->network_state_model()->GetDevice(
+          NetworkType::kCellular);
+  return cellular_device &&
+         !cellular_device->sim_lock_status->lock_type.empty();
+}
+
 }  // namespace
 
 NetworkListViewControllerImpl::NetworkListViewControllerImpl(
@@ -426,10 +434,22 @@
       return;
     }
 
-    const bool toggle_enabled =
-        !is_secondary_user && (cellular_state == DeviceStateType::kEnabled ||
-                               cellular_state == DeviceStateType::kDisabled);
     const bool cellular_enabled = cellular_state == DeviceStateType::kEnabled;
+
+    // The toggle will never be enabled for secondary users.
+    bool toggle_enabled = !is_secondary_user;
+
+    // The toggle will never be enabled during cellular state transitions.
+    toggle_enabled &=
+        cellular_enabled || cellular_state == DeviceStateType::kDisabled;
+
+    // The toggle will never be enabled if the device is SIM locked and we
+    // cannot open the Settings UI.
+    toggle_enabled &=
+        cellular_enabled ||
+        Shell::Get()->session_controller()->ShouldEnableSettings() ||
+        !IsCellularSimLocked();
+
     mobile_header_view_->SetToggleVisibility(/*visibility=*/true);
     mobile_header_view_->SetToggleState(/*enabled=*/toggle_enabled,
                                         /*is_on=*/cellular_enabled);
diff --git a/ash/system/network/network_list_view_controller_unittest.cc b/ash/system/network/network_list_view_controller_unittest.cc
index 1d89354..d6b4095 100644
--- a/ash/system/network/network_list_view_controller_unittest.cc
+++ b/ash/system/network/network_list_view_controller_unittest.cc
@@ -367,6 +367,20 @@
     base::RunLoop().RunUntilIdle();
   }
 
+  void SetCellularSimLockStatus(const std::string& lock_type, bool sim_locked) {
+    base::Value sim_lock_status(base::Value::Type::DICTIONARY);
+    sim_lock_status.SetKey(shill::kSIMLockEnabledProperty,
+                           base::Value(sim_locked));
+    sim_lock_status.SetKey(shill::kSIMLockTypeProperty, base::Value(lock_type));
+    sim_lock_status.SetKey(shill::kSIMLockRetriesLeftProperty, base::Value(3));
+    network_state_helper()->device_test()->SetDeviceProperty(
+        kCellularDevicePath, shill::kSIMLockStatusProperty,
+        std::move(sim_lock_status),
+        /*notify_changed=*/true);
+
+    base::RunLoop().RunUntilIdle();
+  }
+
   // Adds a Tether network state, adds a Wifi network to be used as the Wifi
   // hotspot, and associates the two networks.
   void AddTetherNetworkState() {
@@ -853,6 +867,16 @@
   EXPECT_TRUE(GetMobileSubHeader()->is_toggle_enabled());
   EXPECT_FALSE(GetMobileSubHeader()->is_toggle_on());
   EXPECT_TRUE(GetMobileToggleButton()->GetVisible());
+
+  // The toggle is not enabled, the cellular device SIM is locked, and user
+  // cannot open the settings page.
+  GetSessionControllerClient()->SetSessionState(
+      session_manager::SessionState::LOGIN_SECONDARY);
+  SetCellularSimLockStatus(shill::kSIMLockPin, /*sim_locked=*/true);
+
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(GetMobileSubHeader()->is_toggle_enabled());
 }
 
 TEST_F(NetworkListViewControllerTest, HasCorrectTetherStatusMessage) {
diff --git a/ash/webui/common/backend/plural_string_handler.cc b/ash/webui/common/backend/plural_string_handler.cc
index c56934b..167be83 100644
--- a/ash/webui/common/backend/plural_string_handler.cc
+++ b/ash/webui/common/backend/plural_string_handler.cc
@@ -35,7 +35,11 @@
   const std::string callback = args[0].GetString();
   const std::string name = args[1].GetString();
   const int count = args[2].GetInt();
-  DCHECK(base::Contains(string_id_map_, name));
+  if (string_id_map_.find(name) == string_id_map_.end()) {
+    // Only reachable if the WebUI renderer is misbehaving.
+    LOG(ERROR) << "Invalid string ID received: " << name;
+    return;
+  }
   const std::u16string localized_string =
       l10n_util::GetPluralStringFUTF16(string_id_map_.at(name), count);
   ResolveJavascriptCallback(base::Value(callback),
diff --git a/ash/webui/common/backend/plural_string_handler_unittest.cc b/ash/webui/common/backend/plural_string_handler_unittest.cc
index c660aca..a086428 100644
--- a/ash/webui/common/backend/plural_string_handler_unittest.cc
+++ b/ash/webui/common/backend/plural_string_handler_unittest.cc
@@ -83,4 +83,15 @@
   EXPECT_EQ("Edit file", call_data.arg3()->GetString());
 }
 
+TEST_F(PluralStringHandlerTest, InvalidPluralStringRequest) {
+  base::ListValue args;
+  args.Append(kHandlerFunctionName);
+  args.Append(/*name=*/"invalidKey");
+  args.Append(/*count=*/2);
+  web_ui_.HandleReceivedMessage("getPluralString", &args);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(0u, web_ui_.call_data().size());
+}
+
 }  // namespace ash
diff --git a/ash/webui/connectivity_diagnostics/connectivity_diagnostics_ui.cc b/ash/webui/connectivity_diagnostics/connectivity_diagnostics_ui.cc
index 3810b3e..354dc05e 100644
--- a/ash/webui/connectivity_diagnostics/connectivity_diagnostics_ui.cc
+++ b/ash/webui/connectivity_diagnostics/connectivity_diagnostics_ui.cc
@@ -51,13 +51,13 @@
   ~ConnectivityDiagnosticsMessageHandler() override = default;
 
   void RegisterMessages() override {
-    web_ui()->RegisterDeprecatedMessageCallback(
+    web_ui()->RegisterMessageCallback(
         "sendFeedbackReport",
         base::BindRepeating(
             &ConnectivityDiagnosticsMessageHandler::SendFeedbackReportRequest,
             base::Unretained(this)));
 
-    web_ui()->RegisterDeprecatedMessageCallback(
+    web_ui()->RegisterMessageCallback(
         "getShowFeedbackButton",
         base::BindRepeating(
             &ConnectivityDiagnosticsMessageHandler::GetShowFeedbackButton,
@@ -65,23 +65,23 @@
   }
 
  private:
-  void SendFeedbackReportRequest(const base::ListValue* value) {
+  void SendFeedbackReportRequest(const base::Value::List& value) {
     send_feedback_report_callback_.Run(/*extra_diagnostics*/ "");
   }
 
   // TODO(crbug/1220965): Remove conditional feedback button when WebUI feedback
   // is launched.
-  void GetShowFeedbackButton(const base::ListValue* value) {
-    auto args = value->GetListDeprecated();
+  void GetShowFeedbackButton(const base::Value::List& args) {
     if (args.size() < 1 || !args[0].is_string())
       return;
 
     auto callback_id = args[0].GetString();
-    base::Value response(base::Value::Type::LIST);
+    base::Value::List response;
     response.Append(base::Value(show_feedback_button_));
 
     AllowJavascript();
-    ResolveJavascriptCallback(base::Value(callback_id), response);
+    ResolveJavascriptCallback(base::Value(callback_id),
+                              base::Value(std::move(response)));
   }
 
   ConnectivityDiagnosticsUI::SendFeedbackReportCallback
diff --git a/ash/webui/os_feedback_ui/resources/share_data_page.html b/ash/webui/os_feedback_ui/resources/share_data_page.html
index 034c9c4..1cd1a485 100644
--- a/ash/webui/os_feedback_ui/resources/share_data_page.html
+++ b/ash/webui/os_feedback_ui/resources/share_data_page.html
@@ -42,19 +42,37 @@
     flex: 1;
   }
 
+  #screenshotContainer > button {
+    cursor: pointer;
+  }
+
   #screenshotImage {
     display: block;
     height: auto;
-    margin: 0 0 0 auto;
     transition: all 250ms ease;
     width: 68px;
   }
 
   #screenshotImage:hover {
-    margin: 170px 0 0 150px;
-    max-width: 600px;
-    transform: scale(8);
-    z-index: 1;
+    opacity: 0.7;
+  }
+
+  dialog {
+    bottom: auto;
+    top: auto;
+  }
+
+  cr-dialog::part(dialog) {
+    --cr-dialog-body-padding-horizontal: 0px;
+    --cr-dialog-title-slot-padding-start: 0px;
+    --cr-icon-button-margin-end: 20px;
+    width: 590px;
+  }
+
+  .image-preview {
+    height: auto;
+    max-height: 600px;
+    width: 580px;
   }
 
   #addFileContainer {
@@ -93,8 +111,10 @@
               aria-labelledby="screenshotCheckLabel"
               disabled="[[!hasScreenshot_(screenshotUrl)]]">
           <label id="screenshotCheckLabel">[[i18n('attachScreenshotLabel')]]</label>
-          <img id="screenshotImage" aria-label="$i18n{screenshotA11y}"
+          <button id="imageButton" on-click="handleScreenshotClick_">
+            <img id="screenshotImage" aria-label="$i18n{screenshotA11y}"
               src="[[screenshotUrl]]">
+           </button>
         </div>
         <!-- Attach a file -->
         <div id="addFileContainer" class="card-frame">
@@ -149,3 +169,15 @@
     </cr-button>
   </div>
 </div>
+<cr-dialog id="screenshotDialog">
+  <div id="modalDialogTitle" slot="title">
+    <cr-icon-button id="closeDialogButton" class="icon-arrow-back"
+        on-click="handleScreenshotDialogCloseClick_"
+        title="[[i18n('attachScreenshotLabel')]]">
+    </cr-icon-button>
+    <span>[[i18n('attachScreenshotLabel')]]</span>
+  </div>
+  <div slot="body">
+    <img src="[[screenshotUrl]]" class="image-preview">
+  </div>
+</cr-dialog>
diff --git a/ash/webui/os_feedback_ui/resources/share_data_page.js b/ash/webui/os_feedback_ui/resources/share_data_page.js
index 5f3d077..68ebb31 100644
--- a/ash/webui/os_feedback_ui/resources/share_data_page.js
+++ b/ash/webui/os_feedback_ui/resources/share_data_page.js
@@ -5,6 +5,7 @@
 import './os_feedback_shared_css.js';
 import './file_attachment.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
+import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -97,6 +98,17 @@
   }
 
   /** @protected */
+  handleScreenshotClick_() {
+    this.$.screenshotDialog.showModal();
+    this.$.closeDialogButton.focus();
+  }
+
+  /** @protected */
+  handleScreenshotDialogCloseClick_() {
+    this.$.screenshotDialog.close();
+  }
+
+  /** @protected */
   handleUserEmailDropDownChanged_() {
     const email = this.$.userEmailDropDown.value;
     const consentCheckbox = this.$.userConsentCheckbox;
diff --git a/ash/webui/personalization_app/resources/BUILD.gn b/ash/webui/personalization_app/resources/BUILD.gn
index 8e6040ca..e4ff9644 100644
--- a/ash/webui/personalization_app/resources/BUILD.gn
+++ b/ash/webui/personalization_app/resources/BUILD.gn
@@ -134,8 +134,7 @@
 
 # Files that are passed as input to css_to_wrapper().
 css_files = [
-  "common/common_style.css",
-
+  "css/common.css",
   "css/cros_button_style.css",
   "css/wallpaper.css",
 ]
diff --git a/ash/webui/personalization_app/resources/common/common_style.css b/ash/webui/personalization_app/resources/css/common.css
similarity index 100%
rename from ash/webui/personalization_app/resources/common/common_style.css
rename to ash/webui/personalization_app/resources/css/common.css
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.html b/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.html
index 9d7b961..3d53618 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.html
@@ -1,4 +1,4 @@
-<style include="wallpaper common-style">
+<style include="wallpaper common">
   :host {
     overflow: hidden;
   }
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.ts
index afc7bfe5..f0bafb4 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/album_list_element.ts
@@ -7,7 +7,7 @@
  */
 
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 
 import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import {afterNextRender} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.html b/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.html
index 6968a40..d166d18 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.html
@@ -1,4 +1,4 @@
-<style include="cr-shared-style common-style">
+<style include="cr-shared-style common">
   :host {
     height: 100%;
   }
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts
index 2f251d3648..3d5638a6 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/albums_subpage_element.ts
@@ -12,7 +12,7 @@
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import './album_list_element.js';
 import './art_album_dialog_element.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.html b/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.html
index 27433c0e..ad4ce17e 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.html
@@ -1,4 +1,4 @@
-<style include="common-style cros-button-style">
+<style include="common cros-button-style">
   :host([main-page]) #container {
     border: 1px solid var(--cros-separator-color);
     border-radius: 16px;
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.ts
index f85a621..706c091 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_preview_element.ts
@@ -10,7 +10,7 @@
 import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
 import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import 'chrome://resources/polymer/v3_0/paper-tooltip/paper-tooltip.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 import '../../css/cros_button_style.css.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.html b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.html
index 2b2aa16..a3d1f8f2 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.html
@@ -1,4 +1,4 @@
-<style include="common-style">
+<style include="common">
   :host {
     flex-grow: 1;
   }
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts
index 7c27844..05f81ca 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_subpage_element.ts
@@ -7,7 +7,7 @@
  * the ambient mode settings.
  */
 
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 import './albums_subpage_element.js';
 import './ambient_weather_element.js';
 import './ambient_preview_element.js';
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.html b/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.html
index 4d4515712..a0420f9 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.html
@@ -1,4 +1,4 @@
-<style include="common-style">
+<style include="common">
   cr-radio-group {
     width: 100%;
   }
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.ts
index 1665ceb..e04988a 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/ambient_weather_element.ts
@@ -7,7 +7,7 @@
  * behaviors similar to a radio button group, e.g. single selection.
  */
 
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.html b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.html
index 70682ba..32c09d5 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.html
@@ -1,4 +1,4 @@
-<style include="common-style">
+<style include="common">
   :host {
     -webkit-tap-highlight-color: transparent;
     box-sizing: border-box;
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.ts
index 91366c1..18c6de8 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_item_element.ts
@@ -6,7 +6,7 @@
  * @fileoverview The element for displaying an animation theme.
  */
 
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.html b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.html
index 022990f..f4814596 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.html
@@ -1,4 +1,4 @@
-<style include="common-style">
+<style include="common">
   iron-list {
     height: 100%;
     /* 516px is the total width + padding for all 3 options */
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.ts
index 1d71e0e..3b9bdd29 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/animation_theme_list_element.ts
@@ -6,7 +6,7 @@
  * @fileoverview The element for displaying a list of animation themes.
  */
 import './animation_theme_item_element.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 
 import {AnimationTheme} from '../personalization_app.mojom-webui.js';
 import {WithPersonalizationStore} from '../personalization_store.js';
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.html b/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.html
index d6a78587..b34b72c 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.html
@@ -1,4 +1,4 @@
-<style include="common-style">
+<style include="common">
 </style>
 <div class="ambient-toggle-row-container">
   <div class="ambient-toggle-row">
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.ts
index d2c828d..f1a9b8e8 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/toggle_row_element.ts
@@ -6,7 +6,7 @@
  * @fileoverview This component displays a description text and a toggle button.
  */
 
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
 
 import {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.html b/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.html
index 2a66713..80f21e7 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.html
@@ -1,4 +1,4 @@
-<style include="common-style">
+<style include="common">
   topic-source-item {
     align-items: center;
     height: 64px;
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.ts
index 953e534..26c3090 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/topic_source_list_element.ts
@@ -7,7 +7,7 @@
  * behaviors similar to a radio button group, e.g. single selection.
  */
 
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 import './topic_source_item_element.js';
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.html b/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.html
index e874408..3b81676 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.html
+++ b/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.html
@@ -1,4 +1,4 @@
-<style include="wallpaper common-style">
+<style include="wallpaper common">
   :host {
     align-items: center;
     display: flex;
diff --git a/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.ts b/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.ts
index 345173e..f3d9a9e 100644
--- a/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/ambient/zero_state_element.ts
@@ -6,7 +6,7 @@
  * @fileoverview Polymer element that displays the Ambient zero state.
  */
 
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 import 'chrome://resources/polymer/v3_0/iron-media-query/iron-media-query.js';
 
 import {WithPersonalizationStore} from '../personalization_store.js';
diff --git a/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.html b/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.html
index 294bd5d7..7f067e3 100644
--- a/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.html
+++ b/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.html
@@ -1,4 +1,4 @@
-<style include="common-style cros-button-style">
+<style include="common cros-button-style">
   :host {
     --color-container-size: 48px;
   }
diff --git a/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.ts b/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.ts
index cca59e3..d94e189 100644
--- a/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/keyboard_backlight/keyboard_backlight_element.ts
@@ -5,7 +5,7 @@
 import 'chrome://resources/polymer/v3_0/iron-a11y-keys/iron-a11y-keys.js';
 import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
 import 'chrome://resources/polymer/v3_0/paper-ripple/paper-ripple.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 import '../../css/cros_button_style.css.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.html b/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.html
index 4b6b7e4..7d7abc4 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.html
+++ b/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.html
@@ -1,4 +1,4 @@
-<style include="cr-icons common-style cros-button-style">
+<style include="cr-icons common cros-button-style">
   #container {
     align-items: center;
     box-sizing: border-box;
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.ts b/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.ts
index 77d9681..c7f6960 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_breadcrumb_element.ts
@@ -15,7 +15,7 @@
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/polymer/v3_0/iron-a11y-keys/iron-a11y-keys.js';
 import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
-import '../common/common_style.css.js';
+import '../css/common.css.js';
 import '../css/cros_button_style.css.js';
 
 import {IronA11yKeysElement} from 'chrome://resources/polymer/v3_0/iron-a11y-keys/iron-a11y-keys.js';
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_main_element.html b/ash/webui/personalization_app/resources/trusted/personalization_main_element.html
index 511f2b2..74aca8c 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_main_element.html
+++ b/ash/webui/personalization_app/resources/trusted/personalization_main_element.html
@@ -1,4 +1,4 @@
-<style include="common-style cros-button-style">
+<style include="common cros-button-style">
   #container {
     display: grid;
     grid-template-areas:
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_router_element.html b/ash/webui/personalization_app/resources/trusted/personalization_router_element.html
index c27e2b83..61f9e5d 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_router_element.html
+++ b/ash/webui/personalization_app/resources/trusted/personalization_router_element.html
@@ -1,4 +1,4 @@
-<style include="common-style">
+<style include="common">
   #container {
     display: flex;
     flex-flow: column nowrap;
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_theme_element.ts b/ash/webui/personalization_app/resources/trusted/personalization_theme_element.ts
index 56851e8..06a7457 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_theme_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_theme_element.ts
@@ -11,7 +11,7 @@
 import 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
 import 'chrome://resources/polymer/v3_0/iron-a11y-keys/iron-a11y-keys.js';
 import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
-import '../common/common_style.css.js';
+import '../css/common.css.js';
 import '../css/cros_button_style.css.js';
 
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.html
index de2fcdb9..d96d3ce 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.html
@@ -1,4 +1,4 @@
-<style include="wallpaper common-style">
+<style include="wallpaper common">
   :host {
     overflow: hidden;
   }
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts
index 98b6ced..0b8a13af 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts
@@ -9,7 +9,7 @@
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import 'chrome://resources/polymer/v3_0/iron-scroll-threshold/iron-scroll-threshold.js';
 import '../../css/wallpaper.css.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.html
index d07f8c04..2055558 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.html
@@ -1,4 +1,4 @@
-<style include="wallpaper common-style">
+<style include="wallpaper common">
   :host {
     overflow: hidden;
   }
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.ts
index 2433174a..e7c966f 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_collection_element.ts
@@ -9,7 +9,7 @@
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import '../../css/wallpaper.css.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 
 import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.html
index fc3d6c5..642a20c5 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.html
@@ -1,4 +1,4 @@
-<style include="wallpaper common-style">
+<style include="wallpaper common">
   :host {
     overflow: hidden;
   }
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts
index 035170b6..c0df8d16 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts
@@ -10,7 +10,7 @@
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import 'chrome://resources/polymer/v3_0/iron-scroll-threshold/iron-scroll-threshold.js';
 import '../../css/wallpaper.css.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.html
index c7112bc..ef347b9a 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.html
@@ -1,4 +1,4 @@
-<style include="wallpaper common-style">
+<style include="wallpaper common">
   :host {
     overflow: hidden;
   }
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
index b151da0..134a89d9 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
@@ -9,7 +9,7 @@
 import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import 'chrome://resources/polymer/v3_0/iron-scroll-threshold/iron-scroll-threshold.js';
 import '../../css/wallpaper.css.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_zero_state_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_zero_state_element.html
index 03227e72..d19638a 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_zero_state_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_zero_state_element.html
@@ -1,4 +1,4 @@
-<style include="wallpaper common-style">
+<style include="wallpaper common">
   :host {
     align-items: center;
     display: flex;
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_zero_state_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_zero_state_element.ts
index e51576d..10fe5726 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_zero_state_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_zero_state_element.ts
@@ -7,7 +7,7 @@
  */
 
 import '../../css/wallpaper.css.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 
 import {WithPersonalizationStore} from '../personalization_store.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.html
index ab814d11..9a6a7ee 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.html
@@ -1,4 +1,4 @@
-<style include="wallpaper common-style">
+<style include="wallpaper common">
   :host {
     overflow: hidden;
   }
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.ts
index e201ff12..81ab9dd22 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/local_images_element.ts
@@ -13,7 +13,7 @@
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import '../../css/wallpaper.css.js';
 import '../../common/icons.html.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 
 import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
 import {FilePath} from 'chrome://resources/mojo/mojo/public/mojom/base/file_path.mojom-webui.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.html
index 731efe84..1357ec3 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.html
@@ -1,4 +1,4 @@
-<style include="common-style">
+<style include="common">
   :host {
     box-sizing: border-box;
     display: block;
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.ts
index 8a96f134..51bd866 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.ts
@@ -7,7 +7,7 @@
  */
 
 import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.html
index 63122fc..fe95043 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.html
@@ -1,4 +1,4 @@
-<style include="common-style cros-button-style">
+<style include="common cros-button-style">
   #wallpaperLabel {
     align-items: center;
     background: none;
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.ts
index 5be57bc2..f6c36e1 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_preview_element.ts
@@ -10,7 +10,7 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import '../../common/icons.html.js';
-import '../../common/common_style.css.js';
+import '../../css/common.css.js';
 import '../../css/wallpaper.css.js';
 import '../../css/cros_button_style.css.js';
 
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_selected_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_selected_element.html
index 52ca14b..3411e348 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_selected_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_selected_element.html
@@ -1,4 +1,4 @@
-<style include="wallpaper common-style">
+<style include="wallpaper common">
   #container {
     border-bottom: 1px solid var(--cros-separator-color);
     box-sizing: border-box;
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.html
index 3064e49a..267e36fb 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_subpage_element.html
@@ -1,4 +1,4 @@
-<style include="common-style">
+<style include="common">
   #wallpaperContainer {
     display: grid;
     grid-template-areas:
diff --git a/ash/webui/personalization_app/resources/untrusted/collections_grid.html b/ash/webui/personalization_app/resources/untrusted/collections_grid.html
index ced320b1..74161c2b 100644
--- a/ash/webui/personalization_app/resources/untrusted/collections_grid.html
+++ b/ash/webui/personalization_app/resources/untrusted/collections_grid.html
@@ -1,4 +1,4 @@
-<style include="common-style wallpaper">
+<style include="common wallpaper">
   :host {
     margin: 12px 0;
   }
diff --git a/ash/webui/personalization_app/resources/untrusted/images_grid.html b/ash/webui/personalization_app/resources/untrusted/images_grid.html
index 0d0e5c9..a90ddab 100644
--- a/ash/webui/personalization_app/resources/untrusted/images_grid.html
+++ b/ash/webui/personalization_app/resources/untrusted/images_grid.html
@@ -1,4 +1,4 @@
-<style include="common-style wallpaper">
+<style include="common wallpaper">
   :host {
     margin: 12px 0;
   }
diff --git a/ash/webui/personalization_app/resources/untrusted/setup.ts b/ash/webui/personalization_app/resources/untrusted/setup.ts
index 4dc1fe8..a6e16c4e 100644
--- a/ash/webui/personalization_app/resources/untrusted/setup.ts
+++ b/ash/webui/personalization_app/resources/untrusted/setup.ts
@@ -12,5 +12,5 @@
 import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
 import '//resources/cr_elements/shared_vars_css.m.js';
 import '../common/icons.html.js';
-import '../common/common_style.css.js';
+import '../css/common.css.js';
 import '/strings.m.js';
diff --git a/ash/webui/projector_app/BUILD.gn b/ash/webui/projector_app/BUILD.gn
index 1112912..66b0400 100644
--- a/ash/webui/projector_app/BUILD.gn
+++ b/ash/webui/projector_app/BUILD.gn
@@ -18,6 +18,8 @@
     "projector_message_handler.h",
     "projector_oauth_token_fetcher.cc",
     "projector_oauth_token_fetcher.h",
+    "projector_screencast.cc",
+    "projector_screencast.h",
     "projector_xhr_sender.cc",
     "projector_xhr_sender.h",
     "trusted_projector_annotator_ui.cc",
diff --git a/ash/webui/projector_app/projector_message_handler.cc b/ash/webui/projector_app/projector_message_handler.cc
index 0b2be6d..d455447 100644
--- a/ash/webui/projector_app/projector_message_handler.cc
+++ b/ash/webui/projector_app/projector_message_handler.cc
@@ -11,6 +11,7 @@
 #include "ash/public/cpp/projector/projector_controller.h"
 #include "ash/public/cpp/projector/projector_new_screencast_precondition.h"
 #include "ash/webui/projector_app/projector_app_client.h"
+#include "ash/webui/projector_app/projector_screencast.h"
 #include "ash/webui/projector_app/projector_xhr_sender.h"
 #include "base/bind.h"
 #include "base/check.h"
@@ -222,6 +223,10 @@
       "openFeedbackDialog",
       base::BindRepeating(&ProjectorMessageHandler::OpenFeedbackDialog,
                           base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "getScreencast",
+      base::BindRepeating(&ProjectorMessageHandler::GetScreencast,
+                          base::Unretained(this)));
 }
 
 void ProjectorMessageHandler::OnScreencastsPendingStatusChanged(
@@ -484,4 +489,31 @@
                             ScreencastListToValue(pending_screencasts));
 }
 
+void ProjectorMessageHandler::GetScreencast(const base::Value::List& args) {
+  AllowJavascript();
+  DCHECK_EQ(args.size(), 2u);
+  const auto& func_args = args[1].GetList();
+  DCHECK_EQ(func_args.size(), 1u);
+
+  // 1. TODO(b/236857019):Locates the local path of container folder local path
+  // by server side file id.
+  //  2. TODO(b/236857019): Locates the local path of media file for given
+  //  container folder path.
+  //  3. TODO(b/237089852) With the media file path, issues an open file request
+  //  to retrieve video url.
+  //  4. TODO(b/236857019): Populate the screencast info and return the promise
+  //  with it.
+  ProjectorScreencast screencast;
+  // 5. TODO(b/236857019): Investigate load screencast outside DriveFS. Finds a
+  // way(maybe by checking the pattern) to distinguish whether args[1] is a
+  // container folder id or path/blob uuid. Or add a separate API like
+  // GetScreencastByPath?
+  screencast.container_folder_id = func_args[0].GetString();
+  // Sets the "name" with a random string for now.
+  // TODO(b/236857019) Gets screencast name by using DriveFS service.
+  screencast.name = "name";
+
+  ResolveJavascriptCallback(args[0], screencast.ToValue());
+}
+
 }  // namespace ash
diff --git a/ash/webui/projector_app/projector_message_handler.h b/ash/webui/projector_app/projector_message_handler.h
index 25c50a24..cfc485da 100644
--- a/ash/webui/projector_app/projector_message_handler.h
+++ b/ash/webui/projector_app/projector_message_handler.h
@@ -121,6 +121,9 @@
   // upload or failed to upload.
   void GetPendingScreencasts(const base::Value::List& args);
 
+  // Requested by the Projector SWA to fetch a single screencast.
+  void GetScreencast(const base::Value::List& args);
+
   ProjectorOAuthTokenFetcher oauth_token_fetcher_;
   std::unique_ptr<ProjectorXhrSender> xhr_sender_;
 
diff --git a/ash/webui/projector_app/projector_screencast.cc b/ash/webui/projector_app/projector_screencast.cc
new file mode 100644
index 0000000..cc897012
--- /dev/null
+++ b/ash/webui/projector_app/projector_screencast.cc
@@ -0,0 +1,35 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/webui/projector_app/projector_screencast.h"
+
+#include "base/values.h"
+
+namespace ash {
+
+base::Value ProjectorScreencastVideo::ToValue() const {
+  base::Value::Dict dict;
+  dict.Set("srcUrl", srcUrl);
+  return base::Value(std::move(dict));
+}
+
+ProjectorScreencast::ProjectorScreencast() = default;
+
+ProjectorScreencast::ProjectorScreencast(const ProjectorScreencast&) = default;
+
+ProjectorScreencast& ProjectorScreencast::operator=(
+    const ProjectorScreencast&) = default;
+
+ProjectorScreencast::~ProjectorScreencast() = default;
+
+base::Value ProjectorScreencast::ToValue() const {
+  base::Value::Dict dict;
+  dict.Set("container_folder_id", container_folder_id);
+  dict.Set("name", name);
+  dict.Set("video", video.ToValue());
+
+  return base::Value(std::move(dict));
+}
+
+}  // namespace ash
diff --git a/ash/webui/projector_app/projector_screencast.h b/ash/webui/projector_app/projector_screencast.h
new file mode 100644
index 0000000..40a59e3
--- /dev/null
+++ b/ash/webui/projector_app/projector_screencast.h
@@ -0,0 +1,47 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WEBUI_PROJECTOR_APP_PROJECTOR_SCREENCAST_H_
+#define ASH_WEBUI_PROJECTOR_APP_PROJECTOR_SCREENCAST_H_
+
+#include <string>
+
+namespace base {
+class Value;
+}  // namespace base
+
+namespace ash {
+
+// The video object for screencast.
+struct ProjectorScreencastVideo {
+  base::Value ToValue() const;
+  // TODO(b/236857019): Add thumbnail link and video file id.
+  std::string srcUrl;
+};
+
+// Struct of screencast model.
+struct ProjectorScreencast {
+  ProjectorScreencast();
+  ProjectorScreencast(const ProjectorScreencast&);
+  ProjectorScreencast& operator=(const ProjectorScreencast&);
+  ~ProjectorScreencast();
+
+  base::Value ToValue() const;
+
+  // Only available for screencasts locate in DriveFs.
+  std::string container_folder_id;
+
+  std::string name;
+
+  ProjectorScreencastVideo video;
+
+  // TODO(b/236857019): 1 Implement following fields: status, metadata_file_id,
+  // metadata, is_editable, upload_progress.
+  //  2 Replace the PendingScreencast struct defined in ProjectorAppClient with
+  //  this struct.
+};
+
+}  // namespace ash
+
+#endif  // ASH_WEBUI_PROJECTOR_APP_PROJECTOR_SCREENCAST_H_
diff --git a/ash/webui/projector_app/resources/app/trusted/trusted_app_comm_factory.js b/ash/webui/projector_app/resources/app/trusted/trusted_app_comm_factory.js
index 4a1eaa4b..75d4c73 100644
--- a/ash/webui/projector_app/resources/app/trusted/trusted_app_comm_factory.js
+++ b/ash/webui/projector_app/resources/app/trusted/trusted_app_comm_factory.js
@@ -131,6 +131,9 @@
     this.registerMethod('openFeedbackDialog', (args) => {
       return this.browserProxy_.openFeedbackDialog();
     });
+    this.registerMethod('getScreencast', (args) => {
+      return this.browserProxy_.getScreencast(args[0]);
+    });
   }
 }
 
diff --git a/ash/webui/projector_app/resources/app/untrusted/untrusted_app_comm_factory.js b/ash/webui/projector_app/resources/app/untrusted/untrusted_app_comm_factory.js
index 30726af..67662fd 100644
--- a/ash/webui/projector_app/resources/app/untrusted/untrusted_app_comm_factory.js
+++ b/ash/webui/projector_app/resources/app/untrusted/untrusted_app_comm_factory.js
@@ -181,9 +181,9 @@
    * @param {string} screencastId The Drive item id of container folder.
    * @return {!Promise<projectorApp.Screencast>}
    */
-  // TODO(b/236857019) Wires up this with trusted context and message handle.
   getScreencast(screencastId) {
-    return Promise.reject('Unsupported');
+    return AppUntrustedCommFactory.getPostMessageAPIClient().callApiFn(
+        'getScreencast', [screencastId]);
   },
 
 };
diff --git a/ash/webui/projector_app/resources/communication/projector_browser_proxy.js b/ash/webui/projector_app/resources/communication/projector_browser_proxy.js
index 6960ff0..4f3697c 100644
--- a/ash/webui/projector_app/resources/communication/projector_browser_proxy.js
+++ b/ash/webui/projector_app/resources/communication/projector_browser_proxy.js
@@ -123,6 +123,13 @@
    * @return {!Promise}
    */
   openFeedbackDialog() {}
+
+  /**
+   * Gets information about the specified screencast from DriveFS.
+   * @param {string} screencastId The Drive item id of container folder.
+   * @return {!Promise<projectorApp.Screencast>}
+   */
+  getScreencast(screencastId) {}
 }
 
 /**
@@ -200,6 +207,11 @@
   openFeedbackDialog() {
     return sendWithPromise('openFeedbackDialog');
   }
+
+  /** @override */
+  getScreencast(screencastId) {
+    return sendWithPromise('getScreencast', [screencastId]);
+  }
 }
 
 addSingletonGetter(ProjectorBrowserProxyImpl);
diff --git a/ash/webui/projector_app/test/projector_message_handler_unittest.cc b/ash/webui/projector_app/test/projector_message_handler_unittest.cc
index 5d1f2c4d..a5103ac 100644
--- a/ash/webui/projector_app/test/projector_message_handler_unittest.cc
+++ b/ash/webui/projector_app/test/projector_message_handler_unittest.cc
@@ -5,9 +5,9 @@
 #include "ash/webui/projector_app/projector_message_handler.h"
 
 #include "ash/constants/ash_pref_names.h"
-#include "ash/public/cpp/projector/projector_controller.h"
 #include "ash/public/cpp/projector/projector_new_screencast_precondition.h"
 #include "ash/public/cpp/test/mock_projector_controller.h"
+#include "ash/webui/projector_app/projector_screencast.h"
 #include "ash/webui/projector_app/projector_xhr_sender.h"
 #include "ash/webui/projector_app/test/mock_app_client.h"
 #include "base/files/file_path.h"
@@ -47,6 +47,7 @@
 const char kOnSodaInstallProgressUpdated[] = "onSodaInstallProgressUpdated";
 const char kOnSodaInstalled[] = "onSodaInstalled";
 const char kOnSodaInstallError[] = "onSodaInstallError";
+const char kGetScreencastCallback[] = "getScreencastCallback";
 
 const char kShouldDownloadSodaCallback[] = "shouldDownloadSodaCallbck";
 const char kInstallSodaCallback[] = "installSodaCallback";
@@ -538,6 +539,37 @@
   EXPECT_EQ(*(rejected_args->FindPath(kRejectedRequestArgsKey)), func_args);
 }
 
+TEST_F(ProjectorMessageHandlerUnitTest, GetScreencast) {
+  base::ListValue list_args;
+  list_args.Append(kGetScreencastCallback);
+  base::ListValue args;
+  const std::string container_folder_id = "test_container_id";
+  args.Append(container_folder_id);
+
+  list_args.Append(std::move(args));
+
+  web_ui().HandleReceivedMessage("getScreencast", &list_args);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(web_ui().call_data().size(), 1u);
+
+  const content::TestWebUI::CallData& call_data = FetchCallData(0);
+  EXPECT_EQ(call_data.function_name(), kWebUIResponse);
+  EXPECT_EQ(call_data.arg1()->GetString(), kGetScreencastCallback);
+
+  // Whether the callback was rejected or not.
+  EXPECT_TRUE(call_data.arg2()->GetBool());
+  ASSERT_TRUE(call_data.arg3()->is_dict());
+  const base::Value::Dict& dict = std::move(call_data.arg3()->GetDict());
+
+  EXPECT_EQ(*(dict.FindString("container_folder_id")), container_folder_id);
+  // TODO(b/236857019) Updates the |name| value when getting screencast name by
+  // using DriveFS service.
+  EXPECT_EQ(*(dict.FindString("name")), "name");
+  EXPECT_EQ(*(dict.FindDict("video")),
+            ash::ProjectorScreencastVideo().ToValue());
+}
+
 class ProjectorStorageDirNameValidationTest
     : public ::testing::WithParamInterface<
           ::testing::tuple<::std::string, bool>>,
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_delegate.h b/ash/webui/shimless_rma/backend/shimless_rma_delegate.h
index 49da0260..68d9911 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_delegate.h
+++ b/ash/webui/shimless_rma/backend/shimless_rma_delegate.h
@@ -19,6 +19,10 @@
 
   // Starts the post-boot diagnostics app.
   virtual void ShowDiagnosticsDialog() = 0;
+
+  // Sets the AccessibilityManager profile to the active profile to enable
+  // accessibility features.
+  virtual void RefreshAccessibilityManagerProfile() = 0;
 };
 
 }  // namespace shimless_rma
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service.cc b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
index ad3316b..c1d89c9 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service.cc
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
@@ -100,6 +100,9 @@
     : shimless_rma_delegate_(std::move(shimless_rma_delegate)) {
   RmadClient::Get()->AddObserver(this);
 
+  // Enable accessibility features.
+  shimless_rma_delegate_->RefreshAccessibilityManagerProfile();
+
   network_config::BindToInProcessInstance(
       remote_cros_network_config_.BindNewPipeAndPassReceiver());
 
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc b/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
index 329ff1f..94e2357 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service_unittest.cc
@@ -106,6 +106,7 @@
 
   void ExitRmaThenRestartChrome() override {}
   void ShowDiagnosticsDialog() override {}
+  void RefreshAccessibilityManagerProfile() override {}
 };
 
 }  // namespace
diff --git a/base/threading/platform_thread_win.cc b/base/threading/platform_thread_win.cc
index cdb8135..30b51f48 100644
--- a/base/threading/platform_thread_win.cc
+++ b/base/threading/platform_thread_win.cc
@@ -376,7 +376,9 @@
     // Exit background mode if the new priority is not BACKGROUND. This is a
     // no-op if not in background mode.
     ::SetThreadPriority(thread_handle, THREAD_MODE_BACKGROUND_END);
-    internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_NORMAL);
+    // We used to DCHECK that memory priority is MEMORY_PRIORITY_NORMAL here,
+    // but found that it is not always the case (e.g. in the installer).
+    // crbug.com/1340578#c2
   }
 
   int desired_priority = THREAD_PRIORITY_ERROR_RETURN;
@@ -423,10 +425,9 @@
     // https://crbug.com/901483
     if (GetCurrentThreadPriority() != ThreadPriority::BACKGROUND) {
       ::SetThreadPriority(thread_handle, THREAD_PRIORITY_LOWEST);
-      // Make sure that using THREAD_PRIORITY_LOWEST didn't affect the memory
-      // priority set by THREAD_MODE_BACKGROUND_BEGIN. There is no practical
-      // way to verify the I/O priority.
-      internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_VERY_LOW);
+      // We used to DCHECK that memory priority is MEMORY_PRIORITY_VERY_LOW
+      // here, but found that it is not always the case (e.g. in the installer).
+      // crbug.com/1340578#c2
     }
   }
 }
diff --git a/build/config/c++/BUILD.gn b/build/config/c++/BUILD.gn
index 2e5843b..0932458 100644
--- a/build/config/c++/BUILD.gn
+++ b/build/config/c++/BUILD.gn
@@ -7,14 +7,6 @@
 
 assert(use_custom_libcxx, "should only be used if use_custom_libcxx is set")
 
-libcxx_abi_unstable = true
-
-# TODO(xiaohuic): https://crbug/917533 Crashes on internal ChromeOS build.
-# Do unconditionally once the underlying problem is fixed.
-if (is_chromeos_ash && is_chrome_branded) {
-  libcxx_abi_unstable = false
-}
-
 # This is included by reference in the //build/config/compiler:runtime_library
 # config that is applied to all targets. It is here to separate out the logic
 # that is specific to libc++. Please see that target for advice on what should
@@ -37,11 +29,10 @@
   #    on Windows, the increase is great enough that we go above the 4GB size
   #    limit for PDBs (https://crbug.com/1327710#c5). To fix this, we set
   #    _LIBCPP_ABI_NAMESPACE to a shorter value.
-  defines += [ "_LIBCPP_ABI_NAMESPACE=Cr" ]
-
-  if (libcxx_abi_unstable) {
-    defines += [ "_LIBCPP_ABI_VERSION=2" ]
-  }
+  defines += [
+    "_LIBCPP_ABI_NAMESPACE=Cr",
+    "_LIBCPP_ABI_VERSION=2",
+  ]
 
   if (!libcxx_is_shared) {
     # Don't leak any symbols on a static build.
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index fd1953a..d142c527 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-8.20220701.1.1
+8.20220701.3.1
diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc
index 9561ff8a..70bd832 100644
--- a/cc/paint/paint_op_buffer.cc
+++ b/cc/paint/paint_op_buffer.cc
@@ -275,14 +275,6 @@
 static const VoidFunction g_destructor_functions[kNumOpTypes] = {TYPES(M)};
 #undef M
 
-using MoveFunction = void (*)(PaintOp* op, char* dst);
-#define M(T)                                        \
-  [](PaintOp* op, char* dst) {                      \
-    new (dst) T(std::move(*(static_cast<T*>(op)))); \
-  },
-static const MoveFunction g_move_functions[kNumOpTypes] = {TYPES(M)};
-#undef M
-
 #define M(T) T::kIsDrawOp,
 static bool g_is_draw_op[kNumOpTypes] = {TYPES(M)};
 #undef M
@@ -1561,8 +1553,8 @@
 }
 
 void ConcatOp::Raster(const ConcatOp* op,
-                      SkCanvas* canvas,
-                      const PlaybackParams& params) {
+                        SkCanvas* canvas,
+                        const PlaybackParams& params) {
   canvas->concat(op->matrix);
 }
 
@@ -1939,8 +1931,8 @@
 }
 
 void SetMatrixOp::Raster(const SetMatrixOp* op,
-                         SkCanvas* canvas,
-                         const PlaybackParams& params) {
+                           SkCanvas* canvas,
+                           const PlaybackParams& params) {
   canvas->setMatrix(params.original_ctm * op->matrix);
 }
 
@@ -2695,12 +2687,6 @@
     func(this);
 }
 
-void PaintOp::MoveThis(char* dst) {
-  auto func = g_move_functions[type];
-  if (func)
-    func(this, dst);
-}
-
 bool PaintOpWithFlags::HasDiscardableImagesFromFlags() const {
   return flags.HasDiscardableImages();
 }
@@ -2784,8 +2770,6 @@
       rect(rect),
       data(std::move(data)) {}
 
-AnnotateOp::AnnotateOp(AnnotateOp&&) = default;
-
 AnnotateOp::~AnnotateOp() = default;
 
 DrawImageOp::DrawImageOp() : PaintOpWithFlags(kType) {}
@@ -2808,8 +2792,6 @@
       top(top),
       sampling(sampling) {}
 
-DrawImageOp::DrawImageOp(DrawImageOp&&) = default;
-
 bool DrawImageOp::HasDiscardableImages() const {
   return image && !image.IsTextureBacked();
 }
@@ -2842,8 +2824,6 @@
       sampling(sampling),
       constraint(constraint) {}
 
-DrawImageRectOp::DrawImageRectOp(DrawImageRectOp&&) = default;
-
 bool DrawImageRectOp::HasDiscardableImages() const {
   return image && !image.IsTextureBacked();
 }
@@ -2853,8 +2833,6 @@
 DrawRecordOp::DrawRecordOp(sk_sp<const PaintRecord> record)
     : PaintOp(kType), record(std::move(record)) {}
 
-DrawRecordOp::DrawRecordOp(DrawRecordOp&&) = default;
-
 DrawRecordOp::~DrawRecordOp() = default;
 
 size_t DrawRecordOp::AdditionalBytesUsed() const {
@@ -2881,8 +2859,6 @@
 
 DrawSkottieOp::DrawSkottieOp() : PaintOp(kType) {}
 
-DrawSkottieOp::DrawSkottieOp(DrawSkottieOp&&) = default;
-
 DrawSkottieOp::~DrawSkottieOp() = default;
 
 bool DrawSkottieOp::HasDiscardableImages() const {
@@ -2912,8 +2888,6 @@
       y(y),
       node_id(node_id) {}
 
-DrawTextBlobOp::DrawTextBlobOp(DrawTextBlobOp&&) = default;
-
 DrawTextBlobOp::~DrawTextBlobOp() = default;
 
 PaintOpBuffer::CompositeIterator::CompositeIterator(
@@ -3273,14 +3247,8 @@
   DCHECK_GE(new_size, used_);
   std::unique_ptr<char, base::AlignedFreeDeleter> new_data(
       static_cast<char*>(base::AlignedAlloc(new_size, PaintOpAlign)));
-  char* dst = new_data.get();
-  DCHECK(dst);
-  for (auto* op : Iterator(this)) {
-    size_t skip = op->skip;
-    op->MoveThis(dst);
-    op->DestroyThis();
-    dst += skip;
-  }
+  if (data_)
+    memcpy(new_data.get(), data_.get(), used_);
   data_ = std::move(new_data);
   reserved_ = new_size;
 }
diff --git a/cc/paint/paint_op_buffer.h b/cc/paint/paint_op_buffer.h
index 75bd3f8f..06eedb5f 100644
--- a/cc/paint/paint_op_buffer.h
+++ b/cc/paint/paint_op_buffer.h
@@ -62,7 +62,6 @@
   explicit ThreadsafePath(const SkPath& path) : SkPath(path) {
     updateBoundsCache();
   }
-  ThreadsafePath(ThreadsafePath&&) = default;
   ThreadsafePath() { updateBoundsCache(); }
 };
 
@@ -313,8 +312,6 @@
   // memory buffers and so don't have their destructors run automatically.
   void DestroyThis();
 
-  void MoveThis(char* dst);
-
   // DrawColor is more restrictive on the blend modes that can be used.
   static bool IsValidDrawColorSkBlendMode(SkBlendMode mode) {
     return static_cast<uint32_t>(mode) <=
@@ -397,7 +394,6 @@
   AnnotateOp(PaintCanvas::AnnotationType annotation_type,
              const SkRect& rect,
              sk_sp<SkData> data);
-  AnnotateOp(AnnotateOp&&);
   ~AnnotateOp();
   static void Raster(const AnnotateOp* op,
                      SkCanvas* canvas,
@@ -426,7 +422,6 @@
         op(op),
         antialias(antialias),
         use_cache(use_paint_cache) {}
-  ClipPathOp(ClipPathOp&&) = default;
   static void Raster(const ClipPathOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
@@ -450,7 +445,6 @@
   static constexpr PaintOpType kType = PaintOpType::ClipRect;
   ClipRectOp(const SkRect& rect, SkClipOp op, bool antialias)
       : PaintOp(kType), rect(rect), op(op), antialias(antialias) {}
-  ClipRectOp(ClipRectOp&&) = default;
   static void Raster(const ClipRectOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
@@ -471,7 +465,6 @@
   static constexpr PaintOpType kType = PaintOpType::ClipRRect;
   ClipRRectOp(const SkRRect& rrect, SkClipOp op, bool antialias)
       : PaintOp(kType), rrect(rrect), op(op), antialias(antialias) {}
-  ClipRRectOp(ClipRRectOp&&) = default;
   static void Raster(const ClipRRectOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
@@ -492,7 +485,6 @@
  public:
   static constexpr PaintOpType kType = PaintOpType::Concat;
   explicit ConcatOp(const SkM44& matrix) : PaintOp(kType), matrix(matrix) {}
-  ConcatOp(ConcatOp&&) = default;
   static void Raster(const ConcatOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
@@ -510,7 +502,6 @@
  public:
   static constexpr PaintOpType kType = PaintOpType::CustomData;
   explicit CustomDataOp(uint32_t id) : PaintOp(kType), id(id) {}
-  CustomDataOp(CustomDataOp&&) = default;
   static void Raster(const CustomDataOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
@@ -531,7 +522,6 @@
   static constexpr bool kIsDrawOp = true;
   DrawColorOp(SkColor4f color, SkBlendMode mode)
       : PaintOp(kType), color(color), mode(mode) {}
-  DrawColorOp(DrawColorOp&&) = default;
   static void Raster(const DrawColorOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
@@ -554,7 +544,6 @@
                const SkRRect& inner,
                const PaintFlags& flags)
       : PaintOpWithFlags(kType, flags), outer(outer), inner(inner) {}
-  DrawDRRectOp(DrawDRRectOp&&) = default;
   static void RasterWithFlags(const DrawDRRectOp* op,
                               const PaintFlags* flags,
                               SkCanvas* canvas,
@@ -582,7 +571,6 @@
               SkScalar top,
               const SkSamplingOptions&,
               const PaintFlags* flags);
-  DrawImageOp(DrawImageOp&&);
   ~DrawImageOp();
   static void RasterWithFlags(const DrawImageOp* op,
                               const PaintFlags* flags,
@@ -624,7 +612,6 @@
                   const SkSamplingOptions&,
                   const PaintFlags* flags,
                   SkCanvas::SrcRectConstraint constraint);
-  DrawImageRectOp(DrawImageRectOp&&);
   ~DrawImageRectOp();
   static void RasterWithFlags(const DrawImageRectOp* op,
                               const PaintFlags* flags,
@@ -684,7 +671,6 @@
              SkScalar y1,
              const PaintFlags& flags)
       : PaintOpWithFlags(kType, flags), x0(x0), y0(y0), x1(x1), y1(y1) {}
-  DrawLineOp(DrawLineOp&&) = default;
   static void RasterWithFlags(const DrawLineOp* op,
                               const PaintFlags* flags,
                               SkCanvas* canvas,
@@ -710,7 +696,6 @@
   static constexpr bool kIsDrawOp = true;
   DrawOvalOp(const SkRect& oval, const PaintFlags& flags)
       : PaintOpWithFlags(kType, flags), oval(oval) {}
-  DrawOvalOp(DrawOvalOp&&) = default;
   static void RasterWithFlags(const DrawOvalOp* op,
                               const PaintFlags* flags,
                               SkCanvas* canvas,
@@ -739,7 +724,6 @@
         path(path),
         sk_path_fill_type(static_cast<uint8_t>(path.getFillType())),
         use_cache(use_paint_cache) {}
-  DrawPathOp(DrawPathOp&&) = default;
   static void RasterWithFlags(const DrawPathOp* op,
                               const PaintFlags* flags,
                               SkCanvas* canvas,
@@ -767,7 +751,6 @@
   static constexpr PaintOpType kType = PaintOpType::DrawRecord;
   static constexpr bool kIsDrawOp = true;
   explicit DrawRecordOp(sk_sp<const PaintRecord> record);
-  DrawRecordOp(DrawRecordOp&&);
   ~DrawRecordOp();
   static void Raster(const DrawRecordOp* op,
                      SkCanvas* canvas,
@@ -794,7 +777,6 @@
   static constexpr bool kIsDrawOp = true;
   DrawRectOp(const SkRect& rect, const PaintFlags& flags)
       : PaintOpWithFlags(kType, flags), rect(rect) {}
-  DrawRectOp(DrawRectOp&&) = default;
   static void RasterWithFlags(const DrawRectOp* op,
                               const PaintFlags* flags,
                               SkCanvas* canvas,
@@ -815,7 +797,6 @@
   static constexpr bool kIsDrawOp = true;
   DrawRRectOp(const SkRRect& rrect, const PaintFlags& flags)
       : PaintOpWithFlags(kType, flags), rrect(rrect) {}
-  DrawRRectOp(DrawRRectOp&&) = default;
   static void RasterWithFlags(const DrawRRectOp* op,
                               const PaintFlags* flags,
                               SkCanvas* canvas,
@@ -840,7 +821,6 @@
                 SkottieFrameDataMap images,
                 const SkottieColorMap& color_map,
                 SkottieTextPropertyValueMap text_map);
-  DrawSkottieOp(DrawSkottieOp&&);
   ~DrawSkottieOp();
   static void Raster(const DrawSkottieOp* op,
                      SkCanvas* canvas,
@@ -892,7 +872,6 @@
                  SkScalar y,
                  NodeId node_id,
                  const PaintFlags& flags);
-  DrawTextBlobOp(DrawTextBlobOp&&);
   ~DrawTextBlobOp();
   static void RasterWithFlags(const DrawTextBlobOp* op,
                               const PaintFlags* flags,
@@ -919,7 +898,6 @@
  public:
   static constexpr PaintOpType kType = PaintOpType::Noop;
   NoopOp() : PaintOp(kType) {}
-  NoopOp(NoopOp&&) = default;
   static void Raster(const NoopOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params) {}
@@ -932,7 +910,6 @@
  public:
   static constexpr PaintOpType kType = PaintOpType::Restore;
   RestoreOp() : PaintOp(kType) {}
-  RestoreOp(RestoreOp&&) = default;
   static void Raster(const RestoreOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
@@ -945,7 +922,6 @@
  public:
   static constexpr PaintOpType kType = PaintOpType::Rotate;
   explicit RotateOp(SkScalar degrees) : PaintOp(kType), degrees(degrees) {}
-  RotateOp(RotateOp&&) = default;
   static void Raster(const RotateOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
@@ -963,7 +939,6 @@
  public:
   static constexpr PaintOpType kType = PaintOpType::Save;
   SaveOp() : PaintOp(kType) {}
-  SaveOp(SaveOp&&) = default;
   static void Raster(const SaveOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
@@ -978,7 +953,6 @@
   SaveLayerOp(const SkRect* bounds, const PaintFlags* flags)
       : PaintOpWithFlags(kType, flags ? *flags : PaintFlags()),
         bounds(bounds ? *bounds : kUnsetRect) {}
-  SaveLayerOp(SaveLayerOp&&) = default;
   static void RasterWithFlags(const SaveLayerOp* op,
                               const PaintFlags* flags,
                               SkCanvas* canvas,
@@ -1004,7 +978,6 @@
   static constexpr PaintOpType kType = PaintOpType::SaveLayerAlpha;
   SaveLayerAlphaOp(const SkRect* bounds, uint8_t alpha)
       : PaintOp(kType), bounds(bounds ? *bounds : kUnsetRect), alpha(alpha) {}
-  SaveLayerAlphaOp(SaveLayerAlphaOp&&) = default;
   static void Raster(const SaveLayerAlphaOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
@@ -1025,7 +998,6 @@
  public:
   static constexpr PaintOpType kType = PaintOpType::Scale;
   ScaleOp(SkScalar sx, SkScalar sy) : PaintOp(kType), sx(sx), sy(sy) {}
-  ScaleOp(ScaleOp&&) = default;
   static void Raster(const ScaleOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
@@ -1044,7 +1016,6 @@
  public:
   static constexpr PaintOpType kType = PaintOpType::SetMatrix;
   explicit SetMatrixOp(const SkM44& matrix) : PaintOp(kType), matrix(matrix) {}
-  SetMatrixOp(SetMatrixOp&&) = default;
   // This is the only op that needs the original ctm of the SkCanvas
   // used for raster (since SetMatrix is relative to the recording origin and
   // shouldn't clobber the SkCanvas raster origin).
@@ -1068,7 +1039,6 @@
  public:
   static constexpr PaintOpType kType = PaintOpType::SetNodeId;
   explicit SetNodeIdOp(int node_id) : PaintOp(kType), node_id(node_id) {}
-  SetNodeIdOp(SetNodeIdOp&&) = default;
   static void Raster(const SetNodeIdOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
@@ -1086,7 +1056,6 @@
  public:
   static constexpr PaintOpType kType = PaintOpType::Translate;
   TranslateOp(SkScalar dx, SkScalar dy) : PaintOp(kType), dx(dx), dy(dy) {}
-  TranslateOp(TranslateOp&&) = default;
   static void Raster(const TranslateOp* op,
                      SkCanvas* canvas,
                      const PlaybackParams& params);
diff --git a/chrome/VERSION b/chrome/VERSION
index 38a8e2f5..a34b7a31 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=105
 MINOR=0
-BUILD=5155
+BUILD=5156
 PATCH=0
diff --git a/chrome/android/java/res/layout/bookmark_save_flow.xml b/chrome/android/java/res/layout/bookmark_save_flow.xml
index 7a066f27..e53bb1f 100644
--- a/chrome/android/java/res/layout/bookmark_save_flow.xml
+++ b/chrome/android/java/res/layout/bookmark_save_flow.xml
@@ -10,69 +10,74 @@
     android:layout_height="wrap_content"
     android:focusable="true">
 
-  <LinearLayout
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:orientation="vertical"
-      android:paddingTop="16dp"
-      android:paddingBottom="10dp">
-
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="horizontal">
+        android:orientation="vertical"
+        android:paddingTop="16dp"
+        android:paddingBottom="10dp">
 
-      <org.chromium.ui.widget.ChromeImageView
-          android:id="@+id/bookmark_select_folder"
-          android:layout_width="72dp"
-          android:layout_height="72dp"
-          android:layout_marginHorizontal="16dp"
-          android:layout_gravity="center_vertical|start"
-          android:background="@drawable/bookmark_save_flow_background"
-          android:foreground="@drawable/bookmark_save_flow_ripple"
-          android:scaleType="centerInside"
-          android:contentDescription="@string/bookmark_action_bar_edit_folder" />
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
 
-      <LinearLayout
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:layout_gravity="center_vertical|start"
-          android:orientation="vertical"
-          android:screenReaderFocusable="true"
-          android:focusable="true">
+            <org.chromium.ui.widget.ChromeImageView
+                android:id="@+id/bookmark_select_folder"
+                android:layout_width="72dp"
+                android:layout_height="72dp"
+                android:layout_marginHorizontal="16dp"
+                android:layout_gravity="center_vertical|start"
+                android:background="@drawable/bookmark_save_flow_background"
+                android:foreground="@drawable/bookmark_save_flow_ripple"
+                android:scaleType="centerInside"
+                android:contentDescription="@string/bookmark_action_bar_edit_folder" />
 
-        <TextView
-            android:id="@+id/title_text"
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical|start"
+                android:orientation="vertical"
+                android:screenReaderFocusable="true"
+                android:focusable="true">
+
+            <TextView
+                android:id="@+id/title_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:textAppearance="@style/TextAppearance.TextMediumThick.Primary"
+                android:focusable="false" />
+
+            <TextView
+                android:id="@+id/subtitle_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:textAppearance="@style/TextAppearance.TextMedium.Primary"
+                android:focusable="false" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1" />
+
+        <FrameLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:ellipsize="end"
-            android:textAppearance="@style/TextAppearance.TextMediumThick.Primary"
-            android:focusable="false" />
-
-        <TextView
-            android:id="@+id/subtitle_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:ellipsize="end"
-            android:textAppearance="@style/TextAppearance.TextMedium.Primary"
-            android:focusable="false" />
-      </LinearLayout>
-
-      <View
-          android:layout_width="0dp"
-          android:layout_height="match_parent"
-          android:layout_weight="1" />
-
-      <org.chromium.ui.widget.ButtonCompat
-          android:id="@+id/bookmark_edit"
-          android:layout_width="wrap_content"
-          android:layout_height="72dp"
-          android:text="@string/bookmark_item_edit"
-          style="@style/TextButton"
-          android:paddingStart="24dp"
-          android:paddingEnd="24dp"
-          android:textAppearance="@style/TextAppearance.TextMedium.Link"/>
-  </LinearLayout>
+            android:layout_gravity="center_vertical">
+            <org.chromium.ui.widget.ButtonCompat
+                android:id="@+id/bookmark_edit"
+                android:layout_width="wrap_content"
+                android:layout_height="48dp"
+                android:text="@string/bookmark_item_edit"
+                style="@style/TextButton"
+                android:paddingStart="24dp"
+                android:paddingEnd="24dp"
+                android:textAppearance="@style/TextAppearance.TextMedium.Link"/>
+        </FrameLayout>
+    </LinearLayout>
 
     <View
         android:id="@+id/notification_switch_divider"
@@ -88,44 +93,44 @@
         android:orientation="horizontal"
         android:visibility="gone">
 
-      <org.chromium.ui.widget.ChromeImageView
-          android:id="@+id/notification_switch_start_icon"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:layout_marginHorizontal="16dp"
-          android:layout_gravity="center_vertical"
-          android:importantForAccessibility="no" />
+        <org.chromium.ui.widget.ChromeImageView
+            android:id="@+id/notification_switch_start_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="16dp"
+            android:layout_gravity="center_vertical"
+            android:importantForAccessibility="no" />
 
-      <LinearLayout
-          android:layout_width="0dp"
-          android:layout_height="wrap_content"
-          android:layout_weight="1"
-          android:layout_gravity="center_vertical"
-          android:orientation="vertical"
-          android:focusable="true">
-        <TextView
-            android:id="@+id/notification_switch_title"
-            android:text="@string/enable_price_tracking_menu_item"
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_gravity="center_vertical"
+            android:orientation="vertical"
+            android:focusable="true">
+            <TextView
+                android:id="@+id/notification_switch_title"
+                android:text="@string/enable_price_tracking_menu_item"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:textAppearance="@style/TextAppearance.TextMedium.Primary"
+                android:labelFor="@+id/notification_switch" />
+
+            <TextView
+                android:id="@+id/notification_switch_subtitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:textAppearance="@style/TextAppearance.TextSmall.Secondary" />
+        </LinearLayout>
+
+        <androidx.appcompat.widget.SwitchCompat
+            android:id="@+id/notification_switch"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
-            android:textAppearance="@style/TextAppearance.TextMedium.Primary"
-            android:labelFor="@+id/notification_switch" />
-
-        <TextView
-            android:id="@+id/notification_switch_subtitle"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:textAppearance="@style/TextAppearance.TextSmall.Secondary" />
-      </LinearLayout>
-
-      <androidx.appcompat.widget.SwitchCompat
-          android:id="@+id/notification_switch"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:layout_gravity="center_vertical"
-          android:layout_marginEnd="24dp" />
+            android:layout_marginEnd="24dp" />
+        </LinearLayout>
     </LinearLayout>
-  </LinearLayout>
 </org.chromium.ui.widget.ViewLookupCachingFrameLayout>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 2451cdb4..6d1dcca 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -435,4 +435,7 @@
     <!-- Section tab dimensions -->
     <dimen name="section_tab_background_corner_radius">60dp</dimen>
     <dimen name="section_tab_bezel_width">2dp</dimen>
+
+    <!-- Price Drop dimensions -->
+    <dimen name="price_drop_spotted_iph_ntp_tabswitcher_y_inset">4dp</dimen>
 </resources>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/DEPS
index 183a8072..8a85d99 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/DEPS
@@ -25,6 +25,7 @@
     "+chrome/android/java/src/org/chromium/chrome/browser",
     "-chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
     "-chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java",
+    "+components/feature_engagement",
   ],
   'ToolbarButtonInProductHelpController.java': [
     "+chrome/android/java/src/org/chromium/chrome/browser",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 5f4f6a8..e486b27 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -92,6 +92,7 @@
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
 import org.chromium.chrome.browser.page_info.ChromePageInfo;
 import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations;
+import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
@@ -141,6 +142,7 @@
 import org.chromium.chrome.browser.ui.native_page.NativePage;
 import org.chromium.chrome.browser.ui.system.StatusBarColorController;
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
+import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
 import org.chromium.chrome.browser.user_education.UserEducationHelper;
 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
 import org.chromium.chrome.browser.vr.VrModuleProvider;
@@ -149,9 +151,12 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.styles.ChromeColors;
 import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
+import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightParams;
+import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightShape;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.embedder_support.util.UrlUtilities;
+import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.page_info.PageInfoController.OpenedFromSource;
 import org.chromium.components.search_engines.TemplateUrl;
 import org.chromium.components.search_engines.TemplateUrlService;
@@ -1913,11 +1918,36 @@
         checkIfNtpLoaded();
     }
 
+    private void maybeShowPriceDropIPH() {
+        // TODO(crbug.com/1335197): Check if price drop occurred before showing IPH
+        if (!PriceTrackingFeatures.isPriceDropIphEnabled()) {
+            return;
+        }
+
+        ToggleTabStackButton toggleTabStackButton =
+                mControlContainer.findViewById(R.id.tab_switcher_button);
+        UserEducationHelper userEducationHelper = new UserEducationHelper(mActivity, mHandler);
+        HighlightParams params = new HighlightParams(HighlightShape.CIRCLE);
+        params.setBoundsRespectPadding(true);
+        userEducationHelper.requestShowIPH(
+                new IPHCommandBuilder(mControlContainer.getResources(),
+                        FeatureConstants.PRICE_DROP_NTP_FEATURE, R.string.price_drop_spotted_iph,
+                        R.string.price_drop_spotted_iph)
+                        .setInsetRect(new Rect(0, 0, 0,
+                                -(mControlContainer.getResources().getDimensionPixelOffset(
+                                        R.dimen.price_drop_spotted_iph_ntp_tabswitcher_y_inset))))
+                        .setAnchorView(toggleTabStackButton)
+                        .setHighlightParams(params)
+                        .setDismissOnTouch(true)
+                        .build());
+    }
+
     private void checkIfNtpLoaded() {
         NewTabPage ntp = getNewTabPageForCurrentTab();
         if (ntp != null) {
             ntp.setOmniboxStub(mLocationBar.getOmniboxStub());
             mLocationBarModel.notifyNtpStartedLoading();
+            maybeShowPriceDropIPH();
         }
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/UndoRefocusHelperTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/UndoRefocusHelperTest.java
index b3c3ff1..d00bd15b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/UndoRefocusHelperTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/UndoRefocusHelperTest.java
@@ -220,6 +220,34 @@
     }
 
     @Test
+    @Feature("Tab Strip Improvements")
+    public void testUndoSingleTabClose_AfterClosingSelectedTabs_ReselectsMostRecentlyClosedTab() {
+        // Arrange: Start with fourth tab as selected index
+        initializeTabModel(3);
+        TabModelSelectorTabModelObserver tabModelSelectorTabModelObserver =
+                mUndoRefocusHelper.getTabModelSelectorTabModelObserverForTests();
+
+        // Act 1: Close the fourth tab.
+        Tab fourthTab = getMockedTab(3);
+        tabModelSelectorTabModelObserver.willCloseTab(fourthTab, false);
+        // After fourth tab is closed, the third one should be selected.
+        mModel.setIndex(2);
+
+        // Act 2: Close the third tab after it is selected.
+        Tab thirdTab = getMockedTab(2);
+        tabModelSelectorTabModelObserver.willCloseTab(thirdTab, false);
+        // After third tab is closed, the second one should be selected.
+        mModel.setIndex(1);
+
+        // Undo tab closures.
+        tabModelSelectorTabModelObserver.tabClosureUndone(thirdTab);
+        tabModelSelectorTabModelObserver.tabClosureUndone(fourthTab);
+
+        // Assert: Third tab is still selected after undo instead of the fourth tab.
+        assertEquals(2, mModel.index());
+    }
+
+    @Test
     public void testUndoTabClose_TabStrip_RecordsUserAction() {
         // Arrange: Start with fourth tab as selected index
         initializeTabModel(3);
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 0366893..e7faf59 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -2230,6 +2230,11 @@
     Set up button
   </message>
 
+  <!-- Device audio page (OS settings) -->
+  <message name="IDS_SETTINGS_AUDIO_TITLE" desc="In Device Settings, the title of the audio settings subpage.">
+    Audio
+  </message>
+
   <!-- Device pointer page (OS settings) -->
   <message name="IDS_SETTINGS_MOUSE_TITLE" desc="In Device Settings, the title of the mouse settings subpage.">
     Mouse
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_AUDIO_TITLE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_AUDIO_TITLE.png.sha1
new file mode 100644
index 0000000..b351018
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_AUDIO_TITLE.png.sha1
@@ -0,0 +1 @@
+1490e652723da8aa449da7cf145e3bcf4fb023d0
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 61b400e..fe3c333 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1536,14 +1536,6 @@
     "security_events/security_event_sync_bridge_impl.h",
     "segmentation_platform/chrome_browser_main_extra_parts_segmentation_platform.cc",
     "segmentation_platform/chrome_browser_main_extra_parts_segmentation_platform.h",
-    "segmentation_platform/default_model/feed_user_segment.cc",
-    "segmentation_platform/default_model/feed_user_segment.h",
-    "segmentation_platform/default_model/low_user_engagement_model.cc",
-    "segmentation_platform/default_model/low_user_engagement_model.h",
-    "segmentation_platform/default_model/price_tracking_action_model.cc",
-    "segmentation_platform/default_model/price_tracking_action_model.h",
-    "segmentation_platform/default_model/query_tiles_model.cc",
-    "segmentation_platform/default_model/query_tiles_model.h",
     "segmentation_platform/model_provider_factory_impl.cc",
     "segmentation_platform/model_provider_factory_impl.h",
     "segmentation_platform/segmentation_platform_config.cc",
@@ -2262,6 +2254,7 @@
     "//components/security_state/content",
     "//components/security_state/core",
     "//components/segmentation_platform/embedder",
+    "//components/segmentation_platform/embedder/default_model",
     "//components/segmentation_platform/internal",
     "//components/segmentation_platform/internal/proto",
     "//components/segmentation_platform/public",
@@ -2856,8 +2849,6 @@
       "android/usage_stats/usage_stats_bridge.h",
       "android/usage_stats/usage_stats_database.cc",
       "android/usage_stats/usage_stats_database.h",
-      "android/usb/web_usb_chooser_android.cc",
-      "android/usb/web_usb_chooser_android.h",
       "android/warmup_manager.cc",
       "android/web_contents_factory.cc",
       "android/webapk/webapk_handler_delegate.cc",
@@ -3257,6 +3248,8 @@
       "translate/android/translate_bridge.cc",
       "translate/android/translate_bridge.h",
       "usb/android/usb_bridge.cc",
+      "usb/android/web_usb_chooser_android.cc",
+      "usb/android/web_usb_chooser_android.h",
       "video_tutorials/internal/android/video_tutorial_service_bridge_factory.cc",
       "webapps/web_app_offline_android.cc",
       "webauthn/android/cable_module_android.cc",
@@ -3949,6 +3942,8 @@
       "new_tab_page/modules/drive/drive_service.h",
       "new_tab_page/modules/drive/drive_service_factory.cc",
       "new_tab_page/modules/drive/drive_service_factory.h",
+      "new_tab_page/modules/feed/feed_handler.cc",
+      "new_tab_page/modules/feed/feed_handler.h",
       "new_tab_page/modules/photos/photos_handler.cc",
       "new_tab_page/modules/photos/photos_handler.h",
       "new_tab_page/modules/photos/photos_service.cc",
@@ -4395,6 +4390,7 @@
       "//chrome/browser/new_tab_page/chrome_colors:generate_chrome_colors_info",
       "//chrome/browser/new_tab_page/chrome_colors:generate_colors_info",
       "//chrome/browser/new_tab_page/modules/drive:mojo_bindings",
+      "//chrome/browser/new_tab_page/modules/feed:mojo_bindings",
       "//chrome/browser/new_tab_page/modules/photos:mojo_bindings",
       "//chrome/browser/new_tab_page/modules/safe_browsing:mojo_bindings",
       "//chrome/browser/new_tab_page/modules/task_module:mojo_bindings",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 2158012ab..2057fb7 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -5850,6 +5850,13 @@
          "CCTResizableThirdPartiesDefaultPolicy")},
 #endif
 
+#if BUILDFLAG(IS_ANDROID)
+    {"cct-real-time-engagement-signals",
+     flag_descriptions::kCCTRealTimeEngagementSignalsName,
+     flag_descriptions::kCCTRealTimeEngagementSignalsDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(chrome::android::kCCTRealTimeEngagementSignals)},
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS)
     {"allow-dsp-based-aec", flag_descriptions::kCrOSDspBasedAecAllowedName,
      flag_descriptions::kCrOSDspBasedAecAllowedDescription, kOsCrOS | kOsLacros,
diff --git a/chrome/browser/android/customtabs/tab_interaction_recorder_android.cc b/chrome/browser/android/customtabs/tab_interaction_recorder_android.cc
index d7c268cb..8748565 100644
--- a/chrome/browser/android/customtabs/tab_interaction_recorder_android.cc
+++ b/chrome/browser/android/customtabs/tab_interaction_recorder_android.cc
@@ -46,9 +46,7 @@
   autofill_manager->AddObserver(this);
 }
 
-AutofillObserverImpl::~AutofillObserverImpl() {
-  Invalidate();
-}
+AutofillObserverImpl::~AutofillObserverImpl() = default;
 
 void AutofillObserverImpl::OnFormSubmitted() {
   OnFormInteraction();
@@ -66,16 +64,10 @@
   OnFormInteraction();
 }
 
-void AutofillObserverImpl::Invalidate() {
-  if (IsInObserverList()) {
-    DCHECK(autofill_manager_);
-    autofill_manager_->RemoveObserver(this);
-    autofill_manager_ = nullptr;
-  }
-}
-
 void AutofillObserverImpl::OnFormInteraction() {
-  Invalidate();
+  DCHECK(autofill_manager_);
+  autofill_manager_->RemoveObserver(this);
+  autofill_manager_ = nullptr;
   std::move(form_interaction_callback_).Run();
 }
 
@@ -128,10 +120,6 @@
 
 void TabInteractionRecorderAndroid::StartObservingFrame(
     RenderFrameHost* render_frame_host) {
-  // Do not observe the same frame more than once.
-  if (rfh_observer_map_[render_frame_host->GetGlobalId()])
-    return;
-
   AutofillManager* autofill_manager =
       test_autofill_manager_ ? test_autofill_manager_.get()
                              : GetAutofillManager(render_frame_host);
diff --git a/chrome/browser/android/customtabs/tab_interaction_recorder_android.h b/chrome/browser/android/customtabs/tab_interaction_recorder_android.h
index 8c9c7f8..0951db6 100644
--- a/chrome/browser/android/customtabs/tab_interaction_recorder_android.h
+++ b/chrome/browser/android/customtabs/tab_interaction_recorder_android.h
@@ -38,7 +38,6 @@
 
  private:
   void OnFormInteraction();
-  void Invalidate();
 
   raw_ptr<autofill::AutofillManager> autofill_manager_;
   OnFormInteractionCallback form_interaction_callback_;
@@ -65,17 +64,10 @@
   bool has_form_interactions() const { return has_form_interactions_; }
 
   // content::WebContentsObserver:
-
-  // Dispatch an AutofillManagerObserver when a frame becomes active, or remove
-  // the old AutofillManagerObserver when a frame becomes inactive.
   void RenderFrameHostStateChanged(
       content::RenderFrameHost* render_frame_host,
       content::RenderFrameHost::LifecycleState old_state,
       content::RenderFrameHost::LifecycleState new_state) override;
-  // When a new frame is created, |RenderFrameHostStateChanged| happen earlier
-  // than a AutofillManager initialized in such frame, which happened at
-  // |DidFinishNavigation|. Observing |DidFinishNavigation| ensure this class
-  // to dispatch the AutofillManagerObserver for newly created AutofillManager.
   void DidFinishNavigation(
       content::NavigationHandle* navigation_handle) override;
   void DidGetUserInteraction(const blink::WebInputEvent& event) override;
diff --git a/chrome/browser/android/customtabs/tab_interaction_recorder_android_unittest.cc b/chrome/browser/android/customtabs/tab_interaction_recorder_android_unittest.cc
index 15864bc..c62365d1 100644
--- a/chrome/browser/android/customtabs/tab_interaction_recorder_android_unittest.cc
+++ b/chrome/browser/android/customtabs/tab_interaction_recorder_android_unittest.cc
@@ -80,12 +80,13 @@
     manager_ = std::make_unique<MockAutofillManager>(driver_.get(), &client_);
   }
 
-  void TearDown() override { driver_.reset(); }
+  void TearDown() override {
+    manager_.reset();
+    driver_.reset();
+  }
 
   MockAutofillManager* autofill_manager() { return manager_.get(); }
 
-  void DestroyManager() { manager_.release(); }
-
  protected:
   base::test::TaskEnvironment task_environment_;
   NiceMock<MockAutofillClient> client_;
@@ -113,16 +114,6 @@
   delete observer;
 }
 
-TEST_F(AutofillObserverImplTest, TestAutofillManagerDestroy) {
-  base::MockOnceCallback<void()> callback;
-  auto* observer = new AutofillObserverImpl(autofill_manager(), callback.Get());
-
-  DestroyManager();
-
-  EXPECT_CALL(callback, Run()).Times(0);
-  delete observer;
-}
-
 // === TabInteractionRecorderAndroidTest ===
 
 class TabInteractionRecorderAndroidTest
@@ -177,30 +168,6 @@
   EXPECT_TRUE(helper->has_form_interactions());
 }
 
-TEST_F(TabInteractionRecorderAndroidTest, HadFormInteractionOn2ndPage) {
-  std::unique_ptr<content::WebContents> contents = CreateTestWebContents();
-  auto* helper = TabInteractionRecorderAndroid::FromWebContents(contents.get());
-
-  EXPECT_FALSE(helper->has_form_interactions());
-
-  // Set a new autofill manager, assuming it is the autofill manager in the new
-  // frame. Since web content has navigated away from prev frame, autofill
-  // manager observer should be removed from previous autofill manager.
-  std::unique_ptr<MockAutofillManager> new_autofill_manager =
-      std::make_unique<MockAutofillManager>(driver_.get(), &client_);
-  helper->SetAutofillManagerForTest(new_autofill_manager.get());
-
-  content::WebContentsTester::For(contents.get())
-      ->NavigateAndCommit(GURL("https://bar.com"));
-  task_environment()->RunUntilIdle();
-
-  OnTextFieldDidChangeForAutofillManager(autofill_manager());
-  EXPECT_FALSE(helper->has_form_interactions());
-
-  OnTextFieldDidChangeForAutofillManager(new_autofill_manager.get());
-  EXPECT_TRUE(helper->has_form_interactions());
-}
-
 TEST_F(TabInteractionRecorderAndroidTest, HasNavigatedFromFirstPage) {
   std::unique_ptr<content::WebContents> contents = CreateTestWebContents();
   auto* helper = TabInteractionRecorderAndroid::FromWebContents(contents.get());
diff --git a/chrome/browser/apps/app_service/app_service_proxy_desktop.cc b/chrome/browser/apps/app_service/app_service_proxy_desktop.cc
index 2a10ac2..ee1204b1 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_desktop.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_desktop.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_desktop.h"
 
 #include "chrome/browser/web_applications/app_service/web_app_publisher_helper.h"
+#include "chrome/browser/web_applications/web_app_command_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "components/services/app_service/app_service_mojom_impl.h"
 #include "components/services/app_service/public/cpp/app_types.h"
@@ -44,6 +45,9 @@
 void AppServiceProxy::FlushMojoCallsForTesting() {
   app_service_mojom_impl_->FlushMojoCallsForTesting();
   receivers_.FlushForTesting();
+  web_app::WebAppProvider::GetForTest(profile())
+      ->command_manager()
+      .AwaitAllCommandsCompleteForTesting();
 }
 
 void AppServiceProxy::SetRunOnOsLoginMode(
diff --git a/chrome/browser/ash/accessibility/OWNERS b/chrome/browser/ash/accessibility/OWNERS
index b39af675..4dec886 100644
--- a/chrome/browser/ash/accessibility/OWNERS
+++ b/chrome/browser/ash/accessibility/OWNERS
@@ -1,3 +1,4 @@
 file://ui/accessibility/OWNERS
 
 akihiroota@chromium.org
+anastasi@google.com
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.cc b/chrome/browser/ash/accessibility/accessibility_manager.cc
index 0f061785..1760adc 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.cc
+++ b/chrome/browser/ash/accessibility/accessibility_manager.cc
@@ -1516,8 +1516,16 @@
   app_terminating_ = true;
 }
 
+void AccessibilityManager::OnShimlessRmaLaunched() {
+  SetActiveProfile();
+}
+
 void AccessibilityManager::OnLoginOrLockScreenVisible() {
   // Update `profile_` when entering the login screen.
+  SetActiveProfile();
+}
+
+void AccessibilityManager::SetActiveProfile() {
   Profile* profile = ProfileManager::GetActiveUserProfile();
   if (ProfileHelper::IsSigninProfile(profile))
     SetProfile(profile);
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.h b/chrome/browser/ash/accessibility/accessibility_manager.h
index d50f948..a5f7419 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.h
+++ b/chrome/browser/ash/accessibility/accessibility_manager.h
@@ -360,6 +360,9 @@
   void OnSelectToSpeakPanelAction(SelectToSpeakPanelAction action,
                                   double value);
 
+  // Called when Shimless RMA launches to enable accessibility features.
+  void OnShimlessRmaLaunched();
+
   // SodaInstaller::Observer:
   void OnSodaInstalled(speech::LanguageCode language_code) override;
   void OnSodaError(speech::LanguageCode language_code) override;
@@ -457,6 +460,9 @@
   // session_manager::SessionManagerObserver:
   void OnLoginOrLockScreenVisible() override;
 
+  // Sets the current profile using the active profile.
+  void SetActiveProfile();
+
   // extensions::api::braille_display_private::BrailleObserver implementation.
   // Enables spoken feedback if a braille display becomes available.
   void OnBrailleDisplayStateChanged(
diff --git a/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc b/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc
index 637ec9a1..763d6f9 100644
--- a/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc
+++ b/chrome/browser/ash/accessibility/spoken_feedback_app_list_browsertest.cc
@@ -221,7 +221,10 @@
   void ReadWindowTitle() {
     extensions::browsertest_util::ExecuteScriptInBackgroundPageNoWait(
         browser()->profile(), extension_misc::kChromeVoxExtensionId,
-        "CommandHandlerInterface.instance.onCommand('readCurrentTitle');");
+        "import('/chromevox/background/"
+        "command_handler_interface.js').then(module => "
+        "module.CommandHandlerInterface.instance.onCommand('readCurrentTitle'))"
+        ";");
   }
 
   AppListItem* FindItemByName(const std::string& name, int* index) {
diff --git a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
index d177393..f9df3fd8 100644
--- a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
+++ b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
@@ -138,9 +138,7 @@
 
 void LoggedInSpokenFeedbackTest::SendStickyKeyCommand() {
   // To avoid flakes in sending keys, execute the command directly in js.
-  extensions::browsertest_util::ExecuteScriptInBackgroundPageNoWait(
-      browser()->profile(), extension_misc::kChromeVoxExtensionId,
-      "CommandHandlerInterface.instance.onCommand('toggleStickyMode');");
+  ExecuteCommandHandlerCommand("toggleStickyMode");
 }
 
 void LoggedInSpokenFeedbackTest::SendMouseMoveTo(const gfx::Point& location) {
@@ -185,6 +183,16 @@
   sm_.ExpectSpeech("Click me");
 }
 
+void LoggedInSpokenFeedbackTest::ExecuteCommandHandlerCommand(
+    std::string command) {
+  extensions::browsertest_util::ExecuteScriptInBackgroundPageNoWait(
+      browser()->profile(), extension_misc::kChromeVoxExtensionId,
+      "import('/chromevox/background/"
+      "command_handler_interface.js').then(module => "
+      "module.CommandHandlerInterface.instance.onCommand('" +
+          command + "'));");
+}
+
 // Flaky test, crbug.com/1081563
 IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest, DISABLED_AddBookmark) {
   EnableChromeVox();
@@ -269,11 +277,7 @@
 // logged in.
 IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest, LearnModeHardwareKeys) {
   EnableChromeVox();
-  sm_.Call([this]() {
-    extensions::browsertest_util::ExecuteScriptInBackgroundPageNoWait(
-        browser()->profile(), extension_misc::kChromeVoxExtensionId,
-        "CommandHandlerInterface.instance.onCommand('showLearnModePage');");
-  });
+  sm_.Call([this]() { ExecuteCommandHandlerCommand("showLearnModePage"); });
   sm_.ExpectSpeechPattern(
       "Press a qwerty key, refreshable braille key, or touch gesture to learn "
       "*");
@@ -306,11 +310,7 @@
 
 IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest, LearnModeEscapeWithGesture) {
   EnableChromeVox();
-  sm_.Call([this]() {
-    extensions::browsertest_util::ExecuteScriptInBackgroundPageNoWait(
-        browser()->profile(), extension_misc::kChromeVoxExtensionId,
-        "CommandHandlerInterface.instance.onCommand('showLearnModePage');");
-  });
+  sm_.Call([this]() { ExecuteCommandHandlerCommand("showLearnModePage"); });
   sm_.ExpectSpeechPattern(
       "Press a qwerty key, refreshable braille key, or touch gesture to learn "
       "*");
diff --git a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.h b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.h
index ae39a21..a3f919e 100644
--- a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.h
+++ b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.h
@@ -54,6 +54,8 @@
 
   void StablizeChromeVoxState();
 
+  void ExecuteCommandHandlerCommand(std::string command);
+
   void PressRepeatedlyUntilUtterance(ui::KeyboardCode key,
                                      const std::string& expected_utterance);
 
diff --git a/chrome/browser/ash/crosapi/browser_data_migrator.cc b/chrome/browser/ash/crosapi/browser_data_migrator.cc
index 8069753..d437d807f 100644
--- a/chrome/browser/ash/crosapi/browser_data_migrator.cc
+++ b/chrome/browser/ash/crosapi/browser_data_migrator.cc
@@ -302,27 +302,19 @@
     return true;
   }
 
-  crosapi::browser_util::MigrationMode mode =
-      crosapi::browser_util::GetMigrationMode(user, policy_init_state);
-
-  if (crosapi::browser_util::IsProfileMigrationCompletedForUser(
-          local_state, user_id_hash, mode)) {
+  if (crosapi::browser_util::IsCopyOrMoveProfileMigrationCompletedForUser(
+          local_state, user_id_hash)) {
     // TODO(crbug.com/1277848): Once `BrowserDataMigrator` stabilises,
     // remove this log message.
+    if (crosapi::browser_util::IsProfileMigrationCompletedForUser(
+            local_state, user_id_hash,
+            crosapi::browser_util::MigrationMode::kMove)) {
+      LOG(WARNING) << "Profile move migration has been completed already.";
+    }
     LOG(WARNING) << "Profile migration has been completed already.";
     return false;
   }
 
-  // TODO(crbug.com/1277848): Once `BrowserDataMigrator` stabilises,
-  // remove this log message.
-  if (mode == crosapi::browser_util::MigrationMode::kMove &&
-      crosapi::browser_util::IsProfileMigrationCompletedForUser(
-          local_state, user_id_hash,
-          crosapi::browser_util::MigrationMode::kCopy)) {
-    LOG(WARNING) << "Only copy migration is marked as completed. Running "
-                    "profile move migraiton.";
-  }
-
   return true;
 }
 
diff --git a/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc b/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
index a578e33..baa47b86 100644
--- a/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
@@ -494,8 +494,11 @@
   }
 
   {
-    // If copy migration is marked as completed but not move then migration
-    // should run if move migration is enabled.
+    // If copy migration is marked as completed then migration should not run
+    // even if move migration is enabled.
+    // TODO(crbug.com/1340438): This previously checked the opposite where the
+    // expectation was to run move migration even if copy migration is
+    // completed. Revisit this part if we decide to restore that behaviour.
     base::test::ScopedFeatureList feature_list;
     feature_list.InitWithFeatures(
         {ash::features::kLacrosSupport, ash::features::kLacrosPrimary,
@@ -505,7 +508,7 @@
     EXPECT_EQ(crosapi::browser_util::GetMigrationMode(
                   user, crosapi::browser_util::PolicyInitState::kAfterInit),
               crosapi::browser_util::MigrationMode::kMove);
-    EXPECT_TRUE(BrowserDataMigratorImpl::MaybeRestartToMigrateInternal(
+    EXPECT_FALSE(BrowserDataMigratorImpl::MaybeRestartToMigrateInternal(
         user->GetAccountId(), user->username_hash(),
         crosapi::browser_util::PolicyInitState::kAfterInit));
   }
diff --git a/chrome/browser/ash/crosapi/browser_util.cc b/chrome/browser/ash/crosapi/browser_util.cc
index 356817d4..8553ae5 100644
--- a/chrome/browser/ash/crosapi/browser_util.cc
+++ b/chrome/browser/ash/crosapi/browser_util.cc
@@ -367,12 +367,10 @@
           user_manager::UserManager::Get()->GetPrimaryUser()->GetAccountId())) {
     PrefService* local_state = g_browser_process->local_state();
     // Note that local_state can be nullptr in tests.
-    if (local_state &&
-        !IsProfileMigrationCompletedForUser(
-            local_state,
-            user_manager::UserManager::Get()->GetPrimaryUser()->username_hash(),
-            GetMigrationMode(user_manager::UserManager::Get()->GetPrimaryUser(),
-                             PolicyInitState::kAfterInit))) {
+    if (local_state && !IsCopyOrMoveProfileMigrationCompletedForUser(
+                           local_state, user_manager::UserManager::Get()
+                                            ->GetPrimaryUser()
+                                            ->username_hash())) {
       // If migration has not been completed, do not enable lacros.
       return false;
     }
@@ -457,9 +455,8 @@
     return false;
 
   // If migration is already completed, it is not necessary to run again.
-  if (IsProfileMigrationCompletedForUser(
-          user_manager->GetLocalState(), user->username_hash(),
-          GetMigrationMode(user, PolicyInitState::kAfterInit))) {
+  if (IsCopyOrMoveProfileMigrationCompletedForUser(
+          user_manager->GetLocalState(), user->username_hash())) {
     return false;
   }
 
@@ -910,6 +907,15 @@
   return MigrationMode::kCopy;
 }
 
+bool IsCopyOrMoveProfileMigrationCompletedForUser(
+    PrefService* local_state,
+    const std::string& user_id_hash) {
+  // Completion of profile move migration sets copy migration as completed so
+  // only checking completion of copy migration is sufficient.
+  return IsProfileMigrationCompletedForUser(local_state, user_id_hash,
+                                            MigrationMode::kCopy);
+}
+
 bool IsProfileMigrationCompletedForUser(PrefService* local_state,
                                         const std::string& user_id_hash,
                                         MigrationMode mode) {
diff --git a/chrome/browser/ash/crosapi/browser_util.h b/chrome/browser/ash/crosapi/browser_util.h
index e225697..d8edb49 100644
--- a/chrome/browser/ash/crosapi/browser_util.h
+++ b/chrome/browser/ash/crosapi/browser_util.h
@@ -321,6 +321,14 @@
 MigrationMode GetMigrationMode(const user_manager::User* user,
                                PolicyInitState policy_init_state);
 
+// Returns true if either copy or move migration is completed. Used as a wrapper
+// over `IsProfileMigrationCompletedForUser()`.
+// TODO(crbug.com/1340438): This function is introduced to prevent running
+// profile move migration for users who have already completed copy migration.
+bool IsCopyOrMoveProfileMigrationCompletedForUser(
+    PrefService* local_state,
+    const std::string& user_id_hash);
+
 // Checks if profile migration has been completed. This is reset if profile
 // migration is initiated for example due to lacros data directory being wiped.
 bool IsProfileMigrationCompletedForUser(PrefService* local_state,
diff --git a/chrome/browser/ash/crosapi/browser_util_unittest.cc b/chrome/browser/ash/crosapi/browser_util_unittest.cc
index 64fa19b..bb6d9a6 100644
--- a/chrome/browser/ash/crosapi/browser_util_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_util_unittest.cc
@@ -784,6 +784,31 @@
       &pref_service_, user_id_hash, browser_util::MigrationMode::kMove));
 }
 
+TEST_F(BrowserUtilTest, IsCopyOrMoveProfileMigrationCompletedForUser) {
+  const std::string user_id_hash = "abcd";
+  // `IsCopyOrMoveProfileMigrationCompletedForUser()` should return
+  // false by default.
+  EXPECT_FALSE(browser_util::IsCopyOrMoveProfileMigrationCompletedForUser(
+      &pref_service_, user_id_hash));
+
+  // Setting copy migration as completed makes
+  // `IsCopyOrMoveProfileMigrationCompletedForUser()` return true.
+  browser_util::SetProfileMigrationCompletedForUser(
+      &pref_service_, user_id_hash, browser_util::MigrationMode::kCopy);
+  EXPECT_TRUE(browser_util::IsCopyOrMoveProfileMigrationCompletedForUser(
+      &pref_service_, user_id_hash));
+
+  browser_util::ClearProfileMigrationCompletedForUser(&pref_service_,
+                                                      user_id_hash);
+
+  // Setting move migration as completed makes
+  // `IsCopyOrMoveProfileMigrationCompletedForUser()` return true.
+  browser_util::SetProfileMigrationCompletedForUser(
+      &pref_service_, user_id_hash, browser_util::MigrationMode::kMove);
+  EXPECT_TRUE(browser_util::IsCopyOrMoveProfileMigrationCompletedForUser(
+      &pref_service_, user_id_hash));
+}
+
 TEST_F(BrowserUtilTest, IsAshBrowserSyncEnabled) {
   {
     browser_util::SetLacrosEnabledForTest(false);
diff --git a/chrome/browser/ash/dbus/encrypted_reporting_service_provider.cc b/chrome/browser/ash/dbus/encrypted_reporting_service_provider.cc
index e4846b7..92dbac8 100644
--- a/chrome/browser/ash/dbus/encrypted_reporting_service_provider.cc
+++ b/chrome/browser/ash/dbus/encrypted_reporting_service_provider.cc
@@ -35,7 +35,7 @@
 namespace {
 
 static constexpr uint64_t kDefaultMemoryAllocation =
-    64u * 1024uLL * 1024uLL;  // 64 MiB by default
+    16u * 1024uLL * 1024uLL;  // 16 MiB by default
 
 void SendStatusAsResponse(std::unique_ptr<dbus::Response> response,
                           dbus::ExportedObject::ResponseSender response_sender,
@@ -151,12 +151,15 @@
     return;
   }
 
-  reporting::UploadEncryptedRecordRequest request;
   dbus::MessageReader reader(method_call);
-  if (!reader.PopArrayOfBytesAsProto(&request)) {
+  const char* serialized_request_buf = nullptr;
+  size_t serialized_request_buf_size = 0;
+  if (!reader.PopArrayOfBytes(
+          reinterpret_cast<const uint8_t**>(&serialized_request_buf),
+          &serialized_request_buf_size)) {
     reporting::Status status{
         reporting::error::INVALID_ARGUMENT,
-        "Message was not decipherable as an UploadEncryptedRecordRequest"};
+        "Error reading UploadEncryptedRecordRequest as array of bytes"};
     LOG(ERROR) << "Unable to process UploadEncryptedRecordRequest. status: "
                << status;
     SendStatusAsResponse(std::move(response), std::move(response_sender),
@@ -164,7 +167,7 @@
     return;
   }
 
-  ::reporting::ScopedReservation scoped_reservation(request.ByteSizeLong(),
+  ::reporting::ScopedReservation scoped_reservation(serialized_request_buf_size,
                                                     memory_resource_);
   if (!scoped_reservation.reserved()) {
     reporting::Status status{reporting::error::RESOURCE_EXHAUSTED,
@@ -177,6 +180,19 @@
     return;
   }
 
+  reporting::UploadEncryptedRecordRequest request;
+  if (!request.ParseFromArray(serialized_request_buf,
+                              serialized_request_buf_size)) {
+    reporting::Status status{
+        reporting::error::INVALID_ARGUMENT,
+        "Failed to parse UploadEncryptedRecordRequest from array of bytes."};
+    LOG(ERROR) << "Unable to process UploadEncryptedRecordRequest. status: "
+               << status;
+    SendStatusAsResponse(std::move(response), std::move(response_sender),
+                         status);
+    return;
+  }
+
   // Missive should always send the remaining storage capacity and new events
   // rate. If not, probably an outdated version of missive is running. In this
   // case, we ignore the effect of remaining storage capacity/new events rate
diff --git a/chrome/browser/ash/login/reporting/login_logout_reporter_unittest.cc b/chrome/browser/ash/login/reporting/login_logout_reporter_unittest.cc
index b8dfc68..ef79898 100644
--- a/chrome/browser/ash/login/reporting/login_logout_reporter_unittest.cc
+++ b/chrome/browser/ash/login/reporting/login_logout_reporter_unittest.cc
@@ -159,7 +159,8 @@
 
     auto reporter_helper =
         std::make_unique<::reporting::UserEventReporterHelperTesting>(
-            reporting_enabled, should_report_user, std::move(mock_queue));
+            reporting_enabled, should_report_user, /*is_kiosk_user=*/false,
+            std::move(mock_queue));
     return reporter_helper;
   }
 
diff --git a/chrome/browser/ash/policy/reporting/remoting_host_event_reporter_delegate_impl.cc b/chrome/browser/ash/policy/reporting/remoting_host_event_reporter_delegate_impl.cc
index 1f3ac01..b8926bff 100644
--- a/chrome/browser/ash/policy/reporting/remoting_host_event_reporter_delegate_impl.cc
+++ b/chrome/browser/ash/policy/reporting/remoting_host_event_reporter_delegate_impl.cc
@@ -2,12 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/ash/policy/reporting/remoting_host_event_reporter_delegate_impl.h"
+
 #include <memory>
 
 #include "ash/components/settings/cros_settings_names.h"
 #include "base/memory/ref_counted_delete_on_sequence.h"
 #include "base/memory/scoped_refptr.h"
-#include "base/strings/string_piece.h"
 #include "chrome/browser/ash/policy/reporting/user_event_reporter_helper.h"
 #include "chrome/browser/policy/messaging_layer/proto/synced/crd_event.pb.h"
 #include "components/reporting/proto/synced/record_constants.pb.h"
@@ -15,77 +16,70 @@
 
 namespace remoting {
 
-class HostStatusMonitor;
-
-namespace {
-
 // Production implementation of CRD event reporter for remoting host.
-class HostEventReporterDelegateImpl : public HostEventReporterImpl::Delegate {
+HostEventReporterDelegateImpl::HostEventReporterDelegateImpl(
+    std::unique_ptr<::reporting::UserEventReporterHelper> user_event_helper)
+    : helper_(base::MakeRefCounted<Helper>(
+          ::reporting::UserEventReporterHelper::valid_task_runner(),
+          std::move(user_event_helper))) {}
+
+HostEventReporterDelegateImpl::~HostEventReporterDelegateImpl() = default;
+
+// Helper class - refcounted in order to keep it alive until asynchronous
+// Enqueue task completes.
+class HostEventReporterDelegateImpl::Helper
+    : public base::RefCountedDeleteOnSequence<
+          HostEventReporterDelegateImpl::Helper> {
  public:
-  HostEventReporterDelegateImpl()
-      : helper_(base::MakeRefCounted<Helper>(
-            ::reporting::UserEventReporterHelper::valid_task_runner())) {}
+  Helper(
+      scoped_refptr<base::SequencedTaskRunner> task_runner,
+      std::unique_ptr<::reporting::UserEventReporterHelper> user_event_helper)
+      : base::RefCountedDeleteOnSequence<Helper>(task_runner),
+        task_runner_(task_runner),
+        user_event_helper_(std::move(user_event_helper)) {}
 
-  HostEventReporterDelegateImpl(const HostEventReporterDelegateImpl& other) =
-      delete;
-  HostEventReporterDelegateImpl& operator=(
-      const HostEventReporterDelegateImpl& other) = delete;
-  ~HostEventReporterDelegateImpl() override = default;
-
- private:
-  // Helper class - refcounted in order to keep it alive until asynchronous
-  // Enqueue task completes.
-  class Helper : public base::RefCountedDeleteOnSequence<Helper> {
-   public:
-    explicit Helper(scoped_refptr<base::SequencedTaskRunner> task_runner)
-        : base::RefCountedDeleteOnSequence<Helper>(task_runner),
-          task_runner_(task_runner),
-          user_event_helper_(::reporting::Destination::CRD_EVENTS,
-                             ::reporting::EventType::kUser) {}
-
-    void EnqueueEvent(::ash::reporting::CRDRecord record) {
-      task_runner_->PostTask(
-          FROM_HERE,
-          base::BindOnce(&Helper::EnqueueEventSync, base::WrapRefCounted(this),
-                         std::move(record)));
-    }
-
-   private:
-    friend class base::RefCountedDeleteOnSequence<Helper>;
-    friend class base::DeleteHelper<Helper>;
-
-    // Private destructor in ref-counted class.
-    ~Helper() = default;
-
-    void EnqueueEventSync(::ash::reporting::CRDRecord record) {
-      if (!user_event_helper_.ReportingEnabled(::ash::kReportCRDSessions)) {
-        return;
-      }
-      if (!user_event_helper_.ShouldReportUser(
-              record.host_user().user_email())) {
-        record.clear_host_user();  // anonymize host user.
-      }
-      user_event_helper_.ReportEvent(
-          std::make_unique<::ash::reporting::CRDRecord>(std::move(record)),
-          ::reporting::Priority::FAST_BATCH);
-    }
-
-    // Task runner for asynchronous actions (including destructor).
-    const scoped_refptr<base::SequencedTaskRunner> task_runner_;
-
-    // Helper for posting events.
-    ::reporting::UserEventReporterHelper user_event_helper_;
-  };
-
-  void EnqueueEvent(::ash::reporting::CRDRecord record) override {
-    helper_->EnqueueEvent(std::move(record));
+  void EnqueueEvent(::ash::reporting::CRDRecord record) {
+    task_runner_->PostTask(FROM_HERE, base::BindOnce(&Helper::EnqueueEventSync,
+                                                     base::WrapRefCounted(this),
+                                                     std::move(record)));
   }
 
+ private:
+  friend class base::RefCountedDeleteOnSequence<
+      HostEventReporterDelegateImpl::Helper>;
+  friend class base::DeleteHelper<HostEventReporterDelegateImpl::Helper>;
+
+  // Private destructor in ref-counted class.
+  ~Helper() = default;
+
+  void EnqueueEventSync(::ash::reporting::CRDRecord record) {
+    if (!user_event_helper_->ReportingEnabled(::ash::kReportCRDSessions)) {
+      return;
+    }
+    if (!user_event_helper_->ShouldReportUser(
+            record.host_user().user_email())) {
+      record.clear_host_user();  // anonymize host user.
+      if (user_event_helper_->IsKioskUser()) {
+        record.set_is_kiosk_session(true);
+      }
+    }
+    user_event_helper_->ReportEvent(
+        std::make_unique<::ash::reporting::CRDRecord>(std::move(record)),
+        ::reporting::Priority::FAST_BATCH);
+  }
+
+  // Task runner for asynchronous actions (including destructor).
+  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
   // Helper for posting events.
-  scoped_refptr<Helper> helper_;
+  const std::unique_ptr<::reporting::UserEventReporterHelper>
+      user_event_helper_;
 };
 
-}  // namespace
+void HostEventReporterDelegateImpl::EnqueueEvent(
+    ::ash::reporting::CRDRecord record) {
+  helper_->EnqueueEvent(std::move(record));
+}
 
 // static
 std::unique_ptr<HostEventReporter> HostEventReporter::Create(
@@ -93,5 +87,4 @@
   return std::make_unique<HostEventReporterImpl>(
       monitor, std::make_unique<HostEventReporterDelegateImpl>());
 }
-
 }  // namespace remoting
diff --git a/chrome/browser/ash/policy/reporting/remoting_host_event_reporter_delegate_impl.h b/chrome/browser/ash/policy/reporting/remoting_host_event_reporter_delegate_impl.h
new file mode 100644
index 0000000..1a8f492
--- /dev/null
+++ b/chrome/browser/ash/policy/reporting/remoting_host_event_reporter_delegate_impl.h
@@ -0,0 +1,47 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_POLICY_REPORTING_REMOTING_HOST_EVENT_REPORTER_DELEGATE_IMPL_H_
+#define CHROME_BROWSER_ASH_POLICY_REPORTING_REMOTING_HOST_EVENT_REPORTER_DELEGATE_IMPL_H_
+
+#include <memory>
+
+#include "ash/components/settings/cros_settings_names.h"
+#include "base/memory/ref_counted_delete_on_sequence.h"
+#include "base/memory/scoped_refptr.h"
+#include "chrome/browser/ash/policy/reporting/user_event_reporter_helper.h"
+#include "chrome/browser/policy/messaging_layer/proto/synced/crd_event.pb.h"
+#include "components/reporting/proto/synced/record_constants.pb.h"
+#include "remoting/host/chromeos/host_event_reporter_impl.h"
+
+namespace remoting {
+
+// Production implementation of CRD event reporter for remoting host.
+class HostEventReporterDelegateImpl : public HostEventReporterImpl::Delegate {
+ public:
+  explicit HostEventReporterDelegateImpl(
+      std::unique_ptr<::reporting::UserEventReporterHelper> user_event_helper =
+          std::make_unique<::reporting::UserEventReporterHelper>(
+              ::reporting::Destination::CRD_EVENTS,
+              ::reporting::EventType::kUser));
+
+  HostEventReporterDelegateImpl(const HostEventReporterDelegateImpl& other) =
+      delete;
+  HostEventReporterDelegateImpl& operator=(
+      const HostEventReporterDelegateImpl& other) = delete;
+  ~HostEventReporterDelegateImpl() override;
+
+  void EnqueueEvent(::ash::reporting::CRDRecord record) override;
+
+ private:
+  // Helper class - refcounted in order to keep it alive until asynchronous
+  // Enqueue task completes.
+  class Helper;
+
+  // Helper for posting events.
+  const scoped_refptr<Helper> helper_;
+};
+}  // namespace remoting
+
+#endif  // CHROME_BROWSER_ASH_POLICY_REPORTING_REMOTING_HOST_EVENT_REPORTER_DELEGATE_IMPL_H_
diff --git a/chrome/browser/ash/policy/reporting/remoting_host_event_reporter_delegate_impl_unittest.cc b/chrome/browser/ash/policy/reporting/remoting_host_event_reporter_delegate_impl_unittest.cc
new file mode 100644
index 0000000..c98ee81a
--- /dev/null
+++ b/chrome/browser/ash/policy/reporting/remoting_host_event_reporter_delegate_impl_unittest.cc
@@ -0,0 +1,170 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/policy/reporting/remoting_host_event_reporter_delegate_impl.h"
+
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/strings/string_piece.h"
+#include "base/test/repeating_test_future.h"
+#include "chrome/browser/ash/policy/reporting/user_event_reporter_helper.h"
+#include "chrome/browser/ash/policy/reporting/user_event_reporter_helper_testing.h"
+#include "chrome/browser/policy/messaging_layer/proto/synced/crd_event.pb.h"
+#include "components/reporting/client/mock_report_queue.h"
+#include "components/reporting/proto/synced/record_constants.pb.h"
+#include "content/public/test/browser_task_environment.h"
+#include "remoting/host/chromeos/host_event_reporter_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::ash::reporting::CRDRecord;
+using ::reporting::SessionAffiliatedUser;
+
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::Eq;
+using ::testing::Property;
+using ::testing::StrEq;
+
+namespace remoting {
+namespace {
+
+// Production implementation of CRD event reporter for remoting host.
+class HostEventReporterDelegateImplTest : public ::testing::Test {
+ protected:
+  std::unique_ptr<::reporting::UserEventReporterHelperTesting>
+  GetReporterHelper(bool reporting_enabled,
+                    bool should_report_user,
+                    bool is_kiosk_user,
+                    ::reporting::Status status = ::reporting::Status()) {
+    auto mock_queue = std::unique_ptr<::reporting::MockReportQueue,
+                                      base::OnTaskRunnerDeleter>(
+        new ::reporting::MockReportQueue(),
+        base::OnTaskRunnerDeleter(base::SequencedTaskRunnerHandle::Get()));
+
+    ON_CALL(*mock_queue, AddRecord(_, ::reporting::Priority::FAST_BATCH, _))
+        .WillByDefault(
+            [this, status](base::StringPiece record_string,
+                           ::reporting::Priority event_priority,
+                           ::reporting::ReportQueue::EnqueueCallback cb) {
+              if (status.ok()) {
+                CRDRecord record;
+                EXPECT_TRUE(record.ParseFromArray(record_string.data(),
+                                                  record_string.size()));
+                records_.AddValue(std::move(record));
+              }
+              std::move(cb).Run(status);
+            });
+
+    auto reporter_helper =
+        std::make_unique<::reporting::UserEventReporterHelperTesting>(
+            reporting_enabled, should_report_user, is_kiosk_user,
+            std::move(mock_queue));
+    return reporter_helper;
+  }
+
+  CRDRecord WaitForEvent() { return records_.Take(); }
+
+  bool IsEmpty() const { return records_.IsEmpty(); }
+
+ private:
+  content::BrowserTaskEnvironment task_environment;
+
+  std::unique_ptr<HostEventReporterImpl::Delegate> delegate_;
+
+  base::test::RepeatingTestFuture<CRDRecord> records_;
+};
+
+TEST_F(HostEventReporterDelegateImplTest, RegularCrdEvent) {
+  static constexpr char kHostUser[] = "user@host.com";
+  HostEventReporterDelegateImpl delegate(
+      GetReporterHelper(/*reporting_enabled=*/true, /*should_report_user=*/true,
+                        /*is_kiosk_user=*/false));
+
+  {
+    CRDRecord record;
+    record.mutable_host_user()->set_user_email(kHostUser);
+    delegate.EnqueueEvent(record);
+  }
+
+  EXPECT_THAT(WaitForEvent(),
+              AllOf(Property(&CRDRecord::is_kiosk_session, Eq(false)),
+                    Property(&CRDRecord::host_user,
+                             Property(&SessionAffiliatedUser::user_email,
+                                      StrEq(kHostUser)))));
+  EXPECT_TRUE(IsEmpty());
+}
+
+TEST_F(HostEventReporterDelegateImplTest, AnonymousCrdEvent) {
+  static constexpr char kNonAffiliatedUser[] = "user@alien.com";
+  HostEventReporterDelegateImpl delegate(GetReporterHelper(
+      /*reporting_enabled=*/true, /*should_report_user=*/false,
+      /*is_kiosk_user=*/false));
+
+  {
+    CRDRecord record;
+    record.mutable_host_user()->set_user_email(kNonAffiliatedUser);
+    delegate.EnqueueEvent(record);
+  }
+
+  EXPECT_THAT(WaitForEvent(),
+              AllOf(Property(&CRDRecord::is_kiosk_session, Eq(false)),
+                    Property(&CRDRecord::host_user,
+                             Property(&SessionAffiliatedUser::has_user_email,
+                                      Eq(false)))));
+  EXPECT_TRUE(IsEmpty());
+}
+
+TEST_F(HostEventReporterDelegateImplTest, KioskCrdEvent) {
+  static constexpr char kKioskUser[] = "user@host.com";
+  HostEventReporterDelegateImpl delegate(GetReporterHelper(
+      /*reporting_enabled=*/true, /*should_report_user=*/false,
+      /*is_kiosk_user=*/true));
+
+  {
+    CRDRecord record;
+    record.mutable_host_user()->set_user_email(kKioskUser);
+    delegate.EnqueueEvent(record);
+  }
+
+  EXPECT_THAT(WaitForEvent(),
+              AllOf(Property(&CRDRecord::is_kiosk_session, Eq(true)),
+                    Property(&CRDRecord::host_user,
+                             Property(&SessionAffiliatedUser::has_user_email,
+                                      Eq(false)))));
+  EXPECT_TRUE(IsEmpty());
+}
+
+TEST_F(HostEventReporterDelegateImplTest, DisabledCrdEvent) {
+  static constexpr char kHostUser[] = "user@host.com";
+  HostEventReporterDelegateImpl delegate(GetReporterHelper(
+      /*reporting_enabled=*/false, /*should_report_user=*/true,
+      /*is_kiosk_user=*/false));
+  {
+    CRDRecord record;
+    record.mutable_host_user()->set_user_email(kHostUser);
+    delegate.EnqueueEvent(record);
+  }
+
+  EXPECT_TRUE(IsEmpty());
+}
+
+TEST_F(HostEventReporterDelegateImplTest, ErrorPostingCrdEvent) {
+  static constexpr char kHostUser[] = "user@host.com";
+  HostEventReporterDelegateImpl delegate(GetReporterHelper(
+      /*reporting_enabled=*/true, /*should_report_user=*/true,
+      /*is_kiosk_user=*/false,
+      ::reporting::Status(::reporting::error::INTERNAL, "")));
+  {
+    CRDRecord record;
+    record.mutable_host_user()->set_user_email(kHostUser);
+    delegate.EnqueueEvent(record);
+  }
+
+  EXPECT_TRUE(IsEmpty());
+}
+
+}  // namespace
+}  // namespace remoting
diff --git a/chrome/browser/ash/policy/reporting/user_event_reporter_helper_testing.cc b/chrome/browser/ash/policy/reporting/user_event_reporter_helper_testing.cc
index 72ea4b3..e1e44bf 100644
--- a/chrome/browser/ash/policy/reporting/user_event_reporter_helper_testing.cc
+++ b/chrome/browser/ash/policy/reporting/user_event_reporter_helper_testing.cc
@@ -11,10 +11,12 @@
 UserEventReporterHelperTesting::UserEventReporterHelperTesting(
     bool reporting_enabled,
     bool should_report_user,
+    bool is_kiosk_user,
     std::unique_ptr<ReportQueue, base::OnTaskRunnerDeleter> report_queue)
     : UserEventReporterHelper(std::move(report_queue)),
       reporting_enabled_(reporting_enabled),
-      should_report_user_(should_report_user) {}
+      should_report_user_(should_report_user),
+      is_kiosk_user_(is_kiosk_user) {}
 
 bool UserEventReporterHelperTesting::ReportingEnabled(
     const std::string&) const {
@@ -26,4 +28,8 @@
   return should_report_user_;
 }
 
+bool UserEventReporterHelperTesting::IsKioskUser() const {
+  return is_kiosk_user_;
+}
+
 }  // namespace reporting
diff --git a/chrome/browser/ash/policy/reporting/user_event_reporter_helper_testing.h b/chrome/browser/ash/policy/reporting/user_event_reporter_helper_testing.h
index 2b86c49..335a2beb 100644
--- a/chrome/browser/ash/policy/reporting/user_event_reporter_helper_testing.h
+++ b/chrome/browser/ash/policy/reporting/user_event_reporter_helper_testing.h
@@ -14,6 +14,7 @@
   UserEventReporterHelperTesting(
       bool reporting_enabled,
       bool should_report_user,
+      bool is_kiosk_user,
       std::unique_ptr<ReportQueue, base::OnTaskRunnerDeleter> report_queue);
   ~UserEventReporterHelperTesting() override;
 
@@ -21,9 +22,12 @@
 
   bool ShouldReportUser(const std::string&) const override;
 
+  bool IsKioskUser() const override;
+
  private:
   const bool reporting_enabled_;
   const bool should_report_user_;
+  const bool is_kiosk_user_;
 };
 }  // namespace reporting
 
diff --git a/chrome/browser/ash/shimless_rma/chrome_shimless_rma_delegate.cc b/chrome/browser/ash/shimless_rma/chrome_shimless_rma_delegate.cc
index 41743d0..ab9788a 100644
--- a/chrome/browser/ash/shimless_rma/chrome_shimless_rma_delegate.cc
+++ b/chrome/browser/ash/shimless_rma/chrome_shimless_rma_delegate.cc
@@ -5,7 +5,9 @@
 #include "chrome/browser/ash/shimless_rma/chrome_shimless_rma_delegate.h"
 
 #include "ash/constants/ash_switches.h"
+#include "base/check.h"
 #include "base/command_line.h"
+#include "chrome/browser/ash/accessibility/accessibility_manager.h"
 #include "chrome/browser/ash/login/chrome_restart_request.h"
 #include "chrome/browser/ash/system/device_disabling_manager.h"
 #include "chrome/browser/ui/webui/chromeos/diagnostics_dialog.h"
@@ -35,5 +37,11 @@
   chromeos::DiagnosticsDialog::ShowDialog();
 }
 
+void ChromeShimlessRmaDelegate::RefreshAccessibilityManagerProfile() {
+  AccessibilityManager* accessibility_manager = AccessibilityManager::Get();
+  DCHECK(accessibility_manager);
+  accessibility_manager->OnShimlessRmaLaunched();
+}
+
 }  // namespace shimless_rma
 }  // namespace ash
diff --git a/chrome/browser/ash/shimless_rma/chrome_shimless_rma_delegate.h b/chrome/browser/ash/shimless_rma/chrome_shimless_rma_delegate.h
index 15f9e65..0966b83 100644
--- a/chrome/browser/ash/shimless_rma/chrome_shimless_rma_delegate.h
+++ b/chrome/browser/ash/shimless_rma/chrome_shimless_rma_delegate.h
@@ -23,6 +23,7 @@
   // ShimlessRmaDelegate:
   void ExitRmaThenRestartChrome() override;
   void ShowDiagnosticsDialog() override;
+  void RefreshAccessibilityManagerProfile() override;
 };
 
 }  // namespace shimless_rma
diff --git a/chrome/browser/ash/system_web_apps/system_web_app_manager_browsertest.cc b/chrome/browser/ash/system_web_apps/system_web_app_manager_browsertest.cc
index db97876a..21070a5c 100644
--- a/chrome/browser/ash/system_web_apps/system_web_app_manager_browsertest.cc
+++ b/chrome/browser/ash/system_web_apps/system_web_app_manager_browsertest.cc
@@ -1114,8 +1114,9 @@
   bool swa_found = false;
   app_service_proxy->AppRegistryCache().ForEachApp(
       [&](const apps::AppUpdate& app) {
-        if (app.AppType() == apps::AppType::kSystemWeb ||
-            app.AppType() == apps::AppType::kWeb) {
+        if ((app.AppType() == apps::AppType::kSystemWeb ||
+             app.AppType() == apps::AppType::kWeb) &&
+            app.Readiness() != apps::Readiness::kUninstalledByUser) {
           swa_found = true;
         }
       });
diff --git a/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc b/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc
index b79e7d8a..53495c4 100644
--- a/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc
+++ b/chrome/browser/ash/system_web_apps/system_web_app_manager_unittest.cc
@@ -208,11 +208,12 @@
         &install_manager(), &controller().registrar(), &ui_manager(),
         &controller().sync_bridge(), &controller().os_integration_manager(),
         &icon_manager(), web_app_policy_manager_.get(),
-        &controller().translation_manager());
+        &controller().translation_manager(), &command_manager());
 
     install_manager().SetSubsystems(
         &controller().registrar(), &controller().os_integration_manager(),
-        &controller().command_manager(), &install_finalizer());
+        &controller().command_manager(), &install_finalizer(), &icon_manager(),
+        &controller().sync_bridge(), &controller().translation_manager());
 
     icon_manager().SetSubsystems(&controller().registrar(), &install_manager());
 
diff --git a/chrome/browser/autofill/autofill_interactive_uitest.cc b/chrome/browser/autofill/autofill_interactive_uitest.cc
index 826b382..9fd55e20 100644
--- a/chrome/browser/autofill/autofill_interactive_uitest.cc
+++ b/chrome/browser/autofill/autofill_interactive_uitest.cc
@@ -378,7 +378,7 @@
   bool field_was_focused_initially = IsFocusedField(e, rfh);
   for (size_t i = 1; i <= p.max_tries; ++i) {
     a = a << "Iteration " << i << "/" << p.max_tries << ". ";
-    // The translate bubble may overlap with the Autofill popup, which causes
+    // A Translate bubble may overlap with the Autofill popup, which causes
     // flakiness. See crbug.com/1175735#c10.
     // Also, the address-save prompts and others may overlap with the Autofill
     // popup. So we preemptively close all bubbles, which however is not
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index c305d13..5fb35f4a 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -953,6 +953,7 @@
     "../ash/policy/reporting/metrics_reporting/usb/usb_events_observer.cc",
     "../ash/policy/reporting/metrics_reporting/usb/usb_events_observer.h",
     "../ash/policy/reporting/remoting_host_event_reporter_delegate_impl.cc",
+    "../ash/policy/reporting/remoting_host_event_reporter_delegate_impl.h",
     "../ash/policy/reporting/single_arc_app_install_event_log.cc",
     "../ash/policy/reporting/single_arc_app_install_event_log.h",
     "../ash/policy/reporting/single_extension_install_event_log.cc",
@@ -2818,6 +2819,7 @@
     "../ash/policy/reporting/metrics_reporting/network/network_telemetry_sampler_unittest.cc",
     "../ash/policy/reporting/metrics_reporting/network/wifi_signal_strength_rssi_fetcher_unittest.cc",
     "../ash/policy/reporting/metrics_reporting/usb/usb_events_observer_unittest.cc",
+    "../ash/policy/reporting/remoting_host_event_reporter_delegate_impl_unittest.cc",
     "../ash/policy/reporting/single_arc_app_install_event_log_unittest.cc",
     "../ash/policy/reporting/user_added_removed/user_added_removed_reporter_unittest.cc",
     "../ash/policy/reporting/user_event_reporter_helper_unittest.cc",
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
index cf0682a..688088c 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
@@ -34,7 +34,6 @@
 #include "chrome/browser/policy/dm_token_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h"
-#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request.h"
 #include "chrome/browser/safe_browsing/download_protection/check_client_download_request.h"
@@ -535,7 +534,8 @@
 }
 
 BinaryUploadService* ContentAnalysisDelegate::GetBinaryUploadService() {
-  return safe_browsing::BinaryUploadServiceFactory::GetForProfile(profile_);
+  return safe_browsing::BinaryUploadService::GetForProfile(profile_,
+                                                           data_.settings);
 }
 
 void ContentAnalysisDelegate::UploadTextForDeepScanning(
diff --git a/chrome/browser/enterprise/connectors/analysis/request_handler_base.cc b/chrome/browser/enterprise/connectors/analysis/request_handler_base.cc
index edef234d..317d3347 100644
--- a/chrome/browser/enterprise/connectors/analysis/request_handler_base.cc
+++ b/chrome/browser/enterprise/connectors/analysis/request_handler_base.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/enterprise/connectors/analysis/request_handler_base.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h"
-#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.h"
 
 namespace enterprise_connectors {
 
diff --git a/chrome/browser/extensions/api/offscreen/offscreen_document_manager_browsertest.cc b/chrome/browser/extensions/api/offscreen/offscreen_document_manager_browsertest.cc
new file mode 100644
index 0000000..994ee17
--- /dev/null
+++ b/chrome/browser/extensions/api/offscreen/offscreen_document_manager_browsertest.cc
@@ -0,0 +1,118 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/offscreen/offscreen_document_manager.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "extensions/browser/disable_reason.h"
+#include "extensions/browser/extension_host_test_helper.h"
+#include "extensions/browser/offscreen_document_host.h"
+#include "extensions/common/extension_features.h"
+#include "extensions/common/mojom/view_type.mojom.h"
+#include "extensions/test/test_extension_dir.h"
+
+namespace extensions {
+
+class OffscreenDocumentManagerBrowserTest : public ExtensionApiTest {
+ public:
+  OffscreenDocumentManagerBrowserTest() {
+    feature_list_.InitAndEnableFeature(
+        extensions_features::kExtensionsOffscreenDocuments);
+  }
+  ~OffscreenDocumentManagerBrowserTest() override = default;
+
+  OffscreenDocumentManager* offscreen_document_manager() {
+    return OffscreenDocumentManager::Get(profile());
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Tests the flow of the OffscreenDocumentManager creating a new offscreen
+// document for an extension.
+IN_PROC_BROWSER_TEST_F(OffscreenDocumentManagerBrowserTest,
+                       CreateOffscreenDocument) {
+  static constexpr char kManifest[] =
+      R"({
+           "name": "Offscreen Document Test",
+           "manifest_version": 3,
+           "version": "0.1"
+         })";
+  static constexpr char kOffscreenDocumentHtml[] =
+      R"(<html>
+           <body>
+             <div id="signal">Hello, World</div>
+           </body>
+         </html>)";
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  test_dir.WriteFile(FILE_PATH_LITERAL("offscreen.html"),
+                     kOffscreenDocumentHtml);
+
+  // Note: We wrap `extension` in a refptr because we'll unload it later in the
+  // test and need to make sure the object isn't deleted.
+  scoped_refptr<const Extension> extension =
+      LoadExtension(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  // To start, the manager should not have any offscreen documents registered.
+  EXPECT_EQ(nullptr,
+            offscreen_document_manager()->GetOffscreenDocumentForExtension(
+                *extension));
+
+  OffscreenDocumentHost* offscreen_document = nullptr;
+  {
+    // Instruct the manager to create a new offscreen document and wait for it
+    // to load.
+    ExtensionHostTestHelper host_waiter(profile());
+    host_waiter.RestrictToType(mojom::ViewType::kOffscreenDocument);
+    offscreen_document = offscreen_document_manager()->CreateOffscreenDocument(
+        *extension, extension->GetResourceURL("offscreen.html"));
+    ASSERT_TRUE(offscreen_document);
+    host_waiter.WaitForHostCompletedFirstLoad();
+  }
+
+  {
+    // Check the document loaded properly. Note: general capabilities of
+    // offscreen documents are exercised more in the OffscreenDocumentHost
+    // tests, but this helps sanity check that the manager created it properly.
+    static constexpr char kScript[] =
+        R"({
+             let div = document.getElementById('signal');
+             domAutomationController.send(div ? div.innerText : '<no div>');
+           })";
+    std::string result;
+    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
+        offscreen_document->host_contents(), kScript, &result));
+    EXPECT_EQ("Hello, World", result);
+  }
+
+  // The manager should now have a record of a document for the extension.
+  EXPECT_EQ(offscreen_document,
+            offscreen_document_manager()->GetOffscreenDocumentForExtension(
+                *extension));
+
+  {
+    // Disable the extension. This causes it to unload, and the offscreen
+    // document should be closed.
+    ExtensionHostTestHelper host_waiter(profile());
+    host_waiter.RestrictToHost(offscreen_document);
+    extension_service()->DisableExtension(extension->id(),
+                                          disable_reason::DISABLE_USER_ACTION);
+    host_waiter.WaitForHostDestroyed();
+    // Note: `offscreen_document` is destroyed at this point.
+  }
+
+  // There should no longer be a document for the extension.
+  EXPECT_EQ(nullptr,
+            offscreen_document_manager()->GetOffscreenDocumentForExtension(
+                *extension));
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/site_permissions_helper.cc b/chrome/browser/extensions/site_permissions_helper.cc
index 7c54fe7..05fa3c0 100644
--- a/chrome/browser/extensions/site_permissions_helper.cc
+++ b/chrome/browser/extensions/site_permissions_helper.cc
@@ -65,13 +65,15 @@
 
   if (page_access == PermissionsData::PageAccess::kAllowed ||
       script_access == PermissionsData::PageAccess::kAllowed) {
-    return SiteInteraction::kActive;
+    return SiteInteraction::kGranted;
   }
 
-  if (HasActiveTabAndCanAccess(extension, url)) {
-    return SiteInteraction::kActiveTab;
-  }
-
+  // An extension can request both host permissions and activeTab permission.
+  // Withholding a host permission takes priority over activeTab permission,
+  // because withheld hosts are hosts that the extension explicitly marked as
+  // 'required' permissions, so it is a stronger signal that the extension
+  // should run on the site. ActiveTab extensions, by contrast, are designed to
+  // run when the user explicitly invokes them.
   // TODO(tjudkins): Investigate if we need to check HasBeenBlocked() for this
   // case. We do know that extensions that have been blocked should always be
   // marked pending, but those cases should be covered by the withheld page
@@ -79,7 +81,11 @@
   if (page_access == PermissionsData::PageAccess::kWithheld ||
       script_access == PermissionsData::PageAccess::kWithheld ||
       HasBeenBlocked(extension, web_contents)) {
-    return SiteInteraction::kPending;
+    return SiteInteraction::kWithheld;
+  }
+
+  if (HasActiveTabAndCanAccess(extension, url)) {
+    return SiteInteraction::kActiveTab;
   }
 
   return SiteInteraction::kNone;
diff --git a/chrome/browser/extensions/site_permissions_helper.h b/chrome/browser/extensions/site_permissions_helper.h
index f8f561e..c833db1 100644
--- a/chrome/browser/extensions/site_permissions_helper.h
+++ b/chrome/browser/extensions/site_permissions_helper.h
@@ -32,14 +32,13 @@
   enum class SiteInteraction {
     // The extension cannot run on the site.
     kNone,
-    // The extension would like access to the site, but is pending user
-    // approval.
-    kPending,
+    // The extension has withheld site access by the user.
+    kWithheld,
     // The extension has activeTab permission to run on the site, but is pending
     // user action to run.
     kActiveTab,
     // The extension has permission to run on the site.
-    kActive,
+    kGranted,
   };
 
   explicit SitePermissionsHelper(Profile* profile);
diff --git a/chrome/browser/extensions/site_permissions_helper_unittest.cc b/chrome/browser/extensions/site_permissions_helper_unittest.cc
index 9c99c46..061f990 100644
--- a/chrome/browser/extensions/site_permissions_helper_unittest.cc
+++ b/chrome/browser/extensions/site_permissions_helper_unittest.cc
@@ -7,6 +7,7 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_with_install.h"
 #include "chrome/browser/extensions/permissions_updater.h"
+#include "chrome/browser/extensions/scripting_permissions_modifier.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/test_browser_window.h"
 #include "components/crx_file/id_util.h"
@@ -135,7 +136,7 @@
       InstallExtensionWithPermissions("AllUrls Extension", {"<all_urls>"});
 
   {
-    // Verify a non-restricted url has "on all sites" site access and "active"
+    // Verify a non-restricted url has "on all sites" site access and "granted"
     // site interaction when the extension has all urls permission.
     const GURL non_restricted_url("http://www.non-restricted-url.com");
     auto* web_contents = AddTab(non_restricted_url);
@@ -144,7 +145,7 @@
         SiteAccess::kOnAllSites);
     EXPECT_EQ(
         permissions_helper()->GetSiteInteraction(*extension, web_contents),
-        SiteInteraction::kActive);
+        SiteInteraction::kGranted);
   }
 
   {
@@ -164,14 +165,14 @@
                                                    {requested_url.spec()});
 
   {
-    // Verify a non-restricted url has "on site" site access and "active" site
+    // Verify a non-restricted url has "on site" site access and "granted" site
     // interaction by default when the extension requests it.
     auto* web_contents = AddTab(requested_url);
     EXPECT_EQ(permissions_helper()->GetSiteAccess(*extension, requested_url),
               SiteAccess::kOnSite);
     EXPECT_EQ(
         permissions_helper()->GetSiteInteraction(*extension, web_contents),
-        SiteInteraction::kActive);
+        SiteInteraction::kGranted);
   }
 
   {
@@ -223,19 +224,7 @@
       /*permissions=*/{"activeTab"});
 
   {
-    // Verify a non-restricted url has "on site" site access and "active" site
-    // interaction by default when the extension requests it, regardless if the
-    // extension also has active tab permission.
-    auto* web_contents = AddTab(requested_url);
-    EXPECT_EQ(permissions_helper()->GetSiteAccess(*extension, requested_url),
-              SiteAccess::kOnSite);
-    EXPECT_EQ(
-        permissions_helper()->GetSiteInteraction(*extension, web_contents),
-        SiteInteraction::kActive);
-  }
-
-  {
-    // Verify a non-restricted url has "on click" site access and  "active tab"
+    // Verify a url has "on click" site access and  "active tab"
     // site interaction when the extension does not request it but has active
     // tab permission.
     const GURL non_requested_url("http://www.non-requested.com");
@@ -247,6 +236,35 @@
         permissions_helper()->GetSiteInteraction(*extension, web_contents),
         SiteInteraction::kActiveTab);
   }
+
+  {
+    // Verify a url has "on site" site access and "granted" site
+    // interaction when the extension requests it and has access (default
+    // behavior). "granted" takes priority over "activeTab" since the extension
+    // has access to the site.
+    auto* web_contents = AddTab(requested_url);
+    EXPECT_EQ(permissions_helper()->GetSiteAccess(*extension, requested_url),
+              SiteAccess::kOnSite);
+    EXPECT_EQ(
+        permissions_helper()->GetSiteInteraction(*extension, web_contents),
+        SiteInteraction::kGranted);
+  }
+
+  ScriptingPermissionsModifier(profile(), extension.get())
+      .RemoveAllGrantedHostPermissions();
+
+  {
+    // Verify a url has "on site" site access and "granted" site
+    // interaction when the extension requests it and its access is withheld.
+    // "withheld" takes priority over "activeTab" since the extension is
+    // explicitly requesting access to the site.
+    auto* web_contents = AddTab(requested_url);
+    EXPECT_EQ(permissions_helper()->GetSiteAccess(*extension, requested_url),
+              SiteAccess::kOnClick);
+    EXPECT_EQ(
+        permissions_helper()->GetSiteInteraction(*extension, web_contents),
+        SiteInteraction::kWithheld);
+  }
 }
 
 TEST_F(SitePermissionsHelperUnitTest,
diff --git a/chrome/browser/feedback/android/family_info_feedback_source.h b/chrome/browser/feedback/android/family_info_feedback_source.h
index 8e60ab05..064e6a28 100644
--- a/chrome/browser/feedback/android/family_info_feedback_source.h
+++ b/chrome/browser/feedback/android/family_info_feedback_source.h
@@ -51,6 +51,8 @@
   raw_ptr<signin::IdentityManager> identity_manager_;
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
   base::android::ScopedJavaGlobalRef<jobject> java_ref_;
+
+  base::WeakPtrFactory<FamilyInfoFeedbackSource> weak_factory_{this};
 };
 
 }  // namespace chrome::android
diff --git a/chrome/browser/feedback/android/family_info_feedback_source_unittest.cc b/chrome/browser/feedback/android/family_info_feedback_source_unittest.cc
index b25ca459..104c0eb 100644
--- a/chrome/browser/feedback/android/family_info_feedback_source_unittest.cc
+++ b/chrome/browser/feedback/android/family_info_feedback_source_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/android/jni_string.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/signin/chrome_signin_client_factory.h"
 #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
@@ -44,6 +45,7 @@
         CreateProfileForIdentityTestEnvironment(builder);
     identity_test_env_profile_adaptor_ =
         std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile_.get());
+    j_feedback_source_ = CreateJavaObjectForTesting();
   }
 
  protected:
@@ -52,32 +54,38 @@
   }
 
   // Methods to access Java counterpart FamilyInfoFeedbackSource.
-  std::string GetFeedbackForTesting(
-      ScopedJavaLocalRef<jobject> j_feedback_source) {
+  std::string GetFeedbackValue() {
     const base::android::JavaRef<jstring>& j_value =
         Java_FamilyInfoFeedbackSourceTestBridge_getValue(
             env_,
-            base::android::JavaParamRef<jobject>(env_, j_feedback_source.obj()),
+            base::android::JavaParamRef<jobject>(env_,
+                                                 j_feedback_source_.obj()),
             base::android::ConvertUTF8ToJavaString(env_, "Family_Member_Role"));
     return base::android::ConvertJavaStringToUTF8(env_, j_value);
   }
 
-  void GetFamilyMembersSuccess(
-      ScopedJavaLocalRef<jobject> j_feedback_source,
+  void OnGetFamilyMembersSuccess(
+      base::WeakPtr<FamilyInfoFeedbackSource> feedback_source,
       const std::vector<FamilyInfoFetcher::FamilyMember>& members) {
-    FamilyInfoFeedbackSource* feedback_source = new FamilyInfoFeedbackSource(
-        base::android::JavaParamRef<jobject>(env_, j_feedback_source.obj()),
-        profile_.get());
     feedback_source->OnGetFamilyMembersSuccess(members);
   }
 
-  void GetFamilyMembersFailure(ScopedJavaLocalRef<jobject> j_feedback_source) {
-    FamilyInfoFeedbackSource* feedback_source = new FamilyInfoFeedbackSource(
-        base::android::JavaParamRef<jobject>(env_, j_feedback_source.obj()),
-        profile_.get());
+  void OnGetFamilyMembersFailure(
+      base::WeakPtr<FamilyInfoFeedbackSource> feedback_source) {
     feedback_source->OnFailure(FamilyInfoFetcher::ErrorCode::kTokenError);
   }
 
+  // Creates a new instance of FamilyInfoFeedbackSource that is destroyed on
+  // completion of OnGetFamilyMembers* methods.
+  base::WeakPtr<FamilyInfoFeedbackSource> CreateFamilyInfoFeedbackSource() {
+    FamilyInfoFeedbackSource* source = new FamilyInfoFeedbackSource(
+        base::android::JavaParamRef<jobject>(env_, j_feedback_source_.obj()),
+        profile_.get());
+    return source->weak_factory_.GetWeakPtr();
+  }
+
+ private:
+  // Creates a Java instance of FamilyInfoFeedbackSource.
   base::android::ScopedJavaLocalRef<jobject> CreateJavaObjectForTesting() {
     ProfileAndroid* profile_android =
         ProfileAndroid::FromProfile(profile_.get());
@@ -86,9 +94,9 @@
                   env_, profile_android->GetJavaObject().Release()));
   }
 
- private:
   content::BrowserTaskEnvironment task_environment_;
 
+  base::android::ScopedJavaLocalRef<jobject> j_feedback_source_;
   raw_ptr<JNIEnv> env_;
   std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
       identity_test_env_profile_adaptor_;
@@ -100,8 +108,6 @@
   CoreAccountInfo primary_account =
       identity_test_env()->MakePrimaryAccountAvailable(
           kTestEmail, signin::ConsentLevel::kSignin);
-  base::android::ScopedJavaLocalRef<jobject> j_feedback_source =
-      CreateJavaObjectForTesting();
 
   FamilyInfoFetcher::FamilyMemberRole role = GetParam();
   std::vector<FamilyInfoFetcher::FamilyMember> members(
@@ -109,13 +115,15 @@
           primary_account.gaia, role, "Name", kTestEmail,
           /*profile_url=*/std::string(), /*profile_image_url=*/std::string())});
 
-  GetFamilyMembersSuccess(j_feedback_source, members);
+  base::WeakPtr<FamilyInfoFeedbackSource> feedback_source =
+      CreateFamilyInfoFeedbackSource();
+  OnGetFamilyMembersSuccess(feedback_source, members);
 
   std::string expected_role =
       role == FamilyInfoFetcher::FamilyMemberRole::HEAD_OF_HOUSEHOLD
           ? "family_manager"
           : FamilyInfoFetcher::RoleToString(role);
-  EXPECT_EQ(expected_role, GetFeedbackForTesting(j_feedback_source));
+  EXPECT_EQ(expected_role, GetFeedbackValue());
 }
 
 // Tests that a user that is not in a Family group is not processed.
@@ -123,13 +131,13 @@
   CoreAccountInfo primary_account =
       identity_test_env()->MakePrimaryAccountAvailable(
           kTestEmail, signin::ConsentLevel::kSignin);
-  base::android::ScopedJavaLocalRef<jobject> j_feedback_source =
-      CreateJavaObjectForTesting();
 
+  base::WeakPtr<FamilyInfoFeedbackSource> feedback_source =
+      CreateFamilyInfoFeedbackSource();
   std::vector<FamilyInfoFetcher::FamilyMember> members;
-  GetFamilyMembersSuccess(j_feedback_source, members);
+  OnGetFamilyMembersSuccess(feedback_source, members);
 
-  EXPECT_EQ("", GetFeedbackForTesting(j_feedback_source));
+  EXPECT_EQ("", GetFeedbackValue());
 }
 
 // Tests that a signed-in user that fails its request to the server is not
@@ -138,12 +146,29 @@
   CoreAccountInfo primary_account =
       identity_test_env()->MakePrimaryAccountAvailable(
           kTestEmail, signin::ConsentLevel::kSignin);
-  base::android::ScopedJavaLocalRef<jobject> j_feedback_source =
-      CreateJavaObjectForTesting();
 
-  GetFamilyMembersFailure(j_feedback_source);
+  base::WeakPtr<FamilyInfoFeedbackSource> feedback_source =
+      CreateFamilyInfoFeedbackSource();
+  OnGetFamilyMembersFailure(feedback_source);
 
-  EXPECT_EQ("", GetFeedbackForTesting(j_feedback_source));
+  EXPECT_EQ("", GetFeedbackValue());
+}
+
+TEST_F(FamilyInfoFeedbackSourceTest, FeedbackSourceDestroyedOnCompletion) {
+  std::vector<FamilyInfoFetcher::FamilyMember> members;
+  base::WeakPtr<FamilyInfoFeedbackSource> feedback_source =
+      CreateFamilyInfoFeedbackSource();
+  OnGetFamilyMembersSuccess(feedback_source, members);
+
+  EXPECT_TRUE(feedback_source.WasInvalidated());
+}
+
+TEST_F(FamilyInfoFeedbackSourceTest, FeedbackSourceDestroyedOnFailure) {
+  base::WeakPtr<FamilyInfoFeedbackSource> feedback_source =
+      CreateFamilyInfoFeedbackSource();
+  OnGetFamilyMembersFailure(feedback_source);
+
+  EXPECT_TRUE(feedback_source.WasInvalidated());
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 56bb3a81..0cd8572 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -753,6 +753,11 @@
     "expiry_milestone": 110
   },
   {
+    "name": "cct-real-time-engagement-signals",
+    "owners": ["sinansahin@google.com", "jinsukkim"],
+    "expiry_milestone": 110
+  },
+  {
     "name": "cct-resizable-90-maximum-height",
     "owners": ["ctzsm", "peconn"],
     "expiry_milestone": 105
@@ -1376,14 +1381,14 @@
   },
   {
     "name": "download-bubble",
-    "owners": [ "bhatiarohit", "chrome-safebrowsing-alerts@google.com"],
+    "owners": [ "bhatiarohit", "chrome-counter-abuse-alerts@google.com"],
     "expiry_milestone": 106
   },
   {
     "name": "download-bubble-v2",
     "owners": [
       "bhatiarohit",
-      "chrome-safebrowsing-alerts@google.com"
+      "chrome-counter-abuse-alerts@google.com"
     ],
     "expiry_milestone": 106
   },
@@ -1480,7 +1485,7 @@
   {
     "name": "enable-accessibility-live-caption",
     "owners": [ "abigailbklein@google.com", "evliu@google.com", "//ui/accessibility/OWNERS" ],
-    "expiry_milestone": 104
+    "expiry_milestone": 110
   },
   {
     "name": "enable-accessibility-os-settings-visibility",
@@ -1489,8 +1494,8 @@
   },
   {
     "name": "enable-accessibility-page-zoom",
-    "owners": [ "mschillaci@google.com", "//ui/accessibility/OWNERS" ],
-    "expiry_milestone": 105
+    "owners": [ "mschillaci@google.com", "aldietz@google.com", "//ui/accessibility/OWNERS" ],
+    "expiry_milestone": 110
   },
   {
     "name": "enable-android-gamepad-vibration",
@@ -1550,7 +1555,7 @@
   {
     "name": "enable-auto-disable-accessibility",
     "owners": [ "abigailbklein@google.com", "//ui/accessibility/OWNERS" ],
-    "expiry_milestone": 104
+    "expiry_milestone": 110
   },
   {
     "name": "enable-autofill-credit-card-authentication",
@@ -4749,11 +4754,6 @@
     "expiry_milestone": 92
   },
   {
-    "name": "omnibox-tab-switch-suggestions",
-    "owners": [ "gangwu", "chrome-omnibox-team@google.com" ],
-    "expiry_milestone": 104
-  },
-  {
     "name": "omnibox-trending-zero-prefix-suggestions-on-ntp",
     "owners": [ "ender", "stkhapugin", "mahmadi", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 110
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 79ce981c..9148258 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3192,6 +3192,12 @@
 const char kCCTResizableForThirdPartiesDescription[] =
     "Enable bottom sheet Custom Tabs for third party apps.";
 
+const char kCCTRealTimeEngagementSignalsName[] =
+    "Enable CCT real-time engagement signals.";
+const char kCCTRealTimeEngagementSignalsDescription[] =
+    "Enables sending real-time engagement signals (e.g. scroll) through "
+    "CustomTabsCallback.";
+
 const char kChimeAlwaysShowNotificationDescription[] =
     "A debug flag to always show Chime notification after receiving a payload.";
 const char kChimeAlwaysShowNotificationName[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index a4d7e8c..39c6889 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1793,6 +1793,9 @@
 extern const char kCCTResizableForThirdPartiesName[];
 extern const char kCCTResizableForThirdPartiesDescription[];
 
+extern const char kCCTRealTimeEngagementSignalsName[];
+extern const char kCCTRealTimeEngagementSignalsDescription[];
+
 extern const char kChimeAlwaysShowNotificationDescription[];
 extern const char kChimeAlwaysShowNotificationName[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index b5cfcf9..6b04133 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -183,6 +183,7 @@
     &kCCTIncognitoAvailableToThirdParty,
     &kCCTNewDownloadTab,
     &kCCTPostMessageAPI,
+    &kCCTRealTimeEngagementSignals,
     &kCCTRedirectPreconnect,
     &kCCTRemoveRemoteViewIds,
     &kCCTReportParallelRequestStatus,
@@ -504,6 +505,9 @@
 const base::Feature kCCTPostMessageAPI{"CCTPostMessageAPI",
                                        base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kCCTRealTimeEngagementSignals{
+    "CCTRealTimeEngagementSignals", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kCCTRedirectPreconnect{"CCTRedirectPreconnect",
                                            base::FEATURE_ENABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 5276e21..e52eb78 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -40,6 +40,7 @@
 extern const base::Feature kCCTNewDownloadTab;
 extern const base::Feature kCCTPackageNameRecording;
 extern const base::Feature kCCTPostMessageAPI;
+extern const base::Feature kCCTRealTimeEngagementSignals;
 extern const base::Feature kCCTRedirectPreconnect;
 extern const base::Feature kCCTRemoveRemoteViewIds;
 extern const base::Feature kCCTReportParallelRequestStatus;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index b91e3df..e476f95d 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -250,6 +250,7 @@
             "CCTIncognitoAvailableToThirdParty";
     public static final String CCT_NEW_DOWNLOAD_TAB = "CCTNewDownloadTab";
     public static final String CCT_POST_MESSAGE_API = "CCTPostMessageAPI";
+    public static final String CCT_REAL_TIME_ENGAGEMENT_SIGNALS = "CCTRealTimeEngagementSignals";
     public static final String CCT_REDIRECT_PRECONNECT = "CCTRedirectPreconnect";
     public static final String CCT_REMOVE_REMOTE_VIEW_IDS = "CCTRemoveRemoteViewIds";
     public static final String CCT_RESIZABLE_90_MAXIMUM_HEIGHT = "CCTResizable90MaximumHeight";
diff --git a/chrome/browser/new_tab_page/modules/feed/BUILD.gn b/chrome/browser/new_tab_page/modules/feed/BUILD.gn
new file mode 100644
index 0000000..e98a2610
--- /dev/null
+++ b/chrome/browser/new_tab_page/modules/feed/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("mojo_bindings") {
+  sources = [ "feed.mojom" ]
+  webui_module_path = "/"
+  public_deps = [
+    "//mojo/public/mojom/base",
+    "//url/mojom:url_mojom_gurl",
+  ]
+}
diff --git a/chrome/browser/new_tab_page/modules/feed/DEPS b/chrome/browser/new_tab_page/modules/feed/DEPS
new file mode 100644
index 0000000..3a4d90a5
--- /dev/null
+++ b/chrome/browser/new_tab_page/modules/feed/DEPS
@@ -0,0 +1,5 @@
+specific_include_rules = {
+  "feed_handler\.cc": [
+    "+chrome/browser/ui/views",
+  ]
+}
diff --git a/chrome/browser/new_tab_page/modules/feed/OWNERS b/chrome/browser/new_tab_page/modules/feed/OWNERS
new file mode 100644
index 0000000..08850f4
--- /dev/null
+++ b/chrome/browser/new_tab_page/modules/feed/OWNERS
@@ -0,0 +1,2 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/chrome/browser/new_tab_page/modules/feed/feed.mojom b/chrome/browser/new_tab_page/modules/feed/feed.mojom
new file mode 100644
index 0000000..de76530f
--- /dev/null
+++ b/chrome/browser/new_tab_page/modules/feed/feed.mojom
@@ -0,0 +1,30 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module ntp.feed.mojom;
+
+import "url/mojom/url.mojom";
+
+// A feed article.
+struct Article {
+  // Link to article.
+  url.mojom.Url url;
+  // Article title.
+  string title;
+  // URL for article thumbnail.
+  url.mojom.Url thumbnail_url;
+  // URL for article favicon.
+  url.mojom.Url favicon_url;
+  // Article publisher.
+  string publisher;
+};
+
+// Browser-side handler for requests from NTP Module UI.
+interface FeedHandler {
+  // Fetches articles.
+  GetFollowingFeedArticles() => (array<Article> articles);
+  // Handle a click on a following feed article.
+  ArticleOpened();
+};
+
diff --git a/chrome/browser/new_tab_page/modules/feed/feed_handler.cc b/chrome/browser/new_tab_page/modules/feed/feed_handler.cc
new file mode 100644
index 0000000..997eba9
--- /dev/null
+++ b/chrome/browser/new_tab_page/modules/feed/feed_handler.cc
@@ -0,0 +1,103 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/new_tab_page/modules/feed/feed_handler.h"
+
+#include "base/bind.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
+#include "chrome/common/channel_info.h"
+#include "chrome/common/chrome_version.h"
+#include "components/feed/core/v2/public/ntp_feed_content_fetcher.h"
+#include "components/prefs/pref_service.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+#include "google_apis/google_api_keys.h"
+#include "mojo/public/cpp/bindings/clone_traits.h"
+
+class Browser;
+
+namespace ntp {
+namespace {
+
+void HandleFetchArticlesResponse(
+    ntp::feed::mojom::FeedHandler::GetFollowingFeedArticlesCallback callback,
+    std::vector<::feed::NtpFeedContentFetcher::Article> articles) {
+  std::vector<ntp::feed::mojom::ArticlePtr> result;
+  for (auto& article : articles) {
+    auto article_ptr = ntp::feed::mojom::Article::New();
+    article_ptr->title = std::move(article.title);
+    article_ptr->publisher = std::move(article.publisher);
+    article_ptr->url = std::move(article.url);
+    article_ptr->thumbnail_url = std::move(article.thumbnail_url);
+    article_ptr->favicon_url = std::move(article.favicon_url);
+    result.push_back(std::move(article_ptr));
+  }
+  std::move(callback).Run(mojo::Clone(result));
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<FeedHandler> FeedHandler::Create(
+    mojo::PendingReceiver<ntp::feed::mojom::FeedHandler> handler,
+    Profile* profile) {
+  auto url_loader_factory = profile->GetDefaultStoragePartition()
+                                ->GetURLLoaderFactoryForBrowserProcess();
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfile(profile);
+  std::string api_key;
+  if (google_apis::IsGoogleChromeAPIKeyUsed()) {
+    bool is_stable_channel =
+        chrome::GetChannel() == version_info::Channel::STABLE;
+    api_key = is_stable_channel ? google_apis::GetAPIKey()
+                                : google_apis::GetNonStableAPIKey();
+  }
+
+  return std::make_unique<FeedHandler>(
+      std::move(handler),
+      std::make_unique<::feed::NtpFeedContentFetcher>(
+          identity_manager, url_loader_factory, api_key, profile->GetPrefs()),
+      profile);
+}
+
+FeedHandler::FeedHandler(
+    mojo::PendingReceiver<ntp::feed::mojom::FeedHandler> handler,
+    std::unique_ptr<::feed::NtpFeedContentFetcher> ntp_feed_content_fetcher,
+    Profile* profile)
+    : handler_(this, std::move(handler)),
+      ntp_feed_content_fetcher_(std::move(ntp_feed_content_fetcher)),
+      profile_(profile) {}
+
+FeedHandler::~FeedHandler() = default;
+
+void FeedHandler::GetFollowingFeedArticles(
+    ntp::feed::mojom::FeedHandler::GetFollowingFeedArticlesCallback callback) {
+  ntp_feed_content_fetcher_->FetchFollowingFeedArticles(
+      base::BindOnce(&HandleFetchArticlesResponse, std::move(callback)));
+}
+
+void FeedHandler::ArticleOpened() {
+  Browser* browser = chrome::FindLastActiveWithProfile(profile_);
+  if (!browser)
+    return;
+
+  BrowserView* const browser_view =
+      BrowserView::GetBrowserViewForBrowser(browser);
+  if (!browser_view)
+    return;
+
+  // TODO(https://crbug.com/1341399): When possible, show the side panel feed in
+  // a way that doesn't depend on Views and remove the DEPS rule.
+  if (browser_view->side_panel_coordinator()) {
+    browser_view->side_panel_coordinator()->Show(SidePanelEntry::Id::kFeed);
+  }
+}
+
+}  // namespace ntp
diff --git a/chrome/browser/new_tab_page/modules/feed/feed_handler.h b/chrome/browser/new_tab_page/modules/feed/feed_handler.h
new file mode 100644
index 0000000..86a7bef
--- /dev/null
+++ b/chrome/browser/new_tab_page/modules/feed/feed_handler.h
@@ -0,0 +1,46 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NEW_TAB_PAGE_MODULES_FEED_FEED_HANDLER_H_
+#define CHROME_BROWSER_NEW_TAB_PAGE_MODULES_FEED_FEED_HANDLER_H_
+
+#include "base/memory/raw_ptr.h"
+#include "chrome/browser/new_tab_page/modules/feed/feed.mojom.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/feed/core/v2/public/ntp_feed_content_fetcher.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+
+class Profile;
+
+namespace ntp {
+
+// Handles loading of articles for the Feed NTP module and user interaction with
+// articles.
+class FeedHandler : public ntp::feed::mojom::FeedHandler {
+ public:
+  static std::unique_ptr<FeedHandler> Create(
+      mojo::PendingReceiver<ntp::feed::mojom::FeedHandler> handler,
+      Profile* profile);
+
+  FeedHandler(
+      mojo::PendingReceiver<ntp::feed::mojom::FeedHandler> handler,
+      std::unique_ptr<::feed::NtpFeedContentFetcher> ntp_feed_content_fetcher,
+      Profile* profile);
+  ~FeedHandler() override;
+
+  void GetFollowingFeedArticles(
+      ntp::feed::mojom::FeedHandler::GetFollowingFeedArticlesCallback callback)
+      override;
+  void ArticleOpened() override;
+
+ private:
+  mojo::Receiver<ntp::feed::mojom::FeedHandler> handler_;
+  std::unique_ptr<::feed::NtpFeedContentFetcher> ntp_feed_content_fetcher_;
+  raw_ptr<Profile> profile_;
+};
+
+}  // namespace ntp
+
+#endif  // CHROME_BROWSER_NEW_TAB_PAGE_MODULES_FEED_FEED_HANDLER_H_
diff --git a/chrome/browser/new_tab_page/modules/feed/feed_handler_unittest.cc b/chrome/browser/new_tab_page/modules/feed/feed_handler_unittest.cc
new file mode 100644
index 0000000..106633b5
--- /dev/null
+++ b/chrome/browser/new_tab_page/modules/feed/feed_handler_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/new_tab_page/modules/feed/feed_handler.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/mock_callback.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "components/feed/core/common/pref_names.h"
+#include "components/feed/core/proto/v2/wire/data_operation.pb.h"
+#include "components/feed/core/proto/v2/wire/feed_response.pb.h"
+#include "components/feed/core/proto/v2/wire/payload_metadata.pb.h"
+#include "components/feed/core/proto/v2/wire/response.pb.h"
+#include "components/feed/core/proto/v2/wire/stream_structure.pb.h"
+#include "components/feed/core/v2/public/ntp_feed_content_fetcher.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "components/variations/scoped_variations_ids_provider.h"
+#include "content/public/test/browser_task_environment.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/test/test_shared_url_loader_factory.h"
+#include "url/gurl.h"
+
+namespace ntp::test {
+namespace {
+
+const char kExampleUrl[] = "http://example.com/";
+const int kArticleCount = 3;
+
+std::vector<::feed::NtpFeedContentFetcher::Article> MakeArticleList(int count) {
+  std::vector<::feed::NtpFeedContentFetcher::Article> articles;
+  for (int i = 0; i < count; ++i) {
+    std::string number = base::NumberToString(i);
+    auto& article = articles.emplace_back();
+    article.title = base::StrCat({"Article ", number});
+    article.publisher = base::StrCat({"Publisher ", number});
+    article.url = GURL(base::StrCat({kExampleUrl, number}));
+    article.thumbnail_url =
+        GURL(base::StrCat({kExampleUrl, number, "/thumbnail.jpg"}));
+    article.favicon_url =
+        GURL(base::StrCat({kExampleUrl, number, "/favicon.ico"}));
+  }
+  return articles;
+}
+
+class TestNtpFeedContentFetcher : public ::feed::NtpFeedContentFetcher {
+ public:
+  TestNtpFeedContentFetcher()
+      : ::feed::NtpFeedContentFetcher(
+            /*identity_manager=*/nullptr,
+            base::MakeRefCounted<network::TestSharedURLLoaderFactory>(),
+            /*api_key=*/"api key",
+            /*pref_service=*/nullptr) {}
+
+  void FetchFollowingFeedArticles(
+      base::OnceCallback<
+          void(std::vector<::feed::NtpFeedContentFetcher::Article>)> callback)
+      override {
+    std::move(callback).Run(MakeArticleList(kArticleCount));
+  }
+};
+
+}  // namespace
+
+class FeedHandlerTest : public testing::Test {
+ public:
+  FeedHandlerTest()
+      : testing_profile_manager_(TestingBrowserProcess::GetGlobal()) {}
+
+  void SetUp() override {
+    testing::Test::SetUp();
+    ASSERT_TRUE(testing_profile_manager_.SetUp());
+    profile_ = testing_profile_manager_.CreateTestingProfile("Test Profile");
+
+    ::feed::RegisterProfilePrefs(profile_prefs_.registry());
+
+    handler_ = std::make_unique<FeedHandler>(
+        mojo::PendingReceiver<ntp::feed::mojom::FeedHandler>(),
+        std::make_unique<TestNtpFeedContentFetcher>(), profile_);
+  }
+
+  void TearDown() override { handler_.reset(); }
+
+ protected:
+  std::unique_ptr<FeedHandler> handler_;
+  TestingProfile* profile_;
+  content::BrowserTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  variations::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
+      variations::VariationsIdsProvider::Mode::kUseSignedInState};
+  TestingProfileManager testing_profile_manager_;
+  TestingPrefServiceSimple profile_prefs_;
+  signin::IdentityTestEnvironment identity_test_env_;
+};
+
+TEST_F(FeedHandlerTest, GetFollowingFeedArticles) {
+  std::vector<ntp::feed::mojom::ArticlePtr> actual_articles;
+  base::MockCallback<
+      ntp::feed::mojom::FeedHandler::GetFollowingFeedArticlesCallback>
+      callback;
+  EXPECT_CALL(callback, Run(testing::_))
+      .Times(1)
+      .WillOnce(testing::Invoke(
+          [&](std::vector<ntp::feed::mojom::ArticlePtr> articles) {
+            actual_articles = std::move(articles);
+          }));
+
+  // Run the above.
+  handler_->GetFollowingFeedArticles(callback.Get());
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_EQ(3ul, actual_articles.size());
+  auto& article = actual_articles.front();
+  EXPECT_EQ("http://example.com/0", article->url);
+  EXPECT_EQ("Article 0", article->title);
+  EXPECT_EQ("Publisher 0", article->publisher);
+  EXPECT_EQ("http://example.com/0/favicon.ico", article->favicon_url);
+  EXPECT_EQ("http://example.com/0/thumbnail.jpg", article->thumbnail_url);
+}
+
+}  // namespace ntp::test
diff --git a/chrome/browser/optimization_guide/hints_fetcher_browsertest.cc b/chrome/browser/optimization_guide/hints_fetcher_browsertest.cc
index 30b236d..52b6b60 100644
--- a/chrome/browser/optimization_guide/hints_fetcher_browsertest.cc
+++ b/chrome/browser/optimization_guide/hints_fetcher_browsertest.cc
@@ -437,12 +437,6 @@
       hosts_and_urls_requested.erase(host_or_url);
     }
     EXPECT_EQ(0u, hosts_and_urls_requested.size());
-
-    // We only expect 1 field trial to be allowed and sent up.
-    EXPECT_EQ(1, hints_request.active_field_trials_size());
-    EXPECT_EQ(variations::HashName(
-                  "scoped_feature_list_trial_for_OptimizationHintsFetching"),
-              hints_request.active_field_trials(0).name_hash());
   }
 
   void TearDownOnMainThread() override {
@@ -507,9 +501,6 @@
             {optimization_guide::features::kOptimizationHints, {}},
             {optimization_guide::features::kRemoteOptimizationGuideFetching,
              {{"max_concurrent_page_navigation_fetches", "2"}}},
-            {optimization_guide::features::kOptimizationHintsFieldTrials,
-             {{"allowed_field_trial_names",
-               "scoped_feature_list_trial_for_OptimizationHintsFetching"}}},
         },
         {});
     // Call to inherited class to match same set up with feature flags added.
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc b/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
index 4ee6c9bd..5cc116c 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
@@ -205,11 +205,6 @@
     return optimization_guide_keyed_service->GetPredictionManager();
   }
 
-  void SetExpectedFieldTrialNames(
-      const base::flat_set<uint32_t>& expected_field_trial_name_hashes) {
-    expected_field_trial_name_hashes_ = expected_field_trial_name_hashes;
-  }
-
   GURL https_url_with_content() { return https_url_with_content_; }
   GURL https_url_without_content() { return https_url_without_content_; }
 
@@ -240,18 +235,6 @@
     EXPECT_NE(request.headers.end(), request.headers.find("X-Client-Data"));
     optimization_guide::proto::GetModelsRequest models_request;
     EXPECT_TRUE(models_request.ParseFromString(request.content));
-    // Make sure we actually filter field trials appropriately.
-    EXPECT_EQ(expected_field_trial_name_hashes_.size(),
-              static_cast<size_t>(models_request.active_field_trials_size()));
-    base::flat_set<uint32_t> seen_field_trial_name_hashes;
-    for (const auto& field_trial : models_request.active_field_trials()) {
-      EXPECT_TRUE(
-          expected_field_trial_name_hashes_.find(field_trial.name_hash()) !=
-          expected_field_trial_name_hashes_.end());
-      seen_field_trial_name_hashes.insert(field_trial.name_hash());
-    }
-    EXPECT_EQ(seen_field_trial_name_hashes.size(),
-              expected_field_trial_name_hashes_.size());
 
     response->set_code(net::HTTP_OK);
     std::unique_ptr<optimization_guide::proto::GetModelsResponse>
@@ -293,7 +276,6 @@
   std::unique_ptr<net::EmbeddedTestServer> models_server_;
   PredictionModelsFetcherRemoteResponseType response_type_ =
       PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile;
-  base::flat_set<uint32_t> expected_field_trial_name_hashes_;
 };
 
 class PredictionManagerBrowserTest : public PredictionManagerBrowserTestBase {
@@ -396,58 +378,6 @@
       "OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 0);
 }
 
-class PredictionManagerNoUserPermissionsTest
-    : public PredictionManagerBrowserTest {
- public:
-  PredictionManagerNoUserPermissionsTest() {
-    // Field trials should not be sent.
-    SetExpectedFieldTrialNames({});
-  }
-
-  ~PredictionManagerNoUserPermissionsTest() override = default;
-
-  void SetUpCommandLine(base::CommandLine* cmd) override {
-    PredictionManagerBrowserTest::SetUpCommandLine(cmd);
-
-    // Remove switches that enable user permissions.
-    cmd->RemoveSwitch(switches::kDisableCheckingUserPermissionsForTesting);
-  }
-
- private:
-  void InitializeFeatureList() override {
-    scoped_feature_list_.InitWithFeaturesAndParameters(
-        {
-            {features::kOptimizationHints, {}},
-            {features::kRemoteOptimizationGuideFetching, {}},
-            {features::kOptimizationTargetPrediction,
-             {{"fetch_startup_delay_ms", "2000"}}},
-            {features::kOptimizationHintsFieldTrials,
-             {{"allowed_field_trial_names",
-               "scoped_feature_list_trial_for_OptimizationHints,scoped_feature_"
-               "list_trial_for_OptimizationHintsFetching"}}},
-        },
-        {});
-  }
-};
-
-IN_PROC_BROWSER_TEST_F(PredictionManagerNoUserPermissionsTest,
-                       FieldTrialsNotPassedWhenNoUserPermissions) {
-  ModelFileObserver model_file_observer;
-  base::HistogramTester histogram_tester;
-
-  SetResponseType(
-      PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile);
-  RegisterWithKeyedService(&model_file_observer);
-
-  RetryForHistogramUntilCountReached(
-      &histogram_tester,
-      "OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
-
-  RetryForHistogramUntilCountReached(
-      &histogram_tester,
-      "OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 1);
-}
-
 class PredictionManagerModelDownloadingBrowserTest
     : public PredictionManagerBrowserTest {
  public:
@@ -493,17 +423,8 @@
             {features::kOptimizationTargetPrediction, {}},
             {features::kOptimizationGuideModelDownloading,
              {{"unrestricted_model_downloading", "true"}}},
-            {features::kOptimizationHintsFieldTrials,
-             {{"allowed_field_trial_names",
-               "scoped_feature_list_trial_for_OptimizationHints,scoped_feature_"
-               "list_trial_for_OptimizationHintsFetching"}}},
         },
         {});
-    SetExpectedFieldTrialNames(base::flat_set<uint32_t>(
-        {variations::HashName(
-             "scoped_feature_list_trial_for_OptimizationHints"),
-         variations::HashName(
-             "scoped_feature_list_trial_for_OptimizationHintsFetching")}));
   }
 
   std::unique_ptr<ModelFileObserver> model_file_observer_;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index 05d23a8b..728edef 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -33,11 +33,9 @@
   "background/braille/pan_strategy.js",
   "background/braille/spans.js",
   "background/chromevox.js",
-  "background/command_handler_interface.js",
   "background/focus_bounds.js",
   "background/logging/log_store.js",
   "background/output/output_format_tree.js",
-  "background/output/output_logger.js",
   "background/output/output_role_info.js",
   "background/output/output_types.js",
   "background/phonetic_data.js",
@@ -87,6 +85,7 @@
   "background/classic_background.js",
   "background/color.js",
   "background/command_handler.js",
+  "background/command_handler_interface.js",
   "background/console_tts.js",
   "background/desktop_automation_handler.js",
   "background/desktop_automation_interface.js",
@@ -113,6 +112,7 @@
   "background/output/output.js",
   "background/output/output_ancestry_info.js",
   "background/output/output_format_parser.js",
+  "background/output/output_logger.js",
   "background/page_load_sound_handler.js",
   "background/panel/i_search.js",
   "background/panel/i_search_handler.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler.js
index 9234e52..d9fdc4c3 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler.js
@@ -6,6 +6,7 @@
  * @fileoverview Handles auto scrolling on navigation.
  */
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
+import {CommandHandlerInterface} from '/chromevox/background/command_handler_interface.js';
 
 // setTimeout and its clean-up are referencing each other. So, we need to set
 // "ignoreReadBeforeAssign" in this file. ESLint doesn't support per-line rule
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
index e4e9a861..cc11d16 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
@@ -6,6 +6,7 @@
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
 import {ChromeVoxBackground} from '/chromevox/background/classic_background.js';
 import {CommandHandler} from '/chromevox/background/command_handler.js';
+import {CommandHandlerInterface} from '/chromevox/background/command_handler_interface.js';
 import {ConsoleTts} from '/chromevox/background/console_tts.js';
 import {DesktopAutomationHandler} from '/chromevox/background/desktop_automation_handler.js';
 import {DesktopAutomationInterface} from '/chromevox/background/desktop_automation_interface.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
index 6e15d8b37..0751373a 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
@@ -35,6 +35,9 @@
     await importModule(
         'ChromeVoxState', '/chromevox/background/chromevox_state.js');
     await importModule(
+        'CommandHandlerInterface',
+        '/chromevox/background/command_handler_interface.js');
+    await importModule(
         'CustomAutomationEvent',
         '/chromevox/common/custom_automation_event.js');
     await importModule(
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_command_handler.js
index 8654ddca..8a65661 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/braille_command_handler.js
@@ -6,6 +6,7 @@
  * @fileoverview ChromeVox braille commands.
  */
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
+import {CommandHandlerInterface} from '/chromevox/background/command_handler_interface.js';
 import {DesktopAutomationInterface} from '/chromevox/background/desktop_automation_interface.js';
 import {EventSourceState} from '/chromevox/background/event_source.js';
 import {Output} from '/chromevox/background/output/output.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/liblouis_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/liblouis_test.js
index 75d36d1..ecbe6adb 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/liblouis_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/liblouis_test.js
@@ -114,6 +114,16 @@
   });
 });
 
+LIBLOUIS_TEST_F('testTranslateSpaceIsNotDropped', function(liblouis) {
+  this.withTranslator(liblouis, 'en-ueb-g2.ctb', function(translator) {
+    translator.translate(
+        ' ', [],
+        this.newCallback(function(cells, textToBraille, brailleToText) {
+          assertEqualsUint8Array([0x0], cells);
+        }));
+  });
+});
+
 LIBLOUIS_TEST_F('testBackTranslateGermanComputerBraille', function(liblouis) {
   this.withTranslator(liblouis, 'de-de-comp8.ctb', function(translator) {
     const cells = new Uint8Array([0xb3]);
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
index 2f7a9e40..e0e1d5c 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler.js
@@ -11,6 +11,7 @@
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
 import {ChromeVoxBackground} from '/chromevox/background/classic_background.js';
 import {Color} from '/chromevox/background/color.js';
+import {CommandHandlerInterface} from '/chromevox/background/command_handler_interface.js';
 import {DesktopAutomationInterface} from '/chromevox/background/desktop_automation_interface.js';
 import {TypingEcho} from '/chromevox/background/editing/editable_text_base.js';
 import {EventSourceState} from '/chromevox/background/event_source.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler_interface.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler_interface.js
index d8e8f1cac..a8d9513 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler_interface.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/command_handler_interface.js
@@ -2,9 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-goog.provide('CommandHandlerInterface');
-
-CommandHandlerInterface = class {
+export class CommandHandlerInterface {
   /**
    * Handles ChromeVox commands.
    * @param {string} command
@@ -20,7 +18,7 @@
    * @return {cursors.Range} The resulting range.
    */
   skipLabelOrDescriptionFor(current, dir) {}
-};
+}
 
 /**
  * @type {CommandHandlerInterface}
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js
index a4fb3529..f81c5ca 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler.js
@@ -8,6 +8,7 @@
 import {AutoScrollHandler} from '/chromevox/background/auto_scroll_handler.js';
 import {AutomationObjectConstructorInstaller} from '/chromevox/background/automation_object_constructor_installer.js';
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
+import {CommandHandlerInterface} from '/chromevox/background/command_handler_interface.js';
 import {DesktopAutomationInterface} from '/chromevox/background/desktop_automation_interface.js';
 import {TextEditHandler} from '/chromevox/background/editing/editing.js';
 import {EventSourceState} from '/chromevox/background/event_source.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js
index ce0e2aa..048274f 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing.js
@@ -670,13 +670,14 @@
    * @private
    */
   handleBraille_(baseLineOnStart) {
+    const isEmpty = !this.node_.find({role: RoleType.STATIC_TEXT});
     const isFirstLine = this.isSelectionOnFirstLine();
     const cur = this.line_;
     if (cur.value === null) {
       return;
     }
 
-    let value = new MultiSpannable(cur.value);
+    let value = new MultiSpannable(isEmpty ? '' : cur.value);
     if (!this.node_.constructor) {
       return;
     }
@@ -707,6 +708,8 @@
           start, end);
     });
 
+    value.setSpan(new ValueSpan(0), 0, value.length);
+
     // Provide context for the current selection.
     const context = baseLineOnStart ? cur.startContainer : cur.endContainer;
     if (context && context.role !== RoleType.TEXT_FIELD) {
@@ -729,16 +732,25 @@
       }
     }
 
+    let start = cur.startOffset;
+    let end = cur.endOffset;
     if (isFirstLine) {
       if (!/\s/.test(value.toString()[value.length - 1])) {
         value.append(Output.SPACE);
       }
+
+      if (isEmpty) {
+        // When the text field is empty, place the selection cursor immediately
+        // after the space and before the 'ed' role msg indicator below.
+        start = value.length - 1;
+        end = start;
+      }
       value.append(Msgs.getMsg('tag_textarea_brl'));
     }
-    value.setSpan(new ValueSpan(0), 0, cur.value.length);
-    value.setSpan(new ValueSelectionSpan(), cur.startOffset, cur.endOffset);
-    ChromeVox.braille.write(new NavBraille(
-        {text: value, startIndex: cur.startOffset, endIndex: cur.endOffset}));
+
+    value.setSpan(new ValueSelectionSpan(), start, end);
+    ChromeVox.braille.write(
+        new NavBraille({text: value, startIndex: start, endIndex: end}));
   }
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js
index 6161097..5828782b 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js
@@ -2324,3 +2324,24 @@
 
           .replay();
     });
+
+AX_TEST_F(
+    'ChromeVoxEditingTest', 'BackspaceToEmptyTextField', async function() {
+      const mockFeedback = this.createMockFeedback();
+      const site = `
+    <p>start</p>
+    <div aria-label="test" role="textbox" contenteditable></div>
+  `;
+      const root = await this.runWithLoadedTree(site);
+      await this.focusFirstTextField(root);
+
+      const textField = root.find({role: RoleType.TEXT_FIELD});
+      mockFeedback.expectSpeech('Text area')
+          .expectBraille('test mled', {startIndex: -1, endIndex: -1})
+          .call(this.press(KeyCode.A))
+          .expectBraille('a mled', {startIndex: 1, endIndex: 1})
+          .call(this.press(KeyCode.BACK))
+          .expectBraille(' mled', {startIndex: 0, endIndex: 0})
+
+          .replay();
+    });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/gesture_command_handler.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/gesture_command_handler.js
index 9fdd06a..e92be2e 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/gesture_command_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/gesture_command_handler.js
@@ -6,6 +6,7 @@
  * @fileoverview Handles gesture-based commands.
  */
 import {ChromeVoxState} from '/chromevox/background/chromevox_state.js';
+import {CommandHandlerInterface} from '/chromevox/background/command_handler_interface.js';
 import {EventSourceState} from '/chromevox/background/event_source.js';
 import {GestureInterface} from '/chromevox/background/gesture_interface.js';
 import {Output} from '/chromevox/background/output/output.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
index 4fdbc73..c056871 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
@@ -16,7 +16,6 @@
 goog.require('BrailleKeyEvent');
 goog.require('BridgeHelper');
 goog.require('ChromeVox');
-goog.require('CommandHandlerInterface');
 goog.require('ExtraCellsSpan');
 goog.require('FocusBounds');
 goog.require('KeyCode');
@@ -34,7 +33,6 @@
 goog.require('OutputFormatTree');
 goog.require('OutputNodeSpan');
 goog.require('OutputRoleInfo');
-goog.require('OutputRulesStr');
 goog.require('OutputSelectionSpan');
 goog.require('OutputSpeechProperties');
 goog.require('PanelBridge');
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
index d436a3c..aff0d24 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
@@ -8,6 +8,7 @@
 import {EventSourceState} from '/chromevox/background/event_source.js';
 import {OutputAncestryInfo} from '/chromevox/background/output/output_ancestry_info.js';
 import {OutputFormatParser, OutputFormatParserObserver} from '/chromevox/background/output/output_format_parser.js';
+import {OutputRulesStr} from '/chromevox/background/output/output_logger.js';
 import {EventSourceType} from '/chromevox/common/event_source_type.js';
 
 const AriaCurrentState = chrome.automation.AriaCurrentState;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_logger.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_logger.js
index 3ac0a8ed..0aca5ad 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_logger.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_logger.js
@@ -6,9 +6,7 @@
  * @fileoverview Provides output logger.
  */
 
-goog.provide('OutputRulesStr');
-
-OutputRulesStr = class {
+export class OutputRulesStr {
   /**
    * @param {string} enableKey The key to enable logging in localStorage
    */
@@ -88,7 +86,7 @@
     this.str += errorMsg;
     this.str += '\n';
   }
-};
+}
 
 /**
  * @typedef {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/tts_background_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/tts_background_test.js
index d1b7b87d..9ad6a4b5 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/tts_background_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/tts_background_test.js
@@ -15,6 +15,9 @@
   async setUpDeferred() {
     await super.setUpDeferred();
     await importModule(
+        'CommandHandlerInterface',
+        '/chromevox/background/command_handler_interface.js');
+    await importModule(
         'TtsBackground', '/chromevox/background/tts_background.js');
     window.tts = new TtsBackground();
   }
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor.js
index d8ccfe4c..7254d36 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor.js
@@ -5,6 +5,7 @@
 /**
  * @fileoverview Monitors user actions.
  */
+import {CommandHandlerInterface} from '/chromevox/background/command_handler_interface.js';
 import {Output} from '/chromevox/background/output/output.js';
 import {KeySequence} from '/chromevox/common/key_sequence.js';
 import {PanelCommand, PanelCommandType} from '/chromevox/common/panel_command.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/learn_mode_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/learn_mode_test.js
index 02a26bf..35556cfb 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/learn_mode_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/learn_mode_test.js
@@ -19,6 +19,14 @@
     window.doBrailleKeyEvent = this.doBrailleKeyEvent.bind(this);
   }
 
+  /** @override */
+  async setUpDeferred() {
+    await super.setUpDeferred();
+    await importModule(
+        'CommandHandlerInterface',
+        '/chromevox/background/command_handler_interface.js');
+  }
+
   async runOnLearnModePage() {
     return new Promise(async resolve => {
       const mockFeedback = this.createMockFeedback();
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
index ded48889d..f328ae7 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
@@ -20,6 +20,9 @@
   async setUpDeferred() {
     await super.setUpDeferred();
     await importModule('AbstractTts', '/chromevox/common/abstract_tts.js');
+    await importModule(
+        'CommandHandlerInterface',
+        '/chromevox/background/command_handler_interface.js');
     await importModule('EventGenerator', '/common/event_generator.js');
   }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js
index c99c36b..89f786d 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js
@@ -24,7 +24,6 @@
 goog.require('OutputFormatTree');
 goog.require('OutputNodeSpan');
 goog.require('OutputRoleInfo');
-goog.require('OutputRulesStr');
 goog.require('OutputSelectionSpan');
 goog.require('OutputSpeechProperties');
 goog.require('PanelNodeMenuData');
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_test.js
index 8737dde..bb35ca3a 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_test.js
@@ -15,6 +15,9 @@
     await importModule(
         'ChromeVoxState', '/chromevox/background/chromevox_state.js');
     await importModule(
+        'CommandHandlerInterface',
+        '/chromevox/background/command_handler_interface.js');
+    await importModule(
         ['PanelCommand', 'PanelCommandType'],
         '/chromevox/common/panel_command.js');
   }
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js
index 5886305..37926d0 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js
@@ -16,6 +16,9 @@
     await importModule(
         'ChromeVoxState', '/chromevox/background/chromevox_state.js');
     await importModule(
+        'CommandHandlerInterface',
+        '/chromevox/background/command_handler_interface.js');
+    await importModule(
         'UserActionMonitor', '/chromevox/background/user_action_monitor.js');
     await importModule(
         ['PanelCommand', 'PanelCommandType'],
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/testing/chromevox_next_e2e_test_base.js b/chrome/browser/resources/chromeos/accessibility/chromevox/testing/chromevox_next_e2e_test_base.js
index 09ef2cd..ca40412 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/testing/chromevox_next_e2e_test_base.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/testing/chromevox_next_e2e_test_base.js
@@ -120,6 +120,9 @@
       await importModule(
           'CommandHandler', '/chromevox/background/command_handler.js');
       await importModule(
+          'CommandHandlerInterface',
+          '/chromevox/background/command_handler_interface.js');
+      await importModule(
           'GestureCommandHandler',
           '/chromevox/background/gesture_command_handler.js');
 
diff --git a/chrome/browser/resources/chromeos/notification_tester/form_constants.js b/chrome/browser/resources/chromeos/notification_tester/form_constants.js
index a2d26ecc..d836c52 100644
--- a/chrome/browser/resources/chromeos/notification_tester/form_constants.js
+++ b/chrome/browser/resources/chromeos/notification_tester/form_constants.js
@@ -9,6 +9,43 @@
  */
 
 /*
+  Matches NotificationPriority in
+  ui/message_center/public/cpp/notification_types.h
+  @enum {number}
+*/
+export const NotificationPriority = {
+  MIN_PRIORITY: -2,
+  LOW_PRIORITY: -1,
+  DEFAULT_PRIORITY: 0,
+  HIGH_PRIORITY: 1,
+  MAX_PRIORITY: 2,
+  SYSTEM_PRIORITY: 3,
+};
+
+/*
+  Matches NotificationType in
+  ui/message_center/public/cpp/notification_types.h
+  @enum {number}
+*/
+export const NotificationType = {
+  NOTIFICATION_TYPE_SIMPLE: 0,
+  NOTIFICATION_TYPE_BASE_FORMAT: 1,
+  NOTIFICATION_TYPE_IMAGE: 2,
+  NOTIFICATION_TYPE_MULTIPLE: 3,
+  NOTIFICATION_TYPE_PROGRESS: 4,
+};
+
+/*
+  Matches NotifierType in
+  ui/message_center/public/cpp/notifier_id.h
+  @enum {number}
+*/
+export const NotifierType = {
+  WEB_PAGE: 2,
+  SYSTEM_COMPONENT: 3,
+};
+
+/*
  * Stores the content of the notification tester form select
  * elements. Options correspond to the naming scheme defined in
  * ui/message_center/public/cpp/notification.h.
@@ -16,45 +53,51 @@
  */
 export const FormSelectOptions = {
   TITLE_OPTIONS: [
-    {displayText: 'Short Sentence (LTR)', value: 'Notification Title'},
-    {displayText: 'Short Sentence (RTL)', value: 'כותרת הודעה'}, {
-      displayText: 'Long Sentence (LTR)',
+    {
+      displayText: 'Short Sentence (Left-to-Right)',
+      value: 'Notification Title'
+    },
+    {displayText: 'Short Sentence (Right-to-Left))', value: 'כותרת הודעה'}, {
+      displayText: 'Long Sentence (Left-to-Right)',
       value:
           'Hamburgers: the cornerstone of any nutritious breakfast. Ch-cheeseburgers'
     },
     {
-      displayText: 'Long Sentence (RTL)',
+      displayText: 'Long Sentence (Right-to-Left))',
       value: 'המבורגרים: אבן הפינה של כל ארוחת בוקר מזינה. ציזבורגר'
     },
     {
-      displayText: 'Repetitive Characters (LTR)',
+      displayText: 'Repetitive Characters (Left-to-Right)',
       value: 'sshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh'
     },
     {
-      displayText: 'Repetitive Characters (RTL)',
+      displayText: 'Repetitive Characters (Right-to-Left))',
       value: 'שששששששששששששששששששששששששששששששששששששששששששששששששששש'
     }
   ],
   MESSAGE_OPTIONS: [
-    {displayText: 'One Sentence (LTR)', value: 'Notification content'},
-    {displayText: 'One Sentence (RTL)', value: 'תוכן הודעה'},
     {
-      displayText: 'Multiple Sentences (LTR)',
+      displayText: 'One Sentence (Left-to-Right)',
+      value: 'Notification content'
+    },
+    {displayText: 'One Sentence (Right-to-Left))', value: 'תוכן הודעה'},
+    {
+      displayText: 'Multiple Sentences (Left-to-Right)',
       value:
           'This is the notification\'s message.It may be able to stretch over multiple lines, or become visible when the notification is expanded by the user, depending on the notification center that\'s being used.'
     },
     {
-      displayText: 'Multiple Sentences (RTL)',
+      displayText: 'Multiple Sentences (Right-to-Left))',
       value:
           'זהו המסר של ההודעה. זה עשוי להיות מסוגל למתוח על קווים מרובים, או להיות גלוי, כאשר ההודעה מורחבת על ידי המשתמש, בהתאם להודעה שהמרכז נמצא בשימוש'
     },
     {
-      displayText: 'Repetitive Characters (LTR)',
+      displayText: 'Repetitive Characters (Left-to-Right)',
       value:
           'sshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh'
     },
     {
-      displayText: 'Repetitive Characters (RTL)',
+      displayText: 'Repetitive Characters (Right-to-Left))',
       value:
           'ששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששששש'
     },
@@ -77,46 +120,72 @@
     {displayText: 'No Image', value: 'none'},
     {displayText: 'CrOS Logo (1218x317, PNG)', value: 'chromeos_logo_main'},
   ],
+  URL_OPTIONS: [
+    {displayText: 'URL (Left-to-Right)', value: 'https://testurl.xyz'},
+    {displayText: 'URL (Right-to-Left)', value: 'https://اختبار.النهاي'},
+    {displayText: 'Empty', value: ''},
+  ],
   SOURCE_OPTIONS: [
-    {displayText: 'testurl.xyz', value: 'url_ltr'},
-    {displayText: 'Sample Display Source', value: 'dis_ltr'},
-    {displayText: 'اختبار.النهاية', value: 'url_rtl'},
-    {displayText: 'مصدر عرض العينة', value: 'dis_rtl'},
-    {displayText: 'Empty', value: 'none'},
-  ],
-  NOTIFICATION_TYPE_OPTIONS: [
-    {displayText: 'Simple', value: 'simple'},
-    {displayText: 'Base Format', value: 'base'},
-    {displayText: 'Image', value: 'image'},
-    {displayText: 'Multiple', value: 'mult'},
-    {displayText: 'Progress', value: 'progress'},
-  ],
-  PRIORITY_OPTIONS: [
-    {displayText: 'Default', value: 'default'},
-    {displayText: 'Minimum', value: 'min'},
-    {displayText: 'Low', value: 'low'},
-    {displayText: 'High', value: 'high'},
-    {displayText: 'Max', value: 'max'},
-    {displayText: 'System', value: 'system'},
-  ],
-  PROGRESS_STATUS_OPTIONS: [
-    {displayText: 'Short Sentence (LTR)', value: 'Progress Status'},
-    {displayText: 'Short Sentence (RTL)', value: 'כותרת הודעה'},
     {
-      displayText: 'Long Sentence (LTR)',
+      displayText: 'Short Sentence (Left-to-Right)',
+      value: 'Sample Display Source'
+    },
+    {displayText: 'Short Sentence (Right-to-Left)', value: 'مصدر عرض العينة'},
+    {
+      displayText: 'Long Sentence (Left-to-Right)',
       value:
           'Hamburgers: the cornerstone of any nutritious breakfast. Ch-cheeseburgers'
     },
     {
-      displayText: 'Long Sentence (RTL)',
+      displayText: 'Long Sentence (Right-to-Left)',
+      value:
+          'مصدر عرض العينةمصدر عرض العينةمصدر عرض العينةمصدر عرض العينةمصدر عرض العينةمصدر عرض العينة'
+    },
+    {displayText: 'Empty', value: ''},
+
+  ],
+  NOTIFICATION_TYPE_OPTIONS: [
+    {displayText: 'Simple', value: NotificationType.NOTIFICATION_TYPE_SIMPLE},
+    {
+      displayText: 'Base Format',
+      value: NotificationType.NOTIFICATION_TYPE_BASE_FORMAT
+    },
+    {displayText: 'Image', value: NotificationType.NOTIFICATION_TYPE_IMAGE},
+    {
+      displayText: 'Multiple',
+      value: NotificationType.NOTIFICATION_TYPE_MULTIPLE
+    },
+    {
+      displayText: 'Progress',
+      value: NotificationType.NOTIFICATION_TYPE_PROGRESS
+    },
+  ],
+  PRIORITY_OPTIONS: [
+    {displayText: 'Default', value: NotificationPriority.DEFAULT_PRIORITY},
+    {displayText: 'Minimum', value: NotificationPriority.MIN_PRIORITY},
+    {displayText: 'Low', value: NotificationPriority.LOW_PRIORITY},
+    {displayText: 'High', value: NotificationPriority.HIGH_PRIORITY},
+    {displayText: 'Max', value: NotificationPriority.MAX_PRIORITY},
+    {displayText: 'System', value: NotificationPriority.SYSTEM_PRIORITY},
+  ],
+  PROGRESS_STATUS_OPTIONS: [
+    {displayText: 'Short Sentence (Left-to-Right)', value: 'Progress Status'},
+    {displayText: 'Short Sentence (Right-to-Left))', value: 'כותרת הודעה'},
+    {
+      displayText: 'Long Sentence (Left-to-Right)',
+      value:
+          'Hamburgers: the cornerstone of any nutritious breakfast. Ch-cheeseburgers'
+    },
+    {
+      displayText: 'Long Sentence (Right-to-Left))',
       value: 'המבורגרים: אבן הפינה של כל ארוחת בוקר מזינה. ציזבורגר'
     },
     {
-      displayText: 'Repetitive Characters (LTR)',
+      displayText: 'Repetitive Characters (Left-to-Right)',
       value: 'sshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh'
     },
     {
-      displayText: 'Repetitive Characters (RTL)',
+      displayText: 'Repetitive Characters (Right-to-Left))',
       value: 'שששששששששששששששששששששששששששששששששששששששששששששששששששש'
     },
     {displayText: 'Unicode Emojis', value: '🌇😃🍈😆🍜🍻😋⛅⛳😚ඞ'},
diff --git a/chrome/browser/resources/chromeos/notification_tester/notification_tester.html b/chrome/browser/resources/chromeos/notification_tester/notification_tester.html
index cb7d90c..5aaf5f2 100644
--- a/chrome/browser/resources/chromeos/notification_tester/notification_tester.html
+++ b/chrome/browser/resources/chromeos/notification_tester/notification_tester.html
@@ -69,12 +69,12 @@
     --cr-radio-button-label-spacing: 10px;
   }
 
-  cr-radio-group#progress-percent > cr-radio-button,
-  cr-radio-group#num-buttons > cr-radio-button {
+  cr-radio-group#progress-percent>cr-radio-button,
+  cr-radio-group#num-buttons>cr-radio-button {
     --cr-radio-group-item-padding: 6px;
   }
 
-  .config-form > button {
+  .config-form>button {
     background: rgb(0, 134, 179);
     border-radius: 0.25rem;
     color: #f8f8f8;
@@ -85,7 +85,6 @@
     padding: 0.25em 0.5em;
     width: max-content;
   }
-
 </style>
 <app-header>
   <app-toolbar>Notification Tester</app-toolbar>
@@ -97,117 +96,150 @@
 
       <div class="form-item">
         <label for="notifier-type"> Notifier Type </label>
-        <cr-radio-group id="notifier-type" selected="System">
+        <cr-radio-group id="notifier-type"
+          selected="{{notifMetadata.notifierType}}">
           <cr-radio-button name="System" label="System"></cr-radio-button>
           <cr-radio-button name="Web" label="Web"></cr-radio-button>
         </cr-radio-group>
       </div>
 
-      <select-custom select-value="" display-label="Notification Type"
-      select-elements="[[notificationTypeSelectList]]"
-      selectid="notification-type" no-custom-input="true"></select-custom>
+      <select-custom select-value="{{notifMetadata.notificationType}}"
+        display-label="Notification Type"
+        select-elements="[[notificationTypeSelectList]]"
+        selectid="notification-type" no-custom-input="true"></select-custom>
 
-      <select-custom select-value="" display-label="Notification ID"
-      select-elements="[[notificationIDSelectList]]"
-      selectid="notification-id"></select-custom>
+      <select-custom select-value="{{notifMetadata.id}}"
+        display-label="Notification ID"
+        select-elements="[[notificationIDSelectList]]"
+        selectid="notification-id"></select-custom>
 
-      <select-custom select-value="{{notifMetadata.title}}" display-label="Title"
-      select-elements="[[titleSelectList]]" selectid="title"></select-custom>
+      <select-custom select-value="{{notifMetadata.title}}"
+        display-label="Title" select-elements="[[titleSelectList]]"
+        selectid="title"></select-custom>
 
       <select-custom select-value="{{notifMetadata.message}}"
-      display-label="Message" select-elements="[[messageSelectList]]"
-      selectid="message"></select-custom>
+        display-label="Message" select-elements="[[messageSelectList]]"
+        selectid="message"></select-custom>
 
       <select-custom select-value="{{notifMetadata.icon}}" display-label="Icon"
-      select-elements="[[iconSelectList]]" selectid="icon"
-      no-custom-input="true"></select-custom>
-    
-      <select-custom select-value="" display-label="Source"
-      select-elements="[[sourceSelectList]]" selectid="source"></select-custom>
+        select-elements="[[iconSelectList]]" selectid="icon"
+        no-custom-input="true"></select-custom>
+
+      <template is="dom-if" if="[[showDisplaySource]]">
+        <select-custom select-value="{{notifMetadata.displaySource}}"
+          display-label="Source" select-elements="[[sourceSelectList]]"
+          selectid="source"></select-custom>
+      </template>
+
+      <template is="dom-if" if="[[showOriginURL]]">
+        <select-custom select-value="{{notifMetadata.originURL}}"
+          display-label="Origin URL" select-elements="[[originURLSelectList]]"
+          selectid="origin-url"></select-custom>
+      </template>
     </div>
     <div class="config-form">
       <h2> Rich Notification Data (optional fields) </h2>
 
       <select-custom select-value="{{notifMetadata.richDataImage}}"
-      display-label="Image" select-elements="[[imageSelectList]]"
-      selectid="image" no-custom-input="true"></select-custom>
+        display-label="Image" select-elements="[[imageSelectList]]"
+        selectid="image" no-custom-input="true"></select-custom>
 
-      <select-custom select-value="" display-label="Small Image" select-elements="[[smallImageSelectList]]"
-      selectid="small-image" no-custom-input="true"></select-custom>
+      <select-custom select-value="{{notifMetadata.richDataSmallImage}}"
+        display-label="Small Image" select-elements="[[smallImageSelectList]]"
+        selectid="small-image" no-custom-input="true"></select-custom>
 
-      <select-custom select-value="" display-label="Priority"
-      select-elements="[[prioritySelectList]]" selectid="priority" no-custom-input="true"></select-custom>
-      
+      <select-custom select-value="{{notifMetadata.richDataPriority}}"
+        display-label="Priority" select-elements="[[prioritySelectList]]"
+        selectid="priority" no-custom-input="true"></select-custom>
+
       <div class="form-item">
         <label for="never-timeout">Never Timeout</label>
-        <cr-checkbox id="never-timeout"></cr-checkbox>
+        <cr-checkbox id="never-timeout"
+          checked="{{notifMetadata.richDataNeverTimeout}}"></cr-checkbox>
       </div>
 
       <div class="form-item">
         <label for="pinned">Pinned</label>
-        <cr-checkbox id="pinned"></cr-checkbox>
+        <cr-checkbox id="pinned" checked="{{notifMetadata.richDataPinned}}">
+        </cr-checkbox>
       </div>
 
       <div class="form-item">
         <label for="renotify">Renotify</label>
-        <cr-checkbox id="renotify"></cr-checkbox>
+        <cr-checkbox id="renotify" checked="{{notifMetadata.richDataRenotify}}">
+        </cr-checkbox>
       </div>
 
       <h3> Type Specific Fields </h3>
-      
-      <div class="form-item">
-        <label for="num-notif-items"> # Notif Items </label>
-        <cr-radio-group id="num-notif-items" selected="2">
-          <cr-radio-button name="0" label="0"></cr-radio-button>
-          <cr-radio-button name="1" label="1"></cr-radio-button>
-          <cr-radio-button name="2" label="2"></cr-radio-button>
-          <cr-radio-button name="3" label="3"></cr-radio-button>
-          <cr-radio-button name="4" label="4"></cr-radio-button>
-        </cr-radio-group>
-      </div>
+      <template is="dom-if" if="[[showTypeSpecificDesc]]">
+        <p> Change the notification type to 'Progress' or 'Multiple' to see more
+          options! </p>
+      </template>
 
-      <div class="form-item">
-        <label for="progress-percent"> Progress % </label>
-        <cr-radio-group id="progress-percent" selected="none">
-          <cr-radio-button name="none" label="N/A"></cr-radio-button>
-          <cr-radio-button name="0" label="0%"></cr-radio-button>
-          <cr-radio-button name="50" label="50%"></cr-radio-button>
-          <cr-radio-button name="100" label="100%"></cr-radio-button>
-        </cr-radio-group>
-      </div>
+      <template is="dom-if" if="[[showMultiOptions]]">
+        <div class="form-item">
+          <label for="num-notif-items"> # Notif Items </label>
+          <cr-radio-group id="num-notif-items"
+            selected="{{notifMetadata.richDataNumNotifItems}}">
+            <cr-radio-button name="0" label="0"></cr-radio-button>
+            <cr-radio-button name="1" label="1"></cr-radio-button>
+            <cr-radio-button name="2" label="2"></cr-radio-button>
+            <cr-radio-button name="3" label="3"></cr-radio-button>
+            <cr-radio-button name="4" label="4"></cr-radio-button>
+          </cr-radio-group>
+        </div>
+      </template>
 
-      <select-custom select-value="" display-label="Progress Status"
-      select-elements="[[progressStatusSelectList]]" selectid="prog-status"></select-custom>
+      <template is="dom-if" if="[[showProgressOptions]]">
+        <div class="form-item">
+          <label for="progress-percent"> Progress % </label>
+          <cr-radio-group id="progress-percent"
+            selected="{{notifMetadata.richDataProgress}}">
+            <cr-radio-button name="none" label="N/A"></cr-radio-button>
+            <cr-radio-button name="0" label="0%"></cr-radio-button>
+            <cr-radio-button name="50" label="50%"></cr-radio-button>
+            <cr-radio-button name="100" label="100%"></cr-radio-button>
+          </cr-radio-group>
+        </div>
+        <select-custom select-value="{{notifMetadata.richDataProgressStatus}}"
+          display-label="Progress Status"
+          select-elements="[[progressStatusSelectList]]" selectid="prog-status">
+        </select-custom>
+      </template>
     </div>
   </div>
   <div class="column">
     <div class="config-form">
       <h2> Buttons </h2>
+
       <div class="form-item">
         <label for="show-settings">Show Settings</label>
-        <cr-checkbox id="show-settings"></cr-checkbox>
+        <cr-checkbox id="show-settings"
+          checked="{{notifMetadata.richDataShowSettings}}"></cr-checkbox>
       </div>
 
       <div class="form-item">
         <label for="show-snooze">Show Snooze</label>
-        <cr-checkbox id="show-snooze"></cr-checkbox>
+        <cr-checkbox id="show-snooze"
+          checked="{{notifMetadata.richDataShowSnooze}}"></cr-checkbox>
       </div>
 
       <div class="form-item">
         <label for="num-buttons"> # Buttons </label>
-        <cr-radio-group id="num-buttons" selected="None">
+        <cr-radio-group id="num-buttons"
+          selected="{{notifMetadata.richDataNumButtons}}">
           <cr-radio-button name="none" label="None"></cr-radio-button>
           <cr-radio-button name="1" label="1"></cr-radio-button>
           <cr-radio-button name="2" label="2"></cr-radio-button>
           <cr-radio-button name="3" label="3"></cr-radio-button>
         </cr-radio-group>
       </div>
+
     </div>
     <div class="config-form">
       <button type="button" id="generateNotifBtn" on-click="onClickGenerate">
         Generate </button>
-      <button type="button" id="" on-click="">
-          Reset </button>
+      <button type="button" id="resetFormBtn" on-click="">
+        Reset </button>
     </div>
-  </div>
-</div>
\ No newline at end of file
+  </div>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/notification_tester/notification_tester.js b/chrome/browser/resources/chromeos/notification_tester/notification_tester.js
index d611513..43562fe5 100644
--- a/chrome/browser/resources/chromeos/notification_tester/notification_tester.js
+++ b/chrome/browser/resources/chromeos/notification_tester/notification_tester.js
@@ -10,7 +10,7 @@
 
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {FormSelectOptions} from './form_constants.js';
+import {FormSelectOptions, NotificationType, NotifierType} from './form_constants.js';
 import {Notification} from './types.js';
 
 // Web component housing the form for chrome://notification-tester.
@@ -35,6 +35,26 @@
         },
       },
       /*
+      @type {!boolean}
+      */
+      showTypeSpecificDesc: {type: Boolean},
+      /*
+      @type {!boolean}
+      */
+      showProgressOptions: {type: Boolean},
+      /*
+      @type {!boolean}
+      */
+      showMultiOptions: {type: Boolean},
+      /*
+      @type {!boolean}
+      */
+      showDisplaySource: {type: Boolean},
+      /*
+      @type {!boolean}
+      */
+      showOriginURL: {type: Boolean},
+      /*
        * @private
        */
       titleSelectList: {
@@ -79,6 +99,13 @@
       /*
        * @private
        */
+      originURLSelectList: {
+        type: Array,
+        value: FormSelectOptions.URL_OPTIONS,
+      },
+      /*
+       * @private
+       */
       notificationTypeSelectList: {
         type: Array,
         value: FormSelectOptions.NOTIFICATION_TYPE_OPTIONS,
@@ -107,10 +134,41 @@
     };
   }
 
+  static get observers() {
+    return [
+      'notificationTypeChanged_(notifMetadata.notificationType)',
+      'notifierTypeChanged_(notifMetadata.notifierType)'
+    ];
+  }
+
   onClickGenerate() {
     // Send notification data to C++
     chrome.send('generateNotificationForm', [this.notifMetadata]);
   }
+
+  // Show / hide dom elements when this.notifMetadata.notificationType changes
+  notificationTypeChanged_(notificationType) {
+    this.showMultiOptions =
+        (notificationType == NotificationType.NOTIFICATION_TYPE_MULTIPLE);
+    this.showProgressOptions =
+        (notificationType == NotificationType.NOTIFICATION_TYPE_PROGRESS);
+    this.showTypeSpecificDesc =
+        !(this.showMultiOptions || this.showProgressOptions);
+  }
+
+  // Set this.notifMetadata.notifierType to the appropriate number given the
+  // notifier as a string.
+  notifierTypeChanged_(notifierType) {
+    // notifierType is guaranteed to be 'System' or 'Web'.
+    this.showDisplaySource = notifierType == 'System';
+    this.showOriginURL = notifierType == 'Web';
+    if (notifierType == 'System') {
+      this.notifMetadata.notifierType = NotifierType.SYSTEM_COMPONENT;
+      return;
+    }
+
+    this.notifMetadata.notifierType = NotifierType.WEB_PAGE;
+  }
 }
 
 customElements.define(NotificationTester.is, NotificationTester);
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/notification_tester/select_custom.js b/chrome/browser/resources/chromeos/notification_tester/select_custom.js
index 90e642fc..a72fee2 100644
--- a/chrome/browser/resources/chromeos/notification_tester/select_custom.js
+++ b/chrome/browser/resources/chromeos/notification_tester/select_custom.js
@@ -15,7 +15,7 @@
     this.shadowRoot.querySelector('.hidden-input').hidden = true;
 
     // Set default value of select to the first option.
-    this.shadowRoot.querySelector('#' + this.selectid).selectedIndex = 1;
+    this.shadowRoot.querySelector('#' + this.selectid).selectedIndex = 0;
   }
 
   static get is() {
@@ -29,9 +29,7 @@
   static get properties() {
     return {
       selectElements: {type: Array},
-      selectValue:
-          {type: String, notify: true, computed: 'getSelectValue(selectIndex)'},
-      selectIndex: {type: Number, value: 0},
+      selectValue: {type: String, notify: true},
       formItemStyle: {type: String, value: 'form-item'},
       displayLabel: {type: String},
       selectid: {type: String},
@@ -46,29 +44,23 @@
   // <select> element.
   onSelectChange(event) {
     const customInput = this.shadowRoot.querySelector('.hidden-input');
-    if (event.target.value == 'custom' && !this.noCustomInput) {
-      this.customSelected = true;
-      this.formItemStyle = 'form-item-custom';
-      customInput.hidden = false;
-    } else {
-      this.customSelected = false;
+    this.customSelected = event.target.value == 'custom';
+    customInput.hidden = event.target.value != 'custom';
+    if (event.target.value != 'custom') {
       this.formItemStyle = 'form-item';
-      customInput.hidden = true;
       const select = this.shadowRoot.querySelector('#' + this.selectid);
-      this.selectIndex = select.selectedIndex;
+      this.selectValue = this.selectElements[select.selectedIndex].value;
+    } else if (!this.noCustomInput) {
+      this.formItemStyle = 'form-item-custom';
+      this.onInputChange();  // sets the value of this.selectValue to the
+                             // value of the custom input.
     }
   }
 
-  // Computed property callback. Returns the value of an element in
-  // selectElements using its index.
-  getSelectValue(selectIndex) {
-    return this.selectElements[selectIndex].value;
-  }
-
-  // When the custom input field changes (on-change event), store the value in a
-  // property.
+  // When the custom input field changes (on-change event), store the value in
+  // this.selectValue.
   onInputChange() {
-    if (this.customSelected) {
+    if (this.customSelected && !this.noCustomInput) {
       const customInput = this.shadowRoot.querySelector('.hidden-input');
       this.selectValue = customInput.value;
     }
diff --git a/chrome/browser/resources/chromeos/notification_tester/types.js b/chrome/browser/resources/chromeos/notification_tester/types.js
index 7cd7335..12214ad7 100644
--- a/chrome/browser/resources/chromeos/notification_tester/types.js
+++ b/chrome/browser/resources/chromeos/notification_tester/types.js
@@ -7,10 +7,26 @@
  * ui/message_center/public/cpp/notification.h.
  * Items prefixed with "richData" are from RichNotificationData.
  * @typedef {{
+ *   id: string,
  *   title: string,
  *   message: string,
  *   icon: string,
+ *   displaySource: string,
+ *   originURL: string,
+ *   notificationType: number,
+ *   notifierType: number,
  *   richDataImage: string,
+ *   richDataSmallImage: string,
+ *   richDataNeverTimeout: boolean,
+ *   richDataPriority: number,
+ *   richDataPinned: boolean,
+ *   richDataRenotify: boolean,
+ *   richDataShowSnooze: boolean,
+ *   richDataShowSettings: boolean,
+ *   richDataProgress: number,
+ *   richDataProgressStatus: string,
+ *   richDataNumButtons: number,
+ *   richDataNumNotifItems: number,
  * }}
  */
 export let Notification;
\ No newline at end of file
diff --git a/chrome/browser/resources/nearby_share/app.js b/chrome/browser/resources/nearby_share/app.js
index 1b30196..59e9a648 100644
--- a/chrome/browser/resources/nearby_share/app.js
+++ b/chrome/browser/resources/nearby_share/app.js
@@ -184,6 +184,7 @@
    * @private
    */
   onClose_(event) {
+    // TODO(b/237796007): Handle the case of null |event.detail|
     const reason =
         event.detail.reason == null ? CloseReason.UNKNOWN : event.detail.reason;
     chrome.send('close', [reason]);
diff --git a/chrome/browser/resources/nearby_share/shared/nearby_page_template.js b/chrome/browser/resources/nearby_share/shared/nearby_page_template.js
index 9d07c76..a1775e2 100644
--- a/chrome/browser/resources/nearby_share/shared/nearby_page_template.js
+++ b/chrome/browser/resources/nearby_share/shared/nearby_page_template.js
@@ -121,8 +121,8 @@
    * @private
    */
   fire_(eventName, detail) {
-    this.dispatchEvent(
-        new CustomEvent(eventName, {bubbles: true, composed: true, detail}));
+    this.dispatchEvent(new CustomEvent(
+        eventName, {bubbles: true, composed: true, detail: detail || {}}));
   }
 
   /** @private */
diff --git a/chrome/browser/resources/new_tab_page/customize_modules.html b/chrome/browser/resources/new_tab_page/customize_modules.html
index 05ff31f2..d8b03a6 100644
--- a/chrome/browser/resources/new_tab_page/customize_modules.html
+++ b/chrome/browser/resources/new_tab_page/customize_modules.html
@@ -57,48 +57,50 @@
     margin-inline-end: 20px;
   }
 </style>
-<div id="show">
-  <cr-radio-group selected="[[radioSelection_(show_)]]"
-      disabled="[[showManagedByPolicy_]]"
-      on-selected-changed="onShowRadioSelectionChanged_">
-    <cr-radio-button id="hideButton" name="hide"
-        label="$i18n{hideAllCards}">
-    </cr-radio-button>
-    <cr-radio-button id="customizeButton" name="customize"
-        label="$i18n{customizeCards}">
-    </cr-radio-button>
-  </cr-radio-group>
-  <cr-policy-indicator indicator-type="devicePolicy"
-      hidden="[[!showManagedByPolicy_]]">
-  </cr-policy-indicator>
-</div>
-<div id="toggles">
-  <template id="toggleRepeat" is="dom-repeat" items="[[modules_]]">
-    <div class="toggle-section">
-      <div class="toggle-row">
-        <div class="toggle-name">[[item.name]]</div>
-        <cr-policy-indicator indicator-type="devicePolicy"
-            hidden="[[!showManagedByPolicy_]]">
-        </cr-policy-indicator>
-        <cr-toggle checked="{{item.checked}}"
-            disabled="[[moduleToggleDisabled_(show_, showManagedByPolicy_)]]"
-            title="[[item.name]]">
-        </cr-toggle>
-      </div>
-      <!-- Discount toggle is a workaround for crbug.com/1199465 and will be
-      removed after module customization is better defined. Please avoid
-      using similar pattern for other features. -->
-      <template is="dom-if"
-          if=
-      "[[showDiscountToggle_(item.id, item.checked, discountToggleEligible_)]]">
-        <div class="discount-toggle-row">
-          <div class="toggle-name">
-            $i18n{modulesCartDiscountConsentAccept}
-          </div>
-          <cr-toggle checked="{{discountToggle_.enabled}}"
-              title="$i18n{modulesCartDiscountConsentAccept}"></cr-toggle>
+<div id="container" hidden>
+  <div id="show">
+    <cr-radio-group selected="[[radioSelection_(show_)]]"
+        disabled="[[showManagedByPolicy_]]"
+        on-selected-changed="onShowRadioSelectionChanged_">
+      <cr-radio-button id="hideButton" name="hide"
+          label="$i18n{hideAllCards}">
+      </cr-radio-button>
+      <cr-radio-button id="customizeButton" name="customize"
+          label="$i18n{customizeCards}">
+      </cr-radio-button>
+    </cr-radio-group>
+    <cr-policy-indicator indicator-type="devicePolicy"
+        hidden="[[!showManagedByPolicy_]]">
+    </cr-policy-indicator>
+  </div>
+  <div id="toggles">
+    <template id="toggleRepeat" is="dom-repeat" items="[[modules_]]">
+      <div class="toggle-section">
+        <div class="toggle-row">
+          <div class="toggle-name">[[item.name]]</div>
+          <cr-policy-indicator indicator-type="devicePolicy"
+              hidden="[[!showManagedByPolicy_]]">
+          </cr-policy-indicator>
+          <cr-toggle checked="{{item.checked}}"
+              disabled="[[moduleToggleDisabled_(show_, showManagedByPolicy_)]]"
+              title="[[item.name]]">
+          </cr-toggle>
         </div>
-      </template>
-    </div>
-  </template>
+        <!-- Discount toggle is a workaround for crbug.com/1199465 and will be
+        removed after module customization is better defined. Please avoid
+        using similar pattern for other features. -->
+        <template is="dom-if"
+            if=
+        "[[showDiscountToggle_(item.id, item.checked, discountToggleEligible_)]]">
+          <div class="discount-toggle-row">
+            <div class="toggle-name">
+              $i18n{modulesCartDiscountConsentAccept}
+            </div>
+            <cr-toggle checked="{{discountToggle_.enabled}}"
+                title="$i18n{modulesCartDiscountConsentAccept}"></cr-toggle>
+          </div>
+        </template>
+      </div>
+    </template>
+  </div>
 </div>
diff --git a/chrome/browser/resources/new_tab_page/customize_modules.ts b/chrome/browser/resources/new_tab_page/customize_modules.ts
index bef0b99..489c9c2c 100644
--- a/chrome/browser/resources/new_tab_page/customize_modules.ts
+++ b/chrome/browser/resources/new_tab_page/customize_modules.ts
@@ -35,6 +35,7 @@
 
 export interface CustomizeModulesElement {
   $: {
+    container: HTMLElement,
     customizeButton: CrRadioButtonElement,
     hideButton: CrRadioButtonElement,
     toggleRepeat: DomRepeat,
@@ -102,6 +103,7 @@
         NewTabPageProxy.getInstance()
             .callbackRouter.setDisabledModules.addListener(
                 (all: boolean, ids: string[]) => {
+                  this.$.container.hidden = false;
                   this.show_ = !all;
                   this.modules_.forEach(({id}, i) => {
                     const checked = !all && !ids.includes(id);
diff --git a/chrome/browser/resources/print_preview/data/destination_store.ts b/chrome/browser/resources/print_preview/data/destination_store.ts
index c23c2d7..80da54c 100644
--- a/chrome/browser/resources/print_preview/data/destination_store.ts
+++ b/chrome/browser/resources/print_preview/data/destination_store.ts
@@ -6,7 +6,6 @@
 import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
-import {MetricsContext} from '../metrics.js';
 import {CapabilitiesResponse, NativeLayer, NativeLayerImpl} from '../native_layer.js';
 // <if expr="chromeos_ash or chromeos_lacros">
 import {NativeLayerCros, NativeLayerCrosImpl, PrinterSetupResponse} from '../native_layer_cros.js';
@@ -185,11 +184,6 @@
   private initialDestinationSelected_: boolean = false;
 
   /**
-   * Used to track metrics.
-   */
-  private metrics_: MetricsContext = MetricsContext.destinationSearch();
-
-  /**
    * Used to fetch local print destinations.
    */
   private nativeLayer_: NativeLayer = NativeLayerImpl.getInstance();
diff --git a/chrome/browser/resources/print_preview/metrics.ts b/chrome/browser/resources/print_preview/metrics.ts
index 0ca3f3d..dee103e 100644
--- a/chrome/browser/resources/print_preview/metrics.ts
+++ b/chrome/browser/resources/print_preview/metrics.ts
@@ -71,15 +71,6 @@
   }
 
   /**
-   * Destination Search specific usage statistics context.
-   */
-  static destinationSearch(): MetricsContext {
-    return new MetricsContext(
-        'PrintPreview.DestinationAction',
-        DestinationSearchBucket.DESTINATION_SEARCH_MAX_BUCKET);
-  }
-
-  /**
    * Print settings UI specific usage statistics context
    */
   static printSettingsUi(): MetricsContext {
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog.ts b/chrome/browser/resources/print_preview/ui/destination_dialog.ts
index c66a97b..7487f40 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog.ts
@@ -26,7 +26,6 @@
 
 import {Destination} from '../data/destination.js';
 import {DestinationStore, DestinationStoreEventType} from '../data/destination_store.js';
-import {DestinationSearchBucket, MetricsContext} from '../metrics.js';
 import {NativeLayerImpl} from '../native_layer.js';
 
 import {getTemplate} from './destination_dialog.html.js';
@@ -70,8 +69,6 @@
         value: false,
       },
 
-      metrics_: Object,
-
       searchQuery_: {
         type: Object,
         value: null,
@@ -82,7 +79,6 @@
   destinationStore: DestinationStore;
   private destinations_: Destination[];
   private loadingDestinations_: boolean;
-  private metrics_: MetricsContext;
   private searchQuery_: RegExp|null;
 
   private tracker_: EventTracker = new EventTracker();
@@ -144,10 +140,6 @@
     if (this.searchQuery_) {
       this.$.searchBox.setValue('');
     }
-    const cancelled = this.$.dialog.getNative().returnValue !== 'success';
-    this.metrics_.record(
-        cancelled ? DestinationSearchBucket.DESTINATION_CLOSED_UNCHANGED :
-                    DestinationSearchBucket.DESTINATION_CLOSED_CHANGED);
   }
 
   private onCancelButtonClick_() {
@@ -170,9 +162,6 @@
   }
 
   show() {
-    if (!this.metrics_) {
-      this.metrics_ = MetricsContext.destinationSearch();
-    }
     this.$.dialog.showModal();
     const loading = this.destinationStore === undefined ||
         this.destinationStore.isPrintDestinationSearchInProgress;
@@ -181,7 +170,6 @@
       this.updateDestinations_();
     }
     this.loadingDestinations_ = loading;
-    this.metrics_.record(DestinationSearchBucket.DESTINATION_SHOWN);
   }
 
   /** @return Whether the dialog is open. */
@@ -190,7 +178,6 @@
   }
 
   private onManageButtonClick_() {
-    this.metrics_.record(DestinationSearchBucket.MANAGE_BUTTON_CLICKED);
     NativeLayerImpl.getInstance().managePrinters();
   }
 }
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts
index 8f3334a..d6876bb 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts
@@ -30,7 +30,6 @@
 import {Destination, GooglePromotedDestinationId} from '../data/destination.js';
 import {DestinationStore, DestinationStoreEventType} from '../data/destination_store.js';
 import {PrintServerStore, PrintServerStoreEventType} from '../data/print_server_store.js';
-import {DestinationSearchBucket, MetricsContext} from '../metrics.js';
 import {NativeLayerImpl} from '../native_layer.js';
 
 import {getTemplate} from './destination_dialog_cros.html.js';
@@ -87,8 +86,6 @@
         value: false,
       },
 
-      metrics_: Object,
-
       searchQuery_: {
         type: Object,
         value: null,
@@ -123,7 +120,6 @@
   private printServerSelected_: string;
   private destinations_: Destination[];
   private loadingDestinations_: boolean;
-  private metrics_: MetricsContext;
   private searchQuery_: RegExp|null;
   private isSingleServerFetchingMode_: boolean;
   private printServerNames_: string[];
@@ -215,10 +211,6 @@
     if (this.searchQuery_) {
       this.$.searchBox.setValue('');
     }
-    const cancelled = this.$.dialog.getNative().returnValue !== 'success';
-    this.metrics_.record(
-        cancelled ? DestinationSearchBucket.DESTINATION_CLOSED_UNCHANGED :
-                    DestinationSearchBucket.DESTINATION_CLOSED_CHANGED);
   }
 
   private onCancelButtonClick_() {
@@ -289,9 +281,6 @@
   }
 
   show() {
-    if (!this.metrics_) {
-      this.metrics_ = MetricsContext.destinationSearch();
-    }
     this.$.dialog.showModal();
     const loading = this.destinationStore === undefined ||
         this.destinationStore.isPrintDestinationSearchInProgress;
@@ -300,7 +289,6 @@
       this.updateDestinations_();
     }
     this.loadingDestinations_ = loading;
-    this.metrics_.record(DestinationSearchBucket.DESTINATION_SHOWN);
   }
 
   /** @return Whether the dialog is open. */
@@ -336,7 +324,6 @@
   }
 
   private onManageButtonClick_() {
-    this.metrics_.record(DestinationSearchBucket.MANAGE_BUTTON_CLICKED);
     NativeLayerImpl.getInstance().managePrinters();
   }
 }
diff --git a/chrome/browser/resources/settings/chromeos/device_page/device_page.html b/chrome/browser/resources/settings/chromeos/device_page/device_page.html
index 2134544..78e4791 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page.html
@@ -19,6 +19,11 @@
     <cr-link-row class="hr" id="displayRow" label="$i18n{displayTitle}"
         on-click="onDisplayTap_"
         role-description="$i18n{subpageArrowRoleDescription}"></cr-link-row>
+    <template is="dom-if" if="[[showAudioInfo_]]">
+      <cr-link-row class="hr" id="audioRow" label="$i18n{audioTitle}"
+          role-description="$i18n{subpageArrowRoleDescription}">
+      </cr-link-row>
+    </template>
     <cr-link-row class="hr" hidden="[[hideStorageInfo_]]" id="storageRow"
         label="$i18n{storageTitle}" on-click="onStorageTap_"
         role-description="$i18n{subpageArrowRoleDescription}"></cr-link-row>
diff --git a/chrome/browser/resources/settings/chromeos/device_page/device_page.js b/chrome/browser/resources/settings/chromeos/device_page/device_page.js
index 2d7daab..d998fc1 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page.js
@@ -93,6 +93,18 @@
       },
 
       /**
+       * Whether audio management info should be shown.
+       * @protected
+       */
+      showAudioInfo_: {
+        type: Boolean,
+        value() {
+          return loadTimeData.getBoolean('enableAudioSettingsPage');
+        },
+        readOnly: true,
+      },
+
+      /**
        * Whether storage management info should be hidden.
        * @private
        */
@@ -123,6 +135,7 @@
           if (routes.DISPLAY) {
             map.set(routes.DISPLAY.path, '#displayRow');
           }
+          // TODO(crbug.com/1092970): Create routes.Audio page.
           if (routes.STORAGE) {
             map.set(routes.STORAGE.path, '#storageRow');
           }
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index ed8d055..e7637a8 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -212,10 +212,10 @@
         "cloud_content_scanning/binary_fcm_service.h",
         "cloud_content_scanning/binary_upload_service.cc",
         "cloud_content_scanning/binary_upload_service.h",
-        "cloud_content_scanning/binary_upload_service_factory.cc",
-        "cloud_content_scanning/binary_upload_service_factory.h",
         "cloud_content_scanning/cloud_binary_upload_service.cc",
         "cloud_content_scanning/cloud_binary_upload_service.h",
+        "cloud_content_scanning/cloud_binary_upload_service_factory.cc",
+        "cloud_content_scanning/cloud_binary_upload_service_factory.h",
         "cloud_content_scanning/deep_scanning_utils.cc",
         "cloud_content_scanning/deep_scanning_utils.h",
         "cloud_content_scanning/file_analysis_request.cc",
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc
index 885ebf4..1ade739c 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc
@@ -9,6 +9,8 @@
 #include "chrome/browser/enterprise/connectors/analysis/analysis_settings.h"
 #include "chrome/browser/enterprise/connectors/common.h"
 #include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_factory.h"
 #include "components/enterprise/common/strings.h"
 #include "net/base/url_util.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
@@ -223,4 +225,13 @@
   access_token_ = access_token;
 }
 
+// static
+BinaryUploadService* BinaryUploadService::GetForProfile(
+    Profile* profile,
+    const enterprise_connectors::AnalysisSettings& settings) {
+  // TODO(rogerta): If settings.is_local_analysis() is true, use
+  // LocalBinaryUploadServiceFactory instead.
+  return CloudBinaryUploadServiceFactory::GetForProfile(profile);
+}
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h
index bc5c0998..8507448 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h
@@ -12,6 +12,8 @@
 #include "services/network/public/cpp/resource_request.h"
 #include "url/gurl.h"
 
+class Profile;
+
 namespace safe_browsing {
 
 // This class encapsulates the process of getting data scanned through a generic
@@ -188,6 +190,10 @@
     std::string access_token_;
   };
 
+  static BinaryUploadService* GetForProfile(
+      Profile* profile,
+      const enterprise_connectors::AnalysisSettings& settings);
+
   // Upload the given file contents for deep scanning if the browser is
   // authorized to upload data, otherwise queue the request.
   virtual void MaybeUploadForDeepScanning(std::unique_ptr<Request> request) = 0;
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.h b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.h
deleted file mode 100644
index fb6f7b7..0000000
--- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_SAFE_BROWSING_CLOUD_CONTENT_SCANNING_BINARY_UPLOAD_SERVICE_FACTORY_H_
-#define CHROME_BROWSER_SAFE_BROWSING_CLOUD_CONTENT_SCANNING_BINARY_UPLOAD_SERVICE_FACTORY_H_
-
-#include "base/memory/singleton.h"
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
-
-class KeyedService;
-class Profile;
-
-namespace safe_browsing {
-class BinaryUploadService;
-
-// Singleton that owns BinaryUploadService objects, one for each active
-// Profile. It listens to profile destroy events and destroy its associated
-// service. It returns a separate instance if the profile is in the Incognito
-// mode.
-class BinaryUploadServiceFactory : public BrowserContextKeyedServiceFactory {
- public:
-  // Creates the service if it doesn't exist already for the given |profile|.
-  // If the service already exists, return its pointer.
-  static BinaryUploadService* GetForProfile(Profile* profile);
-
-  // Get the singleton instance.
-  static BinaryUploadServiceFactory* GetInstance();
-
-  BinaryUploadServiceFactory(const BinaryUploadServiceFactory&) = delete;
-  BinaryUploadServiceFactory& operator=(const BinaryUploadServiceFactory&) =
-      delete;
-
- private:
-  friend struct base::DefaultSingletonTraits<BinaryUploadServiceFactory>;
-
-  BinaryUploadServiceFactory();
-  ~BinaryUploadServiceFactory() override = default;
-
-  // BrowserContextKeyedServiceFactory:
-  KeyedService* BuildServiceInstanceFor(
-      content::BrowserContext* context) const override;
-  content::BrowserContext* GetBrowserContextToUse(
-      content::BrowserContext* context) const override;
-};
-
-}  // namespace safe_browsing
-
-#endif  // CHROME_BROWSER_SAFE_BROWSING_CLOUD_CONTENT_SCANNING_BINARY_UPLOAD_SERVICE_FACTORY_H_
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.cc b/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_factory.cc
similarity index 70%
rename from chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.cc
rename to chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_factory.cc
index 39be8706..421f1c6 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_factory.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.h"
+#include "chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_factory.h"
 
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
@@ -16,7 +16,7 @@
 namespace safe_browsing {
 
 // static
-BinaryUploadService* BinaryUploadServiceFactory::GetForProfile(
+BinaryUploadService* CloudBinaryUploadServiceFactory::GetForProfile(
     Profile* profile) {
   return static_cast<BinaryUploadService*>(
       GetInstance()->GetServiceForBrowserContext(profile, /* create= */
@@ -24,23 +24,25 @@
 }
 
 // static
-BinaryUploadServiceFactory* BinaryUploadServiceFactory::GetInstance() {
-  return base::Singleton<BinaryUploadServiceFactory>::get();
+CloudBinaryUploadServiceFactory*
+CloudBinaryUploadServiceFactory::GetInstance() {
+  return base::Singleton<CloudBinaryUploadServiceFactory>::get();
 }
 
-BinaryUploadServiceFactory::BinaryUploadServiceFactory()
+CloudBinaryUploadServiceFactory::CloudBinaryUploadServiceFactory()
     : BrowserContextKeyedServiceFactory(
-          "BinaryUploadService",
+          "CloudBinaryUploadService",
           BrowserContextDependencyManager::GetInstance()) {}
 
-KeyedService* BinaryUploadServiceFactory::BuildServiceInstanceFor(
+KeyedService* CloudBinaryUploadServiceFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
   // TODO(b/226679912): Add logic to select service based on analysis settings.
   Profile* profile = Profile::FromBrowserContext(context);
   return new CloudBinaryUploadService(profile);
 }
 
-content::BrowserContext* BinaryUploadServiceFactory::GetBrowserContextToUse(
+content::BrowserContext*
+CloudBinaryUploadServiceFactory::GetBrowserContextToUse(
     content::BrowserContext* context) const {
   return chrome::GetBrowserContextOwnInstanceInIncognito(context);
 }
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_factory.h b/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_factory.h
new file mode 100644
index 0000000..bab9e4f56
--- /dev/null
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_factory.h
@@ -0,0 +1,51 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SAFE_BROWSING_CLOUD_CONTENT_SCANNING_CLOUD_BINARY_UPLOAD_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_SAFE_BROWSING_CLOUD_CONTENT_SCANNING_CLOUD_BINARY_UPLOAD_SERVICE_FACTORY_H_
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class KeyedService;
+class Profile;
+
+namespace safe_browsing {
+class BinaryUploadService;
+
+// Singleton that owns CloudBinaryUploadService objects, one for each active
+// Profile. It listens to profile destroy events and destroy its associated
+// service. It returns a separate instance if the profile is in the Incognito
+// mode.
+class CloudBinaryUploadServiceFactory
+    : public BrowserContextKeyedServiceFactory {
+ public:
+  // Creates the service if it doesn't exist already for the given |profile|.
+  // If the service already exists, return its pointer.
+  static BinaryUploadService* GetForProfile(Profile* profile);
+
+  // Get the singleton instance.
+  static CloudBinaryUploadServiceFactory* GetInstance();
+
+  CloudBinaryUploadServiceFactory(const CloudBinaryUploadServiceFactory&) =
+      delete;
+  CloudBinaryUploadServiceFactory& operator=(
+      const CloudBinaryUploadServiceFactory&) = delete;
+
+ private:
+  friend struct base::DefaultSingletonTraits<CloudBinaryUploadServiceFactory>;
+
+  CloudBinaryUploadServiceFactory();
+  ~CloudBinaryUploadServiceFactory() override = default;
+
+  // CloudBrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
+};
+
+}  // namespace safe_browsing
+
+#endif  // CHROME_BROWSER_SAFE_BROWSING_CLOUD_CONTENT_SCANNING_BINARY_CLOUD_UPLOAD_SERVICE_FACTORY_H_
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
index c6130f0..626ba03eb 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_browsertest.cc
@@ -29,8 +29,8 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/binary_fcm_service.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h"
-#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service.h"
+#include "chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_factory.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_browsertest_base.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h"
 #include "chrome/browser/safe_browsing/download_protection/deep_scanning_request.h"
@@ -325,7 +325,7 @@
   }
 
   void SetBinaryUploadServiceTestFactory() {
-    BinaryUploadServiceFactory::GetInstance()->SetTestingFactory(
+    CloudBinaryUploadServiceFactory::GetInstance()->SetTestingFactory(
         browser()->profile(),
         base::BindRepeating(
             &DownloadDeepScanningBrowserTestBase::CreateBinaryUploadService,
@@ -360,7 +360,7 @@
 
   void AuthorizeForDeepScanning() {
     static_cast<safe_browsing::CloudBinaryUploadService*>(
-        BinaryUploadServiceFactory::GetForProfile(browser()->profile()))
+        CloudBinaryUploadServiceFactory::GetForProfile(browser()->profile()))
         ->SetAuthForTesting("dm_token", /*authorized=*/true);
   }
 
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
index d5eb305..ea0040b 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
@@ -518,7 +518,7 @@
   Profile* profile = Profile::FromBrowserContext(
       content::DownloadItemUtils::GetBrowserContext(item_));
   BinaryUploadService* binary_upload_service =
-      download_service_->GetBinaryUploadService(profile);
+      download_service_->GetBinaryUploadService(profile, analysis_settings_);
   if (binary_upload_service) {
     binary_upload_service->MaybeUploadForDeepScanning(
         std::move(deep_scan_request));
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
index e0e1ea6..55e344d5 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
@@ -181,7 +181,9 @@
 
   void RequestFinished(DeepScanningRequest* request) override {}
 
-  BinaryUploadService* GetBinaryUploadService(Profile* profile) override {
+  BinaryUploadService* GetBinaryUploadService(
+      Profile* profile,
+      const enterprise_connectors::AnalysisSettings&) override {
     return &binary_upload_service_;
   }
 
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection/download_protection_service.cc
index b8c7407..a2fbbad 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service.cc
@@ -19,8 +19,6 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager.h"
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager_factory.h"
-#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h"
-#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.h"
 #include "chrome/browser/safe_browsing/download_protection/check_client_download_request.h"
 #include "chrome/browser/safe_browsing/download_protection/check_file_system_access_write_request.h"
 #include "chrome/browser/safe_browsing/download_protection/deep_scanning_request.h"
@@ -782,8 +780,9 @@
 }
 
 BinaryUploadService* DownloadProtectionService::GetBinaryUploadService(
-    Profile* profile) {
-  return BinaryUploadServiceFactory::GetForProfile(profile);
+    Profile* profile,
+    const enterprise_connectors::AnalysisSettings& settings) {
+  return BinaryUploadService::GetForProfile(profile, settings);
 }
 
 SafeBrowsingNavigationObserverManager*
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service.h b/chrome/browser/safe_browsing/download_protection/download_protection_service.h
index afb0a0b..c1db8d9 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service.h
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service.h
@@ -315,7 +315,9 @@
 
   // Get the BinaryUploadService for the given |profile|. Virtual so it can be
   // overridden in tests.
-  virtual BinaryUploadService* GetBinaryUploadService(Profile* profile);
+  virtual BinaryUploadService* GetBinaryUploadService(
+      Profile* profile,
+      const enterprise_connectors::AnalysisSettings& settings);
 
   // Get the SafeBrowsingNavigationObserverManager for the given |web_contents|.
   SafeBrowsingNavigationObserverManager* GetNavigationObserverManager(
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc b/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
index 0bbc46f..991bf469 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
@@ -52,7 +52,7 @@
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager.h"
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager_factory.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h"
-#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_factory.h"
+#include "chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service_factory.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h"
 #include "chrome/browser/safe_browsing/cloud_content_scanning/test_binary_upload_service.h"
 #include "chrome/browser/safe_browsing/download_protection/check_file_system_access_write_request.h"
@@ -178,7 +178,7 @@
  public:
   explicit FakeSafeBrowsingService(Profile* profile) {
     services_delegate_ = ServicesDelegate::CreateForTest(this, this);
-    BinaryUploadServiceFactory::GetInstance()->SetTestingFactory(
+    CloudBinaryUploadServiceFactory::GetInstance()->SetTestingFactory(
         profile, base::BindRepeating(&CreateTestBinaryUploadService));
     mock_database_manager_ = new NiceMock<MockSafeBrowsingDatabaseManager>();
   }
@@ -3311,7 +3311,7 @@
 
   TestBinaryUploadService* test_upload_service =
       static_cast<TestBinaryUploadService*>(
-          BinaryUploadServiceFactory::GetForProfile(profile()));
+          CloudBinaryUploadServiceFactory::GetForProfile(profile()));
   test_upload_service->SetResponse(
       BinaryUploadService::Result::FILE_ENCRYPTED,
       enterprise_connectors::ContentAnalysisResponse());
@@ -3395,7 +3395,7 @@
 
   TestBinaryUploadService* test_upload_service =
       static_cast<TestBinaryUploadService*>(
-          BinaryUploadServiceFactory::GetForProfile(profile()));
+          CloudBinaryUploadServiceFactory::GetForProfile(profile()));
   test_upload_service->SetResponse(
       BinaryUploadService::Result::FILE_TOO_LARGE,
       enterprise_connectors::ContentAnalysisResponse());
@@ -3469,7 +3469,7 @@
 
   TestBinaryUploadService* test_upload_service =
       static_cast<TestBinaryUploadService*>(
-          BinaryUploadServiceFactory::GetForProfile(profile()));
+          CloudBinaryUploadServiceFactory::GetForProfile(profile()));
   test_upload_service->SetResponse(
       BinaryUploadService::Result::DLP_SCAN_UNSUPPORTED_FILE_TYPE,
       enterprise_connectors::ContentAnalysisResponse());
@@ -4305,7 +4305,7 @@
 
   TestBinaryUploadService* test_upload_service =
       static_cast<TestBinaryUploadService*>(
-          BinaryUploadServiceFactory::GetForProfile(profile()));
+          CloudBinaryUploadServiceFactory::GetForProfile(profile()));
 
   {
     PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK, net::OK);
@@ -4352,7 +4352,7 @@
 
   TestBinaryUploadService* test_upload_service =
       static_cast<TestBinaryUploadService*>(
-          BinaryUploadServiceFactory::GetForProfile(profile()));
+          CloudBinaryUploadServiceFactory::GetForProfile(profile()));
 
   {
     PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK, net::OK);
@@ -4411,7 +4411,7 @@
 
     TestBinaryUploadService* test_upload_service =
         static_cast<TestBinaryUploadService*>(
-            BinaryUploadServiceFactory::GetForProfile(profile()));
+            CloudBinaryUploadServiceFactory::GetForProfile(profile()));
 
     PrepareResponse(response.first, net::HTTP_OK, net::OK);
     test_upload_service->SetResponse(
@@ -4651,7 +4651,7 @@
 
   TestBinaryUploadService* test_upload_service =
       static_cast<TestBinaryUploadService*>(
-          BinaryUploadServiceFactory::GetForProfile(profile()));
+          CloudBinaryUploadServiceFactory::GetForProfile(profile()));
 
   PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK, net::OK);
   test_upload_service->SetResponse(
@@ -4698,7 +4698,7 @@
 
   TestBinaryUploadService* test_upload_service =
       static_cast<TestBinaryUploadService*>(
-          BinaryUploadServiceFactory::GetForProfile(profile()));
+          CloudBinaryUploadServiceFactory::GetForProfile(profile()));
 
   PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK, net::OK);
   test_upload_service->SetResponse(
@@ -4744,7 +4744,7 @@
 
   TestBinaryUploadService* test_upload_service =
       static_cast<TestBinaryUploadService*>(
-          BinaryUploadServiceFactory::GetForProfile(profile()));
+          CloudBinaryUploadServiceFactory::GetForProfile(profile()));
 
   PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK, net::OK);
   test_upload_service->SetResponse(
diff --git a/chrome/browser/segmentation_platform/segmentation_platform_config.cc b/chrome/browser/segmentation_platform/segmentation_platform_config.cc
index 7f9a8bd..9329cb8 100644
--- a/chrome/browser/segmentation_platform/segmentation_platform_config.cc
+++ b/chrome/browser/segmentation_platform/segmentation_platform_config.cc
@@ -11,9 +11,9 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
-#include "chrome/browser/segmentation_platform/default_model/feed_user_segment.h"
-#include "chrome/browser/segmentation_platform/default_model/low_user_engagement_model.h"
-#include "chrome/browser/segmentation_platform/default_model/price_tracking_action_model.h"
+#include "components/segmentation_platform/embedder/default_model/feed_user_segment.h"
+#include "components/segmentation_platform/embedder/default_model/low_user_engagement_model.h"
+#include "components/segmentation_platform/embedder/default_model/price_tracking_action_model.h"
 #include "components/segmentation_platform/public/config.h"
 #include "components/segmentation_platform/public/features.h"
 #include "components/segmentation_platform/public/model_provider.h"
@@ -24,9 +24,9 @@
 #include "chrome/browser/flags/android/cached_feature_flags.h"
 #include "chrome/browser/flags/android/chrome_feature_list.h"
 #include "chrome/browser/segmentation_platform/default_model/chrome_start_model_android.h"
-#include "chrome/browser/segmentation_platform/default_model/query_tiles_model.h"
 #include "chrome/browser/ui/android/start_surface/start_surface_android.h"
 #include "components/query_tiles/switches.h"
+#include "components/segmentation_platform/embedder/default_model/query_tiles_model.h"
 #endif
 
 namespace segmentation_platform {
diff --git a/chrome/browser/ssl/sct_reporting_service_browsertest.cc b/chrome/browser/ssl/sct_reporting_service_browsertest.cc
index 502ba86..ad86cc7 100644
--- a/chrome/browser/ssl/sct_reporting_service_browsertest.cc
+++ b/chrome/browser/ssl/sct_reporting_service_browsertest.cc
@@ -7,6 +7,8 @@
 
 #include "base/base64.h"
 #include "base/callback.h"
+#include "base/files/file_path_watcher.h"
+#include "base/files/file_util.h"
 #include "base/json/json_writer.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/synchronization/lock.h"
@@ -23,17 +25,20 @@
 #include "chrome/browser/ssl/sct_reporting_service.h"
 #include "chrome/browser/ssl/sct_reporting_service_factory.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/browsing_data_remover.h"
 #include "content/public/browser/network_service_instance.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/common/network_service_util.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/browsing_data_remover_test_util.h"
 #include "content/public/test/content_mock_cert_verifier.h"
 #include "content/public/test/network_service_test_helper.h"
 #include "mojo/public/cpp/bindings/sync_call_restrictions.h"
@@ -949,3 +954,169 @@
   SetSafeBrowsingEnabled(false);  // Clears the deduplication cache.
   EXPECT_TRUE(FlushAndCheckZeroReports());
 }
+
+// Wrapper around FilePathWatcher to help tests wait for an auditing report to
+// be persisted to disk. This is also robust to the persistence file being
+// written to before the test initiates the wait, helping avoid race conditions
+// that can cause hard-to-debug flakes.
+//
+// This currently monitors *two* file paths, because depending on the platform
+// (and the state of the network service sandbox rollout) the persisted data
+// file path may have an extra "Network/" subdirectory component, but it is
+// difficult to determine this from test data. WaitUntilPersisted() will wait
+// until *either* of the two paths have been written to.
+//
+// ReportPersistenceWaiter also takes a `filesize_threshold` as the "empty"
+// persistence file still has some structure/data in it. For the current
+// persistence format (list of JSON dicts), the "empty" persistence file is 2
+// bytes (the empty list `[]`).
+class ReportPersistenceWaiter {
+ public:
+  ReportPersistenceWaiter(const base::FilePath& watched_file_path,
+                          const base::FilePath& alternative_file_path,
+                          int64_t filesize_threshold)
+      : watched_file_path1_(watched_file_path),
+        watched_file_path2_(alternative_file_path),
+        filesize_threshold_(filesize_threshold) {}
+  ReportPersistenceWaiter(const ReportPersistenceWaiter&) = delete;
+  ReportPersistenceWaiter& operator=(const ReportPersistenceWaiter&) = delete;
+
+  void WaitUntilPersisted() {
+    DCHECK(!watcher1_);
+    DCHECK(!watcher2_);
+    {
+      // Check if either file was already written and if so return early.
+      base::ScopedAllowBlockingForTesting allow_blocking;
+      int64_t file_size;
+      // GetFileSize() will return `false` if the file does not yet exist.
+      if (base::GetFileSize(watched_file_path1_, &file_size) &&
+          file_size > filesize_threshold_) {
+        return;
+      }
+      if (base::GetFileSize(watched_file_path2_, &file_size) &&
+          file_size > filesize_threshold_) {
+        return;
+      }
+    }
+    watcher1_ = std::make_unique<base::FilePathWatcher>();
+    watcher2_ = std::make_unique<base::FilePathWatcher>();
+    EXPECT_TRUE(watcher1_->Watch(
+        watched_file_path1_, base::FilePathWatcher::Type::kNonRecursive,
+        base::BindRepeating(&ReportPersistenceWaiter::OnPathChanged,
+                            base::Unretained(this))));
+    EXPECT_TRUE(watcher2_->Watch(
+        watched_file_path2_, base::FilePathWatcher::Type::kNonRecursive,
+        base::BindRepeating(&ReportPersistenceWaiter::OnPathChanged,
+                            base::Unretained(this))));
+    run_loop_.Run();
+    // The watchers should be destroyed before quitting the run loop.
+    DCHECK(!watcher1_);
+    DCHECK(!watcher2_);
+  }
+
+ private:
+  void OnPathChanged(const base::FilePath& path, bool error) {
+    EXPECT_TRUE(path == watched_file_path1_ || path == watched_file_path2_);
+    EXPECT_FALSE(error);
+    watcher1_.reset();
+    watcher2_.reset();
+    run_loop_.Quit();
+  }
+
+  base::RunLoop run_loop_;
+  const base::FilePath watched_file_path1_;
+  const base::FilePath watched_file_path2_;
+  const int64_t filesize_threshold_;
+  std::unique_ptr<base::FilePathWatcher> watcher1_;
+  std::unique_ptr<base::FilePathWatcher> watcher2_;
+};
+
+// Subclass to force-enable kSCTAuditingPersistReports. Parent class will handle
+// enabling the other required features and setup.
+class SCTReportingServiceWithPersistenceBrowserTest
+    : public SCTReportingServiceBrowserTest {
+ public:
+  SCTReportingServiceWithPersistenceBrowserTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        network::features::kSCTAuditingPersistReports);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(SCTReportingServiceWithPersistenceBrowserTest,
+                       PersistedReportClearedOnClearBrowsingHistory) {
+  // Set a long retry delay so that retries don't occur immediately.
+  {
+    mojo::ScopedAllowSyncCallForTesting allow_sync_call;
+    network_service_test()->SetSCTAuditingRetryDelay(base::Minutes(1));
+  }
+  // Don't immediately succeed, so report stays persisted to disk.
+  set_error_count(10);
+
+  SetExtendedReportingEnabled(true);
+
+  // The empty/cleared persistence file will be 2 bytes (the empty JSON list).
+  constexpr int64_t kEmptyPersistenceFileSize = 2;
+
+  base::FilePath persistence_path1 = browser()->profile()->GetPath();
+  // If the network service sandbox is enabled, then the network service data
+  // dir path has an additional "Network" subdirectory in it. This means that
+  // different platforms will have different persistence paths depending on the
+  // current state of the network service sandbox rollout.
+  // TODO(crbug.com/715679): Simplify this once the paths are consistent (i.e.,
+  // after the network service sandbox is fully rolled out.)
+  base::FilePath persistence_path2 =
+      persistence_path1.Append(chrome::kNetworkDataDirname);
+  persistence_path1 =
+      persistence_path1.Append(chrome::kSCTAuditingPendingReportsFileName);
+  persistence_path2 =
+      persistence_path2.Append(chrome::kSCTAuditingPendingReportsFileName);
+
+  // Visit an HTTPS page to generate an SCT auditing report. Sending the report
+  // will result in an error, so the pending report will remain.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server()->GetURL("a.test", "/")));
+
+  // Check that the report got persisted to disk.
+  {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    ReportPersistenceWaiter waiter(persistence_path1, persistence_path2,
+                                   kEmptyPersistenceFileSize);
+    waiter.WaitUntilPersisted();
+    int64_t file_size1;
+    int64_t file_size2;
+    bool one_file_is_written =
+        base::GetFileSize(persistence_path1, &file_size1) ||
+        base::GetFileSize(persistence_path2, &file_size2);
+    EXPECT_TRUE(one_file_is_written);
+    EXPECT_TRUE(file_size1 > kEmptyPersistenceFileSize ||
+                file_size2 > kEmptyPersistenceFileSize);
+  }
+
+  // Trigger removal and wait for completion.
+  auto* contents = browser()->tab_strip_model()->GetActiveWebContents();
+  content::BrowsingDataRemover* remover =
+      contents->GetBrowserContext()->GetBrowsingDataRemover();
+  content::BrowsingDataRemoverCompletionObserver completion_observer(remover);
+  remover->RemoveAndReply(
+      base::Time(), base::Time::Max(),
+      content::BrowsingDataRemover::DATA_TYPE_CACHE,
+      content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
+      &completion_observer);
+  completion_observer.BlockUntilCompletion();
+
+  // Check that the persistence file is cleared.
+  {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    int64_t file_size;
+    if (base::GetFileSize(persistence_path1, &file_size)) {
+      EXPECT_EQ(file_size, kEmptyPersistenceFileSize);
+    } else if (base::GetFileSize(persistence_path2, &file_size)) {
+      EXPECT_EQ(file_size, kEmptyPersistenceFileSize);
+    } else {
+      FAIL() << "Neither persistence file was ever written";
+    }
+  }
+}
diff --git a/chrome/browser/sync/test/integration/apps_helper.cc b/chrome/browser/sync/test/integration/apps_helper.cc
index 12ae499..60aaa76 100644
--- a/chrome/browser/sync/test/integration/apps_helper.cc
+++ b/chrome/browser/sync/test/integration/apps_helper.cc
@@ -40,31 +40,6 @@
   return "fakeapp" + base::NumberToString(index);
 }
 
-std::unique_ptr<web_app::WebAppTestUninstallObserver>
-SetupUninstallObserverForProfile(Profile* profile) {
-  std::set<web_app::AppId> apps_in_sync_uninstall =
-      web_app::WebAppProvider::GetForTest(profile)
-          ->sync_bridge()
-          .GetAppsInSyncUninstallForTest();
-
-  std::vector<web_app::AppId> apps_in_uninstall =
-      web_app::WebAppProvider::GetForTest(profile)
-          ->install_finalizer()
-          .GetPendingUninstallsForTesting();
-
-  apps_in_sync_uninstall.insert(apps_in_uninstall.begin(),
-                                apps_in_uninstall.end());
-
-  if (apps_in_sync_uninstall.empty()) {
-    return nullptr;
-  }
-
-  auto uninstall_observer =
-      std::make_unique<web_app::WebAppTestUninstallObserver>(profile);
-  uninstall_observer->BeginListening(apps_in_sync_uninstall);
-  return uninstall_observer;
-}
-
 void FlushPendingOperations(std::vector<Profile*> profiles) {
   for (Profile* profile : profiles) {
     web_app::WebAppProvider::GetForTest(profile)
@@ -105,12 +80,9 @@
 
     // Next, wait for uninstalls. These are easier because they don't have two
     // stages.
-    std::unique_ptr<web_app::WebAppTestUninstallObserver> uninstall_observer =
-        SetupUninstallObserverForProfile(profile);
-    // This actually waits for all observed apps to be installed.
-    if (uninstall_observer) {
-      uninstall_observer->Wait();
-    }
+    web_app::WebAppProvider::GetForTest(profile)
+        ->command_manager()
+        .AwaitAllCommandsCompleteForTesting();
   }
 }
 
diff --git a/chrome/browser/themes/theme_helper.cc b/chrome/browser/themes/theme_helper.cc
index 0d4a1ec07..43f84619 100644
--- a/chrome/browser/themes/theme_helper.cc
+++ b/chrome/browser/themes/theme_helper.cc
@@ -11,17 +11,11 @@
 #include "chrome/browser/themes/browser_theme_pack.h"
 #include "chrome/browser/themes/custom_theme_supplier.h"
 #include "chrome/browser/themes/theme_properties.h"
-#include "chrome/browser/ui/color/chrome_color_provider_utils.h"
-#include "chrome/browser/ui/ui_features.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/theme_resources.h"
 #include "components/grit/components_scaled_resources.h"
-#include "components/omnibox/browser/omnibox_field_trial.h"
 #include "ui/base/resource/resource_bundle.h"
-#include "ui/gfx/color_palette.h"
-#include "ui/gfx/color_utils.h"
 #include "ui/gfx/image/image.h"
-#include "ui/native_theme/common_theme.h"
 #include "ui/native_theme/native_theme.h"
 
 #if BUILDFLAG(IS_LINUX)
@@ -38,22 +32,6 @@
 // unpacked on the filesystem.)
 constexpr char kDefaultThemeGalleryID[] = "hkacjpbfdknhflllbcmjibkdeoafencn";
 
-SkColor IncreaseLightness(SkColor color, double percent) {
-  color_utils::HSL result;
-  color_utils::SkColorToHSL(color, &result);
-  result.l += (1 - result.l) * percent;
-  return color_utils::HSLToSkColor(result, SkColorGetA(color));
-}
-
-// Key for cache of separator colors; pair is <tab color, frame color>.
-using SeparatorColorKey = std::pair<SkColor, SkColor>;
-using SeparatorColorCache = std::map<SeparatorColorKey, SkColor>;
-
-SeparatorColorCache& GetSeparatorColorCache() {
-  static base::NoDestructor<SeparatorColorCache> cache;
-  return *cache;
-}
-
 }  // namespace
 
 const char ThemeHelper::kDefaultThemeID[] = "";
@@ -163,20 +141,6 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
-SkColor ThemeHelper::GetColor(int id,
-                              bool incognito,
-                              const CustomThemeSupplier* theme_supplier) const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  if (theme_supplier && !incognito) {
-    SkColor color;
-    if (theme_supplier->GetColor(id, &color))
-      return color;
-  }
-
-  return GetDefaultColor(id, incognito, theme_supplier);
-}
-
 color_utils::HSL ThemeHelper::GetTint(
     int id,
     bool incognito,
@@ -219,126 +183,6 @@
 #endif
 }
 
-SkColor ThemeHelper::GetDefaultColor(
-    int id,
-    bool incognito,
-    const CustomThemeSupplier* theme_supplier) const {
-  // For backward compat with older themes, some newer colors are generated from
-  // older ones if they are missing.
-  const auto get_frame_color = [this, incognito, theme_supplier](bool active) {
-    return GetColor(active ? TP::COLOR_FRAME_ACTIVE : TP::COLOR_FRAME_INACTIVE,
-                    incognito, theme_supplier);
-  };
-  switch (id) {
-    case TP::COLOR_BOOKMARK_BAR_BACKGROUND:
-    case TP::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_ACTIVE:
-    case TP::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_INACTIVE:
-      return GetColor(TP::COLOR_TOOLBAR, incognito, theme_supplier);
-    case TP::COLOR_BOOKMARK_FAVICON: {
-      SkColor color;
-      return (theme_supplier &&
-              theme_supplier->GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, &color))
-                 ? color
-                 : SK_ColorTRANSPARENT;
-    }
-    case TP::COLOR_FLYING_INDICATOR_BACKGROUND:
-      return GetColor(TP::COLOR_TOOLBAR, incognito, theme_supplier);
-    case TP::COLOR_FLYING_INDICATOR_FOREGROUND:
-      return GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, incognito, theme_supplier);
-    case TP::COLOR_FRAME_CAPTION_ACTIVE:
-    case TP::COLOR_FRAME_CAPTION_INACTIVE:
-      return color_utils::GetColorWithMaxContrast(GetColor(
-          id == TP::COLOR_FRAME_CAPTION_ACTIVE ? TP::COLOR_FRAME_ACTIVE
-                                               : TP::COLOR_FRAME_INACTIVE,
-          incognito, theme_supplier));
-    case TP::COLOR_BOOKMARK_TEXT:
-    case TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE:
-    case TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE:
-      return GetColor(TP::COLOR_TOOLBAR_TEXT, incognito, theme_supplier);
-    case TP::COLOR_TAB_STROKE_FRAME_ACTIVE:
-      return GetColor(TP::COLOR_TOOLBAR_TOP_SEPARATOR_FRAME_ACTIVE, incognito,
-                      theme_supplier);
-    case TP::COLOR_TAB_STROKE_FRAME_INACTIVE:
-      return GetColor(TP::COLOR_TOOLBAR_TOP_SEPARATOR_FRAME_INACTIVE, incognito,
-                      theme_supplier);
-    case TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE:
-      return color_utils::HSLShift(get_frame_color(/*active=*/true),
-                                   GetTint(ThemeProperties::TINT_BACKGROUND_TAB,
-                                           incognito, theme_supplier));
-    case TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE:
-      return color_utils::HSLShift(get_frame_color(/*active=*/false),
-                                   GetTint(ThemeProperties::TINT_BACKGROUND_TAB,
-                                           incognito, theme_supplier));
-    case TP::COLOR_TOOLBAR_BUTTON_ICON:
-    case TP::COLOR_TOOLBAR_BUTTON_ICON_HOVERED:
-    case TP::COLOR_TOOLBAR_BUTTON_ICON_PRESSED:
-      return color_utils::HSLShift(
-          gfx::kGoogleGrey700,
-          GetTint(TP::TINT_BUTTONS, incognito, theme_supplier));
-    case TP::COLOR_TOOLBAR_BUTTON_ICON_INACTIVE:
-      // The active color is overridden in GtkUi.
-      return SkColorSetA(
-          GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, incognito, theme_supplier),
-          gfx::kGoogleGreyAlpha500);
-    case TP::COLOR_LOCATION_BAR_BORDER:
-      return SkColorSetA(SK_ColorBLACK, 0x4D);
-    case TP::COLOR_LOCATION_BAR_BORDER_OPAQUE:
-      return color_utils::GetResultingPaintColor(
-          GetColor(TP::COLOR_LOCATION_BAR_BORDER, incognito, theme_supplier),
-          GetColor(TP::COLOR_TOOLBAR, incognito, theme_supplier));
-    case TP::COLOR_TOOLBAR_TOP_SEPARATOR_FRAME_ACTIVE:
-    case TP::COLOR_TOOLBAR_TOP_SEPARATOR_FRAME_INACTIVE: {
-      const SkColor toolbar_color =
-          GetColor(TP::COLOR_TOOLBAR, incognito, theme_supplier);
-      const SkColor frame_color = get_frame_color(
-          /*active=*/id == TP::COLOR_TOOLBAR_TOP_SEPARATOR_FRAME_ACTIVE);
-      const SeparatorColorKey key(toolbar_color, frame_color);
-      auto i = GetSeparatorColorCache().find(key);
-      if (i != GetSeparatorColorCache().end())
-        return i->second;
-      const SkColor separator_color =
-          GetToolbarTopSeparatorColor(toolbar_color, frame_color);
-      GetSeparatorColorCache()[key] = separator_color;
-      return separator_color;
-    }
-    case TP::COLOR_BOOKMARK_SEPARATOR:
-    case TP::COLOR_TOOLBAR_VERTICAL_SEPARATOR:
-      return SkColorSetA(
-          GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, incognito, theme_supplier),
-          0x4D);
-    case TP::COLOR_TOOLBAR_BUTTON_TEXT:
-      // TODO(crbug.com/967317): Update to match mocks, i.e. return
-      // gfx::kGoogleGrey900, if needed.
-      [[fallthrough]];
-    case TP::COLOR_TOOLBAR_INK_DROP:
-      return color_utils::GetColorWithMaxContrast(
-          GetColor(TP::COLOR_TOOLBAR, incognito, theme_supplier));
-    case TP::COLOR_TOOLBAR_BUTTON_BORDER:
-      return SkColorSetA(
-          GetColor(TP::COLOR_TOOLBAR_INK_DROP, incognito, theme_supplier),
-          0x20);
-    case TP::COLOR_TOOLBAR_CONTENT_AREA_SEPARATOR:
-      return color_utils::AlphaBlend(
-          GetColor(TP::COLOR_TOOLBAR_BUTTON_ICON, incognito, theme_supplier),
-          GetColor(TP::COLOR_TOOLBAR, incognito, theme_supplier),
-          SkAlpha{0x3A});
-    case TP::COLOR_NTP_SECTION_BORDER:
-      return SkColorSetA(
-          GetColor(TP::COLOR_NTP_HEADER, incognito, theme_supplier), 0x50);
-    case TP::COLOR_NTP_TEXT_LIGHT:
-      return IncreaseLightness(
-          GetColor(TP::COLOR_NTP_TEXT, incognito, theme_supplier), 0.40);
-    case TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE:
-    case TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE:
-      return GetColor(id == TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE
-                          ? TP::COLOR_FRAME_ACTIVE
-                          : TP::COLOR_FRAME_INACTIVE,
-                      incognito, theme_supplier);
-  }
-
-  return TP::GetDefaultColor(id, incognito, UseDarkModeColors(theme_supplier));
-}
-
 // static
 bool ThemeHelper::UseDarkModeColors(const CustomThemeSupplier* theme_supplier) {
   // Dark mode is disabled for custom themes so they apply atop a predictable
diff --git a/chrome/browser/themes/theme_helper.h b/chrome/browser/themes/theme_helper.h
index 9af2cf6..6dc522c 100644
--- a/chrome/browser/themes/theme_helper.h
+++ b/chrome/browser/themes/theme_helper.h
@@ -70,10 +70,6 @@
   ThemeHelper(const ThemeHelper&) = delete;
   ThemeHelper& operator=(const ThemeHelper&) = delete;
 
-  SkColor GetColor(int id,
-                   bool incognito,
-                   const CustomThemeSupplier* theme_supplier) const;
-
   // Get the specified tint - |id| is one of the TINT_* enum values.
   color_utils::HSL GetTint(int id,
                            bool incognito,
@@ -99,14 +95,6 @@
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   }
 
- protected:
-  // Returns the color to use for |id| and |incognito| if the theme service does
-  // not provide an override.
-  virtual SkColor GetDefaultColor(
-      int id,
-      bool incognito,
-      const CustomThemeSupplier* theme_supplier) const;
-
  private:
   friend class theme_service_internal::ThemeServiceTest;
 
@@ -118,10 +106,6 @@
                            bool incognito,
                            const CustomThemeSupplier* theme_supplier) const;
 
-  SkColor GetTabGroupColor(int id,
-                           bool incognito,
-                           const CustomThemeSupplier* theme_supplier) const;
-
   SEQUENCE_CHECKER(sequence_checker_);
 };
 
diff --git a/chrome/browser/themes/theme_helper_win.cc b/chrome/browser/themes/theme_helper_win.cc
index 2e0bcff9..c5d0306 100644
--- a/chrome/browser/themes/theme_helper_win.cc
+++ b/chrome/browser/themes/theme_helper_win.cc
@@ -9,116 +9,11 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/win/windows_version.h"
-#include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/win/titlebar_config.h"
 #include "chrome/grit/theme_resources.h"
 #include "skia/ext/skia_utils_win.h"
 #include "ui/base/win/shell.h"
-#include "ui/color/win/accent_color_observer.h"
-#include "ui/gfx/color_utils.h"
 #include "ui/native_theme/native_theme.h"
-#include "ui/views/views_features.h"
-
-namespace {
-
-bool GetPlatformHighContrastColor(int id, SkColor* color) {
-  ui::NativeTheme::SystemThemeColor system_theme_color =
-      ui::NativeTheme::SystemThemeColor::kNotSupported;
-
-  switch (id) {
-    // Window Background
-    case ThemeProperties::COLOR_FRAME_ACTIVE:
-    case ThemeProperties::COLOR_FRAME_ACTIVE_INCOGNITO:
-    case ThemeProperties::COLOR_FRAME_INACTIVE:
-    case ThemeProperties::COLOR_FRAME_INACTIVE_INCOGNITO:
-    case ThemeProperties::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_ACTIVE:
-    case ThemeProperties::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_ACTIVE_INCOGNITO:
-    case ThemeProperties::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_INACTIVE:
-    case ThemeProperties::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_INACTIVE_INCOGNITO:
-    case ThemeProperties::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE:
-    case ThemeProperties::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO:
-    case ThemeProperties::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE:
-    case ThemeProperties::
-        COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO:
-    case ThemeProperties::COLOR_TOOLBAR:
-      system_theme_color = ui::NativeTheme::SystemThemeColor::kWindow;
-      break;
-
-    // Window Text
-    case ThemeProperties::COLOR_BOOKMARK_SEPARATOR:
-    case ThemeProperties::COLOR_TAB_STROKE_FRAME_ACTIVE:
-    case ThemeProperties::COLOR_TAB_STROKE_FRAME_INACTIVE:
-    case ThemeProperties::COLOR_TOOLBAR_VERTICAL_SEPARATOR:
-    case ThemeProperties::COLOR_TOOLBAR_TOP_SEPARATOR_FRAME_ACTIVE:
-    case ThemeProperties::COLOR_TOOLBAR_TOP_SEPARATOR_FRAME_INACTIVE:
-    case ThemeProperties::COLOR_LOCATION_BAR_BORDER:
-      system_theme_color = ui::NativeTheme::SystemThemeColor::kWindowText;
-      break;
-
-    // Button Text Foreground
-    case ThemeProperties::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE:
-    case ThemeProperties::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE_INCOGNITO:
-    case ThemeProperties::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_INACTIVE:
-    case ThemeProperties::
-        COLOR_TAB_FOREGROUND_INACTIVE_FRAME_INACTIVE_INCOGNITO:
-    case ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON:
-    case ThemeProperties::COLOR_TOOLBAR_CONTENT_AREA_SEPARATOR:
-    case ThemeProperties::COLOR_TOOLBAR_TEXT:
-      system_theme_color = ui::NativeTheme::SystemThemeColor::kButtonText;
-      break;
-
-    // Highlight/Selected Background
-    case ThemeProperties::COLOR_TOOLBAR_INK_DROP:
-      if (!base::FeatureList::IsEnabled(
-              views::features::kEnablePlatformHighContrastInkDrop))
-        return false;
-      system_theme_color = ui::NativeTheme::SystemThemeColor::kHighlight;
-      break;
-
-    // Highlight/Selected Text Foreground
-    case ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_HOVERED:
-    case ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_PRESSED:
-      if (!base::FeatureList::IsEnabled(
-              views::features::kEnablePlatformHighContrastInkDrop)) {
-        return GetPlatformHighContrastColor(
-            ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON, color);
-      }
-      [[fallthrough]];
-    case ThemeProperties::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE:
-    case ThemeProperties::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE:
-      system_theme_color = ui::NativeTheme::SystemThemeColor::kHighlightText;
-      break;
-
-    // Gray/Disabled Text
-    case ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_INACTIVE:
-      system_theme_color = ui::NativeTheme::SystemThemeColor::kGrayText;
-      break;
-
-    default:
-      return false;
-  }
-
-  *color = ui::NativeTheme::GetInstanceForNativeUi()
-               ->GetSystemThemeColor(system_theme_color)
-               .value();
-  return true;
-}
-
-SkColor GetDefaultInactiveFrameColor() {
-  return base::win::GetVersion() < base::win::Version::WIN10
-             ? SkColorSetRGB(0xEB, 0xEB, 0xEB)
-             : SK_ColorWHITE;
-}
-
-}  // namespace
-
-ThemeHelperWin::ThemeHelperWin() {
-  subscription_ = ui::AccentColorObserver::Get()->Subscribe(base::BindRepeating(
-      &ThemeHelperWin::OnAccentColorUpdated, base::Unretained(this)));
-  FetchAccentColors();
-}
-
-ThemeHelperWin::~ThemeHelperWin() = default;
 
 bool ThemeHelperWin::ShouldUseNativeFrame(
     const CustomThemeSupplier* theme_supplier) const {
@@ -134,90 +29,3 @@
   // IncreasedContrastThemeSupplier.
   return false;
 }
-
-SkColor ThemeHelperWin::GetDefaultColor(
-    int id,
-    bool incognito,
-    const CustomThemeSupplier* theme_supplier) const {
-  // In high contrast mode on Windows the platform provides the color. Try to
-  // get that color first.
-  SkColor color;
-  if (ui::NativeTheme::GetInstanceForNativeUi()->InForcedColorsMode() &&
-      GetPlatformHighContrastColor(id, &color)) {
-    return color;
-  }
-
-  // In Windows 10, native inactive borders are #555555 with 50% alpha.
-  // Prior to version 1809, native active borders use the accent color.
-  // In version 1809 and following, the active border is #262626 with 66%
-  // alpha unless the accent color is also used for the frame.
-  // NOTE: These cases are always handled, even on Win7, in order to ensure the
-  // the color provider redirection tests function. Win7 callers should never
-  // actually pass in these IDs.
-  if (id == ThemeProperties::COLOR_ACCENT_BORDER_ACTIVE) {
-    return (base::win::GetVersion() >= base::win::Version::WIN10_RS5 &&
-            !dwm_frame_color_)
-               ? SkColorSetARGB(0xa8, 0x26, 0x26, 0x26)
-               : dwm_accent_border_color_;
-  }
-  if (id == ThemeProperties::COLOR_ACCENT_BORDER_INACTIVE)
-    return SkColorSetARGB(0x80, 0x55, 0x55, 0x55);
-
-  if (DwmColorsAllowed(theme_supplier)) {
-    // When we're custom-drawing the titlebar we want to use either the colors
-    // we calculated in OnDwmKeyUpdated() or the default colors. When we're not
-    // custom-drawing the titlebar we want to match the color Windows actually
-    // uses because some things (like the incognito icon) use this color to
-    // decide whether they should draw in light or dark mode. Incognito colors
-    // should be the same as non-incognito in all cases here.
-    if (id == ThemeProperties::COLOR_FRAME_ACTIVE) {
-      if (dwm_frame_color_)
-        return dwm_frame_color_.value();
-      if (!ShouldCustomDrawSystemTitlebar())
-        return SK_ColorWHITE;
-      // Fall through and use default.
-    }
-    if (id == ThemeProperties::COLOR_FRAME_INACTIVE) {
-      if (dwm_inactive_frame_color_)
-        return dwm_inactive_frame_color_.value();
-      if (!ShouldCustomDrawSystemTitlebar())
-        return GetDefaultInactiveFrameColor();
-      if (dwm_frame_color_) {
-        // Tint to create inactive color. Always use the non-incognito version
-        // of the tint, since the frame should look the same in both modes.
-        return color_utils::HSLShift(
-            dwm_frame_color_.value(),
-            GetTint(ThemeProperties::TINT_FRAME_INACTIVE, false,
-                    theme_supplier));
-      }
-      // Fall through and use default.
-    }
-  }
-
-  return ThemeHelper::GetDefaultColor(id, incognito, theme_supplier);
-}
-
-bool ThemeHelperWin::DwmColorsAllowed(
-    const CustomThemeSupplier* theme_supplier) const {
-  return ShouldUseNativeFrame(theme_supplier) &&
-         (base::win::GetVersion() >= base::win::Version::WIN8);
-}
-
-void ThemeHelperWin::OnAccentColorUpdated() {
-  FetchAccentColors();
-  ui::NativeTheme::GetInstanceForNativeUi()->NotifyOnNativeThemeUpdated();
-}
-
-void ThemeHelperWin::FetchAccentColors() {
-  const auto* accent_color_observer = ui::AccentColorObserver::Get();
-  dwm_accent_border_color_ =
-      accent_color_observer->accent_border_color().value_or(
-          GetDefaultInactiveFrameColor());
-
-  if (base::win::GetVersion() < base::win::Version::WIN10) {
-    dwm_frame_color_ = dwm_accent_border_color_;
-  } else {
-    dwm_frame_color_ = accent_color_observer->accent_color();
-    dwm_inactive_frame_color_ = accent_color_observer->accent_color_inactive();
-  }
-}
diff --git a/chrome/browser/themes/theme_helper_win.h b/chrome/browser/themes/theme_helper_win.h
index ef35bd8..648620d 100644
--- a/chrome/browser/themes/theme_helper_win.h
+++ b/chrome/browser/themes/theme_helper_win.h
@@ -5,54 +5,18 @@
 #ifndef CHROME_BROWSER_THEMES_THEME_HELPER_WIN_H_
 #define CHROME_BROWSER_THEMES_THEME_HELPER_WIN_H_
 
-#include "base/callback_list.h"
 #include "chrome/browser/themes/theme_helper.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 
-// Tracks updates to the native colors on Windows 10 and calcuates the values we
-// should use (which are not always what Windows uses). None of the values here
-// are relevant to earlier versions of Windows.
 class ThemeHelperWin : public ThemeHelper {
  public:
-  ThemeHelperWin();
-  ~ThemeHelperWin() override;
+  ThemeHelperWin() = default;
+  ~ThemeHelperWin() override = default;
 
-  ThemeHelperWin(const ThemeHelperWin&) = delete;
-  ThemeHelperWin& operator=(const ThemeHelperWin&) = delete;
-
- private:
   // ThemeService:
   bool ShouldUseNativeFrame(
       const CustomThemeSupplier* theme_supplier) const override;
   bool ShouldUseIncreasedContrastThemeSupplier(
       ui::NativeTheme* native_theme) const override;
-  SkColor GetDefaultColor(
-      int id,
-      bool incognito,
-      const CustomThemeSupplier* theme_supplier) const override;
-
-  // Returns true if colors from DWM can be used, i.e. this is a native frame
-  // on Windows 8+.
-  bool DwmColorsAllowed(const CustomThemeSupplier* theme_supplier) const;
-
-  // Callback executed when the accent color is updated. This re-reads the
-  // accent color and updates |dwm_frame_color_| and
-  // |dwm_inactive_frame_color_|.
-  void OnAccentColorUpdated();
-
-  // Re-reads the accent colors and updates member variables.
-  void FetchAccentColors();
-
-  base::CallbackListSubscription subscription_;
-
-  // The frame color when active. If empty the default colors should be used.
-  absl::optional<SkColor> dwm_frame_color_;
-
-  // The frame color when inactive. If empty the default colors should be used.
-  absl::optional<SkColor> dwm_inactive_frame_color_;
-
-  // The DWM accent border color, if available; white otherwise.
-  SkColor dwm_accent_border_color_;
 };
 
 #endif  // CHROME_BROWSER_THEMES_THEME_HELPER_WIN_H_
diff --git a/chrome/browser/themes/theme_service.cc b/chrome/browser/themes/theme_service.cc
index db9a3b1..66acda8 100644
--- a/chrome/browser/themes/theme_service.cc
+++ b/chrome/browser/themes/theme_service.cc
@@ -91,93 +91,6 @@
 
 bool g_dont_write_theme_pack_for_testing = false;
 
-absl::optional<ui::ColorId> ThemeProviderColorIdToColorId(int color_id) {
-  // clang-format off
-  static constexpr const auto kMap = base::MakeFixedFlatMap<int, ui::ColorId>({
-#if BUILDFLAG(IS_WIN)
-      {TP::COLOR_ACCENT_BORDER_ACTIVE, kColorAccentBorderActive},
-      {TP::COLOR_ACCENT_BORDER_INACTIVE, kColorAccentBorderInactive},
-#endif  // BUILDFLAG(IS_WIN)
-      {TP::COLOR_BOOKMARK_BAR_BACKGROUND, kColorBookmarkBarBackground},
-      {TP::COLOR_BOOKMARK_FAVICON, kColorBookmarkFavicon},
-      {TP::COLOR_BOOKMARK_SEPARATOR, kColorBookmarkBarSeparator},
-      {TP::COLOR_BOOKMARK_TEXT, kColorBookmarkBarForeground},
-      {TP::COLOR_CONTROL_BUTTON_BACKGROUND, kColorCaptionButtonBackground},
-      {TP::COLOR_FEATURE_PROMO_BUBBLE_BACKGROUND,
-       kColorFeaturePromoBubbleBackground},
-      {TP::COLOR_FEATURE_PROMO_BUBBLE_BUTTON_BORDER,
-       kColorFeaturePromoBubbleButtonBorder},
-      {TP::COLOR_FEATURE_PROMO_BUBBLE_CLOSE_BUTTON_INK_DROP,
-       kColorFeaturePromoBubbleCloseButtonInkDrop},
-      {TP::COLOR_FEATURE_PROMO_BUBBLE_DEFAULT_BUTTON_BACKGROUND,
-       kColorFeaturePromoBubbleDefaultButtonBackground},
-      {TP::COLOR_FEATURE_PROMO_BUBBLE_DEFAULT_BUTTON_FOREGROUND,
-       kColorFeaturePromoBubbleDefaultButtonForeground},
-      {TP::COLOR_FEATURE_PROMO_BUBBLE_FOREGROUND,
-       kColorFeaturePromoBubbleForeground},
-      {TP::COLOR_FLYING_INDICATOR_BACKGROUND, kColorFlyingIndicatorBackground},
-      {TP::COLOR_FLYING_INDICATOR_FOREGROUND, kColorFlyingIndicatorForeground},
-      {TP::COLOR_LOCATION_BAR_BORDER, kColorLocationBarBorder},
-      {TP::COLOR_LOCATION_BAR_BORDER_OPAQUE, kColorLocationBarBorderOpaque},
-      {TP::COLOR_NTP_BACKGROUND, kColorNewTabPageBackground},
-      {TP::COLOR_NTP_HEADER, kColorNewTabPageHeader},
-      {TP::COLOR_NTP_LINK, kColorNewTabPageLink},
-      {TP::COLOR_NTP_LOGO, kColorNewTabPageLogo},
-      {TP::COLOR_NTP_SECTION_BORDER, kColorNewTabPageSectionBorder},
-      {TP::COLOR_NTP_SHORTCUT, kColorNewTabPageMostVisitedTileBackground},
-      {TP::COLOR_NTP_TEXT_LIGHT, kColorNewTabPageTextLight},
-      {TP::COLOR_FRAME_CAPTION_ACTIVE, kColorFrameCaptionActive},
-      {TP::COLOR_FRAME_CAPTION_INACTIVE, kColorFrameCaptionInactive},
-      {TP::COLOR_FRAME_ACTIVE, ui::kColorFrameActive},
-      {TP::COLOR_FRAME_INACTIVE, ui::kColorFrameInactive},
-      {TP::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_ACTIVE,
-       kColorTabBackgroundActiveFrameActive},
-      {TP::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_INACTIVE,
-       kColorTabBackgroundActiveFrameInactive},
-      {TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE,
-       kColorTabBackgroundInactiveFrameActive},
-      {TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE,
-       kColorTabBackgroundInactiveFrameInactive},
-      {TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_ACTIVE,
-       kColorTabForegroundActiveFrameActive},
-      {TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE,
-       kColorTabForegroundActiveFrameInactive},
-      {TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE,
-       kColorTabForegroundInactiveFrameActive},
-      {TP::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_INACTIVE,
-       kColorTabForegroundInactiveFrameInactive},
-      {TP::COLOR_TAB_STROKE_FRAME_ACTIVE, kColorTabStrokeFrameActive},
-      {TP::COLOR_TAB_STROKE_FRAME_INACTIVE, kColorTabStrokeFrameInactive},
-      // Toolbar and associated colors.
-      {TP::COLOR_TOOLBAR, kColorToolbar},
-      {TP::COLOR_TOOLBAR_BUTTON_BORDER, kColorToolbarButtonBorder},
-      {TP::COLOR_TOOLBAR_BUTTON_ICON, kColorToolbarButtonIcon},
-      {TP::COLOR_TOOLBAR_BUTTON_ICON_HOVERED, kColorToolbarButtonIconHovered},
-      {TP::COLOR_TOOLBAR_BUTTON_ICON_INACTIVE, kColorToolbarButtonIconInactive},
-      {TP::COLOR_TOOLBAR_BUTTON_ICON_PRESSED, kColorToolbarButtonIconPressed},
-      {TP::COLOR_TOOLBAR_BUTTON_TEXT, kColorToolbarButtonText},
-      {TP::COLOR_TOOLBAR_CONTENT_AREA_SEPARATOR,
-       kColorToolbarContentAreaSeparator},
-      {TP::COLOR_TOOLBAR_INK_DROP, kColorToolbarInkDrop},
-      {TP::COLOR_TOOLBAR_TEXT, kColorToolbarText},
-      {TP::COLOR_TOOLBAR_TOP_SEPARATOR_FRAME_ACTIVE,
-        kColorToolbarTopSeparatorFrameActive},
-      {TP::COLOR_TOOLBAR_TOP_SEPARATOR_FRAME_INACTIVE,
-        kColorToolbarTopSeparatorFrameInactive},
-      {TP::COLOR_TOOLBAR_VERTICAL_SEPARATOR, kColorToolbarSeparator},
-      {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE,
-       kColorWindowControlButtonBackgroundActive},
-      {TP::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE,
-       kColorWindowControlButtonBackgroundInactive},
-  });
-  // clang-format on
-  auto* color_it = kMap.find(color_id);
-  if (color_it != kMap.cend()) {
-    return color_it->second;
-  }
-  return absl::nullopt;
-}
-
 // Writes the theme pack to disk on a separate thread.
 void WritePackToDiskCallback(BrowserThemePack* pack,
                              const base::FilePath& directory) {
@@ -189,12 +102,6 @@
   base::UmaHistogramBoolean("Browser.ThemeService.WritePackToDisk", success);
 }
 
-void ReportHistogramBooleanUsesColorProvider(bool uses_color_provider) {
-  UMA_HISTOGRAM_BOOLEAN(
-      "Browser.ThemeService.BrowserThemeProvider.GetColor.UsesColorProvider",
-      uses_color_provider);
-}
-
 }  // namespace
 
 
@@ -305,18 +212,6 @@
   return theme_helper_.GetImageSkiaNamed(id, incognito_, GetThemeSupplier());
 }
 
-SkColor ThemeService::BrowserThemeProvider::GetColor(int id) const {
-  SCOPED_UMA_HISTOGRAM_TIMER(
-      "Browser.ThemeService.BrowserThemeProvider.GetColor");
-  if (auto color = GetColorProviderColor(id)) {
-    ReportHistogramBooleanUsesColorProvider(true);
-    return color.value();
-  }
-
-  ReportHistogramBooleanUsesColorProvider(false);
-  return theme_helper_.GetColor(id, incognito_, GetThemeSupplier());
-}
-
 color_utils::HSL ThemeService::BrowserThemeProvider::GetTint(int id) const {
   return theme_helper_.GetTint(id, incognito_, GetThemeSupplier());
 }
@@ -339,38 +234,6 @@
   return theme_helper_.GetRawData(id, GetThemeSupplier(), scale_factor);
 }
 
-absl::optional<SkColor>
-ThemeService::BrowserThemeProvider::GetColorProviderColor(int id) const {
-  if (base::FeatureList::IsEnabled(
-          features::kColorProviderRedirectionForThemeProvider)) {
-    if (auto provider_color_id = ThemeProviderColorIdToColorId(id)) {
-      const ui::NativeTheme* native_theme = nullptr;
-
-      if (incognito_) {
-        native_theme = ui::NativeTheme::GetInstanceForDarkUI();
-      } else {
-        native_theme = ui::NativeTheme::GetInstanceForNativeUi();
-#if BUILDFLAG(IS_LINUX)
-        if (const auto* linux_ui = views::LinuxUI::instance()) {
-          native_theme =
-              linux_ui->GetNativeTheme(delegate_->ShouldUseSystemTheme());
-        }
-#endif
-      }
-      if (!native_theme)
-        return absl::nullopt;
-
-      auto color_provider_key = native_theme->GetColorProviderKey(
-          GetThemeSupplier(), delegate_->ShouldUseCustomFrame());
-      auto* color_provider =
-          ui::ColorProviderManager::Get().GetColorProviderFor(
-              color_provider_key);
-      return color_provider->GetColor(provider_color_id.value());
-    }
-  }
-  return absl::nullopt;
-}
-
 CustomThemeSupplier* ThemeService::BrowserThemeProvider::GetThemeSupplier()
     const {
   return incognito_ ? nullptr : delegate_->GetThemeSupplier();
diff --git a/chrome/browser/themes/theme_service.h b/chrome/browser/themes/theme_service.h
index d8619448..ac409b3 100644
--- a/chrome/browser/themes/theme_service.h
+++ b/chrome/browser/themes/theme_service.h
@@ -236,7 +236,6 @@
 
     // Overridden from ui::ThemeProvider:
     gfx::ImageSkia* GetImageSkiaNamed(int id) const override;
-    SkColor GetColor(int original_id) const override;
     color_utils::HSL GetTint(int original_id) const override;
     int GetDisplayProperty(int id) const override;
     bool ShouldUseNativeFrame() const override;
@@ -246,7 +245,6 @@
         ui::ResourceScaleFactor scale_factor) const override;
 
    private:
-    absl::optional<SkColor> GetColorProviderColor(int id) const;
     CustomThemeSupplier* GetThemeSupplier() const;
 
     const ThemeHelper& theme_helper_;
diff --git a/chrome/browser/themes/theme_service_linux_unittest.cc b/chrome/browser/themes/theme_service_linux_unittest.cc
deleted file mode 100644
index 64ce56e7..0000000
--- a/chrome/browser/themes/theme_service_linux_unittest.cc
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/themes/theme_service.h"
-
-#include <cmath>
-
-#include "base/containers/fixed_flat_set.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/run_loop.h"
-#include "base/strings/strcat.h"
-#include "base/strings/string_util.h"
-#include "base/test/task_environment.h"
-#include "chrome/browser/browser_features.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_service_test_base.h"
-#include "chrome/browser/themes/custom_theme_supplier.h"
-#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
-#include "chrome/browser/themes/theme_properties.h"
-#include "chrome/browser/themes/theme_service_factory.h"
-#include "chrome/browser/themes/theme_service_test_utils.h"
-#include "chrome/browser/ui/color/chrome_color_id.h"
-#include "chrome/browser/ui/color/chrome_color_mixers.h"
-#include "chrome/common/buildflags.h"
-#include "chrome/common/chrome_features.h"
-#include "chrome/test/base/testing_browser_process.h"
-#include "chrome/test/base/testing_profile.h"
-#include "chrome/test/base/testing_profile_manager.h"
-#include "content/public/test/test_utils.h"
-#include "extensions/browser/extension_registry.h"
-#include "extensions/browser/extension_util.h"
-#include "extensions/browser/test_extension_registry_observer.h"
-#include "extensions/common/extension.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/gfx/color_palette.h"
-#include "ui/gfx/color_utils.h"
-#include "ui/native_theme/test_native_theme.h"
-#include "ui/ozone/public/ozone_platform.h"
-#include "ui/views/linux_ui/linux_ui.h"
-#include "ui/views/linux_ui/linux_ui_factory.h"
-#include "ui/views/views_features.h"
-
-namespace theme_service_internal {
-
-class ThemeServiceTest : public extensions::ExtensionServiceTestBase {
- public:
-  ThemeServiceTest() = default;
-  ~ThemeServiceTest() override = default;
-
-  void SetUp() override {
-    extensions::ExtensionServiceTestBase::SetUp();
-    extensions::ExtensionServiceTestBase::ExtensionServiceInitParams params =
-        CreateDefaultInitParams();
-    params.pref_file = base::FilePath();
-    InitializeExtensionService(params);
-    service_->Init();
-    registry_ = extensions::ExtensionRegistry::Get(profile());
-    ASSERT_TRUE(registry_);
-    theme_service_ = ThemeServiceFactory::GetForProfile(profile());
-    ASSERT_TRUE(theme_service_);
-  }
-
-  SkColor GetColor(ThemeService* theme_service, int id, bool incognito) const {
-    return theme_service->theme_helper_.GetColor(
-        id, incognito, theme_service->GetThemeSupplier());
-  }
-
- protected:
-  ui::TestNativeTheme test_native_theme_;
-  raw_ptr<extensions::ExtensionRegistry> registry_ = nullptr;
-  raw_ptr<ThemeService> theme_service_ = nullptr;
-};
-
-class ThemeProviderRedirectedEquivalenceLinuxTest : public ThemeServiceTest {
- public:
-  ThemeProviderRedirectedEquivalenceLinuxTest() = default;
-
-  // ThemeServiceTest:
-  void SetUp() override {
-    ThemeServiceTest::SetUp();
-
-    // Only perform mixer initialization once.
-    static bool initialized_mixers = false;
-    if (!initialized_mixers) {
-      // Ensures the toolkit is configured on supported linux platforms.
-      // Initializing the toolkit also adds the native toolkit ColorMixers.
-      ui::OzonePlatform::InitParams ozone_params;
-      ozone_params.single_process = true;
-      ui::OzonePlatform::InitializeForUI(ozone_params);
-      auto linux_ui = CreateLinuxUi();
-      linux_ui_ = linux_ui.get();
-      ASSERT_TRUE(linux_ui_);
-      views::LinuxUI::SetInstance(std::move(linux_ui));
-
-      // Add the Chrome ColorMixers after native ColorMixers.
-      ui::ColorProviderManager::Get().AppendColorProviderInitializer(
-          base::BindRepeating(AddChromeColorMixers));
-
-      initialized_mixers = true;
-    }
-    // Always use system theme.
-    views::LinuxUI::instance()->SetUseSystemThemeCallback(
-        base::BindRepeating([](aura::Window* window) { return true; }));
-    theme_service_->UseSystemTheme();
-  }
-
-  void TearDown() override {
-    // Restore the original NativeTheme parameters.
-    ThemeServiceTest::TearDown();
-  }
-
- protected:
-  raw_ptr<views::LinuxUI> linux_ui_;
-};
-
-// TODO(crbug.com/1310397): There're mismatched colors in some Linux themes.
-// Enable this test after fixing them.
-// TODO(crbug.com/1323745): Fix consecutive failures on Linux.
-#if BUILDFLAG(IS_LINUX)
-#define MAYBE_GetColor DISABLED_GetColor
-#else
-#define MAYBE_GetColor GetColor
-#endif  // BUILDFLAG(IS_LINUX)
-TEST_F(ThemeProviderRedirectedEquivalenceLinuxTest, MAYBE_GetColor) {
-  const ui::ThemeProvider& theme_provider =
-      ThemeService::GetThemeProviderForProfile(profile());
-
-  static constexpr const auto ignored_color_ids = base::MakeFixedFlatSet<
-      ui::ColorId>(
-      {// Ignore IPH colors due to behavior change.
-       // Original: always Google blue, redirected: follow the accent color.
-       ThemeProperties::COLOR_FEATURE_PROMO_BUBBLE_BACKGROUND,
-       ThemeProperties::COLOR_FEATURE_PROMO_BUBBLE_CLOSE_BUTTON_INK_DROP,
-       ThemeProperties::COLOR_FEATURE_PROMO_BUBBLE_DEFAULT_BUTTON_FOREGROUND});
-
-  std::vector<std::string> themes =
-      linux_ui_->GetAvailableSystemThemeNamesForTest();
-  for (const std::string& theme : themes) {
-    linux_ui_->SetSystemThemeByNameForTest(theme);
-    for (auto color_id : theme_service::test::kTestColorIds) {
-      if (ignored_color_ids.contains(color_id))
-        continue;
-      std::string error_message = base::StrCat(
-          {"GTK theme ", theme, ": ",
-           theme_service::test::ThemePropertiesColorToString(color_id),
-           " has mismatched values"});
-      theme_service::test::TestOriginalAndRedirectedColorMatched(
-          theme_provider, color_id, error_message);
-    }
-  }
-}
-
-}  // namespace theme_service_internal
diff --git a/chrome/browser/themes/theme_service_test_utils.cc b/chrome/browser/themes/theme_service_test_utils.cc
index 45089b8..9e2af04 100644
--- a/chrome/browser/themes/theme_service_test_utils.cc
+++ b/chrome/browser/themes/theme_service_test_utils.cc
@@ -7,7 +7,6 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/scoped_feature_list.h"
-#include "chrome/browser/browser_features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace theme_service::test {
@@ -29,81 +28,13 @@
 }
 
 std::string ColorIdToString(ui::ColorId id) {
-#define E(color_id, theme_property_id, ...) {color_id, #color_id},
 #define E_CPONLY(color_id, ...) {color_id, #color_id},
 
   static constexpr const auto kMap =
       base::MakeFixedFlatMap<ui::ColorId, const char*>({CHROME_COLOR_IDS});
 
-#undef E
 #undef E_CPONLY
   return kMap.find(id)->second;
 }
 
-std::string ThemePropertiesColorToString(int id) {
-#define E(color_id, theme_property_id, ...) \
-  {theme_property_id, #theme_property_id},
-#define E_CPONLY(color_id, ...)
-
-  static constexpr const auto kMap =
-      base::MakeFixedFlatMap<int, const char*>({CHROME_COLOR_IDS});
-
-#undef E
-#undef E_CPONLY
-  std::string id_str = kMap.find(id)->second;
-  constexpr char kPrefix[] = "ThemeProperties::";
-  return base::StartsWith(id_str, kPrefix) ? id_str.substr(strlen(kPrefix))
-                                           : id_str;
-}
-
-std::pair<PrintableSkColor, PrintableSkColor> GetOriginalAndRedirected(
-    const ui::ThemeProvider& theme_provider,
-    int color_id) {
-  PrintableSkColor original{theme_provider.GetColor(color_id)};
-
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      features::kColorProviderRedirectionForThemeProvider);
-  PrintableSkColor redirected{theme_provider.GetColor(color_id)};
-
-  return {original, redirected};
-}
-
-void TestOriginalAndRedirectedColorMatched(
-    const ui::ThemeProvider& theme_provider,
-    int color_id,
-    const std::string& error_message) {
-  auto get_tolerance = [](int id) {
-    auto* it = kColorTolerances.find(id);
-    if (it != kColorTolerances.end())
-      return it->second;
-    return 0;
-  };
-
-  // Verifies that colors with and without the ColorProvider are the same.
-  auto [original, redirected] =
-      GetOriginalAndRedirected(theme_provider, color_id);
-  auto tolerance = get_tolerance(color_id);
-  if (!tolerance) {
-    EXPECT_EQ(original, redirected) << error_message;
-  } else {
-    EXPECT_LE(std::abs(static_cast<int>(SkColorGetA(original.color) -
-                                        SkColorGetA(redirected.color))),
-              tolerance)
-        << error_message;
-    EXPECT_LE(std::abs(static_cast<int>(SkColorGetR(original.color) -
-                                        SkColorGetR(redirected.color))),
-              tolerance)
-        << error_message;
-    EXPECT_LE(std::abs(static_cast<int>(SkColorGetG(original.color) -
-                                        SkColorGetG(redirected.color))),
-              tolerance)
-        << error_message;
-    EXPECT_LE(std::abs(static_cast<int>(SkColorGetB(original.color) -
-                                        SkColorGetB(redirected.color))),
-              tolerance)
-        << error_message;
-  }
-}
-
 }  // namespace theme_service::test
diff --git a/chrome/browser/themes/theme_service_test_utils.h b/chrome/browser/themes/theme_service_test_utils.h
index 2885e0f..82a42dd 100644
--- a/chrome/browser/themes/theme_service_test_utils.h
+++ b/chrome/browser/themes/theme_service_test_utils.h
@@ -9,25 +9,11 @@
 #include <string>
 
 #include "base/containers/fixed_flat_map.h"
-#include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "third_party/skia/include/core/SkColor.h"
-#include "ui/base/theme_provider.h"
 
 namespace theme_service::test {
 
-#define E(color_id, theme_property_id, ...) theme_property_id,
-#define E_CPONLY(color_id, ...)
-static constexpr int kTestColorIds[] = {CHROME_COLOR_IDS};
-#undef E
-#undef E_CPONLY
-
-static constexpr const auto kColorTolerances = base::MakeFixedFlatMap<int, int>(
-    {{ThemeProperties::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE, 1},
-     {ThemeProperties::COLOR_TAB_STROKE_FRAME_INACTIVE, 1},
-     {ThemeProperties::COLOR_TOOLBAR_TOP_SEPARATOR_FRAME_INACTIVE, 1},
-     {ThemeProperties::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE, 1}});
-
 // Struct to distinguish SkColor (aliased to uint32_t) for printing.
 struct PrintableSkColor {
   bool operator==(const PrintableSkColor& other) const;
@@ -39,16 +25,6 @@
 std::ostream& operator<<(std::ostream& os, PrintableSkColor printable_color);
 
 std::string ColorIdToString(ui::ColorId id);
-std::string ThemePropertiesColorToString(int id);
-
-std::pair<PrintableSkColor, PrintableSkColor> GetOriginalAndRedirected(
-    const ui::ThemeProvider& theme_provider,
-    int color_id);
-
-void TestOriginalAndRedirectedColorMatched(
-    const ui::ThemeProvider& theme_provider,
-    int color_id,
-    const std::string& error_message);
 
 }  // namespace theme_service::test
 
diff --git a/chrome/browser/themes/theme_service_unittest.cc b/chrome/browser/themes/theme_service_unittest.cc
index a843bb8..2a02794 100644
--- a/chrome/browser/themes/theme_service_unittest.cc
+++ b/chrome/browser/themes/theme_service_unittest.cc
@@ -18,7 +18,6 @@
 #include "build/build_config.h"
 #include "build/buildflag.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/browser_features.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_base.h"
@@ -753,30 +752,4 @@
   EXPECT_TRUE(registry_->GetInstalledExtension(scoper.extension_id()));
 }
 
-TEST_P(ColorProviderTest, GetColor) {
-#if BUILDFLAG(IS_LINUX)
-  const auto param_tuple = GetParam();
-  const auto color_scheme = std::get<ui::NativeTheme::ColorScheme>(param_tuple);
-  const auto contrast_mode = std::get<ContrastMode>(param_tuple);
-  const auto system_theme = std::get<SystemTheme>(param_tuple);
-  // For LinuxUI, test only the light, non high contrast mode.
-  // TODO(crbug.com/1310397): make a dedicated test for LinuxUI colors.
-  if (system_theme == SystemTheme::kCustom &&
-      !(color_scheme == ui::NativeTheme::ColorScheme::kLight &&
-        contrast_mode == ContrastMode::kNonHighContrast))
-    return;
-#endif  // BUILDFLAG(IS_LINUX)
-
-  const ui::ThemeProvider& theme_provider =
-      ThemeService::GetThemeProviderForProfile(profile());
-
-  for (auto color_id : theme_service::test::kTestColorIds) {
-    std::string error_message = base::StrCat(
-        {theme_service::test::ThemePropertiesColorToString(color_id),
-         " has mismatched values"});
-    theme_service::test::TestOriginalAndRedirectedColorMatched(
-        theme_provider, color_id, error_message);
-  }
-}
-
 }  // namespace theme_service_internal
diff --git a/chrome/browser/translate/chrome_translate_client.h b/chrome/browser/translate/chrome_translate_client.h
index 1aa598b..c27ec96a 100644
--- a/chrome/browser/translate/chrome_translate_client.h
+++ b/chrome/browser/translate/chrome_translate_client.h
@@ -137,7 +137,7 @@
   void WebContentsDestroyed() override;
 
 #if !BUILDFLAG(IS_ANDROID)
-  // Shows the translate bubble.
+  // Shows the Full Page Translate bubble.
   ShowTranslateBubbleResult ShowBubble(
       translate::TranslateStep step,
       const std::string& source_language,
diff --git a/chrome/browser/translate/translate_manager_render_view_host_unittest.cc b/chrome/browser/translate/translate_manager_render_view_host_unittest.cc
index d5c9044..4fa38e3e4 100644
--- a/chrome/browser/translate/translate_manager_render_view_host_unittest.cc
+++ b/chrome/browser/translate/translate_manager_render_view_host_unittest.cc
@@ -555,9 +555,9 @@
   }
 }
 
-// The following tests depend on the translate infobar. They should be ported to
-// use the translate bubble. On Aura there is no infobar so the tests are not
-// compiled.
+// The following tests depend on the Translate infobar. They should be ported to
+// use the Full Page Translate bubble. On Aura there is no infobar so the tests
+// are not compiled.
 #if !defined(USE_AURA) && !BUILDFLAG(IS_MAC)
 TEST_F(TranslateManagerRenderViewHostTest, NormalTranslate) {
   // See BubbleNormalTranslate for corresponding bubble UX testing.
diff --git a/chrome/browser/translate/translate_service.h b/chrome/browser/translate/translate_service.h
index 7e38a0e..498738c1 100644
--- a/chrome/browser/translate/translate_service.h
+++ b/chrome/browser/translate/translate_service.h
@@ -33,7 +33,7 @@
   // test can initialize and use the service.
   static void ShutdownForTesting();
 
-  // Returns true if the new translate bubble is enabled.
+  // Returns true if the Full Page Translate bubble is enabled.
   static bool IsTranslateBubbleEnabled();
 
   // Returns the language to translate to. For more details, see
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 1e4915f..5b6801d 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3807,12 +3807,12 @@
       "views/frame/native_browser_frame_factory_aura.cc",
       "views/frame/system_menu_insertion_delegate_win.cc",
       "views/frame/system_menu_insertion_delegate_win.h",
-      "views/frame/windows_10_caption_button.cc",
-      "views/frame/windows_10_caption_button.h",
-      "views/frame/windows_10_icon_painter.cc",
-      "views/frame/windows_10_icon_painter.h",
-      "views/frame/windows_10_tab_search_caption_button.cc",
-      "views/frame/windows_10_tab_search_caption_button.h",
+      "views/frame/windows_caption_button.cc",
+      "views/frame/windows_caption_button.h",
+      "views/frame/windows_icon_painter.cc",
+      "views/frame/windows_icon_painter.h",
+      "views/frame/windows_tab_search_caption_button.cc",
+      "views/frame/windows_tab_search_caption_button.h",
       "views/network_profile_bubble_view.cc",
       "views/settings_reset_prompt_dialog.cc",
       "views/settings_reset_prompt_dialog.h",
@@ -4921,6 +4921,8 @@
       "views/tabs/tab_width_constraints.h",
       "views/tabs/window_finder.cc",
       "views/tabs/window_finder.h",
+      "views/tabs/z_orderable_tab_container_element.cc",
+      "views/tabs/z_orderable_tab_container_element.h",
       "views/task_manager_view.cc",
       "views/task_manager_view.h",
       "views/theme_copying_widget.cc",
diff --git a/chrome/browser/ui/android/toolbar/BUILD.gn b/chrome/browser/ui/android/toolbar/BUILD.gn
index 9497696..9ef99603 100644
--- a/chrome/browser/ui/android/toolbar/BUILD.gn
+++ b/chrome/browser/ui/android/toolbar/BUILD.gn
@@ -66,7 +66,11 @@
     "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuItemState.java",
     "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuUiState.java",
     "java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonConstants.java",
+    "java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonCoordinator.java",
+    "java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonMediator.java",
+    "java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonProperties.java",
     "java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java",
+    "java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewBinder.java",
     "java/src/org/chromium/chrome/browser/toolbar/optional_button/ShrinkTransition.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ActionModeController.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/CaptureReadinessResult.java",
@@ -286,14 +290,10 @@
     "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonCoordinatorTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediatorTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonTest.java",
+    "java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonCoordinatorTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java",
-    "java/src/org/chromium/chrome/browser/toolbar/top/HomeButtonCoordinatorTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/OptionalBrowsingModeButtonControllerTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ToggleTabStackButtonCoordinatorTest.java",
-    "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainerTest.java",
-    "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java",
-    "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java",
-    "java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarInteractabilityManagerTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediatorTest.java",
   ]
 
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonCoordinator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonCoordinator.java
new file mode 100644
index 0000000..c6cb151
--- /dev/null
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonCoordinator.java
@@ -0,0 +1,227 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.toolbar.optional_button;
+
+import android.content.res.ColorStateList;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.base.Callback;
+import org.chromium.base.supplier.BooleanSupplier;
+import org.chromium.chrome.browser.toolbar.ButtonData;
+import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
+import org.chromium.chrome.browser.user_education.UserEducationHelper;
+import org.chromium.components.browser_ui.widget.highlight.PulseDrawable.Bounds;
+import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightParams;
+import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightShape;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+import org.chromium.ui.widget.ViewRectProvider;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The coordinator for a button that may appear on the toolbar whose icon and click handler can be
+ * updated with animations.
+ */
+public class OptionalButtonCoordinator {
+    private final OptionalButtonMediator mMediator;
+    private final OptionalButtonView mView;
+    private final UserEducationHelper mUserEducationHelper;
+    private Callback<Integer> mTransitionFinishedCallback;
+    private IPHCommandBuilder mIphCommandBuilder;
+
+    @IntDef({TransitionType.SWAPPING, TransitionType.SHOWING, TransitionType.HIDING,
+            TransitionType.EXPANDING_ACTION_CHIP, TransitionType.COLLAPSING_ACTION_CHIP})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TransitionType {
+        int SWAPPING = 0;
+        int SHOWING = 1;
+        int HIDING = 2;
+        int EXPANDING_ACTION_CHIP = 3;
+        int COLLAPSING_ACTION_CHIP = 4;
+    }
+
+    public OptionalButtonCoordinator(View view, UserEducationHelper userEducationHelper) {
+        mUserEducationHelper = userEducationHelper;
+        PropertyModel model = new PropertyModel.Builder(OptionalButtonProperties.ALL_KEYS)
+                                      .with(OptionalButtonProperties.TRANSITION_FINISHED_CALLBACK,
+                                              this::onTransitionFinishedCallback)
+                                      .build();
+
+        assert view instanceof OptionalButtonView;
+
+        this.mView = (OptionalButtonView) view;
+
+        PropertyModelChangeProcessor.create(model, mView, OptionalButtonViewBinder::bind);
+
+        mMediator = new OptionalButtonMediator(model);
+    }
+
+    public void setPaddingStart(int paddingStart) {
+        mMediator.setPaddingStart(paddingStart);
+    }
+
+    /**
+     * Sets the ViewGroup that contains all the views that will be affected by our transitions. It
+     * has to be the parent view because the action chip width changes affect our sibling views.
+     * @param transitionRoot
+     */
+    public void setTransitionRoot(ViewGroup transitionRoot) {
+        mMediator.setTransitionRoot(transitionRoot);
+    }
+
+    public void setOnBeforeHideTransitionCallback(Runnable onBeforeHideTransitionCallback) {
+        mMediator.setOnBeforeHideTransitionCallback(onBeforeHideTransitionCallback);
+    }
+
+    /**
+     * Sets a callable that returns a boolean, this is called before any icon updates to ensure that
+     * the containing view is in a state that allows animations. If animations aren't allowed then
+     * the icon update is done instantly without any animation. Even if animations are not allowed
+     * all callbacks are called instantly in order.
+     * @param isAnimationAllowedPredicate
+     */
+    public void setIsAnimationAllowedPredicate(BooleanSupplier isAnimationAllowedPredicate) {
+        mMediator.setIsAnimationAllowedPredicate(isAnimationAllowedPredicate);
+    }
+
+    /**
+     * Sets a callback that's invoked when any transition starts.
+     * @param transitionStartedCallback A callback with an integer argument, this argument a value
+     *         from {@link TransitionType}.
+     */
+    public void setTransitionStartedCallback(Callback<Integer> transitionStartedCallback) {
+        mMediator.setTransitionStartedCallback(transitionStartedCallback);
+    }
+
+    /**
+     * Sets a callback that's invoked when any transition is finished.
+     * @param transitionFinishedCallback A callback with an integer argument, this argument a value
+     *         from {@link TransitionType}.
+     */
+    public void setTransitionFinishedCallback(Callback<Integer> transitionFinishedCallback) {
+        mTransitionFinishedCallback = transitionFinishedCallback;
+    }
+
+    /**
+     * Updates the button to replace the current action with a new one. If animations are allowed
+     * (according to the BooleanSupplier set with setIsAnimationAllowedPredicate) then this update
+     * will be animated. Otherwise it'll instantly switch to the new icon.
+     * @param buttonData
+     */
+    public void updateButton(ButtonData buttonData) {
+        if (buttonData != null && buttonData.getButtonSpec() != null
+                && buttonData.getButtonSpec().getIPHCommandBuilder() != null) {
+            mIphCommandBuilder = buttonData.getButtonSpec().getIPHCommandBuilder();
+            setViewSpecificIphProperties(mIphCommandBuilder);
+        } else {
+            mIphCommandBuilder = null;
+        }
+
+        mMediator.updateButton(buttonData);
+    }
+
+    /**
+     * Updates the button to hide it. If animations are allowed (according to the BooleanSupplier
+     * set with setIsAnimationAllowedPredicate) then this update will be animated. Otherwise it'll
+     * hide instantly.
+     */
+    public void hideButton() {
+        mIphCommandBuilder = null;
+
+        mMediator.updateButton(null);
+    }
+
+    /**
+     * If there's any transition animation it gets canceled and we fast forward to the next visual
+     * state. The TransitionFinished callback is invoked.
+     */
+    public void cancelTransition() {
+        mMediator.cancelTransition();
+    }
+
+    /**
+     * Updates the foreground color on the icons and label to match the current theme/website color.
+     * @param colorStateList
+     */
+    public void setIconForegroundColor(ColorStateList colorStateList) {
+        mMediator.setIconForegroundColor(colorStateList);
+    }
+
+    /**
+     * Updates the color filter of the background to match the current theme/website color.
+     * @param backgroundColor
+     */
+    public void setBackgroundColorFilter(int backgroundColor) {
+        mMediator.setBackgroundColorFilter(backgroundColor);
+    }
+
+    public int getViewVisibility() {
+        return mView.getVisibility();
+    }
+
+    /**
+     * Gets the current width of the container view, used by ToolbarPhone for laying out other
+     * views.
+     */
+    public int getViewWidth() {
+        return mView.getWidth();
+    }
+
+    /**
+     * Gets the container for the button, meant to be used by ToolbarPhone for drawing this view
+     * into a texture.
+     */
+    public View getViewForDrawing() {
+        return mView;
+    }
+
+    /** Gets the underlying ButtonView. */
+    @VisibleForTesting
+    public View getButtonViewForTesting() {
+        return mView.getButtonView();
+    }
+
+    private void onTransitionFinishedCallback(@TransitionType int transitionType) {
+        if (mTransitionFinishedCallback != null) {
+            mTransitionFinishedCallback.onResult(transitionType);
+        }
+
+        if (mIphCommandBuilder != null) {
+            mUserEducationHelper.requestShowIPH(mIphCommandBuilder.build());
+            mIphCommandBuilder = null;
+        }
+    }
+
+    private void setViewSpecificIphProperties(IPHCommandBuilder iphCommandBuilder) {
+        HighlightParams highlightParams = new HighlightParams(HighlightShape.CIRCLE);
+        highlightParams.setCircleRadius(new Bounds() {
+            @Override
+            public float getMaxRadiusPx(Rect bounds) {
+                return mView.getResources().getDisplayMetrics().density * 20;
+            }
+
+            @Override
+            public float getMinRadiusPx(Rect bounds) {
+                return mView.getResources().getDisplayMetrics().density * 20;
+            }
+        });
+
+        ViewRectProvider viewRectProvider = new ViewRectProvider(mView);
+        viewRectProvider.setIncludePadding(false);
+
+        highlightParams.setBoundsRespectPadding(false);
+        iphCommandBuilder.setAnchorView(
+                mView.getButtonView() == null ? mView : mView.getButtonView());
+        iphCommandBuilder.setViewRectProvider(viewRectProvider);
+        iphCommandBuilder.setHighlightParams(highlightParams);
+    }
+}
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonCoordinatorTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonCoordinatorTest.java
new file mode 100644
index 0000000..57b236d
--- /dev/null
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonCoordinatorTest.java
@@ -0,0 +1,261 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.toolbar.optional_button;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.widget.FrameLayout;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import org.chromium.base.Callback;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.toolbar.ButtonData;
+import org.chromium.chrome.browser.toolbar.ButtonData.ButtonSpec;
+import org.chromium.chrome.browser.toolbar.ButtonDataImpl;
+import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarFeatures.AdaptiveToolbarButtonVariant;
+import org.chromium.chrome.browser.toolbar.optional_button.OptionalButtonCoordinator.TransitionType;
+import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
+import org.chromium.chrome.browser.user_education.UserEducationHelper;
+
+/**
+ * Unit tests for OptionalButtonCoordinator.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class OptionalButtonCoordinatorTest {
+    @Mock
+    private OptionalButtonView mMockOptionalButtonView;
+    @Mock
+    private UserEducationHelper mMockUserEducationHelper;
+
+    @Captor
+    ArgumentCaptor<Callback<Integer>> mCallbackArgumentCaptor;
+
+    OptionalButtonCoordinator mOptionalButtonCoordinator;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mOptionalButtonCoordinator =
+                new OptionalButtonCoordinator(mMockOptionalButtonView, mMockUserEducationHelper);
+    }
+
+    @Test
+    public void testSetTransitionRoot() {
+        FrameLayout transitionRoot = new FrameLayout(ApplicationProvider.getApplicationContext());
+
+        mOptionalButtonCoordinator.setTransitionRoot(transitionRoot);
+
+        verify(mMockOptionalButtonView).setTransitionRoot(transitionRoot);
+    }
+
+    @Test
+    public void testSetOnBeforeHideTransitionCallback() {
+        Runnable callback = () -> {};
+
+        mOptionalButtonCoordinator.setOnBeforeHideTransitionCallback(callback);
+
+        verify(mMockOptionalButtonView).setOnBeforeHideTransitionCallback(callback);
+    }
+
+    @Test
+    public void testSetTransitionStartedCallback() {
+        Callback<Integer> callback = result -> {};
+
+        mOptionalButtonCoordinator.setTransitionStartedCallback(callback);
+
+        verify(mMockOptionalButtonView).setTransitionStartedCallback(callback);
+    }
+
+    @Test
+    public void testSetTransitionFinishedCallback() {
+        // On its constructor OptionalButtonCoordinator sets its own transition finished callback.
+        verify(mMockOptionalButtonView)
+                .setTransitionFinishedCallback(mCallbackArgumentCaptor.capture());
+        Callback<Integer> internalCallback = mCallbackArgumentCaptor.getValue();
+
+        Callback<Integer> externalCallback = Mockito.mock(Callback.class);
+
+        // Set a callback.
+        mOptionalButtonCoordinator.setTransitionFinishedCallback(externalCallback);
+
+        // This callback won't be passed to the view, it'll be wrapped by the Coordinator's own
+        // callback.
+        verify(mMockOptionalButtonView, never()).setTransitionFinishedCallback(externalCallback);
+
+        // Check that the external callback is wrapped by the internal one.
+        internalCallback.onResult(5);
+        verify(externalCallback).onResult(5);
+    }
+
+    @Test
+    public void testSetIconForegroundColor() {
+        ColorStateList colorStateList = ColorStateList.valueOf(Color.RED);
+
+        mOptionalButtonCoordinator.setIconForegroundColor(colorStateList);
+
+        verify(mMockOptionalButtonView).setColorStateList(colorStateList);
+    }
+
+    @Test
+    public void testSetBackgroundColorFilter() {
+        mOptionalButtonCoordinator.setBackgroundColorFilter(Color.GREEN);
+
+        verify(mMockOptionalButtonView).setBackgroundColorFilter(Color.GREEN);
+    }
+
+    @Test
+    public void testSetPaddingStart() {
+        mOptionalButtonCoordinator.setPaddingStart(42);
+
+        verify(mMockOptionalButtonView).setPaddingStart(42);
+    }
+
+    @Test
+    public void testCancelTransition() {
+        mOptionalButtonCoordinator.cancelTransition();
+        mOptionalButtonCoordinator.cancelTransition();
+
+        verify(mMockOptionalButtonView, times(2)).cancelTransition();
+    }
+
+    @Test
+    public void testGetViewVisibility() {
+        when(mMockOptionalButtonView.getVisibility()).thenReturn(View.VISIBLE);
+
+        assertEquals(View.VISIBLE, mOptionalButtonCoordinator.getViewVisibility());
+
+        verify(mMockOptionalButtonView).getVisibility();
+    }
+
+    @Test
+    public void testGetViewWidth() {
+        when(mMockOptionalButtonView.getWidth()).thenReturn(100);
+
+        assertEquals(100, mOptionalButtonCoordinator.getViewWidth());
+
+        verify(mMockOptionalButtonView).getWidth();
+    }
+
+    @Test
+    public void testGetViewForDrawing() {
+        assertEquals(mMockOptionalButtonView, mOptionalButtonCoordinator.getViewForDrawing());
+    }
+
+    @Test
+    public void testGetButtonView() {
+        View mockView = mock(View.class);
+        when(mMockOptionalButtonView.getButtonView()).thenReturn(mockView);
+
+        assertEquals(mockView, mOptionalButtonCoordinator.getButtonViewForTesting());
+
+        verify(mMockOptionalButtonView).getButtonView();
+    }
+
+    @Test
+    public void testUpdateButton() {
+        Drawable iconDrawable = mock(Drawable.class);
+        OnClickListener clickListener = view -> {};
+        IPHCommandBuilder mockIphCommandBuilder = mock(IPHCommandBuilder.class);
+        int contentDescriptionResourceId = 123456;
+        boolean isEnabled = true;
+        ButtonData buttonData = new ButtonDataImpl(/* canShow= */ true, iconDrawable, clickListener,
+                contentDescriptionResourceId, /* supportsTinting= */ true, mockIphCommandBuilder,
+                /* isEnabled= */ isEnabled, AdaptiveToolbarButtonVariant.UNKNOWN);
+
+        mOptionalButtonCoordinator.updateButton(buttonData);
+
+        // IPH command builder must be populated with view specific properties.
+        verify(mockIphCommandBuilder).setAnchorView(any());
+        verify(mockIphCommandBuilder).setViewRectProvider(any());
+        verify(mockIphCommandBuilder).setHighlightParams(any());
+        verifyNoMoreInteractions(mockIphCommandBuilder);
+
+        verify(mMockOptionalButtonView).updateButtonWithAnimation(buttonData);
+    }
+
+    @Test
+    public void testUpdateButton_disableButtonWithoutChanges() {
+        View mockButtonView = mock(View.class);
+        when(mMockOptionalButtonView.getButtonView()).thenReturn(mockButtonView);
+
+        Drawable iconDrawable = mock(Drawable.class);
+        OnClickListener clickListener = view -> {};
+        int contentDescriptionResourceId = 123456;
+        ButtonDataImpl buttonData = new ButtonDataImpl(/* canShow= */ true, iconDrawable,
+                clickListener, contentDescriptionResourceId, /* supportsTinting= */ true,
+                /* iphCommandBuilder= */ null,
+                /* isEnabled= */ true, AdaptiveToolbarButtonVariant.UNKNOWN);
+
+        // Call update button with an enabled button.
+        mOptionalButtonCoordinator.updateButton(buttonData);
+
+        buttonData.setEnabled(false);
+
+        // Call updateButton with the same data, but with enabled = false.
+        mOptionalButtonCoordinator.updateButton(buttonData);
+
+        // Button should be disabled.
+        verify(mockButtonView).setEnabled(false);
+        // Animation should only happen at the first call.
+        verify(mMockOptionalButtonView, times(1)).updateButtonWithAnimation(buttonData);
+    }
+
+    @Test
+    public void testShowIphAfterButtonUpdateTransition() {
+        verify(mMockOptionalButtonView)
+                .setTransitionFinishedCallback(mCallbackArgumentCaptor.capture());
+        Callback<Integer> transitionFinishedCallback = mCallbackArgumentCaptor.getValue();
+
+        Drawable iconDrawable = mock(Drawable.class);
+        OnClickListener clickListener = view -> {};
+        OnLongClickListener longClickListener = view -> {
+            return false;
+        };
+        IPHCommandBuilder mockIphCommandBuilder = mock(IPHCommandBuilder.class);
+        int contentDescriptionResourceId = 123456;
+        boolean isEnabled = true;
+        ButtonSpec buttonSpec = new ButtonSpec(iconDrawable, clickListener, longClickListener,
+                contentDescriptionResourceId, true, mockIphCommandBuilder,
+                AdaptiveToolbarButtonVariant.UNKNOWN, /*actionChipLabelResId=*/0);
+        ButtonDataImpl buttonData = new ButtonDataImpl();
+        buttonData.setButtonSpec(buttonSpec);
+        buttonData.setEnabled(isEnabled);
+
+        mOptionalButtonCoordinator.updateButton(buttonData);
+
+        // Call the finished callback twice to ensure the IPH is only shown once.
+        transitionFinishedCallback.onResult(TransitionType.SWAPPING);
+        transitionFinishedCallback.onResult(TransitionType.SWAPPING);
+
+        // IPH should have been built and shown only once.
+        verify(mockIphCommandBuilder).build();
+        verify(mMockUserEducationHelper).requestShowIPH(any());
+    }
+}
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonMediator.java
new file mode 100644
index 0000000..0d4b5d22
--- /dev/null
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonMediator.java
@@ -0,0 +1,67 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.toolbar.optional_button;
+
+import android.content.res.ColorStateList;
+import android.view.ViewGroup;
+
+import org.chromium.base.Callback;
+import org.chromium.base.supplier.BooleanSupplier;
+import org.chromium.chrome.browser.toolbar.ButtonData;
+import org.chromium.ui.modelutil.PropertyModel;
+
+class OptionalButtonMediator {
+    private final PropertyModel mModel;
+
+    public OptionalButtonMediator(PropertyModel model) {
+        mModel = model;
+    }
+
+    void updateButton(ButtonData buttonData) {
+        mModel.set(OptionalButtonProperties.BUTTON_DATA, buttonData);
+        if (buttonData != null) {
+            mModel.set(OptionalButtonProperties.IS_ENABLED, buttonData.isEnabled());
+        }
+    }
+
+    void setTransitionRoot(ViewGroup transitionRoot) {
+        mModel.set(OptionalButtonProperties.TRANSITION_ROOT, transitionRoot);
+    }
+
+    void setTransitionStartedCallback(Callback<Integer> transitionStartedCallback) {
+        mModel.set(OptionalButtonProperties.TRANSITION_STARTED_CALLBACK, transitionStartedCallback);
+    }
+
+    void setTransitionFinishedCallback(Callback<Integer> transitionFinishedCallback) {
+        mModel.set(
+                OptionalButtonProperties.TRANSITION_FINISHED_CALLBACK, transitionFinishedCallback);
+    }
+
+    void setIconForegroundColor(ColorStateList colorStateList) {
+        mModel.set(OptionalButtonProperties.ICON_TINT_LIST, colorStateList);
+    }
+
+    void setBackgroundColorFilter(int backgroundColor) {
+        mModel.set(OptionalButtonProperties.ICON_BACKGROUND_COLOR, backgroundColor);
+    }
+
+    public void setOnBeforeHideTransitionCallback(Runnable onBeforeHideTransitionCallback) {
+        mModel.set(OptionalButtonProperties.ON_BEFORE_HIDE_TRANSITION_CALLBACK,
+                onBeforeHideTransitionCallback);
+    }
+
+    public void setPaddingStart(int paddingStart) {
+        mModel.set(OptionalButtonProperties.PADDING_START, paddingStart);
+    }
+
+    public void cancelTransition() {
+        mModel.set(OptionalButtonProperties.TRANSITION_CANCELLATION_REQUESTED, true);
+    }
+
+    public void setIsAnimationAllowedPredicate(BooleanSupplier isAnimationAllowedPredicate) {
+        mModel.set(OptionalButtonProperties.IS_ANIMATION_ALLOWED_PREDICATE,
+                isAnimationAllowedPredicate);
+    }
+}
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonProperties.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonProperties.java
new file mode 100644
index 0000000..882c6da
--- /dev/null
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonProperties.java
@@ -0,0 +1,44 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.toolbar.optional_button;
+
+import android.content.res.ColorStateList;
+import android.view.ViewGroup;
+
+import org.chromium.base.Callback;
+import org.chromium.base.supplier.BooleanSupplier;
+import org.chromium.chrome.browser.toolbar.ButtonData;
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
+
+class OptionalButtonProperties {
+    public static final WritableObjectPropertyKey<ButtonData> BUTTON_DATA =
+            new WritableObjectPropertyKey<>();
+    public static final WritableBooleanPropertyKey IS_ENABLED = new WritableBooleanPropertyKey();
+    public static final WritableObjectPropertyKey<Callback<Integer>> TRANSITION_STARTED_CALLBACK =
+            new WritableObjectPropertyKey<>();
+    public static final WritableObjectPropertyKey<Callback<Integer>> TRANSITION_FINISHED_CALLBACK =
+            new WritableObjectPropertyKey<>();
+    public static final WritableObjectPropertyKey<Runnable> ON_BEFORE_HIDE_TRANSITION_CALLBACK =
+            new WritableObjectPropertyKey<>();
+    public static final WritableObjectPropertyKey<ViewGroup> TRANSITION_ROOT =
+            new WritableObjectPropertyKey<>();
+    public static final WritableObjectPropertyKey<ColorStateList> ICON_TINT_LIST =
+            new WritableObjectPropertyKey<>();
+    public static final WritableIntPropertyKey ICON_BACKGROUND_COLOR = new WritableIntPropertyKey();
+    public static final WritableIntPropertyKey PADDING_START = new WritableIntPropertyKey();
+    public static final WritableBooleanPropertyKey TRANSITION_CANCELLATION_REQUESTED =
+            new WritableBooleanPropertyKey();
+    public static final WritableObjectPropertyKey<BooleanSupplier> IS_ANIMATION_ALLOWED_PREDICATE =
+            new WritableObjectPropertyKey<>();
+
+    public static final PropertyKey[] ALL_KEYS = {BUTTON_DATA, IS_ENABLED,
+            TRANSITION_STARTED_CALLBACK, TRANSITION_FINISHED_CALLBACK,
+            ON_BEFORE_HIDE_TRANSITION_CALLBACK, TRANSITION_ROOT, ICON_TINT_LIST,
+            ICON_BACKGROUND_COLOR, PADDING_START, TRANSITION_CANCELLATION_REQUESTED,
+            IS_ANIMATION_ALLOWED_PREDICATE};
+}
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewBinder.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewBinder.java
new file mode 100644
index 0000000..bf25bb2
--- /dev/null
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewBinder.java
@@ -0,0 +1,47 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.toolbar.optional_button;
+
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+
+class OptionalButtonViewBinder {
+    static void bind(PropertyModel model, OptionalButtonView view, PropertyKey propertyKey) {
+        if (OptionalButtonProperties.BUTTON_DATA.equals(propertyKey)) {
+            view.updateButtonWithAnimation(model.get(OptionalButtonProperties.BUTTON_DATA));
+        } else if (OptionalButtonProperties.IS_ENABLED.equals(propertyKey)) {
+            if (view.getButtonView() != null) {
+                view.getButtonView().setEnabled(model.get(OptionalButtonProperties.IS_ENABLED));
+            }
+        } else if (OptionalButtonProperties.TRANSITION_STARTED_CALLBACK.equals(propertyKey)) {
+            view.setTransitionStartedCallback(
+                    model.get(OptionalButtonProperties.TRANSITION_STARTED_CALLBACK));
+        } else if (OptionalButtonProperties.TRANSITION_FINISHED_CALLBACK.equals(propertyKey)) {
+            view.setTransitionFinishedCallback(
+                    model.get(OptionalButtonProperties.TRANSITION_FINISHED_CALLBACK));
+        } else if (OptionalButtonProperties.ON_BEFORE_HIDE_TRANSITION_CALLBACK.equals(
+                           propertyKey)) {
+            view.setOnBeforeHideTransitionCallback(
+                    model.get(OptionalButtonProperties.ON_BEFORE_HIDE_TRANSITION_CALLBACK));
+        } else if (OptionalButtonProperties.TRANSITION_ROOT.equals(propertyKey)) {
+            view.setTransitionRoot(model.get(OptionalButtonProperties.TRANSITION_ROOT));
+        } else if (OptionalButtonProperties.ICON_TINT_LIST.equals(propertyKey)) {
+            view.setColorStateList(model.get(OptionalButtonProperties.ICON_TINT_LIST));
+        } else if (OptionalButtonProperties.ICON_BACKGROUND_COLOR.equals(propertyKey)) {
+            view.setBackgroundColorFilter(
+                    model.get(OptionalButtonProperties.ICON_BACKGROUND_COLOR));
+        } else if (OptionalButtonProperties.PADDING_START.equals(propertyKey)) {
+            view.setPaddingStart(model.get(OptionalButtonProperties.PADDING_START));
+        } else if (OptionalButtonProperties.TRANSITION_CANCELLATION_REQUESTED.equals(propertyKey)) {
+            if (model.get(OptionalButtonProperties.TRANSITION_CANCELLATION_REQUESTED)) {
+                view.cancelTransition();
+                model.set(OptionalButtonProperties.TRANSITION_CANCELLATION_REQUESTED, false);
+            }
+        } else if (OptionalButtonProperties.IS_ANIMATION_ALLOWED_PREDICATE.equals(propertyKey)) {
+            view.setIsAnimationAllowedPredicate(
+                    model.get(OptionalButtonProperties.IS_ANIMATION_ALLOWED_PREDICATE));
+        }
+    }
+}
diff --git a/chrome/browser/ui/browser_window.h b/chrome/browser/ui/browser_window.h
index 6c49714..f99d1b56b 100644
--- a/chrome/browser/ui/browser_window.h
+++ b/chrome/browser/ui/browser_window.h
@@ -103,10 +103,11 @@
 }
 
 enum class ShowTranslateBubbleResult {
-  // The translate bubble was successfully shown.
+  // The Full Page Translate bubble was successfully shown.
   SUCCESS,
 
-  // The various reasons for which the translate bubble could fail to be shown.
+  // The various reasons for which the Full Page Translate bubble could fail to
+  // be shown.
   BROWSER_WINDOW_NOT_VALID,
   BROWSER_WINDOW_MINIMIZED,
   BROWSER_WINDOW_NOT_ACTIVE,
@@ -452,7 +453,7 @@
       share::ShareAttempt attempt) = 0;
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
-  // Shows the translate bubble.
+  // Shows the Full Page Translate bubble.
   //
   // |is_user_gesture| is true when the bubble is shown on the user's deliberate
   // action.
diff --git a/chrome/browser/ui/color/chrome_color_id.h b/chrome/browser/ui/color/chrome_color_id.h
index 540dc38..87f1720 100644
--- a/chrome/browser/ui/color/chrome_color_id.h
+++ b/chrome/browser/ui/color/chrome_color_id.h
@@ -26,21 +26,19 @@
   E_CPONLY(kColorAvatarButtonHighlightSyncPaused) \
   E_CPONLY(kColorAvatarStrokeLight) \
   /* Bookmark bar colors. */ \
-  E(kColorBookmarkBarBackground, \
-    ThemeProperties::COLOR_BOOKMARK_BAR_BACKGROUND) \
-  E(kColorBookmarkBarForeground, ThemeProperties::COLOR_BOOKMARK_TEXT) \
-  E(kColorBookmarkBarSeparator, ThemeProperties::COLOR_BOOKMARK_SEPARATOR) \
+  E_CPONLY(kColorBookmarkBarBackground) \
+  E_CPONLY(kColorBookmarkBarForeground) \
+  E_CPONLY(kColorBookmarkBarSeparator) \
   E_CPONLY(kColorBookmarkButtonIcon) \
   E_CPONLY(kColorBookmarkDragImageBackground) \
   E_CPONLY(kColorBookmarkDragImageCountBackground) \
   E_CPONLY(kColorBookmarkDragImageCountForeground) \
   E_CPONLY(kColorBookmarkDragImageForeground) \
   E_CPONLY(kColorBookmarkDragImageIconBackground) \
-  E(kColorBookmarkFavicon, ThemeProperties::COLOR_BOOKMARK_FAVICON) \
+  E_CPONLY(kColorBookmarkFavicon) \
   E_CPONLY(kColorBookmarkFolderIcon) \
   /* Window caption colors. */ \
-  E(kColorCaptionButtonBackground, \
-    ThemeProperties::COLOR_CONTROL_BUTTON_BACKGROUND) \
+  E_CPONLY(kColorCaptionButtonBackground) \
   /* Captured tab colors. */ \
   E_CPONLY(kColorCapturedTabContentsBorder) \
   /* Desktop media tab list colors. */ \
@@ -80,18 +78,12 @@
   E_CPONLY(kColorEyedropperCentralPixelOuterRing) \
   E_CPONLY(kColorEyedropperGrid) \
   /* Feature Promo bubble colors. */ \
-  E(kColorFeaturePromoBubbleBackground, \
-    ThemeProperties::COLOR_FEATURE_PROMO_BUBBLE_BACKGROUND) \
-  E(kColorFeaturePromoBubbleButtonBorder, \
-    ThemeProperties::COLOR_FEATURE_PROMO_BUBBLE_BUTTON_BORDER) \
-  E(kColorFeaturePromoBubbleCloseButtonInkDrop, \
-    ThemeProperties::COLOR_FEATURE_PROMO_BUBBLE_CLOSE_BUTTON_INK_DROP) \
-  E(kColorFeaturePromoBubbleDefaultButtonBackground, \
-    ThemeProperties::COLOR_FEATURE_PROMO_BUBBLE_DEFAULT_BUTTON_BACKGROUND) \
-  E(kColorFeaturePromoBubbleDefaultButtonForeground, \
-    ThemeProperties::COLOR_FEATURE_PROMO_BUBBLE_DEFAULT_BUTTON_FOREGROUND) \
-  E(kColorFeaturePromoBubbleForeground, \
-    ThemeProperties::COLOR_FEATURE_PROMO_BUBBLE_FOREGROUND) \
+  E_CPONLY(kColorFeaturePromoBubbleBackground) \
+  E_CPONLY(kColorFeaturePromoBubbleButtonBorder) \
+  E_CPONLY(kColorFeaturePromoBubbleCloseButtonInkDrop) \
+  E_CPONLY(kColorFeaturePromoBubbleDefaultButtonBackground) \
+  E_CPONLY(kColorFeaturePromoBubbleDefaultButtonForeground) \
+  E_CPONLY(kColorFeaturePromoBubbleForeground) \
   /* Find bar colors. */ \
   E_CPONLY(kColorFindBarBackground) \
   E_CPONLY(kColorFindBarButtonIcon) \
@@ -100,15 +92,13 @@
   E_CPONLY(kColorFindBarMatchCount) \
   E_CPONLY(kColorFindBarSeparator) \
   /* Flying Indicator colors. */ \
-  E(kColorFlyingIndicatorBackground, \
-    ThemeProperties::COLOR_FLYING_INDICATOR_BACKGROUND) \
-  E(kColorFlyingIndicatorForeground, \
-    ThemeProperties::COLOR_FLYING_INDICATOR_FOREGROUND) \
+  E_CPONLY(kColorFlyingIndicatorBackground) \
+  E_CPONLY(kColorFlyingIndicatorForeground) \
   /* Default accessibility focus highlight. */ \
   E_CPONLY(kColorFocusHighlightDefault) \
   /* Frame caption colors. */ \
-  E(kColorFrameCaptionActive, ThemeProperties::COLOR_FRAME_CAPTION_ACTIVE) \
-  E(kColorFrameCaptionInactive, ThemeProperties::COLOR_FRAME_CAPTION_INACTIVE) \
+  E_CPONLY(kColorFrameCaptionActive) \
+  E_CPONLY(kColorFrameCaptionInactive) \
   /* InfoBar colors. */ \
   E_CPONLY(kColorInfoBarBackground) \
   E_CPONLY(kColorInfoBarButtonIcon) \
@@ -119,9 +109,8 @@
   E_CPONLY(kColorIntentPickerItemBackgroundHovered) \
   E_CPONLY(kColorIntentPickerItemBackgroundSelected) \
   /* Location bar colors. */ \
-  E(kColorLocationBarBorder, ThemeProperties::COLOR_LOCATION_BAR_BORDER) \
-  E(kColorLocationBarBorderOpaque, \
-    ThemeProperties::COLOR_LOCATION_BAR_BORDER_OPAQUE) \
+  E_CPONLY(kColorLocationBarBorder) \
+  E_CPONLY(kColorLocationBarBorderOpaque) \
   E_CPONLY(kColorLocationBarClearAllButtonIcon) \
   E_CPONLY(kColorLocationBarClearAllButtonIconDisabled) \
   /* Media router colors. */ \
@@ -135,19 +124,18 @@
   E_CPONLY(kColorNewTabButtonInkDropFrameActive) \
   E_CPONLY(kColorNewTabButtonInkDropFrameInactive) \
   /* New Tab Page colors. */ \
-  E(kColorNewTabPageBackground, ThemeProperties::COLOR_NTP_BACKGROUND) \
-  E(kColorNewTabPageHeader, ThemeProperties::COLOR_NTP_HEADER) \
-  E(kColorNewTabPageLink, ThemeProperties::COLOR_NTP_LINK) \
-  E(kColorNewTabPageLogo, ThemeProperties::COLOR_NTP_LOGO) \
+  E_CPONLY(kColorNewTabPageBackground) \
+  E_CPONLY(kColorNewTabPageHeader) \
+  E_CPONLY(kColorNewTabPageLink) \
+  E_CPONLY(kColorNewTabPageLogo) \
   E_CPONLY(kColorNewTabPageLogoUnthemedDark) \
   E_CPONLY(kColorNewTabPageLogoUnthemedLight) \
-  E(kColorNewTabPageMostVisitedTileBackground, \
-    ThemeProperties::COLOR_NTP_SHORTCUT) \
+  E_CPONLY(kColorNewTabPageMostVisitedTileBackground) \
   E_CPONLY(kColorNewTabPageMostVisitedTileBackgroundUnthemed) \
-  E(kColorNewTabPageSectionBorder, ThemeProperties::COLOR_NTP_SECTION_BORDER) \
-  E(kColorNewTabPageText, ThemeProperties::COLOR_NTP_TEXT) \
+  E_CPONLY(kColorNewTabPageSectionBorder) \
+  E_CPONLY(kColorNewTabPageText) \
   E_CPONLY(kColorNewTabPageTextUnthemed) \
-  E(kColorNewTabPageTextLight, ThemeProperties::COLOR_NTP_TEXT_LIGHT) \
+  E_CPONLY(kColorNewTabPageTextLight) \
   /* Omnibox colors. */ \
   E_CPONLY(kColorOmniboxAnswerIconBackground) \
   E_CPONLY(kColorOmniboxAnswerIconForeground) \
@@ -257,14 +245,10 @@
   E_CPONLY(kColorTabAlertPipPlayingInactiveFrameActive) \
   E_CPONLY(kColorTabAlertPipPlayingInactiveFrameInactive) \
   /* Tab colors. */ \
-  E(kColorTabBackgroundActiveFrameActive, \
-    ThemeProperties::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_ACTIVE) \
-  E(kColorTabBackgroundActiveFrameInactive, \
-    ThemeProperties::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_INACTIVE) \
-  E(kColorTabBackgroundInactiveFrameActive, \
-    ThemeProperties::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE) \
-  E(kColorTabBackgroundInactiveFrameInactive, \
-    ThemeProperties::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_INACTIVE) \
+  E_CPONLY(kColorTabBackgroundActiveFrameActive) \
+  E_CPONLY(kColorTabBackgroundActiveFrameInactive) \
+  E_CPONLY(kColorTabBackgroundInactiveFrameActive) \
+  E_CPONLY(kColorTabBackgroundInactiveFrameInactive) \
   E_CPONLY(kColorTabCloseButtonFocusRingActive) \
   E_CPONLY(kColorTabCloseButtonFocusRingInactive) \
   E_CPONLY(kColorTabFocusRingActive) \
@@ -324,10 +308,8 @@
   E_CPONLY(kColorTabGroupBookmarkBarPurple) \
   E_CPONLY(kColorTabGroupBookmarkBarCyan) \
   E_CPONLY(kColorTabGroupBookmarkBarOrange) \
-  E(kColorTabStrokeFrameActive, \
-    ThemeProperties::COLOR_TAB_STROKE_FRAME_ACTIVE) \
-  E(kColorTabStrokeFrameInactive, \
-    ThemeProperties::COLOR_TAB_STROKE_FRAME_INACTIVE) \
+  E_CPONLY(kColorTabStrokeFrameActive) \
+  E_CPONLY(kColorTabStrokeFrameInactive) \
   E_CPONLY(kColorTabstripLoadingProgressBackground) \
   E_CPONLY(kColorTabstripLoadingProgressForeground) \
   E_CPONLY(kColorTabstripScrollContainerShadow) \
@@ -357,34 +339,27 @@
   E_CPONLY(kColorThumbnailTabStripTabGroupFrameInactiveCyan) \
   E_CPONLY(kColorThumbnailTabStripTabGroupFrameInactiveOrange) \
   /* Toolbar colors. */ \
-  E(kColorToolbar, ThemeProperties::COLOR_TOOLBAR) \
+  E_CPONLY(kColorToolbar) \
   E_CPONLY(kColorToolbarButtonBackgroundHighlightedDefault) \
-  E(kColorToolbarButtonBorder, ThemeProperties::COLOR_TOOLBAR_BUTTON_BORDER) \
-  E(kColorToolbarButtonIcon, ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON) \
+  E_CPONLY(kColorToolbarButtonBorder) \
+  E_CPONLY(kColorToolbarButtonIcon) \
   E_CPONLY(kColorToolbarButtonIconDefault) \
   E_CPONLY(kColorToolbarButtonIconDisabled) \
-  E(kColorToolbarButtonIconHovered, \
-    ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_HOVERED) \
-  E(kColorToolbarButtonIconInactive, \
-    ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_INACTIVE) \
-  E(kColorToolbarButtonIconPressed, \
-    ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON_PRESSED) \
-  E(kColorToolbarButtonText, ThemeProperties::COLOR_TOOLBAR_BUTTON_TEXT) \
-  E(kColorToolbarContentAreaSeparator, \
-    ThemeProperties::COLOR_TOOLBAR_CONTENT_AREA_SEPARATOR) \
+  E_CPONLY(kColorToolbarButtonIconHovered) \
+  E_CPONLY(kColorToolbarButtonIconInactive) \
+  E_CPONLY(kColorToolbarButtonIconPressed) \
+  E_CPONLY(kColorToolbarButtonText) \
+  E_CPONLY(kColorToolbarContentAreaSeparator) \
   E_CPONLY(kColorToolbarFeaturePromoHighlight) \
-  E(kColorToolbarInkDrop, ThemeProperties::COLOR_TOOLBAR_INK_DROP) \
-  E(kColorToolbarSeparator, \
-    ThemeProperties::COLOR_TOOLBAR_VERTICAL_SEPARATOR) \
+  E_CPONLY(kColorToolbarInkDrop) \
+  E_CPONLY(kColorToolbarSeparator) \
   E_CPONLY(kColorToolbarSeparatorDefault) \
-  E(kColorToolbarText, ThemeProperties::COLOR_TOOLBAR_TEXT) \
+  E_CPONLY(kColorToolbarText) \
   E_CPONLY(kColorToolbarTextDefault) \
   E_CPONLY(kColorToolbarTextDisabled) \
   E_CPONLY(kColorToolbarTextDisabledDefault) \
-  E(kColorToolbarTopSeparatorFrameActive, \
-    ThemeProperties::COLOR_TOOLBAR_TOP_SEPARATOR_FRAME_ACTIVE) \
-  E(kColorToolbarTopSeparatorFrameInactive, \
-    ThemeProperties::COLOR_TOOLBAR_TOP_SEPARATOR_FRAME_INACTIVE) \
+  E_CPONLY(kColorToolbarTopSeparatorFrameActive) \
+  E_CPONLY(kColorToolbarTopSeparatorFrameInactive) \
   /* WebAuthn colors. */ \
   E_CPONLY(kColorWebAuthnBackArrowButtonIcon) \
   E_CPONLY(kColorWebAuthnBackArrowButtonIconDisabled) \
@@ -410,10 +385,8 @@
   E_CPONLY(kColorWebUiTabStripTabText) \
   E_CPONLY(kColorWebUiTabStripTabWaitingSpinning) \
   /* Window control button background colors. */ \
-  E(kColorWindowControlButtonBackgroundActive, \
-    ThemeProperties::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_ACTIVE) \
-  E(kColorWindowControlButtonBackgroundInactive, \
-    ThemeProperties::COLOR_WINDOW_CONTROL_BUTTON_BACKGROUND_INACTIVE)
+  E_CPONLY(kColorWindowControlButtonBackgroundActive) \
+  E_CPONLY(kColorWindowControlButtonBackgroundInactive)
 
 #if BUILDFLAG(IS_CHROMEOS)
 #define CHROME_PLATFORM_SPECIFIC_COLOR_IDS \
@@ -427,9 +400,8 @@
 #elif BUILDFLAG(IS_WIN)
 #define CHROME_PLATFORM_SPECIFIC_COLOR_IDS \
     /* The colors of the 1px border around the window on Windows 10. */ \
-    E(kColorAccentBorderActive, ThemeProperties::COLOR_ACCENT_BORDER_ACTIVE) \
-    E(kColorAccentBorderInactive, \
-      ThemeProperties::COLOR_ACCENT_BORDER_INACTIVE) \
+    E_CPONLY(kColorAccentBorderActive) \
+    E_CPONLY(kColorAccentBorderInactive) \
     /* Caption colors. */ \
     E_CPONLY(kColorCaptionButtonForegroundActive) \
     E_CPONLY(kColorCaptionButtonForegroundInactive) \
diff --git a/chrome/browser/ui/color/tools/dump_colors.cc b/chrome/browser/ui/color/tools/dump_colors.cc
index 0a53ebb..3923726b 100644
--- a/chrome/browser/ui/color/tools/dump_colors.cc
+++ b/chrome/browser/ui/color/tools/dump_colors.cc
@@ -53,7 +53,7 @@
     const ui::ColorProviderManager::Key key = {
         color_mode, contrast_mode,
         ui::ColorProviderManager::SystemTheme::kDefault,
-        ui::ColorProviderManager::FrameType::kChromium, nullptr};
+        ui::ColorProviderManager::FrameType::kChromium};
     ui::AddColorMixers(provider, key);
     AddChromeColorMixers(provider, key);
   };
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.cc b/chrome/browser/ui/extensions/extension_action_view_controller.cc
index b1b99a9..9bee2af4 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.cc
@@ -88,7 +88,7 @@
     content::WebContents* web_contents) {
   for (const auto& action : actions) {
     if (action->GetSiteInteraction(web_contents) ==
-        extensions::SitePermissionsHelper::SiteInteraction::kActive) {
+        extensions::SitePermissionsHelper::SiteInteraction::kGranted) {
       return true;
     }
   }
@@ -175,11 +175,11 @@
     case extensions::SitePermissionsHelper::SiteInteraction::kNone:
       // No string for neither having nor wanting access.
       break;
-    case extensions::SitePermissionsHelper::SiteInteraction::kPending:
+    case extensions::SitePermissionsHelper::SiteInteraction::kWithheld:
     case extensions::SitePermissionsHelper::SiteInteraction::kActiveTab:
       site_interaction_description_id = IDS_EXTENSIONS_WANTS_ACCESS_TO_SITE;
       break;
-    case extensions::SitePermissionsHelper::SiteInteraction::kActive:
+    case extensions::SitePermissionsHelper::SiteInteraction::kGranted:
       site_interaction_description_id = IDS_EXTENSIONS_HAS_ACCESS_TO_SITE;
       break;
   }
@@ -209,7 +209,7 @@
   return extension_action_->GetIsVisible(
              sessions::SessionTabHelper::IdForTab(web_contents).id()) ||
          site_interaction ==
-             extensions::SitePermissionsHelper::SiteInteraction::kPending ||
+             extensions::SitePermissionsHelper::SiteInteraction::kWithheld ||
          site_interaction ==
              extensions::SitePermissionsHelper::SiteInteraction::kActiveTab;
 }
@@ -221,7 +221,7 @@
 bool ExtensionActionViewController::IsRequestingSiteAccess(
     content::WebContents* web_contents) const {
   return GetSiteInteraction(web_contents) ==
-         extensions::SitePermissionsHelper::SiteInteraction::kPending;
+         extensions::SitePermissionsHelper::SiteInteraction::kWithheld;
 }
 
 void ExtensionActionViewController::HidePopup() {
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc b/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc
index d2dd545..68b3e7f 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc
@@ -673,7 +673,7 @@
   // and verify the expected appearance.
   NavigateAndCommitActiveTab(kGrantedHost);
   {
-    EXPECT_EQ(SiteInteraction::kActive,
+    EXPECT_EQ(SiteInteraction::kGranted,
               controller->GetSiteInteraction(web_contents));
     // This is a little unintuitive, but if an extension is using a page action
     // and has not specified any declarative rules or manually changed it's
@@ -720,10 +720,10 @@
             controller->GetSiteInteraction(web_contents));
 
   // Click on the action, which grants activeTab and allows the extension to
-  // access the page. This changes the page interaction status to "active".
+  // access the page. This changes the page interaction status to "granted".
   controller->ExecuteUserAction(
       ToolbarActionViewController::InvocationSource::kToolbarButton);
-  EXPECT_EQ(SiteInteraction::kActive,
+  EXPECT_EQ(SiteInteraction::kGranted,
             controller->GetSiteInteraction(web_contents));
 
   // Now navigate to a restricted URL. Clicking the extension won't give access
@@ -789,7 +789,7 @@
             controller->GetSiteInteraction(web_contents));
   controller->ExecuteUserAction(
       ToolbarActionViewController::InvocationSource::kToolbarButton);
-  EXPECT_EQ(SiteInteraction::kActive,
+  EXPECT_EQ(SiteInteraction::kGranted,
             controller->GetSiteInteraction(web_contents));
 }
 
diff --git a/chrome/browser/ui/translate/partial_translate_bubble_ui_action_logger.h b/chrome/browser/ui/translate/partial_translate_bubble_ui_action_logger.h
index f09a28b..1e93fa34 100644
--- a/chrome/browser/ui/translate/partial_translate_bubble_ui_action_logger.h
+++ b/chrome/browser/ui/translate/partial_translate_bubble_ui_action_logger.h
@@ -14,7 +14,7 @@
 
 enum class PartialTranslateBubbleUiEvent {
   // Update PartialTranslateBubbleUiEvent in enums.xml when making changes.
-  // The partial translate bubble was shown to the user.
+  // The Partial Translate bubble was shown to the user.
   BUBBLE_SHOWN = 0,
 
   // The user clicked the target language tab to start a translation.
diff --git a/chrome/browser/ui/translate/translate_bubble_factory.h b/chrome/browser/ui/translate/translate_bubble_factory.h
index 3cca5d7..9972124 100644
--- a/chrome/browser/ui/translate/translate_bubble_factory.h
+++ b/chrome/browser/ui/translate/translate_bubble_factory.h
@@ -18,13 +18,13 @@
 class WebContents;
 }
 
-// Factory to show the Translate bubble.
+// Factory to show the Full Page Translate bubble.
 class TranslateBubbleFactory {
  public:
   virtual ~TranslateBubbleFactory();
 
-  // Shows the translate bubble. The behavior depends on the current factory's
-  // implementation.
+  // Shows the Full Page Translate bubble. The behavior depends on the current
+  // factory's implementation.
   static ShowTranslateBubbleResult Show(
       BrowserWindow* window,
       content::WebContents* web_contents,
@@ -39,7 +39,7 @@
   static void SetFactory(TranslateBubbleFactory* factory);
 
  protected:
-  // Shows the translate bubble.
+  // Shows the Full Page Translate bubble.
   virtual ShowTranslateBubbleResult ShowImplementation(
       BrowserWindow* window,
       content::WebContents* web_contents,
diff --git a/chrome/browser/ui/translate/translate_bubble_model.h b/chrome/browser/ui/translate/translate_bubble_model.h
index 9ae95db8..3b1ea60 100644
--- a/chrome/browser/ui/translate/translate_bubble_model.h
+++ b/chrome/browser/ui/translate/translate_bubble_model.h
@@ -11,8 +11,8 @@
 #include "components/translate/core/browser/translate_metrics_logger_impl.h"
 #include "components/translate/core/common/translate_errors.h"
 
-// The model for the Translate bubble UX. This manages the user's manipulation
-// of the bubble and offers the data to show on the bubble.
+// The model for the Full Page Translate bubble UX. This manages the user's
+// manipulation of the bubble and offers the data to show on the bubble.
 class TranslateBubbleModel : public TranslateLanguageListModel {
  public:
   enum ViewState {
@@ -65,8 +65,8 @@
 
   // Invoked when the user actively declines to translate the page - e.g.
   // selects 'nope', 'never translate this language', etc.
-  // Should not be invoked on a passive decline - i.e. if the translate bubble
-  // is closed due to focus loss.
+  // Should not be invoked on a passive decline - i.e. if the bubble is closed
+  // due to focus loss.
   virtual void DeclineTranslation() = 0;
 
   // Returns if the user doesn't want to have the page translated in the
@@ -107,8 +107,8 @@
   // Reverts translation.
   virtual void RevertTranslation() = 0;
 
-  // Called when the translate bubble is closed. Allows final cleanup and
-  // notification of delegates.
+  // Called when the bubble is closed. Allows final cleanup
+  // and notification of delegates.
   virtual void OnBubbleClosing() = 0;
 
   // Returns true if the page is translated in the currently selected source
diff --git a/chrome/browser/ui/translate/translate_bubble_test_utils.h b/chrome/browser/ui/translate/translate_bubble_test_utils.h
index a8b8fb6..919cfd9 100644
--- a/chrome/browser/ui/translate/translate_bubble_test_utils.h
+++ b/chrome/browser/ui/translate/translate_bubble_test_utils.h
@@ -17,19 +17,19 @@
 // bubble has been migrated over to the new Bubble system.
 namespace test_utils {
 
-// Obtain the TranslateModel associated with the current bubble.
+// Obtain the TranslateBubbleModel associated with the current bubble.
 const TranslateBubbleModel* GetCurrentModel(Browser* browser);
 
 void CloseCurrentBubble(Browser* browser);
 
-// Presses 'Translate' on the currently open translate bubble.
+// Presses 'Translate' on the currently open Full Page Translate bubble.
 void PressTranslate(Browser* browser);
 
-// Presses 'Revert' on the currently opened translate bubble.
+// Presses 'Revert' on the currently opened Full Page Translate bubble.
 void PressRevert(Browser* browser);
 
 // Selects the target language with the given display name on the opened
-// translate bubble.
+// Full Page Translate bubble.
 void SelectTargetLanguageByDisplayName(Browser* browser,
                                        const std::u16string& display_name);
 
diff --git a/chrome/browser/ui/translate/translate_bubble_ui_action_logger.h b/chrome/browser/ui/translate/translate_bubble_ui_action_logger.h
index 4880062..36307b4 100644
--- a/chrome/browser/ui/translate/translate_bubble_ui_action_logger.h
+++ b/chrome/browser/ui/translate/translate_bubble_ui_action_logger.h
@@ -7,7 +7,7 @@
 
 namespace translate {
 
-// Histogram for recording the UI events related to the Translate
+// Histogram for recording the UI events related to the Full Page Translate
 // bubble.
 constexpr char kTranslateBubbleUiEventHistogramName[] =
     "Translate.BubbleUiEvent";
@@ -72,10 +72,10 @@
   // The user deactivated the translate page action icon.
   // [DEPRECATED] PAGE_ACTION_ICON_DEACTIVATED = 19,
 
-  // The translate bubble was shown to the user.
+  // The bubble was shown to the user.
   BUBBLE_SHOWN = 20,
 
-  // The translate bugbble could not be shown to the user, for various reasons.
+  // The bubble could not be shown to the user, for various reasons.
   // [DEPRECATED] BUBBLE_NOT_SHOWN_WINDOW_NOT_VALID = 21,
   BUBBLE_NOT_SHOWN_WINDOW_MINIMIZED = 22,
   // [DEPRECATED] BUBBLE_NOT_SHOWN_WINDOW_NOT_ACTIVE = 23,
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
index dc94e06..5e41c26 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
@@ -87,11 +87,11 @@
       wants_access_{
           nullptr, nullptr, IDS_EXTENSIONS_MENU_WANTS_TO_ACCESS_SITE_DATA_SHORT,
           IDS_EXTENSIONS_MENU_WANTS_TO_ACCESS_SITE_DATA,
-          extensions::SitePermissionsHelper::SiteInteraction::kPending},
-      has_access_{nullptr, nullptr,
-                  IDS_EXTENSIONS_MENU_ACCESSING_SITE_DATA_SHORT,
-                  IDS_EXTENSIONS_MENU_ACCESSING_SITE_DATA,
-                  extensions::SitePermissionsHelper::SiteInteraction::kActive} {
+          extensions::SitePermissionsHelper::SiteInteraction::kWithheld},
+      has_access_{
+          nullptr, nullptr, IDS_EXTENSIONS_MENU_ACCESSING_SITE_DATA_SHORT,
+          IDS_EXTENSIONS_MENU_ACCESSING_SITE_DATA,
+          extensions::SitePermissionsHelper::SiteInteraction::kGranted} {
   // Ensure layer masking is used for the extensions menu to ensure buttons with
   // layer effects sitting flush with the bottom of the bubble are clipped
   // appropriately.
@@ -261,11 +261,11 @@
     case extensions::SitePermissionsHelper::SiteInteraction::kNone:
       section = &cant_access_;
       break;
-    case extensions::SitePermissionsHelper::SiteInteraction::kPending:
+    case extensions::SitePermissionsHelper::SiteInteraction::kWithheld:
     case extensions::SitePermissionsHelper::SiteInteraction::kActiveTab:
       section = &wants_access_;
       break;
-    case extensions::SitePermissionsHelper::SiteInteraction::kActive:
+    case extensions::SitePermissionsHelper::SiteInteraction::kGranted:
       section = &has_access_;
       break;
   }
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view_interactive_uitest.cc b/chrome/browser/ui/views/extensions/extensions_menu_view_interactive_uitest.cc
index 0f1b27e..2d76188 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view_interactive_uitest.cc
@@ -742,7 +742,7 @@
 
   std::vector<InstalledExtensionMenuItemView*> active_menu_items =
       ExtensionsMenuView::GetSortedItemsForSectionForTesting(
-          extensions::SitePermissionsHelper::SiteInteraction::kActive);
+          extensions::SitePermissionsHelper::SiteInteraction::kGranted);
   ASSERT_EQ(1u, active_menu_items.size());
   EXPECT_EQ(u"All Urls Extension", active_menu_items[0]
                                        ->primary_action_button_for_testing()
@@ -780,7 +780,7 @@
       item_button->GetTooltipText());
   std::vector<InstalledExtensionMenuItemView*> pending_menu_items =
       ExtensionsMenuView::GetSortedItemsForSectionForTesting(
-          extensions::SitePermissionsHelper::SiteInteraction::kPending);
+          extensions::SitePermissionsHelper::SiteInteraction::kWithheld);
   ASSERT_EQ(1u, pending_menu_items.size());
   EXPECT_EQ(u"All Urls Extension", pending_menu_items[0]
                                        ->primary_action_button_for_testing()
@@ -804,7 +804,7 @@
                 u"\n"),
             item_button->GetTooltipText());
   active_menu_items = ExtensionsMenuView::GetSortedItemsForSectionForTesting(
-      extensions::SitePermissionsHelper::SiteInteraction::kActive);
+      extensions::SitePermissionsHelper::SiteInteraction::kGranted);
   ASSERT_EQ(1u, active_menu_items.size());
   EXPECT_EQ(u"All Urls Extension", active_menu_items[0]
                                        ->primary_action_button_for_testing()
diff --git a/chrome/browser/ui/views/extensions/extensions_tabbed_menu_view.cc b/chrome/browser/ui/views/extensions/extensions_tabbed_menu_view.cc
index 2c61c34..f60e70d1 100644
--- a/chrome/browser/ui/views/extensions/extensions_tabbed_menu_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_tabbed_menu_view.cc
@@ -824,7 +824,7 @@
       // Extensions with no interaction with the current site don't belong to a
       // site access section.
       return nullptr;
-    case extensions::SitePermissionsHelper::SiteInteraction::kPending:
+    case extensions::SitePermissionsHelper::SiteInteraction::kWithheld:
       return &requests_access_;
     case extensions::SitePermissionsHelper::SiteInteraction::kActiveTab:
       // When all extensions have access, activeTab extensions are labeled as
@@ -840,7 +840,7 @@
       if (site_setting == UserSiteSetting::kGrantAllExtensions)
         return &has_access_;
       return &requests_access_;
-    case extensions::SitePermissionsHelper::SiteInteraction::kActive:
+    case extensions::SitePermissionsHelper::SiteInteraction::kGranted:
       return &has_access_;
   }
 }
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container_interactive_uitest.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container_interactive_uitest.cc
index 1521ec6..8a54c14 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container_interactive_uitest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container_interactive_uitest.cc
@@ -967,11 +967,11 @@
               testing::ElementsAre(kExtensionAName, kExtensionBName));
 
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
-            SiteInteraction::kPending);
+            SiteInteraction::kWithheld);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
-            SiteInteraction::kPending);
+            SiteInteraction::kWithheld);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
-            SiteInteraction::kActive);
+            SiteInteraction::kGranted);
   EXPECT_EQ(permissions.GetSiteAccess(*extensionA, url), SiteAccess::kOnClick);
   EXPECT_EQ(permissions.GetSiteAccess(*extensionB, url), SiteAccess::kOnClick);
   EXPECT_EQ(permissions.GetSiteAccess(*extensionC, url),
@@ -988,11 +988,11 @@
   // Site interaction should stay the same because dialog wasn't accepted.
   EXPECT_TRUE(request_access_button()->GetVisible());
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
-            SiteInteraction::kPending);
+            SiteInteraction::kWithheld);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
-            SiteInteraction::kPending);
+            SiteInteraction::kWithheld);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
-            SiteInteraction::kActive);
+            SiteInteraction::kGranted);
 
   // Click the request access button again, and this time accept the dialog and
   // wait for the page refresh.
@@ -1009,11 +1009,11 @@
   // The request access button should be hidden.
   EXPECT_FALSE(request_access_button()->GetVisible());
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
-            SiteInteraction::kActive);
+            SiteInteraction::kGranted);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
-            SiteInteraction::kActive);
+            SiteInteraction::kGranted);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
-            SiteInteraction::kActive);
+            SiteInteraction::kGranted);
   EXPECT_EQ(permissions.GetSiteAccess(*extensionA, url), SiteAccess::kOnClick);
   EXPECT_EQ(permissions.GetSiteAccess(*extensionB, url), SiteAccess::kOnClick);
   EXPECT_EQ(permissions.GetSiteAccess(*extensionC, url),
@@ -1031,11 +1031,11 @@
   EXPECT_THAT(request_access_button()->GetExtensionsNamesForTesting(),
               testing::ElementsAre(kExtensionAName, kExtensionBName));
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
-            SiteInteraction::kPending);
+            SiteInteraction::kWithheld);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
-            SiteInteraction::kPending);
+            SiteInteraction::kWithheld);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
-            SiteInteraction::kActive);
+            SiteInteraction::kGranted);
 }
 
 // Tests that clicking the request access button grants one time access to the
@@ -1070,11 +1070,11 @@
               testing::ElementsAre(kExtensionAName, kExtensionBName));
   extensions::SitePermissionsHelper permissions(browser()->profile());
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
-            SiteInteraction::kPending);
+            SiteInteraction::kWithheld);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
-            SiteInteraction::kPending);
+            SiteInteraction::kWithheld);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
-            SiteInteraction::kActive);
+            SiteInteraction::kGranted);
   EXPECT_EQ(permissions.GetSiteAccess(*extensionA, url), SiteAccess::kOnClick);
   EXPECT_EQ(permissions.GetSiteAccess(*extensionB, url), SiteAccess::kOnClick);
   EXPECT_EQ(permissions.GetSiteAccess(*extensionC, url),
@@ -1091,11 +1091,11 @@
   // The request access button should be hidden.
   EXPECT_FALSE(request_access_button()->GetVisible());
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
-            SiteInteraction::kActive);
+            SiteInteraction::kGranted);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
-            SiteInteraction::kActive);
+            SiteInteraction::kGranted);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
-            SiteInteraction::kActive);
+            SiteInteraction::kGranted);
   EXPECT_EQ(permissions.GetSiteAccess(*extensionA, url), SiteAccess::kOnClick);
   EXPECT_EQ(permissions.GetSiteAccess(*extensionB, url), SiteAccess::kOnClick);
   EXPECT_EQ(permissions.GetSiteAccess(*extensionC, url),
@@ -1113,9 +1113,9 @@
   EXPECT_THAT(request_access_button()->GetExtensionsNamesForTesting(),
               testing::ElementsAre(kExtensionAName, kExtensionBName));
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionA, web_contents()),
-            SiteInteraction::kPending);
+            SiteInteraction::kWithheld);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionB, web_contents()),
-            SiteInteraction::kPending);
+            SiteInteraction::kWithheld);
   EXPECT_EQ(permissions.GetSiteInteraction(*extensionC, web_contents()),
-            SiteInteraction::kActive);
+            SiteInteraction::kGranted);
 }
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
index 90ee77c..c3d9159e 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
@@ -298,17 +298,30 @@
 // TODO(crbug.com/3671898): Add a test that checks the correct dialog is open
 // when clicking on request access button.
 
-// Tests that extensions with active tab are not taken into account for the
-// request access button visibility.
+// Tests that extensions with activeTab and requested url with withheld access
+// are taken into account for the request access button visibility, but not the
+// ones with just activeTab.
+// TODO(crbug.com/1339370): Withholding host permissions is flaky when the test
+// is run multiple times.
 TEST_F(ExtensionsToolbarControlsUnitTest,
-       RequestAccessButtonVisibility_ActiveTabExtensions) {
+       DISABLED_RequestAccessButtonVisibility_ActiveTabExtensions) {
   content::WebContentsTester* web_contents_tester =
       AddWebContentsAndGetTester();
-  const GURL url("http://www.url.com");
+  const GURL requested_url("http://www.requested-url.com");
 
-  web_contents_tester->NavigateAndCommit(url);
+  InstallExtensionWithPermissions("Extension A", {"activeTab"});
+  constexpr char kExtensionName[] = "Extension B";
+  auto extension = InstallExtensionWithHostPermissions(
+      kExtensionName, {requested_url.spec(), "activeTab"});
+  WithholdHostPermissions(extension.get());
 
-  InstallExtensionWithPermissions("Extension", {"activeTab"});
+  web_contents_tester->NavigateAndCommit(requested_url);
+  EXPECT_TRUE(IsRequestAccessButtonVisible());
+  EXPECT_THAT(request_access_button()->GetExtensionsNamesForTesting(),
+              testing::ElementsAre(kExtensionName));
+
+  web_contents_tester->NavigateAndCommit(
+      GURL("http://www.non-requested-url.com"));
   EXPECT_FALSE(IsRequestAccessButtonVisible());
 }
 
diff --git a/chrome/browser/ui/views/frame/glass_browser_caption_button_container.cc b/chrome/browser/ui/views/frame/glass_browser_caption_button_container.cc
index c288b9c..d314b2ec 100644
--- a/chrome/browser/ui/views/frame/glass_browser_caption_button_container.cc
+++ b/chrome/browser/ui/views/frame/glass_browser_caption_button_container.cc
@@ -9,8 +9,8 @@
 #include "chrome/browser/ui/frame/window_frame_util.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/glass_browser_frame_view.h"
-#include "chrome/browser/ui/views/frame/windows_10_caption_button.h"
-#include "chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.h"
+#include "chrome/browser/ui/views/frame/windows_caption_button.h"
+#include "chrome/browser/ui/views/frame/windows_tab_search_caption_button.h"
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -20,17 +20,17 @@
 
 namespace {
 
-std::unique_ptr<Windows10CaptionButton> CreateCaptionButton(
+std::unique_ptr<WindowsCaptionButton> CreateCaptionButton(
     views::Button::PressedCallback callback,
     GlassBrowserFrameView* frame_view,
     ViewID button_type,
     int accessible_name_resource_id) {
-  return std::make_unique<Windows10CaptionButton>(
+  return std::make_unique<WindowsCaptionButton>(
       std::move(callback), frame_view, button_type,
       l10n_util::GetStringUTF16(accessible_name_resource_id));
 }
 
-bool HitTestCaptionButton(Windows10CaptionButton* button,
+bool HitTestCaptionButton(WindowsCaptionButton* button,
                           const gfx::Point& point) {
   return button && button->GetVisible() && button->bounds().Contains(point);
 }
@@ -68,7 +68,7 @@
   if (WindowFrameUtil::IsWin10TabSearchCaptionButtonEnabled(
           frame_view_->browser_view()->browser())) {
     tab_search_button_ =
-        AddChildViewAt(std::make_unique<Windows10TabSearchCaptionButton>(
+        AddChildViewAt(std::make_unique<WindowsTabSearchCaptionButton>(
                            frame_view_, VIEW_ID_TAB_SEARCH_BUTTON,
                            l10n_util::GetStringUTF16(IDS_ACCNAME_TAB_SEARCH)),
                        0);
diff --git a/chrome/browser/ui/views/frame/glass_browser_caption_button_container.h b/chrome/browser/ui/views/frame/glass_browser_caption_button_container.h
index a584211..fa769a0 100644
--- a/chrome/browser/ui/views/frame/glass_browser_caption_button_container.h
+++ b/chrome/browser/ui/views/frame/glass_browser_caption_button_container.h
@@ -16,10 +16,10 @@
 
 class GlassBrowserFrameView;
 class TabSearchBubbleHost;
-class Windows10CaptionButton;
-class Windows10TabSearchCaptionButton;
+class WindowsCaptionButton;
+class WindowsTabSearchCaptionButton;
 
-// Provides a container for Windows 10 caption buttons that can be moved between
+// Provides a container for Windows caption buttons that can be moved between
 // frame and browser window as needed. When extended horizontally, becomes a
 // grab bar for moving the window.
 class GlassBrowserCaptionButtonContainer : public views::View,
@@ -68,11 +68,11 @@
   void UpdateButtonToolTipsForWindowControlsOverlay();
 
   const raw_ptr<GlassBrowserFrameView> frame_view_;
-  raw_ptr<Windows10TabSearchCaptionButton> tab_search_button_ = nullptr;
-  const raw_ptr<Windows10CaptionButton> minimize_button_;
-  const raw_ptr<Windows10CaptionButton> maximize_button_;
-  const raw_ptr<Windows10CaptionButton> restore_button_;
-  const raw_ptr<Windows10CaptionButton> close_button_;
+  raw_ptr<WindowsTabSearchCaptionButton> tab_search_button_ = nullptr;
+  const raw_ptr<WindowsCaptionButton> minimize_button_;
+  const raw_ptr<WindowsCaptionButton> maximize_button_;
+  const raw_ptr<WindowsCaptionButton> restore_button_;
+  const raw_ptr<WindowsCaptionButton> close_button_;
 
   base::ScopedObservation<views::Widget, views::WidgetObserver>
       widget_observation_{this};
diff --git a/chrome/browser/ui/views/frame/glass_browser_frame_view.h b/chrome/browser/ui/views/frame/glass_browser_frame_view.h
index 9124c2fc..33eed7e 100644
--- a/chrome/browser/ui/views/frame/glass_browser_frame_view.h
+++ b/chrome/browser/ui/views/frame/glass_browser_frame_view.h
@@ -8,7 +8,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/win/scoped_gdi_object.h"
 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
-#include "chrome/browser/ui/views/frame/windows_10_caption_button.h"
+#include "chrome/browser/ui/views/frame/windows_caption_button.h"
 #include "chrome/browser/ui/views/tab_icon_view.h"
 #include "chrome/browser/ui/views/tab_icon_view_model.h"
 #include "ui/base/metadata/metadata_header_macros.h"
diff --git a/chrome/browser/ui/views/frame/glass_browser_frame_view_browsertest_win.cc b/chrome/browser/ui/views/frame/glass_browser_frame_view_browsertest_win.cc
index c47ef23b..b6bfd38 100644
--- a/chrome/browser/ui/views/frame/glass_browser_frame_view_browsertest_win.cc
+++ b/chrome/browser/ui/views/frame/glass_browser_frame_view_browsertest_win.cc
@@ -18,7 +18,7 @@
 #include "chrome/browser/ui/views/frame/app_menu_button.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/glass_browser_caption_button_container.h"
-#include "chrome/browser/ui/views/frame/windows_10_caption_button.h"
+#include "chrome/browser/ui/views/frame/windows_caption_button.h"
 #include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_test_helper.h"
 #include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h"
 #include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_toolbar_button_container.h"
@@ -297,13 +297,13 @@
 
   auto* caption_button_container =
       glass_frame_view_->caption_button_container_for_testing();
-  auto* minimize_button = static_cast<const Windows10CaptionButton*>(
+  auto* minimize_button = static_cast<const WindowsCaptionButton*>(
       caption_button_container->GetViewByID(VIEW_ID_MINIMIZE_BUTTON));
-  auto* maximize_button = static_cast<const Windows10CaptionButton*>(
+  auto* maximize_button = static_cast<const WindowsCaptionButton*>(
       caption_button_container->GetViewByID(VIEW_ID_MAXIMIZE_BUTTON));
-  auto* restore_button = static_cast<const Windows10CaptionButton*>(
+  auto* restore_button = static_cast<const WindowsCaptionButton*>(
       caption_button_container->GetViewByID(VIEW_ID_RESTORE_BUTTON));
-  auto* close_button = static_cast<const Windows10CaptionButton*>(
+  auto* close_button = static_cast<const WindowsCaptionButton*>(
       caption_button_container->GetViewByID(VIEW_ID_CLOSE_BUTTON));
 
   // Verify tooltip text was first empty.
diff --git a/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.h b/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.h
deleted file mode 100644
index d38bf024..0000000
--- a/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_10_TAB_SEARCH_CAPTION_BUTTON_H_
-#define CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_10_TAB_SEARCH_CAPTION_BUTTON_H_
-
-#include "chrome/browser/ui/views/frame/windows_10_caption_button.h"
-#include "ui/base/metadata/metadata_header_macros.h"
-
-class GlassBrowserFrameView;
-class TabSearchBubbleHost;
-
-class Windows10TabSearchCaptionButton : public Windows10CaptionButton {
- public:
-  METADATA_HEADER(Windows10TabSearchCaptionButton);
-  Windows10TabSearchCaptionButton(GlassBrowserFrameView* frame_view,
-                                  ViewID button_type,
-                                  const std::u16string& accessible_name);
-  Windows10TabSearchCaptionButton(const Windows10TabSearchCaptionButton&) =
-      delete;
-  Windows10TabSearchCaptionButton& operator=(
-      const Windows10TabSearchCaptionButton&) = delete;
-  ~Windows10TabSearchCaptionButton() override;
-
-  TabSearchBubbleHost* tab_search_bubble_host() {
-    return tab_search_bubble_host_.get();
-  }
-
- private:
-  std::unique_ptr<TabSearchBubbleHost> tab_search_bubble_host_;
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_10_TAB_SEARCH_CAPTION_BUTTON_H_
diff --git a/chrome/browser/ui/views/frame/windows_10_caption_button.cc b/chrome/browser/ui/views/frame/windows_caption_button.cc
similarity index 92%
rename from chrome/browser/ui/views/frame/windows_10_caption_button.cc
rename to chrome/browser/ui/views/frame/windows_caption_button.cc
index 13ebcaa..4bd4a70 100644
--- a/chrome/browser/ui/views/frame/windows_10_caption_button.cc
+++ b/chrome/browser/ui/views/frame/windows_caption_button.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/views/frame/windows_10_caption_button.h"
+#include "chrome/browser/ui/views/frame/windows_caption_button.h"
 #include <memory>
 
 #include "base/numerics/safe_conversions.h"
@@ -21,7 +21,7 @@
 #include "ui/gfx/geometry/skia_conversions.h"
 #include "ui/gfx/scoped_canvas.h"
 
-Windows10CaptionButton::Windows10CaptionButton(
+WindowsCaptionButton::WindowsCaptionButton(
     PressedCallback callback,
     GlassBrowserFrameView* frame_view,
     ViewID button_type,
@@ -37,17 +37,17 @@
   SetID(button_type);
 }
 
-Windows10CaptionButton::~Windows10CaptionButton() = default;
+WindowsCaptionButton::~WindowsCaptionButton() = default;
 
 std::unique_ptr<Windows10IconPainter>
-Windows10CaptionButton::CreateIconPainter() {
+WindowsCaptionButton::CreateIconPainter() {
   if (base::win::GetVersion() >= base::win::Version::WIN11) {
     return std::make_unique<Windows11IconPainter>();
   }
   return std::make_unique<Windows10IconPainter>();
 }
 
-gfx::Size Windows10CaptionButton::CalculatePreferredSize() const {
+gfx::Size WindowsCaptionButton::CalculatePreferredSize() const {
   // TODO(bsep): The sizes in this function are for 1x device scale and don't
   // match Windows button sizes at hidpi.
   int height = WindowFrameUtil::kWindows10GlassCaptionButtonHeightRestored;
@@ -65,14 +65,14 @@
   return gfx::Size(base_width + GetBetweenButtonSpacing(), height);
 }
 
-SkColor Windows10CaptionButton::GetBaseForegroundColor() const {
+SkColor WindowsCaptionButton::GetBaseForegroundColor() const {
   return GetColorProvider()->GetColor(
       frame_view_->ShouldPaintAsActive()
           ? kColorCaptionButtonForegroundActive
           : kColorCaptionButtonForegroundInactive);
 }
 
-void Windows10CaptionButton::OnPaintBackground(gfx::Canvas* canvas) {
+void WindowsCaptionButton::OnPaintBackground(gfx::Canvas* canvas) {
   // Paint the background of the button (the semi-transparent rectangle that
   // appears when you hover or press the button).
   const ui::ThemeProvider* theme_provider = GetThemeProvider();
@@ -137,18 +137,18 @@
   canvas->FillRect(bounds, SkColorSetA(base_color, alpha));
 }
 
-void Windows10CaptionButton::PaintButtonContents(gfx::Canvas* canvas) {
+void WindowsCaptionButton::PaintButtonContents(gfx::Canvas* canvas) {
   PaintSymbol(canvas);
 }
 
-int Windows10CaptionButton::GetBetweenButtonSpacing() const {
+int WindowsCaptionButton::GetBetweenButtonSpacing() const {
   const int display_order_index = GetButtonDisplayOrderIndex();
   return display_order_index == 0
              ? 0
              : WindowFrameUtil::kWindows10GlassCaptionButtonVisualSpacing;
 }
 
-int Windows10CaptionButton::GetButtonDisplayOrderIndex() const {
+int WindowsCaptionButton::GetButtonDisplayOrderIndex() const {
   int button_display_order = 0;
   const bool tab_search_enabled =
       WindowFrameUtil::IsWin10TabSearchCaptionButtonEnabled(
@@ -181,7 +181,7 @@
   return button_display_order;
 }
 
-void Windows10CaptionButton::PaintSymbol(gfx::Canvas* canvas) {
+void WindowsCaptionButton::PaintSymbol(gfx::Canvas* canvas) {
   SkColor symbol_color = GetBaseForegroundColor();
   const SkColor hovered_color =
       GetColorProvider()->GetColor(kColorCaptionCloseButtonForegroundHovered);
@@ -253,7 +253,7 @@
   }
 }
 
-BEGIN_METADATA(Windows10CaptionButton, views::Button)
+BEGIN_METADATA(WindowsCaptionButton, views::Button)
 ADD_READONLY_PROPERTY_METADATA(int, BetweenButtonSpacing)
 ADD_READONLY_PROPERTY_METADATA(int, ButtonDisplayOrderIndex)
 ADD_READONLY_PROPERTY_METADATA(SkColor,
diff --git a/chrome/browser/ui/views/frame/windows_10_caption_button.h b/chrome/browser/ui/views/frame/windows_caption_button.h
similarity index 67%
rename from chrome/browser/ui/views/frame/windows_10_caption_button.h
rename to chrome/browser/ui/views/frame/windows_caption_button.h
index 57f6a96..35efa7cb 100644
--- a/chrome/browser/ui/views/frame/windows_10_caption_button.h
+++ b/chrome/browser/ui/views/frame/windows_caption_button.h
@@ -2,30 +2,30 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_10_CAPTION_BUTTON_H_
-#define CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_10_CAPTION_BUTTON_H_
+#ifndef CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_CAPTION_BUTTON_H_
+#define CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_CAPTION_BUTTON_H_
 
 #include <memory>
 
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ui/view_ids.h"
-#include "chrome/browser/ui/views/frame/windows_10_icon_painter.h"
+#include "chrome/browser/ui/views/frame/windows_icon_painter.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/gfx/canvas.h"
 #include "ui/views/controls/button/button.h"
 
 class GlassBrowserFrameView;
 
-class Windows10CaptionButton : public views::Button {
+class WindowsCaptionButton : public views::Button {
  public:
-  METADATA_HEADER(Windows10CaptionButton);
-  Windows10CaptionButton(PressedCallback callback,
-                         GlassBrowserFrameView* frame_view,
-                         ViewID button_type,
-                         const std::u16string& accessible_name);
-  Windows10CaptionButton(const Windows10CaptionButton&) = delete;
-  Windows10CaptionButton& operator=(const Windows10CaptionButton&) = delete;
-  ~Windows10CaptionButton() override;
+  METADATA_HEADER(WindowsCaptionButton);
+  WindowsCaptionButton(PressedCallback callback,
+                       GlassBrowserFrameView* frame_view,
+                       ViewID button_type,
+                       const std::u16string& accessible_name);
+  WindowsCaptionButton(const WindowsCaptionButton&) = delete;
+  WindowsCaptionButton& operator=(const WindowsCaptionButton&) = delete;
+  ~WindowsCaptionButton() override;
 
   // views::Button:
   gfx::Size CalculatePreferredSize() const override;
@@ -57,4 +57,4 @@
   ViewID button_type_;
 };
 
-#endif  // CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_10_CAPTION_BUTTON_H_
+#endif  // CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_CAPTION_BUTTON_H_
diff --git a/chrome/browser/ui/views/frame/windows_10_caption_button_unittest.cc b/chrome/browser/ui/views/frame/windows_caption_button_unittest.cc
similarity index 61%
rename from chrome/browser/ui/views/frame/windows_10_caption_button_unittest.cc
rename to chrome/browser/ui/views/frame/windows_caption_button_unittest.cc
index 8472857..5d2a219 100644
--- a/chrome/browser/ui/views/frame/windows_10_caption_button_unittest.cc
+++ b/chrome/browser/ui/views/frame/windows_caption_button_unittest.cc
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/views/frame/windows_10_caption_button.h"
+#include "chrome/browser/ui/views/frame/windows_caption_button.h"
 
 #include "chrome/browser/ui/view_ids.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/view.h"
 
-TEST(Windows10CaptionButtonTest, CheckFocusBehavior) {
-  Windows10CaptionButton button(views::Button::PressedCallback(), nullptr,
-                                VIEW_ID_NONE, std::u16string());
+TEST(WindowsCaptionButton, CheckFocusBehavior) {
+  WindowsCaptionButton button(views::Button::PressedCallback(), nullptr,
+                              VIEW_ID_NONE, std::u16string());
   EXPECT_EQ(views::View::FocusBehavior::ACCESSIBLE_ONLY,
             button.GetFocusBehavior());
 }
diff --git a/chrome/browser/ui/views/frame/windows_10_icon_painter.cc b/chrome/browser/ui/views/frame/windows_icon_painter.cc
similarity index 98%
rename from chrome/browser/ui/views/frame/windows_10_icon_painter.cc
rename to chrome/browser/ui/views/frame/windows_icon_painter.cc
index 7b618df..6a4d8c6 100644
--- a/chrome/browser/ui/views/frame/windows_10_icon_painter.cc
+++ b/chrome/browser/ui/views/frame/windows_icon_painter.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/views/frame/windows_10_icon_painter.h"
+#include "chrome/browser/ui/views/frame/windows_icon_painter.h"
 
 #include "base/numerics/safe_conversions.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
diff --git a/chrome/browser/ui/views/frame/windows_10_icon_painter.h b/chrome/browser/ui/views/frame/windows_icon_painter.h
similarity index 91%
rename from chrome/browser/ui/views/frame/windows_10_icon_painter.h
rename to chrome/browser/ui/views/frame/windows_icon_painter.h
index 1ad64a1..0a51cfa 100644
--- a/chrome/browser/ui/views/frame/windows_10_icon_painter.h
+++ b/chrome/browser/ui/views/frame/windows_icon_painter.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_10_ICON_PAINTER_H_
-#define CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_10_ICON_PAINTER_H_
+#ifndef CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_ICON_PAINTER_H_
+#define CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_ICON_PAINTER_H_
 
 #include "base/memory/raw_ptr.h"
 #include "ui/gfx/canvas.h"
@@ -63,4 +63,4 @@
                         cc::PaintFlags& flags) override;
 };
 
-#endif  // CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_10_ICON_PAINTER_H_
+#endif  // CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_ICON_PAINTER_H_
diff --git a/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.cc b/chrome/browser/ui/views/frame/windows_tab_search_caption_button.cc
similarity index 67%
rename from chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.cc
rename to chrome/browser/ui/views/frame/windows_tab_search_caption_button.cc
index 53952abf..7e89e073 100644
--- a/chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.cc
+++ b/chrome/browser/ui/views/frame/windows_tab_search_caption_button.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/views/frame/windows_10_tab_search_caption_button.h"
+#include "chrome/browser/ui/views/frame/windows_tab_search_caption_button.h"
 
 #include "chrome/browser/ui/browser_element_identifiers.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
@@ -12,14 +12,14 @@
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/views/view_class_properties.h"
 
-Windows10TabSearchCaptionButton::Windows10TabSearchCaptionButton(
+WindowsTabSearchCaptionButton::WindowsTabSearchCaptionButton(
     GlassBrowserFrameView* frame_view,
     ViewID button_type,
     const std::u16string& accessible_name)
-    : Windows10CaptionButton(views::Button::PressedCallback(),
-                             frame_view,
-                             button_type,
-                             accessible_name),
+    : WindowsCaptionButton(views::Button::PressedCallback(),
+                           frame_view,
+                           button_type,
+                           accessible_name),
       tab_search_bubble_host_(std::make_unique<TabSearchBubbleHost>(
           this,
           frame_view->browser_view()->GetProfile())) {
@@ -29,7 +29,7 @@
       kColorTabSearchCaptionButtonFocusRing);
 }
 
-Windows10TabSearchCaptionButton::~Windows10TabSearchCaptionButton() = default;
+WindowsTabSearchCaptionButton::~WindowsTabSearchCaptionButton() = default;
 
-BEGIN_METADATA(Windows10TabSearchCaptionButton, Windows10CaptionButton)
+BEGIN_METADATA(WindowsTabSearchCaptionButton, WindowsCaptionButton)
 END_METADATA
diff --git a/chrome/browser/ui/views/frame/windows_tab_search_caption_button.h b/chrome/browser/ui/views/frame/windows_tab_search_caption_button.h
new file mode 100644
index 0000000..cff8a5a
--- /dev/null
+++ b/chrome/browser/ui/views/frame/windows_tab_search_caption_button.h
@@ -0,0 +1,33 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_TAB_SEARCH_CAPTION_BUTTON_H_
+#define CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_TAB_SEARCH_CAPTION_BUTTON_H_
+
+#include "chrome/browser/ui/views/frame/windows_caption_button.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+
+class GlassBrowserFrameView;
+class TabSearchBubbleHost;
+
+class WindowsTabSearchCaptionButton : public WindowsCaptionButton {
+ public:
+  METADATA_HEADER(WindowsTabSearchCaptionButton);
+  WindowsTabSearchCaptionButton(GlassBrowserFrameView* frame_view,
+                                ViewID button_type,
+                                const std::u16string& accessible_name);
+  WindowsTabSearchCaptionButton(const WindowsTabSearchCaptionButton&) = delete;
+  WindowsTabSearchCaptionButton& operator=(
+      const WindowsTabSearchCaptionButton&) = delete;
+  ~WindowsTabSearchCaptionButton() override;
+
+  TabSearchBubbleHost* tab_search_bubble_host() {
+    return tab_search_bubble_host_.get();
+  }
+
+ private:
+  std::unique_ptr<TabSearchBubbleHost> tab_search_bubble_host_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_FRAME_WINDOWS_TAB_SEARCH_CAPTION_BUTTON_H_
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index dd36d8cc..623afae 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -300,8 +300,10 @@
     // first so that they appear on the left side of the icon container.
     // TODO(crbug.com/1318890): Improve the ordering heuristics for page action
     // icons and determine a way to handle simultaneous icon animations.
-    if (side_search::IsDSESupportEnabled(profile_))
+    if (side_search::IsDSESupportEnabled(profile_) &&
+        browser_->is_type_normal()) {
       params.types_enabled.push_back(PageActionIconType::kSideSearch);
+    }
     params.types_enabled.push_back(PageActionIconType::kSendTabToSelf);
     params.types_enabled.push_back(PageActionIconType::kClickToCall);
     params.types_enabled.push_back(PageActionIconType::kQRCodeGenerator);
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index 4fc2e72..5a7767f 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -87,7 +87,7 @@
   auto* source_context = TabDragController::GetSourceContext();
   if (source_context) {
     gfx::NativeWindow source_window =
-        source_context->AsView()->GetWidget()->GetNativeWindow();
+        source_context->GetWidget()->GetNativeWindow();
     if (source_window)
       return BrowserView::GetBrowserViewForNativeWindow(source_window);
   }
@@ -546,11 +546,6 @@
   return model_->group_model()->GetTabGroup(group)->GetFirstTab();
 }
 
-absl::optional<int> BrowserTabStripController::GetLastTabInGroup(
-    const tab_groups::TabGroupId& group) const {
-  return model_->group_model()->GetTabGroup(group)->GetLastTab();
-}
-
 gfx::Range BrowserTabStripController::ListTabsInGroup(
     const tab_groups::TabGroupId& group) const {
   return model_->group_model()->GetTabGroup(group)->ListTabs();
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
index fc4e42d6..c3c9187 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h
@@ -104,8 +104,6 @@
       const tab_groups::TabGroupVisualData& visual_data) override;
   absl::optional<int> GetFirstTabInGroup(
       const tab_groups::TabGroupId& group) const override;
-  absl::optional<int> GetLastTabInGroup(
-      const tab_groups::TabGroupId& group) const override;
   gfx::Range ListTabsInGroup(
       const tab_groups::TabGroupId& group_id) const override;
   bool IsFrameCondensed() const override;
diff --git a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
index 82cbb63..f63575c1 100644
--- a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.cc
@@ -153,16 +153,6 @@
   return absl::nullopt;
 }
 
-absl::optional<int> FakeBaseTabStripController::GetLastTabInGroup(
-    const tab_groups::TabGroupId& group) const {
-  for (size_t i = tab_groups_.size(); i > 0; --i) {
-    if (tab_groups_[i - 1] == group)
-      return i - 1;
-  }
-
-  return absl::nullopt;
-}
-
 gfx::Range FakeBaseTabStripController::ListTabsInGroup(
     const tab_groups::TabGroupId& group) const {
   int first_tab = -1;
diff --git a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h
index e2d719c5..a25c5808a 100644
--- a/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h
@@ -77,8 +77,6 @@
       const tab_groups::TabGroupVisualData& visual_data) override;
   absl::optional<int> GetFirstTabInGroup(
       const tab_groups::TabGroupId& group) const override;
-  absl::optional<int> GetLastTabInGroup(
-      const tab_groups::TabGroupId& group) const override;
   gfx::Range ListTabsInGroup(
       const tab_groups::TabGroupId& group) const override;
   void AddTabToGroup(int model_index,
diff --git a/chrome/browser/ui/views/tabs/fake_tab_slot_controller.cc b/chrome/browser/ui/views/tabs/fake_tab_slot_controller.cc
index c596dd7..157c1527 100644
--- a/chrome/browser/ui/views/tabs/fake_tab_slot_controller.cc
+++ b/chrome/browser/ui/views/tabs/fake_tab_slot_controller.cc
@@ -144,11 +144,6 @@
   return tab_strip_controller_->IsGroupCollapsed(group);
 }
 
-absl::optional<int> FakeTabSlotController::GetLastTabInGroup(
-    const tab_groups::TabGroupId& group) const {
-  return tab_strip_controller_->GetLastTabInGroup(group);
-}
-
 SkColor FakeTabSlotController::GetPaintedGroupColor(
     const tab_groups::TabGroupColorId& color_id) const {
   return SkColor();
diff --git a/chrome/browser/ui/views/tabs/fake_tab_slot_controller.h b/chrome/browser/ui/views/tabs/fake_tab_slot_controller.h
index 7343343a..8eb6d6b3 100644
--- a/chrome/browser/ui/views/tabs/fake_tab_slot_controller.h
+++ b/chrome/browser/ui/views/tabs/fake_tab_slot_controller.h
@@ -91,8 +91,6 @@
   tab_groups::TabGroupColorId GetGroupColorId(
       const tab_groups::TabGroupId& group) const override;
   bool IsGroupCollapsed(const tab_groups::TabGroupId& group) const override;
-  absl::optional<int> GetLastTabInGroup(
-      const tab_groups::TabGroupId& group) const override;
   SkColor GetPaintedGroupColor(
       const tab_groups::TabGroupColorId& color_id) const override;
   void ShiftGroupLeft(const tab_groups::TabGroupId& group) override {}
diff --git a/chrome/browser/ui/views/tabs/tab_container.cc b/chrome/browser/ui/views/tabs/tab_container.cc
index 7e58a7e..f9bf11f 100644
--- a/chrome/browser/ui/views/tabs/tab_container.cc
+++ b/chrome/browser/ui/views/tabs/tab_container.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
 #include "chrome/browser/ui/views/tabs/tab_style_views.h"
+#include "chrome/browser/ui/views/tabs/z_orderable_tab_container_element.h"
 #include "chrome/grit/theme_resources.h"
 #include "components/tab_groups/tab_group_id.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -48,13 +49,7 @@
 // underlines.
 class TabSlotAnimationDelegate : public gfx::AnimationDelegate {
  public:
-  using OnAnimationProgressedCallback =
-      base::RepeatingCallback<void(TabSlotView*)>;
-
-  TabSlotAnimationDelegate(
-      TabContainer* tab_container,
-      TabSlotView* slot_view,
-      OnAnimationProgressedCallback on_animation_progressed);
+  TabSlotAnimationDelegate(TabContainer* tab_container, TabSlotView* slot_view);
   TabSlotAnimationDelegate(const TabSlotAnimationDelegate&) = delete;
   TabSlotAnimationDelegate& operator=(const TabSlotAnimationDelegate&) = delete;
   ~TabSlotAnimationDelegate() override;
@@ -70,16 +65,11 @@
  private:
   const raw_ptr<TabContainer> tab_container_;
   const raw_ptr<TabSlotView> slot_view_;
-  OnAnimationProgressedCallback on_animation_progressed_;
 };
 
-TabSlotAnimationDelegate::TabSlotAnimationDelegate(
-    TabContainer* tab_container,
-    TabSlotView* slot_view,
-    OnAnimationProgressedCallback on_animation_progressed)
-    : tab_container_(tab_container),
-      slot_view_(slot_view),
-      on_animation_progressed_(on_animation_progressed) {
+TabSlotAnimationDelegate::TabSlotAnimationDelegate(TabContainer* tab_container,
+                                                   TabSlotView* slot_view)
+    : tab_container_(tab_container), slot_view_(slot_view) {
   slot_view_->set_animating(true);
 }
 
@@ -87,7 +77,7 @@
 
 void TabSlotAnimationDelegate::AnimationProgressed(
     const gfx::Animation* animation) {
-  on_animation_progressed_.Run(slot_view());
+  tab_container_->OnTabSlotAnimationProgressed(slot_view_);
 }
 
 void TabSlotAnimationDelegate::AnimationEnded(const gfx::Animation* animation) {
@@ -101,127 +91,6 @@
   AnimationEnded(animation);
 }
 
-// Animation delegate used when a dragged tab is released. When done sets the
-// dragging state to false.
-class ResetDraggingStateDelegate : public TabSlotAnimationDelegate {
- public:
-  ResetDraggingStateDelegate(
-      TabContainer* tab_container,
-      Tab* tab,
-      OnAnimationProgressedCallback on_animation_progressed);
-  ResetDraggingStateDelegate(const ResetDraggingStateDelegate&) = delete;
-  ResetDraggingStateDelegate& operator=(const ResetDraggingStateDelegate&) =
-      delete;
-  ~ResetDraggingStateDelegate() override;
-
-  void AnimationEnded(const gfx::Animation* animation) override;
-  void AnimationCanceled(const gfx::Animation* animation) override;
-};
-
-ResetDraggingStateDelegate::ResetDraggingStateDelegate(
-    TabContainer* tab_container,
-    Tab* tab,
-    OnAnimationProgressedCallback on_animation_progressed)
-    : TabSlotAnimationDelegate(tab_container, tab, on_animation_progressed) {}
-
-ResetDraggingStateDelegate::~ResetDraggingStateDelegate() = default;
-
-void ResetDraggingStateDelegate::AnimationEnded(
-    const gfx::Animation* animation) {
-  static_cast<Tab*>(slot_view())->set_dragging(false);
-  TabSlotAnimationDelegate::AnimationEnded(animation);
-}
-
-void ResetDraggingStateDelegate::AnimationCanceled(
-    const gfx::Animation* animation) {
-  AnimationEnded(animation);
-}
-
-// A class that calculates a z-value for a TabContainer child view (one of a
-// tab, a tab group header, a tab group underline, or a tab group highlight).
-// Can be compared with other ZOrderableTabContainerElements to determine paint
-// order of their associated views.
-class ZOrderableTabContainerElement {
- public:
-  ZOrderableTabContainerElement(views::View* const child,
-                                absl::optional<const TabGroupUnderline* const>
-                                    dragging_tabs_current_group_underline)
-      : child_(child),
-        z_value_(
-            CalculateZValue(child, dragging_tabs_current_group_underline)) {}
-
-  bool operator<(const ZOrderableTabContainerElement& rhs) const {
-    return z_value_ < rhs.z_value_;
-  }
-
-  views::View* view() const { return child_; }
-
- private:
-  // Determines the 'height' of |child|, which should be used to determine the
-  // paint order of TabContainer's children.  Larger z-values should be painted
-  // on top of smaller ones.
-  static float CalculateZValue(views::View* child,
-                               absl::optional<const TabGroupUnderline* const>
-                                   dragging_tabs_current_group_underline) {
-    Tab* tab = views::AsViewClass<Tab>(child);
-    TabGroupHeader* header = views::AsViewClass<TabGroupHeader>(child);
-    TabGroupUnderline* underline = views::AsViewClass<TabGroupUnderline>(child);
-    TabGroupHighlight* highlight = views::AsViewClass<TabGroupHighlight>(child);
-    DCHECK_EQ(1, !!tab + !!header + !!underline + !!highlight);
-
-    // Construct a bitfield that encodes |child|'s z-value. Higher-order bits
-    // encode more important properties - see usage below for details on each.
-    // The lowest-order |num_bits_reserved_for_tab_style_z_value| bits are
-    // reserved for the factors considered by TabStyle, e.g. selection and hover
-    // state.
-    constexpr int num_bits_reserved_for_tab_style_z_value =
-        base::bits::Log2Ceiling(static_cast<int>(TabStyle::kMaximumZValue) + 1);
-    enum ZValue {
-      kActiveTab = (1u << (num_bits_reserved_for_tab_style_z_value + 4)),
-      kDraggedHeader = (1u << (num_bits_reserved_for_tab_style_z_value + 3)),
-      kDragRelevantUnderline =
-          (1u << (num_bits_reserved_for_tab_style_z_value + 2)),
-      kDraggedTab = (1u << (num_bits_reserved_for_tab_style_z_value + 1)),
-      kGroupView = (1u << num_bits_reserved_for_tab_style_z_value)
-    };
-
-    unsigned int z_value = 0;
-
-    // The active tab is always on top.
-    if (tab && tab->IsActive())
-      z_value |= kActiveTab;
-
-    // If we're dragging a header, that is painted above non-active tabs.
-    if (header && header->dragging())
-      z_value |= kDraggedHeader;
-
-    // If we're dragging tabs into a group, or are dragging a group, the
-    // underline for that group is painted above non-active dragged tabs.
-    if (underline && dragging_tabs_current_group_underline.has_value() &&
-        underline == dragging_tabs_current_group_underline.value())
-      z_value |= kDragRelevantUnderline;
-
-    // Dragged tabs are painted above anything that isn't part of the drag.
-    if (tab && tab->dragging())
-      z_value |= kDraggedTab;
-
-    // Group headers, highlights and underlines are painted above non-active,
-    // non-dragged tabs. Note that a group highlight is only visible when the
-    // associated group is being dragged in a header drag.
-    if (header || underline || highlight)
-      z_value |= kGroupView;
-
-    // The remaining (non-active, non-dragged) tabs are painted last. They are
-    // ordered by their selected or hovered state, which is animated and thus
-    // real-valued.
-    const float tab_style_z_value = tab ? tab->tab_style()->GetZValue() : 0.0f;
-    return z_value + tab_style_z_value;
-  }
-
-  views::View* child_;
-  float z_value_;
-};  // ZOrderableTabStripElement
-
 // Helper class that manages the tab scrolling animation.
 class TabScrollingAnimation : public gfx::LinearAnimation,
                               public gfx::AnimationDelegate {
@@ -273,9 +142,7 @@
 // done.
 class TabContainer::RemoveTabDelegate : public TabSlotAnimationDelegate {
  public:
-  RemoveTabDelegate(TabContainer* tab_container,
-                    Tab* tab,
-                    OnAnimationProgressedCallback on_animation_progressed);
+  RemoveTabDelegate(TabContainer* tab_container, Tab* tab);
   RemoveTabDelegate(const RemoveTabDelegate&) = delete;
   RemoveTabDelegate& operator=(const RemoveTabDelegate&) = delete;
 
@@ -283,11 +150,9 @@
   void AnimationCanceled(const gfx::Animation* animation) override;
 };
 
-TabContainer::RemoveTabDelegate::RemoveTabDelegate(
-    TabContainer* tab_container,
-    Tab* tab,
-    OnAnimationProgressedCallback on_animation_progressed)
-    : TabSlotAnimationDelegate(tab_container, tab, on_animation_progressed) {}
+TabContainer::RemoveTabDelegate::RemoveTabDelegate(TabContainer* tab_container,
+                                                   Tab* tab)
+    : TabSlotAnimationDelegate(tab_container, tab) {}
 
 void TabContainer::RemoveTabDelegate::AnimationEnded(
     const gfx::Animation* animation) {
@@ -301,7 +166,7 @@
 
 TabContainer::TabContainer(TabStripController* controller,
                            TabHoverCardController* hover_card_controller,
-                           TabDragContext* drag_context,
+                           TabDragContextBase* drag_context,
                            TabSlotController* tab_slot_controller,
                            views::View* scroll_contents_view)
     : controller_(controller),
@@ -412,6 +277,22 @@
   }
 }
 
+void TabContainer::StoppedDraggingView(TabSlotView* view) {
+  AddChildView(view);
+  Tab* tab = views::AsViewClass<Tab>(view);
+  if (tab && tab->closing()) {
+    // This tab was closed during the drag. It's already been removed from our
+    // other data structures in RemoveTab(), and TabDragContext animated it
+    // closed for us, so we can just destroy it.
+    OnTabCloseAnimationCompleted(tab);
+    return;
+  }
+  OrderTabSlotView(view);
+
+  if (view->group())
+    UpdateTabGroupVisuals(view->group().value());
+}
+
 void TabContainer::ScrollTabToVisible(int model_index) {
   views::ScrollView* scroll_container =
       views::ScrollView::GetScrollViewForContents(scroll_contents_view_);
@@ -468,8 +349,8 @@
 }
 
 void TabContainer::OnGroupCreated(const tab_groups::TabGroupId& group) {
-  auto group_view =
-      std::make_unique<TabGroupViews>(this, tab_slot_controller_, group);
+  auto group_view = std::make_unique<TabGroupViews>(
+      this, drag_context_, tab_slot_controller_, group);
   layout_helper()->InsertGroupHeader(group, group_view->header());
   group_views()[group] = std::move(group_view);
 }
@@ -527,7 +408,7 @@
   // to change at the same time as the tabstrip is starting to animate; the
   // hover card should not be visible at this time.
   // See crbug.com/1220840 for an example case.
-  if (bounds_animator_.IsAnimating()) {
+  if (IsAnimating()) {
     tab = nullptr;
     update_type = TabSlotController::HoverCardUpdateType::kAnimating;
   }
@@ -589,7 +470,6 @@
 }
 
 void TabContainer::StartBasicAnimation() {
-  PrepareForAnimation();
   UpdateIdealBounds();
   AnimateToIdealBounds();
 }
@@ -598,7 +478,13 @@
   last_layout_size_ = gfx::Size();
 }
 
+bool TabContainer::IsAnimating() const {
+  return bounds_animator_.IsAnimating() ||
+         (drag_context_ && drag_context_->IsEndingDrag());
+}
+
 void TabContainer::StopAnimating(bool layout) {
+  drag_context_->FinishEndingDrag();
   if (!bounds_animator_.IsAnimating())
     return;
 
@@ -630,18 +516,6 @@
              : parent()->GetAvailableSize(this).width().value();
 }
 
-void TabContainer::StartResetDragAnimation(int tab_model_index) {
-  Tab* tab = GetTabAtModelIndex(tab_model_index);
-  // Install a delegate to reset the dragging state when done. We have to leave
-  // dragging true for the tab otherwise it'll draw beneath the new tab button.
-  bounds_animator().AnimateViewTo(
-      tab, tabs_view_model_.ideal_bounds(tab_model_index),
-      std::make_unique<ResetDraggingStateDelegate>(
-          this, GetTabAtModelIndex(tab_model_index),
-          base::BindRepeating(&TabContainer::OnTabSlotAnimationProgressed,
-                              base::Unretained(this))));
-}
-
 void TabContainer::EnterTabClosingMode(absl::optional<int> override_width,
                                        CloseTabSource source) {
   in_tab_close_ = true;
@@ -669,7 +543,10 @@
       TabGroupViews* group_view =
           group_views().at(last_tab_group.value()).get();
       group_view->header()->SetVisible(last_tab_visible);
-      group_view->underline()->SetVisible(last_tab_visible);
+      // Hide underlines if they would underline an invisible tab, but don't
+      // show underlines if they're hidden during a header drag session.
+      if (!group_view->header()->dragging())
+        group_view->underline()->SetVisible(last_tab_visible);
     }
     last_tab_visible = ShouldTabBeVisible(tab);
     last_tab_group = tab->closing() ? absl::nullopt : current_group;
@@ -700,7 +577,7 @@
     SetTabSlotVisibility();
   }
 
-  if (bounds_animator_.IsAnimating()) {
+  if (IsAnimating()) {
     // Hide tabs that have animated at least partially out of the clip region.
     SetTabSlotVisibility();
     return;
@@ -716,33 +593,11 @@
 }
 
 void TabContainer::PaintChildren(const views::PaintInfo& paint_info) {
-  // Groups that are being dragged by their header, or that contain the dragged
-  // tabs, need an adjusted z-value. Find that group, if it exists.
-  absl::optional<tab_groups::TabGroupId> dragging_tabs_current_group =
-      absl::nullopt;
-
-  for (const Tab* tab : layout_helper()->GetTabs()) {
-    if (tab->dragging()) {
-      dragging_tabs_current_group = tab->group();
-      break;
-    }
-  }
-
-  absl::optional<const TabGroupUnderline*>
-      dragging_tabs_current_group_underline =
-          dragging_tabs_current_group.has_value()
-              ? absl::optional<const TabGroupUnderline*>(
-                    group_views_[dragging_tabs_current_group.value()]
-                        ->underline())
-              : absl::nullopt;
-
   std::vector<ZOrderableTabContainerElement> orderable_children;
   for (views::View* child : children())
-    orderable_children.emplace_back(child,
-                                    dragging_tabs_current_group_underline);
+    orderable_children.emplace_back(child);
 
-  // Sort in non-descending order. Stable sort breaks z-value ties by index (for
-  // tabs).
+  // Sort in ascending order by z-value. Stable sort breaks ties by child index.
   std::stable_sort(orderable_children.begin(), orderable_children.end());
 
   for (const ZOrderableTabContainerElement& child : orderable_children)
@@ -761,13 +616,19 @@
   // that NTB can be laid out just to the right of the rightmost tab. When the
   // tabs aren't at their ideal bounds (i.e. during animation or a drag), we
   // need to size ourselves to exactly fit wherever the tabs *currently* are.
-  if (bounds_animator_.IsAnimating() || IsDragSessionActive()) {
+  if (IsAnimating() || IsDragSessionActive()) {
     // The visual order of the tabs can be out of sync with the logical order,
     // so we have to check all of them to find the visually trailing-most one.
     int max_x = 0;
-    for (auto* child : children()) {
+    for (views::View* child : children())
       max_x = std::max(max_x, child->bounds().right());
-    }
+
+    // We also need to check each tab, in case the rightmost tab is currently
+    // being dragged. Group headers don't need such treatment, since any drag
+    // session including such a header must also include a tab to its right.
+    for (Tab* tab : layout_helper_->GetTabs())
+      max_x = std::max(max_x, tab->bounds().right());
+
     // The tabs span from 0 to |max_x|, so |max_x| is the current width
     // occupied by tabs. We report the current width as our preferred width so
     // that the tab strip is sized to exactly fit the current position of the
@@ -973,14 +834,6 @@
   arrow_window_ = nullptr;
 }
 
-void TabContainer::PrepareForAnimation() {
-  if (!IsDragSessionActive() &&
-      !TabDragController::IsAttachedTo(drag_context_)) {
-    for (int i = 0; i < GetTabCount(); ++i)
-      GetTabAtModelIndex(i)->set_dragging(false);
-  }
-}
-
 void TabContainer::UpdateIdealBounds() {
   if (GetTabCount() == 0)
     return;  // Should only happen during creation/destruction, ignore.
@@ -998,51 +851,18 @@
   UpdateHoverCard(nullptr, TabSlotController::HoverCardUpdateType::kAnimating);
 
   for (int i = 0; i < GetTabCount(); ++i) {
-    // If the tab is being dragged manually, skip it.
     Tab* tab = GetTabAtModelIndex(i);
-    if (tab->dragging() && !bounds_animator().IsAnimating(tab))
-      continue;
-
-    // Also skip tabs already being animated to the same ideal bounds.  Calling
-    // AnimateViewTo() again restarts the animation, which changes the timing of
-    // how the tab animates, leading to hitches.
     const gfx::Rect& target_bounds = tabs_view_model_.ideal_bounds(i);
-    if (bounds_animator().GetTargetBounds(tab) == target_bounds)
-      continue;
 
-    // Set an animation delegate for the tab so it will clip appropriately.
-    // Don't do this if dragging() is true.  In this case the tab was
-    // previously being dragged and is now animating back to its ideal
-    // bounds; it already has an associated ResetDraggingStateDelegate that
-    // will reset this dragging state. Replacing this delegate would mean
-    // this code would also need to reset the dragging state immediately,
-    // and that could allow the new tab button to be drawn atop this tab.
-    if (bounds_animator().IsAnimating(tab) && tab->dragging()) {
-      bounds_animator().SetTargetBounds(tab, target_bounds);
-    } else {
-      bounds_animator().AnimateViewTo(
-          tab, target_bounds,
-          std::make_unique<TabSlotAnimationDelegate>(
-              this, tab,
-              base::BindRepeating(&TabContainer::OnTabSlotAnimationProgressed,
-                                  base::Unretained(this))));
-    }
+    AnimateTabSlotViewTo(tab, target_bounds);
   }
 
   for (const auto& header_pair : group_views()) {
     TabGroupHeader* const header = header_pair.second->header();
+    const gfx::Rect& target_bounds =
+        layout_helper()->group_header_ideal_bounds().at(header_pair.first);
 
-    // If the header is being dragged manually, skip it.
-    if (header->dragging() && !bounds_animator().IsAnimating(header))
-      continue;
-
-    bounds_animator().AnimateViewTo(
-        header,
-        layout_helper()->group_header_ideal_bounds().at(header_pair.first),
-        std::make_unique<TabSlotAnimationDelegate>(
-            this, header,
-            base::BindRepeating(&TabContainer::OnTabSlotAnimationProgressed,
-                                base::Unretained(this))));
+    AnimateTabSlotViewTo(header, target_bounds);
   }
 
   // Because the preferred size of the tabstrip depends on the IsAnimating()
@@ -1052,6 +872,26 @@
   PreferredSizeChanged();
 }
 
+void TabContainer::AnimateTabSlotViewTo(TabSlotView* tab_slot_view,
+                                        const gfx::Rect& target_bounds) {
+  // If the slot is being dragged, we don't own it - let TabDragContext decide
+  // what to do.
+  if (tab_slot_view->dragging()) {
+    drag_context_->UpdateAnimationTarget(tab_slot_view, target_bounds);
+    return;
+  }
+
+  // Also skip slots already being animated to the same ideal bounds.  Calling
+  // AnimateViewTo() again restarts the animation, which changes the timing of
+  // how the slot animates, leading to hitches.
+  if (bounds_animator().GetTargetBounds(tab_slot_view) == target_bounds)
+    return;
+
+  bounds_animator().AnimateViewTo(
+      tab_slot_view, target_bounds,
+      std::make_unique<TabSlotAnimationDelegate>(this, tab_slot_view));
+}
+
 void TabContainer::SnapToIdealBounds() {
   for (int i = 0; i < GetTabCount(); ++i)
     GetTabAtModelIndex(i)->SetBoundsRect(tabs_view_model_.ideal_bounds(i));
@@ -1111,7 +951,26 @@
 
   StartBasicAnimation();
 
-  const int tab_overlap = TabStyle::GetTabOverlap();
+  gfx::Rect target_bounds =
+      GetTargetBoundsForClosingTab(tab, former_model_index);
+
+  // If the tab is being dragged, we don't own it, and can't run animations on
+  // it. We need to take it back first.
+  if (tab->dragging()) {
+    // Don't bother animating if the tab has been detached rather than closed -
+    // i.e. it's being moved to another tabstrip. At this point it's safe to
+    // just destroy the tab immediately.
+    if (tab->detached()) {
+      OnTabCloseAnimationCompleted(tab);
+      return;
+    }
+
+    DCHECK(drag_context_->IsEndingDrag());
+    // Notify |drag_context_| of the new animation target, since we can't
+    // animate |tab| ourselves.
+    drag_context_->UpdateAnimationTarget(tab, target_bounds);
+    return;
+  }
 
   // TODO(pkasting): When closing multiple tabs, we get repeated RemoveTabAt()
   // calls, each of which closes a new tab and thus generates different ideal
@@ -1119,6 +978,15 @@
   // currently being closed to reflect the new ideal bounds, or else change from
   // removing one tab at a time to animating the removal of all tabs at once.
 
+  bounds_animator().AnimateViewTo(
+      tab, target_bounds, std::make_unique<RemoveTabDelegate>(this, tab));
+}
+
+gfx::Rect TabContainer::GetTargetBoundsForClosingTab(
+    Tab* tab,
+    int former_model_index) const {
+  const int tab_overlap = TabStyle::GetTabOverlap();
+
   // Compute the target bounds for animating this tab closed.  The tab's left
   // edge should stay joined to the right edge of the previous tab, if any.
   gfx::Rect target_bounds = tab->bounds();
@@ -1132,12 +1000,8 @@
   // same speed the surrounding tabs are moving, since at this width the
   // subsequent tab is naturally positioned at the same X coordinate.
   target_bounds.set_width(tab_overlap);
-  bounds_animator().AnimateViewTo(
-      tab, target_bounds,
-      std::make_unique<RemoveTabDelegate>(
-          this, tab,
-          base::BindRepeating(&TabContainer::OnTabSlotAnimationProgressed,
-                              base::Unretained(this))));
+
+  return target_bounds;
 }
 
 void TabContainer::RemoveTabFromViewModel(int index) {
@@ -1273,6 +1137,9 @@
 }
 
 void TabContainer::OrderTabSlotView(TabSlotView* slot_view) {
+  if (slot_view->parent() != this)
+    return;
+
   // |slot_view| is in the wrong place in children(). Fix it.
   std::vector<TabSlotView*> slots = layout_helper_->GetTabSlotViews();
   size_t target_slot_index =
@@ -1295,6 +1162,9 @@
                                 const gfx::Point& point_in_tabstrip_coords) {
   if (!tab->GetVisible())
     return false;
+  if (tab->parent() != this)
+    return false;
+
   gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
   View::ConvertPointToTarget(this, tab, &point_in_tab_coords);
   return tab->HitTestPoint(point_in_tab_coords);
@@ -1383,7 +1253,7 @@
   // We need to check what would happen if the active tab were to move to this
   // tab or before. If animating, we want to use the target bounds in this
   // calculation.
-  if (bounds_animator_.IsAnimating())
+  if (IsAnimating())
     right_edge = bounds_animator_.GetTargetBounds(tab).right();
   return (right_edge + layout_helper_->active_tab_width() -
           layout_helper_->inactive_tab_width()) <= tabstrip_right;
diff --git a/chrome/browser/ui/views/tabs/tab_container.h b/chrome/browser/ui/views/tabs/tab_container.h
index ee8d202..62a80ae 100644
--- a/chrome/browser/ui/views/tabs/tab_container.h
+++ b/chrome/browser/ui/views/tabs/tab_container.h
@@ -27,7 +27,7 @@
 
 class TabStrip;
 class TabHoverCardController;
-class TabDragContext;
+class TabDragContextBase;
 
 // A View that contains a sequence of Tabs for the TabStrip.
 class TabContainer : public views::View,
@@ -40,7 +40,7 @@
 
   TabContainer(TabStripController* controller,
                TabHoverCardController* hover_card_controller,
-               TabDragContext* drag_context,
+               TabDragContextBase* drag_context,
                TabSlotController* tab_slot_controller,
                views::View* scroll_contents_view);
   ~TabContainer() override;
@@ -53,6 +53,8 @@
   void RemoveTab(int index, bool was_active);
   void SetTabPinned(int model_index, TabPinned pinned);
 
+  void StoppedDraggingView(TabSlotView* view);
+
   void ScrollTabToVisible(int model_index);
 
   void OnGroupCreated(const tab_groups::TabGroupId& group);
@@ -94,6 +96,10 @@
   // (cough TabDragController cough) moves tabs directly.
   void InvalidateIdealBounds();
 
+  // Returns true if any tabs are being animated, whether by |this| or by
+  // |drag_context_|.
+  bool IsAnimating() const;
+
   // Stops any ongoing animations. If |layout| is true and an animation is
   // ongoing this does a layout.
   void StopAnimating(bool layout);
@@ -104,8 +110,6 @@
   // Returns the total width available for the TabContainer's use.
   int GetAvailableWidthForTabContainer() const;
 
-  void StartResetDragAnimation(int tab_model_index);
-
   // See |in_tab_close_| for details on tab closing mode. |source| is the input
   // method used to enter tab closing mode, which determines how it is exited
   // due to user inactivity.
@@ -200,9 +204,6 @@
 
   class RemoveTabDelegate;
 
-  // Invoked prior to starting a new animation.
-  void PrepareForAnimation();
-
   // Generates and sets the ideal bounds for each of the tabs as well as the new
   // tab button. Note: Does not animate the tabs to those bounds so callers can
   // use this information for other purposes - see AnimateToIdealBounds.
@@ -213,6 +214,10 @@
   // currently set in ideal_bounds.
   void AnimateToIdealBounds();
 
+  // Animates |tab_slot_view| to |target_bounds|
+  void AnimateTabSlotViewTo(TabSlotView* tab_slot_view,
+                            const gfx::Rect& target_bounds);
+
   // Teleports the tabs to their ideal bounds.
   // NOTE: this does *not* invoke UpdateIdealBounds, it uses the bounds
   // currently set in ideal_bounds.
@@ -228,6 +233,10 @@
 
   void StartRemoveTabAnimation(Tab* tab, int former_model_index);
 
+  // Computes the bounds that `tab` should animate towards as it closes.
+  gfx::Rect GetTargetBoundsForClosingTab(Tab* tab,
+                                         int former_model_index) const;
+
   // Remove the tab from |tabs_view_model_|, but *not* from the View hierarchy,
   // so it can be animated closed.
   void RemoveTabFromViewModel(int index);
@@ -314,7 +323,7 @@
   raw_ptr<TabHoverCardController> hover_card_controller_;
 
   // May be nullptr in tests.
-  raw_ptr<TabDragContext> drag_context_;
+  raw_ptr<TabDragContextBase> drag_context_;
 
   raw_ptr<TabSlotController> tab_slot_controller_;
 
diff --git a/chrome/browser/ui/views/tabs/tab_container_unittest.cc b/chrome/browser/ui/views/tabs/tab_container_unittest.cc
index 72da5b7..7a23aac 100644
--- a/chrome/browser/ui/views/tabs/tab_container_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_container_unittest.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h"
 #include "chrome/browser/ui/views/tabs/fake_tab_slot_controller.h"
+#include "chrome/browser/ui/views/tabs/tab_drag_context.h"
 #include "chrome/browser/ui/views/tabs/tab_group_header.h"
 #include "chrome/browser/ui/views/tabs/tab_group_views.h"
 #include "chrome/browser/ui/views/tabs/tab_style_views.h"
@@ -30,6 +31,19 @@
   }
   return current;
 }
+
+class FakeTabDragContext : public TabDragContextBase {
+ public:
+  FakeTabDragContext() = default;
+  ~FakeTabDragContext() override = default;
+
+  void UpdateAnimationTarget(TabSlotView* tab_slot_view,
+                             const gfx::Rect& target_bounds) override {}
+  bool IsDragSessionActive() const override { return false; }
+  bool IsEndingDrag() const override { return false; }
+  void FinishEndingDrag() override {}
+  int GetTabDragAreaWidth() const override { return width(); }
+};
 }  // namespace
 
 class TabContainerTest : public ChromeViewsTestBase {
@@ -46,23 +60,29 @@
     tab_slot_controller_ =
         std::make_unique<FakeTabSlotController>(tab_strip_controller_.get());
 
+    std::unique_ptr<TabDragContextBase> drag_context =
+        std::make_unique<FakeTabDragContext>();
     std::unique_ptr<TabContainer> tab_container =
         std::make_unique<TabContainer>(
             tab_strip_controller_.get(), nullptr /*hover_card_controller*/,
-            nullptr /*drag_context*/, tab_slot_controller_.get(),
+            drag_context.get(), tab_slot_controller_.get(),
             nullptr /*scroll_contents_view*/);
     tab_container->SetAvailableWidthCallback(base::BindRepeating(
         [](TabContainerTest* test) { return test->tab_container_width_; },
         this));
 
     widget_ = CreateTestWidget();
+    tab_container_ =
+        widget_->GetRootView()->AddChildView(std::move(tab_container));
+    drag_context_ =
+        widget_->GetRootView()->AddChildView(std::move(drag_context));
     SetTabContainerWidth(1000);
-    tab_container_ = widget_->SetContentsView(std::move(tab_container));
 
     tab_slot_controller_->set_tab_container(tab_container_);
   }
 
   void TearDown() override {
+    drag_context_ = nullptr;
     tab_container_ = nullptr;
     widget_.reset();
     tab_slot_controller_.reset();
@@ -217,12 +237,15 @@
 
   void SetTabContainerWidth(int width) {
     tab_container_width_ = width;
-    widget_->SetSize(
-        gfx::Size(tab_container_width_, GetLayoutConstant(TAB_HEIGHT)));
+    gfx::Size size(tab_container_width_, GetLayoutConstant(TAB_HEIGHT));
+    widget_->SetSize(size);
+    drag_context_->SetSize(size);
+    tab_container_->SetSize(size);
   }
 
   std::unique_ptr<FakeBaseTabStripController> tab_strip_controller_;
   std::unique_ptr<FakeTabSlotController> tab_slot_controller_;
+  raw_ptr<TabDragContextBase> drag_context_;
   raw_ptr<TabContainer> tab_container_;
   std::unique_ptr<views::Widget> widget_;
 
diff --git a/chrome/browser/ui/views/tabs/tab_drag_context.h b/chrome/browser/ui/views/tabs/tab_drag_context.h
index 48a0098..6312573 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_context.h
+++ b/chrome/browser/ui/views/tabs/tab_drag_context.h
@@ -12,6 +12,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/models/list_selection_model.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/views/view.h"
 
 class Tab;
 class TabGroupHeader;
@@ -20,18 +21,40 @@
 class TabStripModel;
 class TabDragController;
 
-namespace views {
-class View;
+namespace tab_groups {
+class TabGroupId;
 }
 
+// A limited subset of TabDragContext for use by non-TabDragController clients.
+class TabDragContextBase : public views::View {
+ public:
+  ~TabDragContextBase() override = default;
+
+  // Called when the TabStrip is changed during a drag session.
+  virtual void UpdateAnimationTarget(TabSlotView* tab_slot_view,
+                                     const gfx::Rect& target_bounds) = 0;
+
+  // Returns true if a drag session is currently active.
+  virtual bool IsDragSessionActive() const = 0;
+
+  // Returns true if this DragContext is in the process of returning tabs to the
+  // associated TabContainer.
+  virtual bool IsEndingDrag() const = 0;
+
+  // Immediately completes any ongoing end drag animations, returning the tabs
+  // to the associated TabContainer immediately.
+  virtual void FinishEndingDrag() = 0;
+
+  // Returns the width of the region in which dragged tabs are allowed to exist.
+  virtual int GetTabDragAreaWidth() const = 0;
+};
+
 // Provides tabstrip functionality specifically for TabDragController, much of
 // which should not otherwise be in TabStrip's public interface.
-class TabDragContext {
+class TabDragContext : public TabDragContextBase {
  public:
-  virtual ~TabDragContext() = default;
+  ~TabDragContext() override = default;
 
-  virtual views::View* AsView() = 0;
-  virtual const views::View* AsView() const = 0;
   virtual Tab* GetTabAt(int index) const = 0;
   virtual int GetIndexOf(const TabSlotView* view) const = 0;
   virtual int GetTabCount() const = 0;
@@ -62,21 +85,12 @@
   // operation.
   virtual void DestroyDragController() = 0;
 
-  // Returns true if a drag session is currently active.
-  virtual bool IsDragSessionActive() const = 0;
-
   // Returns true if a tab is being dragged into this tab strip.
   virtual bool IsActiveDropTarget() const = 0;
 
-  // Returns the x-coordinates of the tabs.
-  virtual std::vector<int> GetTabXCoordinates() const = 0;
-
   // Returns the width of the active tab.
   virtual int GetActiveTabWidth() const = 0;
 
-  // Returns the width of the region in which dragged tabs are allowed to exist.
-  virtual int GetTabDragAreaWidth() const = 0;
-
   // Returns where the drag region begins and ends; tabs dragged beyond these
   // points should detach.
   virtual int TabDragAreaEndX() const = 0;
@@ -120,9 +134,7 @@
   // Used by TabDragController when the user stops dragging. |completed| is
   // true if the drag operation completed successfully, false if it was
   // reverted.
-  virtual void StoppedDragging(const std::vector<TabSlotView*>& views,
-                               const std::vector<int>& initial_positions,
-                               bool completed) = 0;
+  virtual void StoppedDragging(const std::vector<TabSlotView*>& views) = 0;
 
   // Invoked during drag to layout the views being dragged in |views| at
   // |location|. If |initial_drag| is true, this is the initial layout after the
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
index 947ec0f..440e582 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
@@ -116,7 +116,7 @@
 
 // Returns the aura::Window which stores the window properties for tab-dragging.
 aura::Window* GetWindowForTabDraggingProperties(const TabDragContext* context) {
-  return context ? context->AsView()->GetWidget()->GetNativeWindow() : nullptr;
+  return context ? context->GetWidget()->GetNativeWindow() : nullptr;
 }
 
 // Returns true if |context| browser window is snapped.
@@ -202,11 +202,11 @@
 #endif  // #if BUILDFLAG(IS_CHROMEOS_ASH)
 
 void SetCapture(TabDragContext* context) {
-  context->AsView()->GetWidget()->SetCapture(context->AsView());
+  context->GetWidget()->SetCapture(context);
 }
 
 gfx::Rect GetTabstripScreenBounds(const TabDragContext* context) {
-  const views::View* view = context->AsView();
+  const views::View* view = context;
   gfx::Point view_topleft;
   views::View::ConvertPointToScreen(view, &view_topleft);
   gfx::Rect view_screen_bounds = view->GetLocalBounds();
@@ -455,7 +455,7 @@
   if (event_source_ == EVENT_SOURCE_TOUCH) {
     TabDragContext* capture_context =
         attached_context_ ? attached_context_.get() : source_context_.get();
-    capture_context->AsView()->GetWidget()->ReleaseCapture();
+    capture_context->GetWidget()->ReleaseCapture();
   }
   CHECK(!IsInObserverList());
 }
@@ -470,9 +470,8 @@
   DCHECK(!dragging_views.empty());
   DCHECK(base::Contains(dragging_views, source_view));
   source_context_ = source_context;
-  was_source_maximized_ = source_context->AsView()->GetWidget()->IsMaximized();
-  was_source_fullscreen_ =
-      source_context->AsView()->GetWidget()->IsFullscreen();
+  was_source_maximized_ = source_context->GetWidget()->IsMaximized();
+  was_source_fullscreen_ = source_context->GetWidget()->IsFullscreen();
   // Do not release capture when transferring capture between widgets on:
   // - Desktop Linux
   //     Mouse capture is not synchronous on desktop Linux. Chrome makes
@@ -489,7 +488,6 @@
   mouse_offset_ = mouse_offset;
   last_point_in_screen_ = start_point_in_screen_;
   last_move_screen_loc_ = start_point_in_screen_.x();
-  initial_tab_positions_ = source_context->GetTabXCoordinates();
 
   source_context_emptiness_tracker_ =
       std::make_unique<SourceTabStripEmptinessTracker>(
@@ -513,7 +511,7 @@
                      END_DRAG_COMPLETE),
       base::BindOnce(&TabDragController::EndDrag, base::Unretained(this),
                      END_DRAG_CANCEL),
-      source_context_->AsView()->GetWidget()->GetNativeWindow());
+      source_context_->GetWidget()->GetNativeWindow());
 
   if (source_view->width() > 0) {
     offset_to_width_ratio_ = static_cast<float>(source_view->GetMirroredXInView(
@@ -538,7 +536,7 @@
 }
 
 // static
-bool TabDragController::IsAttachedTo(const TabDragContext* context) {
+bool TabDragController::IsAttachedTo(const TabDragContextBase* context) {
   return (g_tab_drag_controller && g_tab_drag_controller->active() &&
           g_tab_drag_controller->attached_context() == context);
 }
@@ -858,8 +856,8 @@
 void TabDragController::SaveFocus() {
   DCHECK(source_context_);
   old_focused_view_tracker_->SetView(
-      source_context_->AsView()->GetFocusManager()->GetFocusedView());
-  source_context_->AsView()->GetFocusManager()->ClearFocus();
+      source_context_->GetWidget()->GetFocusManager()->GetFocusedView());
+  source_context_->GetWidget()->GetFocusManager()->ClearFocus();
   // WARNING: we may have been deleted.
 }
 
@@ -974,7 +972,7 @@
     if (tab_strip_changed) {
       // Move the corresponding window to the front. We do this after the
       // move as on windows activate triggers a synchronous paint.
-      attached_context_->AsView()->GetWidget()->Activate();
+      attached_context_->GetWidget()->Activate();
     }
   }
   return Liveness::ALIVE;
@@ -996,8 +994,7 @@
   gfx::NativeView attached_native_view =
       GetAttachedBrowserWidget()->GetNativeView();
   GetAttachedBrowserWidget()->GetGestureRecognizer()->TransferEventsTo(
-      attached_native_view,
-      target_context->AsView()->GetWidget()->GetNativeView(),
+      attached_native_view, target_context->GetWidget()->GetNativeView(),
       ui::TransferTouchesBehavior::kDontCancel);
 #endif
 
@@ -1047,7 +1044,7 @@
       current_state_ = DragState::kDraggingTabs;
       // Move the tabs into position.
       MoveAttached(point_in_screen, true);
-      attached_context_->AsView()->GetWidget()->Activate();
+      attached_context_->GetWidget()->Activate();
     }
 
     return DRAG_BROWSER_RESULT_STOP;
@@ -1105,7 +1102,7 @@
 
   base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());
   GetAttachedBrowserWidget()->RunShellDrag(
-      attached_context_->AsView(),
+      attached_context_,
       std::make_unique<ui::OSExchangeData>(std::move(data_provider)),
       point_in_screen, static_cast<int>(ui::mojom::DragOperation::kMove),
       ui::mojom::DragEventSource::kMouse);
@@ -1159,10 +1156,9 @@
     // larger index).
     if (attach_index_ != -1) {
       gfx::Point tab_strip_point(point_in_screen);
-      views::View::ConvertPointFromScreen(attached_context_->AsView(),
-                                          &tab_strip_point);
+      views::View::ConvertPointFromScreen(attached_context_, &tab_strip_point);
       const int new_x =
-          attached_context_->AsView()->GetMirroredXInView(tab_strip_point.x());
+          attached_context_->GetMirroredXInView(tab_strip_point.x());
       if (new_x < attach_x_)
         to_index = std::min(to_index, attach_index_);
       else
@@ -1217,8 +1213,7 @@
     const gfx::Point& point_in_screen) {
   DCHECK(attached_context_);
   gfx::Point attached_point(point_in_screen);
-  views::View::ConvertPointFromScreen(attached_context_->AsView(),
-                                      &attached_point);
+  views::View::ConvertPointFromScreen(attached_context_, &attached_point);
   if (attached_point.x() < attached_context_->TabDragAreaBeginX())
     return DETACH_BEFORE;
   if (attached_point.x() >= attached_context_->TabDragAreaEndX())
@@ -1335,10 +1330,9 @@
     attach_index_ = index;
 
     gfx::Point tab_strip_point(point_in_screen);
-    views::View::ConvertPointFromScreen(attached_context_->AsView(),
-                                        &tab_strip_point);
+    views::View::ConvertPointFromScreen(attached_context_, &tab_strip_point);
     tab_strip_point.set_x(
-        attached_context_->AsView()->GetMirroredXInView(tab_strip_point.x()));
+        attached_context_->GetMirroredXInView(tab_strip_point.x()));
     tab_strip_point.Offset(0, -mouse_offset_.y());
     attach_x_ = tab_strip_point.x();
 
@@ -1423,16 +1417,15 @@
   DCHECK_EQ(me.get(), this);
 
   if (release_capture == RELEASE_CAPTURE)
-    attached_context_->AsView()->GetWidget()->ReleaseCapture();
+    attached_context_->GetWidget()->ReleaseCapture();
 
   TabStripModel* attached_model = attached_context_->GetTabStripModel();
 
   for (size_t i = first_tab_index(); i < drag_data_.size(); ++i) {
     int index = attached_model->GetIndexOfWebContents(drag_data_[i].contents);
     DCHECK_NE(-1, index);
-
-    // Hide the tab so that the user doesn't see it animate closed.
-    drag_data_[i].attached_view->SetVisible(false);
+    // Move the tab out of `attached_model`. Marking the view as detached tells
+    // the TabStrip to not animate its closure, as it's actually being moved.
     drag_data_[i].attached_view->set_detached();
     drag_data_[i].owned_contents =
         attached_model->DetachWebContentsAtForInsertion(index);
@@ -1490,7 +1483,7 @@
 
 #if defined(USE_AURA)
   // Only Aura windows are gesture consumers.
-  views::Widget* attached_widget = attached_context_->AsView()->GetWidget();
+  views::Widget* attached_widget = attached_context_->GetWidget();
   // Unlike DragBrowserToNewTabStrip, this does not have to special-handle
   // IsUsingWindowServices(), since DesktopWIndowTreeHostMus takes care of it.
   attached_widget->GetGestureRecognizer()->TransferEventsTo(
@@ -1549,7 +1542,7 @@
     std::unique_ptr<TabDragController> me =
         attached_context_->ReleaseDragController();
     DCHECK_EQ(me.get(), this);
-    attached_context_->AsView()->GetWidget()->ReleaseCapture();
+    attached_context_->GetWidget()->ReleaseCapture();
     attached_context_->OwnDragController(std::move(me));
   }
   const views::Widget::MoveLoopSource move_loop_source =
@@ -1594,7 +1587,7 @@
     current_state_ = DragState::kDraggingTabs;
     // Move the tabs into position.
     MoveAttached(point_in_screen, true);
-    attached_context_->AsView()->GetWidget()->Activate();
+    attached_context_->GetWidget()->Activate();
     // Activate may trigger a focus loss, destroying us.
     if (!ref)
       return;
@@ -1627,9 +1620,9 @@
   DCHECK(attached_context_);  // The tab must be attached.
 
   gfx::Point tab_loc(point_in_screen);
-  views::View::ConvertPointFromScreen(attached_context_->AsView(), &tab_loc);
-  const int x = attached_context_->AsView()->GetMirroredXInView(tab_loc.x()) -
-                mouse_offset_.x();
+  views::View::ConvertPointFromScreen(attached_context_, &tab_loc);
+  const int x =
+      attached_context_->GetMirroredXInView(tab_loc.x()) - mouse_offset_.x();
 
   // If the width needed for the `attached_views_` is greater than what is
   // available in the tab drag area the attached drag point should simply be the
@@ -1679,7 +1672,7 @@
       //   window activation cancels minimized status. See
       //   https://crbug.com/902897
       if (!IsShowingInOverview(attached_context_) &&
-          !attached_context_->AsView()->GetWidget()->IsMinimized()) {
+          !attached_context_->GetWidget()->IsMinimized()) {
         RestoreFocus();
       }
 
@@ -1718,7 +1711,7 @@
 void TabDragController::AttachTabsToNewBrowserOnDrop() {
   DCHECK(!attached_context_hidden_);
 
-  views::Widget* widget = attached_context_->AsView()->GetWidget();
+  views::Widget* widget = attached_context_->GetWidget();
   gfx::Rect window_bounds(widget->GetRestoredBounds());
   window_bounds.set_origin(GetWindowCreatePoint(last_point_in_screen_));
 
@@ -1807,7 +1800,7 @@
     if (did_restore_window_)
       MaximizeAttachedWindow();
     if (attached_context_ == source_context_) {
-      source_context_->StoppedDragging(views, initial_tab_positions_, false);
+      source_context_->StoppedDragging(views);
       if (header_drag_)
         source_context_->GetTabStripModel()->MoveTabGroup(group_.value());
     } else {
@@ -1832,7 +1825,7 @@
         initial_selection_model_);
 
   if (source_context_)
-    source_context_->AsView()->GetWidget()->Activate();
+    source_context_->GetWidget()->Activate();
 }
 
 void TabDragController::ResetSelection(TabStripModel* model) {
@@ -1910,7 +1903,9 @@
             &is_removing_last_tab_for_revert_, true);
       }
       // The Tab was inserted into another TabDragContext. We need to
-      // put it back into the original one.
+      // put it back into the original one. Marking the view as detached tells
+      // the TabStrip to not animate its closure, as it's actually being moved.
+      data->attached_view->set_detached();
       std::unique_ptr<content::WebContents> detached_web_contents =
           attached_context_->GetTabStripModel()
               ->DetachWebContentsAtForInsertion(index);
@@ -1985,12 +1980,11 @@
 #endif  // !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_MAC)
     }
     attached_context_->StoppedDragging(
-        GetViewsMatchingDraggedContents(attached_context_),
-        initial_tab_positions_, true);
+        GetViewsMatchingDraggedContents(attached_context_));
   } else {
     // Compel the model to construct a new window for the detached
     // WebContentses.
-    views::Widget* widget = source_context_->AsView()->GetWidget();
+    views::Widget* widget = source_context_->GetWidget();
     gfx::Rect window_bounds(widget->GetRestoredBounds());
     window_bounds.set_origin(GetWindowCreatePoint(last_point_in_screen_));
 
@@ -2108,12 +2102,12 @@
     // The previous call made the window appear on top of the dragged window,
     // move the dragged window to the front.
     if (current_state_ == DragState::kDraggingWindow)
-      attached_context_->AsView()->GetWidget()->StackAtTop();
+      attached_context_->GetWidget()->StackAtTop();
   }
 }
 
 views::Widget* TabDragController::GetAttachedBrowserWidget() {
-  return attached_context_->AsView()->GetWidget();
+  return attached_context_->GetWidget();
 }
 
 bool TabDragController::AreTabsConsecutive() {
@@ -2130,9 +2124,9 @@
     TabDragContext* source,
     const gfx::Point& point_in_screen,
     std::vector<gfx::Rect>* drag_bounds) {
-  gfx::Point center(0, source->AsView()->height() / 2);
-  views::View::ConvertPointToWidget(source->AsView(), &center);
-  gfx::Rect new_bounds(source->AsView()->GetWidget()->GetRestoredBounds());
+  gfx::Point center(0, source->height() / 2);
+  views::View::ConvertPointToWidget(source, &center);
+  gfx::Rect new_bounds(source->GetWidget()->GetRestoredBounds());
 
   gfx::Rect work_area = display::Screen::GetScreen()
                             ->GetDisplayNearestPoint(last_point_in_screen_)
@@ -2151,12 +2145,12 @@
       was_source_maximized_ = true;
   }
 
-  if (source->AsView()->GetWidget()->IsMaximized()) {
+  if (source->GetWidget()->IsMaximized()) {
     // If the restore bounds is really small, we don't want to honor it
     // (dragging a really small window looks wrong), instead make sure the new
     // window is at least 50% the size of the old.
     const gfx::Size max_size(
-        source->AsView()->GetWidget()->GetWindowBoundsInScreen().size());
+        source->GetWidget()->GetWindowBoundsInScreen().size());
     new_bounds.set_width(std::max(max_size.width() / 2, new_bounds.width()));
     new_bounds.set_height(std::max(max_size.height() / 2, new_bounds.height()));
   }
@@ -2164,7 +2158,7 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   if (ash::TabletMode::Get()->InTabletMode()) {
     new_bounds = GetDraggedBrowserBoundsInTabletMode(
-        source->AsView()->GetWidget()->GetNativeWindow());
+        source->GetWidget()->GetNativeWindow());
   }
 #endif
 
@@ -2175,8 +2169,8 @@
       new_bounds.Offset(-mouse_offset_.x(), 0);
       break;
     case DETACH_AFTER: {
-      gfx::Point right_edge(source->AsView()->width(), 0);
-      views::View::ConvertPointToWidget(source->AsView(), &right_edge);
+      gfx::Point right_edge(source->width(), 0);
+      views::View::ConvertPointToWidget(source, &right_edge);
       new_bounds.set_x(point_in_screen.x() - right_edge.x());
       new_bounds.Offset(drag_bounds->back().right() - mouse_offset_.x(), 0);
       OffsetX(-drag_bounds->front().x(), drag_bounds);
@@ -2188,9 +2182,9 @@
 
   // Account for the extra space above the tabstrip on restored windows versus
   // maximized windows.
-  if (source->AsView()->GetWidget()->IsMaximized()) {
+  if (source->GetWidget()->IsMaximized()) {
     const auto* frame_view = static_cast<BrowserNonClientFrameView*>(
-        source->AsView()->GetWidget()->non_client_view()->frame_view());
+        source->GetWidget()->non_client_view()->frame_view());
     new_bounds.Offset(
         0, frame_view->GetTopInset(false) - frame_view->GetTopInset(true));
   }
@@ -2256,11 +2250,11 @@
     int cursor_offset_within_tab =
         base::ClampRound(source_tab_bounds.width() * offset_to_width_ratio_);
     gfx::Point cursor_offset_in_widget(
-        attached_context_->AsView()->GetMirroredXInView(
-            source_tab_bounds.x() + cursor_offset_within_tab),
+        attached_context_->GetMirroredXInView(source_tab_bounds.x() +
+                                              cursor_offset_within_tab),
         0);
-    attached_context_->AsView()->ConvertPointToWidget(
-        attached_context_->AsView(), &cursor_offset_in_widget);
+    views::View::ConvertPointToWidget(attached_context_,
+                                      &cursor_offset_in_widget);
     gfx::Rect bounds = GetAttachedBrowserWidget()->GetWindowBoundsInScreen();
     bounds.set_x(point_in_screen.x() - cursor_offset_in_widget.x());
     GetAttachedBrowserWidget()->SetBounds(bounds);
@@ -2346,8 +2340,7 @@
     const gfx::Point& point_in_screen) {
   TabDragContext* owning_context =
       attached_context_ ? attached_context_.get() : source_context_.get();
-  views::View* toplevel_view =
-      owning_context->AsView()->GetWidget()->GetContentsView();
+  views::View* toplevel_view = owning_context->GetWidget()->GetContentsView();
 
   gfx::Point point = point_in_screen;
   views::View::ConvertPointFromScreen(toplevel_view, &point);
@@ -2361,7 +2354,7 @@
   std::set<gfx::NativeWindow> exclude;
   if (exclude_dragged_view) {
     gfx::NativeWindow dragged_window =
-        attached_context_->AsView()->GetWidget()->GetNativeWindow();
+        attached_context_->GetWidget()->GetNativeWindow();
     if (dragged_window)
       exclude.insert(dragged_window);
   }
@@ -2594,8 +2587,8 @@
 
 int TabDragController::GetOutOfBoundsYCoordinate() const {
   DCHECK(attached_context_);
-  return attached_context_->AsView()->GetBoundsInScreen().y() -
-         kVerticalDetachMagnetism - 1;
+  return attached_context_->GetBoundsInScreen().y() - kVerticalDetachMagnetism -
+         1;
 }
 
 void TabDragController::NotifyEventIfTabAddedToGroup() {
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.h b/chrome/browser/ui/views/tabs/tab_drag_controller.h
index c6291cd..b22df90 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.h
@@ -92,7 +92,7 @@
   // |tab_strip|.
   // NOTE: this returns false if the TabDragController is in the process of
   // finishing the drag.
-  static bool IsAttachedTo(const TabDragContext* tab_strip);
+  static bool IsAttachedTo(const TabDragContextBase* tab_strip);
 
   // Returns true if there is a drag underway.
   static bool IsActive();
@@ -651,10 +651,6 @@
   // The selection model of |attached_context_| before the tabs were attached.
   ui::ListSelectionModel selection_model_before_attach_;
 
-  // Initial x-coordinates of the tabs when the drag started. Only used for
-  // touch mode.
-  std::vector<int> initial_tab_positions_;
-
   // What should occur during ConinueDragging when a tab is attempted to be
   // detached.
   DetachBehavior detach_behavior_;
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index 5c374633..d4648e1 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -297,7 +297,10 @@
 
 void TabDragControllerTest::HandleGestureEvent(TabStrip* tab_strip,
                                                ui::GestureEvent* event) {
-  tab_strip->OnGestureEvent(event);
+  // Manually dispatch the event to the drag context (which has capture during
+  // drags). Mouse/keyboard/touch events can be dispatched normally via test
+  // machinery in ui_test_utils that have no direct gesture event equivalent.
+  tab_strip->GetDragContext()->OnGestureEvent(event);
 }
 
 bool TabDragControllerTest::HasDragStarted(TabStrip* tab_strip) const {
@@ -420,6 +423,8 @@
   EXPECT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
 }
 
+#endif
+
 IN_PROC_BROWSER_TEST_F(TabDragControllerTest, GestureEndShouldEndDragTest) {
   AddTabsAndResetBrowser(browser(), 1);
 
@@ -443,8 +448,6 @@
   EXPECT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
 }
 
-#endif
-
 class DetachToBrowserTabDragControllerTest
     : public TabDragControllerTest,
       public ::testing::WithParamInterface<const char*> {
@@ -1557,7 +1560,8 @@
   gfx::NativeWindow GetLocalProcessWindowAtPoint(
       const gfx::Point& screen_point,
       const std::set<gfx::NativeWindow>& ignore) override {
-    static_cast<views::View*>(tab_strip_)->OnMouseCaptureLost();
+    static_cast<views::View*>(tab_strip_->GetDragContext())
+        ->OnMouseCaptureLost();
     return nullptr;
   }
 
@@ -2113,6 +2117,76 @@
   EXPECT_FALSE(tab_strip->GetWidget()->HasCapture());
 }
 
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+                       TabDragContextOwnsDraggedTabs) {
+  AddTabsAndResetBrowser(browser(), 1);
+
+  TabStrip* tab_strip = GetTabStripForBrowser(browser());
+  TabDragContext* drag_context = tab_strip->GetDragContext();
+  Tab* dragged_tab = tab_strip->tab_at(1);
+
+  // Tabs are not in the TabDragContext when they aren't being dragged.
+  ASSERT_FALSE(drag_context->Contains(dragged_tab));
+
+  ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
+  EXPECT_FALSE(drag_context->Contains(dragged_tab));
+
+  // TabDragContext gets them only after the drag truly begins.
+  ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
+  EXPECT_TRUE(drag_context->Contains(dragged_tab));
+
+  // TabDragContext keeps them after the drag ends...
+  ASSERT_TRUE(ReleaseInput());
+  EXPECT_TRUE(drag_context->Contains(dragged_tab));
+
+  // Until they animate back into place.
+  StopAnimating(tab_strip);
+  EXPECT_FALSE(drag_context->Contains(dragged_tab));
+
+  EXPECT_FALSE(TabDragController::IsActive());
+  EXPECT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
+}
+
+// Flaky on lacros - it appears that the tab close animation sometimes completes
+// before we have a chance to check for ownership during said animation.
+// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
+// of lacros-chrome is complete.
+#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
+#define MAYBE_TabDragContextOwnsClosingDraggedTabs \
+  DISABLED_TabDragContextOwnsClosingDraggedTabs
+#else
+#define MAYBE_TabDragContextOwnsClosingDraggedTabs \
+  TabDragContextOwnsClosingDraggedTabs
+#endif
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+                       MAYBE_TabDragContextOwnsClosingDraggedTabs) {
+  AddTabsAndResetBrowser(browser(), 1);
+
+  TabStrip* tab_strip = GetTabStripForBrowser(browser());
+  TabDragContext* drag_context = tab_strip->GetDragContext();
+  Tab* dragged_tab = tab_strip->tab_at(1);
+
+  ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
+
+  ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
+
+  // Delete the tab being dragged.
+  browser()->tab_strip_model()->DetachWebContentsAtForInsertion(0);
+
+  // Should have canceled dragging.
+  ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
+  ASSERT_FALSE(TabDragController::IsActive());
+
+  // TabDragContext still owns the tab while it's closing.
+  ASSERT_TRUE(dragged_tab->closing());
+  EXPECT_TRUE(drag_context->Contains(dragged_tab));
+
+  // Completing the close animation destroys the tab.
+  views::ViewTracker dragged_tab_tracker(dragged_tab);
+  StopAnimating(tab_strip);
+  EXPECT_EQ(dragged_tab_tracker.view(), nullptr);
+}
+
 namespace {
 
 void DragAllStep2(DetachToBrowserTabDragControllerTest* test,
@@ -2339,6 +2413,7 @@
   ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
       browser()->window()->GetNativeWindow(), ui::VKEY_ESCAPE, false, false,
       false, false));
+  StopAnimating(tab_strip);
 
   EXPECT_EQ(1u, browser_list->size());
   EXPECT_FALSE(group_header->dragging());
@@ -2373,6 +2448,7 @@
       browser()->window()->GetNativeWindow(), ui::VKEY_ESCAPE, false, false,
       false, false));
 
+  StopAnimating(tab_strip);
   EXPECT_EQ(1u, browser_list->size());
   EXPECT_FALSE(group_header->dragging());
   EXPECT_EQ("0 1 2 3", IDString(browser()->tab_strip_model()));
diff --git a/chrome/browser/ui/views/tabs/tab_group_header.cc b/chrome/browser/ui/views/tabs/tab_group_header.cc
index 7dab1df4..ce2ef54f 100644
--- a/chrome/browser/ui/views/tabs/tab_group_header.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_header.cc
@@ -509,7 +509,7 @@
   widget->RemoveObserver(&editor_bubble_tracker_);
 }
 
-BEGIN_METADATA(TabGroupHeader, views::View)
+BEGIN_METADATA(TabGroupHeader, TabSlotView)
 ADD_READONLY_PROPERTY_METADATA(int, DesiredWidth)
 END_METADATA
 
diff --git a/chrome/browser/ui/views/tabs/tab_group_highlight.cc b/chrome/browser/ui/views/tabs/tab_group_highlight.cc
index 14f3595..76612218 100644
--- a/chrome/browser/ui/views/tabs/tab_group_highlight.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_highlight.cc
@@ -17,6 +17,17 @@
                                      const tab_groups::TabGroupId& group)
     : tab_group_views_(tab_group_views), group_(group) {}
 
+void TabGroupHighlight::UpdateBounds(views::View* leading_view,
+                                     views::View* trailing_view) {
+  // If there are no views to highlight, do nothing. Our visibility is
+  // controlled by our parent TabDragContext.
+  if (!leading_view)
+    return;
+  gfx::Rect bounds = leading_view->bounds();
+  bounds.UnionEvenIfEmpty(trailing_view->bounds());
+  SetBoundsRect(bounds);
+}
+
 void TabGroupHighlight::OnPaint(gfx::Canvas* canvas) {
   SkPath path = GetPath();
   cc::PaintFlags flags;
diff --git a/chrome/browser/ui/views/tabs/tab_group_highlight.h b/chrome/browser/ui/views/tabs/tab_group_highlight.h
index 5c93078..55a04da 100644
--- a/chrome/browser/ui/views/tabs/tab_group_highlight.h
+++ b/chrome/browser/ui/views/tabs/tab_group_highlight.h
@@ -23,6 +23,8 @@
   TabGroupHighlight(const TabGroupHighlight&) = delete;
   TabGroupHighlight& operator=(const TabGroupHighlight&) = delete;
 
+  void UpdateBounds(views::View* leading_view, views::View* trailing_view);
+
   // views::View:
   void OnPaint(gfx::Canvas* canvas) override;
   bool GetCanProcessEventsWithinSubtree() const override;
diff --git a/chrome/browser/ui/views/tabs/tab_group_underline.cc b/chrome/browser/ui/views/tabs/tab_group_underline.cc
index f6e7554..a1f62ab 100644
--- a/chrome/browser/ui/views/tabs/tab_group_underline.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_underline.cc
@@ -17,8 +17,10 @@
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/canvas.h"
+#include "ui/gfx/geometry/insets.h"
 #include "ui/views/background.h"
 #include "ui/views/view.h"
+#include "ui/views/view_utils.h"
 
 constexpr int TabGroupUnderline::kStrokeThickness;
 
@@ -35,19 +37,59 @@
   canvas->DrawPath(path, flags);
 }
 
-void TabGroupUnderline::UpdateBounds(const gfx::Rect& group_bounds) {
-  const int start_x = GetStart(group_bounds);
-  const int end_x = GetEnd(group_bounds);
+void TabGroupUnderline::UpdateBounds(views::View* leading_view,
+                                     views::View* trailing_view) {
+  // If there are no views to underline, don't show the underline.
+  if (!leading_view) {
+    SetVisible(false);
+    return;
+  }
+
+  gfx::RectF leading_bounds = gfx::RectF(leading_view->bounds());
+  ConvertRectToTarget(leading_view->parent(), parent(), &leading_bounds);
+  leading_bounds.Inset(gfx::InsetsF(GetInsetsForUnderline(leading_view)));
+
+  gfx::RectF trailing_bounds = gfx::RectF(trailing_view->bounds());
+  ConvertRectToTarget(trailing_view->parent(), parent(), &trailing_bounds);
+  trailing_bounds.Inset(gfx::InsetsF(GetInsetsForUnderline(trailing_view)));
+
+  gfx::Rect group_bounds = ToEnclosingRect(leading_bounds);
+  group_bounds.UnionEvenIfEmpty(ToEnclosingRect(trailing_bounds));
 
   // The width may be zero if the group underline and header are initialized at
-  // the same time, as with tab restore. In this case, don't update the bounds
-  // and defer to the next paint cycle.
-  if (end_x <= start_x)
+  // the same time, as with tab restore. In this case, don't show the underline.
+  if (group_bounds.width() == 0) {
+    SetVisible(false);
     return;
+  }
 
+  SetVisible(true);
   const int y =
       group_bounds.height() - GetLayoutConstant(TABSTRIP_TOOLBAR_OVERLAP);
-  SetBounds(start_x, y - kStrokeThickness, end_x - start_x, kStrokeThickness);
+  SetBounds(group_bounds.x(), y - kStrokeThickness, group_bounds.width(),
+            kStrokeThickness);
+}
+
+gfx::Insets TabGroupUnderline::GetInsetsForUnderline(
+    views::View* sibling_view) const {
+  // Inset normally from a header - this will always be the left boundary of
+  // the group, and may be the right boundary if the group is collapsed.
+  TabGroupHeader* header = views::AsViewClass<TabGroupHeader>(sibling_view);
+  if (header)
+    return gfx::Insets::TLBR(0, GetStrokeInset(), 0, GetStrokeInset());
+
+  Tab* tab = views::AsViewClass<Tab>(sibling_view);
+  DCHECK(tab);
+
+  // Active tabs need the rounded bits of the underline poking out the sides.
+  if (tab->IsActive())
+    return gfx::Insets::TLBR(0, -kStrokeThickness, 0, -kStrokeThickness);
+
+  // Inactive tabs are inset like group headers.
+  int left_inset = GetStrokeInset();
+  int right_inset = GetStrokeInset();
+
+  return gfx::Insets::TLBR(0, left_inset, 0, right_inset);
 }
 
 // static
@@ -55,19 +97,6 @@
   return TabStyle::GetTabOverlap() + kStrokeThickness;
 }
 
-int TabGroupUnderline::GetStart(const gfx::Rect& group_bounds) const {
-  return group_bounds.x() + GetStrokeInset();
-}
-
-int TabGroupUnderline::GetEnd(const gfx::Rect& group_bounds) const {
-  const Tab* last_grouped_tab = tab_group_views_->GetLastTabInGroup();
-  if (!last_grouped_tab)
-    return group_bounds.right() - GetStrokeInset();
-
-  return group_bounds.right() +
-         (last_grouped_tab->IsActive() ? kStrokeThickness : -GetStrokeInset());
-}
-
 SkPath TabGroupUnderline::GetPath() const {
   SkPath path;
 
diff --git a/chrome/browser/ui/views/tabs/tab_group_underline.h b/chrome/browser/ui/views/tabs/tab_group_underline.h
index 191a650cf..b3defda 100644
--- a/chrome/browser/ui/views/tabs/tab_group_underline.h
+++ b/chrome/browser/ui/views/tabs/tab_group_underline.h
@@ -14,8 +14,12 @@
 class TabGroupViews;
 
 // View for tab group underlines in the tab strip, which are markers of group
-// members. There is one underline for each group, which is included in the tab
-// strip flow and positioned across all tabs in the group.
+// members. Underlines are included in the tab
+// strip flow and positioned across all tabs in the group, as well as the group
+// header. There is one underline for the tabs in the TabContainer, and another
+// for any tabs in the group that are being dragged. These merge visually into a
+// single underline, but must be separate views so that paint order requirements
+// can be met.
 class TabGroupUnderline : public views::View {
  public:
   METADATA_HEADER(TabGroupUnderline);
@@ -30,21 +34,16 @@
   TabGroupUnderline(const TabGroupUnderline&) = delete;
   TabGroupUnderline& operator=(const TabGroupUnderline&) = delete;
 
-  // Updates the bounds of the underline for painting, given the current bounds
-  // of the group.
-  void UpdateBounds(const gfx::Rect& group_bounds);
+  // Updates the bounds of the underline for painting.
+  void UpdateBounds(views::View* leading_view, views::View* trailing_view);
 
   // views::View:
   void OnPaint(gfx::Canvas* canvas) override;
 
  private:
-  // The underline starts at the left edge of the header chip.
-  int GetStart(const gfx::Rect& group_bounds) const;
-
-  // The underline ends at the right edge of the last grouped tab's close
-  // button. If the last grouped tab is active, the underline ends at the
-  // right edge of the active tab border stroke.
-  int GetEnd(const gfx::Rect& group_bounds) const;
+  // Returns the insets from |sibling_view|'s bounds this underline would have
+  // if it were underlining only |sibling_view|.
+  gfx::Insets GetInsetsForUnderline(views::View* sibling_view) const;
 
   // The underline is a straight line with half-rounded endcaps. Since this
   // geometry is nontrivial to represent using primitives, it's instead
diff --git a/chrome/browser/ui/views/tabs/tab_group_views.cc b/chrome/browser/ui/views/tabs/tab_group_views.cc
index 045fda4e..c633af66 100644
--- a/chrome/browser/ui/views/tabs/tab_group_views.cc
+++ b/chrome/browser/ui/views/tabs/tab_group_views.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/tabs/tab_group_views.h"
 
+#include <tuple>
 #include <utility>
 
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -20,70 +21,109 @@
 #include "components/tab_groups/tab_group_id.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rect_conversions.h"
+#include "ui/views/view_utils.h"
 
 TabGroupViews::TabGroupViews(views::View* container_view,
+                             views::View* drag_container_view,
                              TabSlotController* tab_slot_controller,
                              const tab_groups::TabGroupId& group)
-    : container_view_(container_view),
-      tab_slot_controller_(tab_slot_controller),
-      group_(group) {
-  header_ = container_view_->AddChildView(
+    : tab_slot_controller_(tab_slot_controller), group_(group) {
+  header_ = container_view->AddChildView(
       std::make_unique<TabGroupHeader>(tab_slot_controller_, group_));
-  underline_ = container_view_->AddChildView(
+  underline_ = container_view->AddChildView(
       std::make_unique<TabGroupUnderline>(this, group_));
-  highlight_ = container_view_->AddChildView(
+  drag_underline_ = drag_container_view->AddChildView(
+      std::make_unique<TabGroupUnderline>(this, group_));
+  highlight_ = drag_container_view->AddChildView(
       std::make_unique<TabGroupHighlight>(this, group_));
   highlight_->SetVisible(false);
 }
 
 TabGroupViews::~TabGroupViews() {
-  container_view_->RemoveChildViewT(std::exchange(header_, nullptr));
-  container_view_->RemoveChildViewT(std::exchange(underline_, nullptr));
-  container_view_->RemoveChildViewT(std::exchange(highlight_, nullptr));
+  header_->parent()->RemoveChildViewT(std::exchange(header_, nullptr));
+  underline_->parent()->RemoveChildViewT(std::exchange(underline_, nullptr));
+  drag_underline_->parent()->RemoveChildViewT(
+      std::exchange(drag_underline_, nullptr));
+  highlight_->parent()->RemoveChildViewT(std::exchange(highlight_, nullptr));
 }
 
 void TabGroupViews::UpdateBounds() {
   // If we're tearing down we should ignore events.
-  if (!header_)
+  if (InTearDown())
     return;
 
-  const gfx::Rect bounds = GetBounds();
-  underline_->UpdateBounds(bounds);
-  highlight_->SetBoundsRect(bounds);
+  auto [leading_group_view, trailing_group_view] =
+      GetLeadingTrailingGroupViews();
+  underline_->UpdateBounds(leading_group_view, trailing_group_view);
+
+  auto [leading_dragged_group_view, trailing_dragged_group_view] =
+      GetLeadingTrailingDraggedGroupViews();
+  drag_underline_->UpdateBounds(leading_dragged_group_view,
+                                trailing_dragged_group_view);
+
+  if (underline_->GetVisible() && drag_underline_->GetVisible()) {
+    // If we are painting `drag_underline_` on top of `underline_`, we may need
+    // to extend `drag_underline_`'s bounds so the two merge seamlessly.
+    // Otherwise, `underline_` may be partially occluded by the dragged views.
+    gfx::RectF underline_bounds_in_drag_coords_f(underline_->GetLocalBounds());
+    views::View::ConvertRectToTarget(underline_, drag_underline_->parent(),
+                                     &underline_bounds_in_drag_coords_f);
+    gfx::Rect underline_bounds_in_drag_coords =
+        ToEnclosingRect(underline_bounds_in_drag_coords_f);
+
+    // Try to match `underline_`, but don't shrink, and don't expand beyond the
+    // dragged views unless already beyond them.
+    int leading_x =
+        std::clamp(underline_bounds_in_drag_coords.x(),
+                   std::min(drag_underline_->bounds().x(),
+                            leading_dragged_group_view->bounds().x()),
+                   drag_underline_->bounds().x());
+    int trailing_x =
+        std::clamp(underline_bounds_in_drag_coords.right(),
+                   drag_underline_->bounds().right(),
+                   std::max(drag_underline_->bounds().right(),
+                            trailing_dragged_group_view->bounds().right()));
+
+    drag_underline_->SetBounds(leading_x, drag_underline_->bounds().y(),
+                               trailing_x - leading_x,
+                               drag_underline_->bounds().height());
+  }
+
+  highlight_->UpdateBounds(leading_dragged_group_view,
+                           trailing_dragged_group_view);
 }
 
 void TabGroupViews::OnGroupVisualsChanged() {
   // If we're tearing down we should ignore events. (We can still receive them
   // here if the editor bubble was open when the tab group was closed.)
-  if (!header_)
+  if (InTearDown())
     return;
 
   header_->VisualsChanged();
   underline_->SchedulePaint();
+  drag_underline_->SchedulePaint();
 }
 
 gfx::Rect TabGroupViews::GetBounds() const {
-  gfx::Rect bounds = header_->bounds();
+  auto [leading_group_view, trailing_group_view] =
+      GetLeadingTrailingGroupViews();
 
-  // If the group is (done animating to) collapsed, the tabs will be stacked at
-  // the right edge of the header, so this is a no-op. But if the group is mid
-  // collapse animation, this will set the header bounds correctly.
-  const Tab* last_tab = GetLastTabInGroup();
-  if (last_tab) {
-    const int width = last_tab->bounds().right() - bounds.x();
-    if (width > 0)
-      bounds.set_width(width);
-  }
+  gfx::RectF leading_bounds = gfx::RectF(leading_group_view->GetLocalBounds());
+  views::View::ConvertRectToTarget(leading_group_view, underline_->parent(),
+                                   &leading_bounds);
+
+  gfx::RectF trailing_bounds =
+      gfx::RectF(trailing_group_view->GetLocalBounds());
+  views::View::ConvertRectToTarget(trailing_group_view, underline_->parent(),
+                                   &trailing_bounds);
+
+  gfx::Rect bounds = gfx::ToEnclosingRect(leading_bounds);
+  bounds.UnionEvenIfEmpty(gfx::ToEnclosingRect(trailing_bounds));
+
   return bounds;
 }
 
-const Tab* TabGroupViews::GetLastTabInGroup() const {
-  const absl::optional<int> last_tab =
-      tab_slot_controller_->GetLastTabInGroup(group_);
-  return last_tab.has_value() ? tab_slot_controller_->tab_at(last_tab.value())
-                              : nullptr;
-}
-
 SkColor TabGroupViews::GetGroupColor() const {
   return tab_slot_controller_->GetPaintedGroupColor(
       tab_slot_controller_->GetGroupColorId(group_));
@@ -101,3 +141,58 @@
                                        TabStyle::kSelectedTabOpacity,
                                        SK_AlphaTRANSPARENT, SK_AlphaOPAQUE));
 }
+
+bool TabGroupViews::InTearDown() const {
+  return !header_ || !header_->GetWidget() || !drag_underline_->GetWidget();
+}
+
+std::tuple<views::View*, views::View*>
+TabGroupViews::GetLeadingTrailingGroupViews() const {
+  std::vector<views::View*> children = underline_->parent()->children();
+  std::vector<views::View*> dragged_children =
+      drag_underline_->parent()->children();
+  children.insert(children.end(), dragged_children.begin(),
+                  dragged_children.end());
+  return GetLeadingTrailingGroupViews(children);
+}
+
+std::tuple<views::View*, views::View*>
+TabGroupViews::GetLeadingTrailingDraggedGroupViews() const {
+  return GetLeadingTrailingGroupViews(drag_underline_->parent()->children());
+}
+
+std::tuple<views::View*, views::View*>
+TabGroupViews::GetLeadingTrailingGroupViews(
+    std::vector<views::View*> children) const {
+  // Elements of |children| may be in different coordinate spaces. Canonicalize
+  // to widget space for comparison, since they will be in the same widget.
+  views::View* leading_child = nullptr;
+  gfx::Rect leading_child_widget_bounds;
+
+  views::View* trailing_child = nullptr;
+  gfx::Rect trailing_child_widget_bounds;
+
+  for (views::View* child : children) {
+    TabSlotView* tab_slot_view = views::AsViewClass<TabSlotView>(child);
+    if (!tab_slot_view || tab_slot_view->group() != group_ ||
+        !tab_slot_view->GetVisible())
+      continue;
+
+    gfx::Rect child_widget_bounds =
+        child->ConvertRectToWidget(child->GetLocalBounds());
+
+    if (!leading_child ||
+        child_widget_bounds.x() < leading_child_widget_bounds.x()) {
+      leading_child = child;
+      leading_child_widget_bounds = child_widget_bounds;
+    }
+
+    if (!trailing_child ||
+        child_widget_bounds.right() > trailing_child_widget_bounds.right()) {
+      trailing_child = child;
+      trailing_child_widget_bounds = child_widget_bounds;
+    }
+  }
+
+  return {leading_child, trailing_child};
+}
diff --git a/chrome/browser/ui/views/tabs/tab_group_views.h b/chrome/browser/ui/views/tabs/tab_group_views.h
index 79ad73d..c9c95f3 100644
--- a/chrome/browser/ui/views/tabs/tab_group_views.h
+++ b/chrome/browser/ui/views/tabs/tab_group_views.h
@@ -12,7 +12,6 @@
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/view.h"
 
-class Tab;
 class TabGroupHeader;
 class TabGroupHighlight;
 class TabGroupUnderline;
@@ -23,9 +22,10 @@
 class TabGroupViews {
  public:
   // Creates the various views representing a tab group and adds them to
-  // |parent_view| as children.  Assumes these views are not destroyed before
-  // |this|.
+  // |container_view| and |drag_container_view| as children.  Assumes these
+  // views are not destroyed before |this|.
   TabGroupViews(views::View* container_view,
+                views::View* drag_container_view,
                 TabSlotController* tab_slot_controller,
                 const tab_groups::TabGroupId& group);
 
@@ -36,6 +36,7 @@
   TabGroupHeader* header() { return header_; }
   TabGroupHighlight* highlight() { return highlight_; }
   TabGroupUnderline* underline() { return underline_; }
+  TabGroupUnderline* drag_underline() { return drag_underline_; }
 
   // Updates bounds of all elements not explicitly positioned by the tab strip.
   // This currently includes both the underline and highlight.
@@ -48,9 +49,6 @@
   // Returns the bounds of the entire group, including the header and all tabs.
   gfx::Rect GetBounds() const;
 
-  // Returns the last tab in the group. Used for some visual calculations.
-  const Tab* GetLastTabInGroup() const;
-
   // Returns the group color.
   SkColor GetGroupColor() const;
 
@@ -63,12 +61,28 @@
   SkColor GetGroupBackgroundColor() const;
 
  private:
-  const raw_ptr<views::View> container_view_;
   const raw_ptr<TabSlotController> tab_slot_controller_;
   const tab_groups::TabGroupId group_;
-  TabGroupHeader* header_;
-  TabGroupHighlight* highlight_;
-  TabGroupUnderline* underline_;
+  raw_ptr<TabGroupHeader> header_;
+  raw_ptr<TabGroupHighlight> highlight_;
+  raw_ptr<TabGroupUnderline> underline_;
+  raw_ptr<TabGroupUnderline> drag_underline_;
+
+  bool InTearDown() const;
+
+  // Finds the first and last tab or group header belonging to |group_| from the
+  // whole TabStrip.
+  std::tuple<views::View*, views::View*> GetLeadingTrailingGroupViews() const;
+
+  // Finds the first and last tab or group header belonging to |group_|, only
+  // including views that are being dragged.
+  std::tuple<views::View*, views::View*> GetLeadingTrailingDraggedGroupViews()
+      const;
+
+  // Finds the first and last tab or group header belonging to |group_| within
+  // |children|.
+  std::tuple<views::View*, views::View*> GetLeadingTrailingGroupViews(
+      std::vector<views::View*> children) const;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TABS_TAB_GROUP_VIEWS_H_
diff --git a/chrome/browser/ui/views/tabs/tab_group_views_unittest.cc b/chrome/browser/ui/views/tabs/tab_group_views_unittest.cc
new file mode 100644
index 0000000..55f0815
--- /dev/null
+++ b/chrome/browser/ui/views/tabs/tab_group_views_unittest.cc
@@ -0,0 +1,270 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/tabs/tab_group_views.h"
+#include <memory>
+
+#include "chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h"
+#include "chrome/browser/ui/views/tabs/fake_tab_slot_controller.h"
+#include "chrome/browser/ui/views/tabs/tab_group_header.h"
+#include "chrome/browser/ui/views/tabs/tab_group_highlight.h"
+#include "chrome/browser/ui/views/tabs/tab_group_underline.h"
+#include "chrome/test/views/chrome_views_test_base.h"
+#include "ui/views/widget/widget.h"
+
+class TabGroupViewsTest : public ChromeViewsTestBase {
+ public:
+  TabGroupViewsTest() = default;
+  TabGroupViewsTest(const TabGroupViewsTest&) = delete;
+  TabGroupViewsTest& operator=(const TabGroupViewsTest&) = delete;
+  ~TabGroupViewsTest() override = default;
+
+  void SetUp() override {
+    ChromeViewsTestBase::SetUp();
+
+    widget_ = CreateTestWidget();
+    tab_container_ = widget_->SetContentsView(std::make_unique<views::View>());
+    tab_container_->SetBounds(0, 0, 1000, 100);
+    drag_context_ =
+        tab_container_->AddChildView(std::make_unique<views::View>());
+    drag_context_->SetBounds(0, 0, 1000, 100);
+
+    tab_strip_controller_ = std::make_unique<FakeBaseTabStripController>();
+    tab_slot_controller_ =
+        std::make_unique<FakeTabSlotController>(tab_strip_controller_.get());
+    group_views_ = std::make_unique<TabGroupViews>(
+        tab_container_.get(), drag_context_.get(), tab_slot_controller_.get(),
+        id_);
+  }
+
+  void TearDown() override {
+    widget_->Close();
+
+    group_views_.release();
+    tab_slot_controller_.release();
+    widget_.release();
+    ChromeViewsTestBase::TearDown();
+  }
+
+ protected:
+  std::unique_ptr<views::Widget> widget_;
+  raw_ptr<views::View> tab_container_;
+  raw_ptr<views::View> drag_context_;
+  std::unique_ptr<FakeBaseTabStripController> tab_strip_controller_;
+  std::unique_ptr<FakeTabSlotController> tab_slot_controller_;
+  tab_groups::TabGroupId id_ = tab_groups::TabGroupId::GenerateNew();
+  std::unique_ptr<TabGroupViews> group_views_;
+};
+
+TEST_F(TabGroupViewsTest, GroupViewsCreated) {
+  EXPECT_NE(nullptr, group_views_->header());
+  EXPECT_NE(nullptr, group_views_->underline());
+  EXPECT_NE(nullptr, group_views_->drag_underline());
+  EXPECT_NE(nullptr, group_views_->highlight());
+
+  EXPECT_EQ(tab_container_.get(), group_views_->header()->parent());
+  EXPECT_EQ(tab_container_.get(), group_views_->underline()->parent());
+  EXPECT_EQ(drag_context_.get(), group_views_->drag_underline()->parent());
+  EXPECT_EQ(drag_context_.get(), group_views_->highlight()->parent());
+}
+
+// Underline should actually underline the group.
+TEST_F(TabGroupViewsTest, UnderlineBoundsNoDrag) {
+  TabGroupHeader* header = group_views_->header();
+  Tab* tab_1 = tab_container_->AddChildView(
+      std::make_unique<Tab>(tab_slot_controller_.get()));
+  tab_1->set_group(id_);
+  Tab* tab_2 = tab_container_->AddChildView(
+      std::make_unique<Tab>(tab_slot_controller_.get()));
+  tab_2->set_group(id_);
+
+  header->SetBounds(0, 0, 100, 0);
+  tab_1->SetBounds(50, 0, 100, 0);
+  tab_2->SetBounds(100, 0, 100, 0);
+
+  group_views_->UpdateBounds();
+
+  EXPECT_TRUE(group_views_->underline()->GetVisible());
+  const gfx::Rect underline_bounds = group_views_->underline()->bounds();
+
+  // Underline should begin within the header.
+  EXPECT_GT(underline_bounds.x(), header->bounds().x());
+  EXPECT_LT(underline_bounds.x(), header->bounds().right());
+
+  // Underline should end within the last tab.
+  EXPECT_GT(underline_bounds.right(), tab_2->bounds().x());
+  EXPECT_LT(underline_bounds.right(), tab_2->bounds().right());
+
+  EXPECT_FALSE(group_views_->drag_underline()->GetVisible());
+}
+
+// Drag_underline should underline the group when the group is being dragged,
+// and the highlight should highlight it.
+TEST_F(TabGroupViewsTest, UnderlineBoundsHeaderDrag) {
+  TabGroupHeader* header = group_views_->header();
+  drag_context_->AddChildView(header);
+  Tab* tab_1 = drag_context_->AddChildView(
+      std::make_unique<Tab>(tab_slot_controller_.get()));
+  tab_1->set_group(id_);
+  Tab* tab_2 = drag_context_->AddChildView(
+      std::make_unique<Tab>(tab_slot_controller_.get()));
+  tab_2->set_group(id_);
+
+  header->SetBounds(0, 0, 100, 0);
+  tab_1->SetBounds(50, 0, 100, 0);
+  tab_2->SetBounds(100, 0, 100, 0);
+  group_views_->highlight()->SetVisible(true);
+
+  group_views_->UpdateBounds();
+
+  // The underline and the drag underline should match exactly.
+  EXPECT_TRUE(group_views_->underline()->GetVisible());
+  EXPECT_EQ(group_views_->underline()->bounds(),
+            group_views_->drag_underline()->bounds());
+
+  EXPECT_TRUE(group_views_->drag_underline()->GetVisible());
+  const gfx::Rect drag_underline_bounds =
+      group_views_->drag_underline()->bounds();
+
+  // Drag underline should begin within the header.
+  EXPECT_GT(drag_underline_bounds.x(), header->bounds().x());
+  EXPECT_LT(drag_underline_bounds.x(), header->bounds().right());
+
+  // Drag underline should end within the last tab.
+  EXPECT_GT(drag_underline_bounds.right(), tab_2->bounds().x());
+  EXPECT_LT(drag_underline_bounds.right(), tab_2->bounds().right());
+
+  // Highlight should span the dragged views exactly.
+  EXPECT_EQ(group_views_->highlight()->bounds().x(), header->bounds().x());
+  EXPECT_EQ(group_views_->highlight()->bounds().right(),
+            tab_2->bounds().right());
+}
+
+// Underline and drag_underline should align with one another correctly when
+// dragging a tab within a group.
+TEST_F(TabGroupViewsTest, UnderlineBoundsDragTabInGroup) {
+  TabGroupHeader* header = group_views_->header();
+  Tab* other_tab = tab_container_->AddChildView(
+      std::make_unique<Tab>(tab_slot_controller_.get()));
+  other_tab->set_group(id_);
+  Tab* dragged_tab = drag_context_->AddChildView(
+      std::make_unique<Tab>(tab_slot_controller_.get()));
+  dragged_tab->set_group(id_);
+
+  header->SetBounds(0, 0, 100, 0);
+  other_tab->SetBounds(50, 0, 100, 0);
+  dragged_tab->SetBounds(100, 0, 100, 0);
+  group_views_->highlight()->SetVisible(true);
+
+  /////////////// Case 1: `dragged_tab` is right of `other_tab`. ///////////////
+  {
+    group_views_->UpdateBounds();
+
+    EXPECT_TRUE(group_views_->underline()->GetVisible());
+    const gfx::Rect underline_bounds = group_views_->underline()->bounds();
+
+    // Underline should begin within the header.
+    EXPECT_GT(underline_bounds.x(), header->bounds().x());
+    EXPECT_LT(underline_bounds.x(), header->bounds().right());
+
+    // Underline should end within the last tab.
+    EXPECT_GT(underline_bounds.right(), dragged_tab->bounds().x());
+    EXPECT_LT(underline_bounds.right(), dragged_tab->bounds().right());
+
+    EXPECT_TRUE(group_views_->drag_underline()->GetVisible());
+    const gfx::Rect drag_underline_bounds =
+        group_views_->drag_underline()->bounds();
+
+    // Drag underline should begin right at the beginning of the dragged tab.
+    EXPECT_EQ(drag_underline_bounds.x(), dragged_tab->x());
+
+    // Drag underline end should match the other underline's.
+    EXPECT_EQ(drag_underline_bounds.right(), underline_bounds.right());
+  }
+
+  //// Case 2: `dragged_tab` is a bit left of and overlapping `other_tab`. /////
+  {
+    dragged_tab->SetBounds(45, 0, 100, 0);
+    group_views_->UpdateBounds();
+
+    EXPECT_TRUE(group_views_->underline()->GetVisible());
+    const gfx::Rect underline_bounds = group_views_->underline()->bounds();
+
+    // Underline should begin within the header.
+    EXPECT_GT(underline_bounds.x(), header->bounds().x());
+    EXPECT_LT(underline_bounds.x(), header->bounds().right());
+
+    // Underline should end within the last tab, now `other_tab`.
+    EXPECT_GT(underline_bounds.right(), other_tab->bounds().x());
+    EXPECT_LT(underline_bounds.right(), other_tab->bounds().right());
+
+    EXPECT_TRUE(group_views_->drag_underline()->GetVisible());
+    const gfx::Rect drag_underline_bounds =
+        group_views_->drag_underline()->bounds();
+
+    // Drag underline should begin right at the beginning of the dragged tab.
+    EXPECT_EQ(drag_underline_bounds.x(), dragged_tab->x());
+
+    // Drag underline end should match the other underline's. Note that this is
+    // different from the case above because the drag underline must be extended
+    // from its natural end to meet the other underline.
+    EXPECT_EQ(drag_underline_bounds.right(), underline_bounds.right());
+  }
+
+  ///// Case 3: `dragged_tab` is a bit right of and overlapping `header`. //////
+  {
+    dragged_tab->SetBounds(5, 0, 100, 0);
+    group_views_->UpdateBounds();
+
+    EXPECT_TRUE(group_views_->underline()->GetVisible());
+    const gfx::Rect underline_bounds = group_views_->underline()->bounds();
+
+    // Underline should begin within the header.
+    EXPECT_GT(underline_bounds.x(), header->bounds().x());
+    EXPECT_LT(underline_bounds.x(), header->bounds().right());
+
+    // Underline should end within the last tab, now `other_tab`.
+    EXPECT_GT(underline_bounds.right(), other_tab->bounds().x());
+    EXPECT_LT(underline_bounds.right(), other_tab->bounds().right());
+
+    EXPECT_TRUE(group_views_->drag_underline()->GetVisible());
+    const gfx::Rect drag_underline_bounds =
+        group_views_->drag_underline()->bounds();
+
+    // Drag underline begin should match the other underline's. Unlike case 4,
+    // the drag underline must be extended to match the underline.
+    EXPECT_EQ(drag_underline_bounds.x(), underline_bounds.x());
+
+    // Drag underline end should match the dragged tab's end.
+    EXPECT_EQ(drag_underline_bounds.right(), dragged_tab->bounds().right());
+  }
+
+  ///////////////// Case 4: `dragged_tab` is left of `header`. /////////////////
+  {
+    dragged_tab->SetBounds(-50, 0, 100, 0);
+    group_views_->UpdateBounds();
+
+    EXPECT_TRUE(group_views_->underline()->GetVisible());
+    const gfx::Rect underline_bounds = group_views_->underline()->bounds();
+
+    // Underline should begin within `dragged_tab`, now that it's leftmost.
+    EXPECT_GT(underline_bounds.x(), dragged_tab->bounds().x());
+    EXPECT_LT(underline_bounds.x(), dragged_tab->bounds().right());
+
+    // Underline should end within the last tab, now `other_tab`.
+    EXPECT_GT(underline_bounds.right(), other_tab->bounds().x());
+    EXPECT_LT(underline_bounds.right(), other_tab->bounds().right());
+
+    EXPECT_TRUE(group_views_->drag_underline()->GetVisible());
+    const gfx::Rect drag_underline_bounds =
+        group_views_->drag_underline()->bounds();
+
+    // Drag underline begin should match the other underline's. Unlike case 3,
+    // this is the drag underline's natural start point.
+    EXPECT_EQ(drag_underline_bounds.x(), underline_bounds.x());
+
+    // Drag underline end should match the dragged tab's end.
+    EXPECT_EQ(drag_underline_bounds.right(), dragged_tab->bounds().right());
+  }
+}
diff --git a/chrome/browser/ui/views/tabs/tab_slot_controller.h b/chrome/browser/ui/views/tabs/tab_slot_controller.h
index 7f01f26..3afecb7 100644
--- a/chrome/browser/ui/views/tabs/tab_slot_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_slot_controller.h
@@ -224,12 +224,6 @@
   // exist or is not collapsed.
   virtual bool IsGroupCollapsed(const tab_groups::TabGroupId& group) const = 0;
 
-  // Gets the last tab index in |group|, or nullopt if the group is
-  // currently empty. This is always safe to call unlike
-  // ListTabsInGroup().
-  virtual absl::optional<int> GetLastTabInGroup(
-      const tab_groups::TabGroupId& group) const = 0;
-
   // Returns the actual painted color of the given |group|, which depends on the
   // current theme.
   virtual SkColor GetPaintedGroupColor(
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 28ca3e8..1814059 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -65,6 +65,7 @@
 #include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_types.h"
 #include "chrome/browser/ui/views/tabs/tab_style_views.h"
+#include "chrome/browser/ui/views/tabs/z_orderable_tab_container_element.h"
 #include "chrome/browser/ui/views/touch_uma/touch_uma.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/grit/generated_resources.h"
@@ -132,9 +133,64 @@
 ///////////////////////////////////////////////////////////////////////////////
 // TabStrip::TabDragContextImpl
 //
-class TabStrip::TabDragContextImpl : public TabDragContext {
+class TabStrip::TabDragContextImpl : public TabDragContext,
+                                     public views::BoundsAnimatorObserver {
  public:
-  explicit TabDragContextImpl(TabStrip* tab_strip) : tab_strip_(tab_strip) {}
+  METADATA_HEADER(TabDragContextImpl);
+  explicit TabDragContextImpl(TabStrip* tab_strip)
+      : tab_strip_(tab_strip), bounds_animator_(this) {
+    SetCanProcessEventsWithinSubtree(false);
+
+    bounds_animator_.AddObserver(this);
+  }
+  // If a window is closed during a drag session, all our tabs will be taken
+  // from us before our destructor is even called.
+  ~TabDragContextImpl() override = default;
+
+  void Layout() override { SetBoundsRect(parent()->GetLocalBounds()); }
+
+  bool OnMouseDragged(const ui::MouseEvent& event) override {
+    ContinueDrag(this, event);
+    return true;
+  }
+
+  void OnMouseReleased(const ui::MouseEvent& event) override {
+    EndDrag(END_DRAG_COMPLETE);
+  }
+
+  void OnMouseCaptureLost() override { EndDrag(END_DRAG_CAPTURE_LOST); }
+
+  void OnGestureEvent(ui::GestureEvent* event) override {
+    switch (event->type()) {
+      case ui::ET_GESTURE_SCROLL_END:
+      case ui::ET_SCROLL_FLING_START:
+      case ui::ET_GESTURE_END:
+        EndDrag(END_DRAG_COMPLETE);
+        break;
+
+      case ui::ET_GESTURE_LONG_TAP: {
+        EndDrag(END_DRAG_CANCEL);
+        break;
+      }
+
+      case ui::ET_GESTURE_SCROLL_UPDATE:
+        ContinueDrag(this, *event);
+        break;
+
+      case ui::ET_GESTURE_TAP_DOWN:
+        EndDrag(END_DRAG_CANCEL);
+        break;
+
+      default:
+        break;
+    }
+    event->SetHandled();
+
+    // TabDragContext gets event capture as soon as a drag session begins, which
+    // precludes TabStrip from ever getting events like tap or long tap. Forward
+    // this on to TabStrip so it can respond to those events.
+    tab_strip_->OnGestureEvent(event);
+  }
 
   bool IsDragStarted() const {
     return drag_controller_ && drag_controller_->started_drag();
@@ -227,11 +283,6 @@
       if (!weak_ptr)
         return;
     }
-
-    // Note: |drag_controller| can be set to null during the drag above.
-    if (drag_controller_ && drag_controller_->group())
-      tab_strip_->tab_container_->UpdateTabGroupVisuals(
-          *drag_controller_->group());
   }
 
   bool EndDrag(EndDragReason reason) {
@@ -252,10 +303,6 @@
   }
 
   // TabDragContext:
-  views::View* AsView() override { return tab_strip_; }
-
-  const views::View* AsView() const override { return tab_strip_; }
-
   Tab* GetTabAt(int i) const override { return tab_strip_->tab_at(i); }
 
   int GetIndexOf(const TabSlotView* view) const override {
@@ -307,10 +354,26 @@
     drag_controller_set_callback_ = std::move(callback);
   }
 
+  void UpdateAnimationTarget(TabSlotView* tab_slot_view,
+                             const gfx::Rect& target_bounds) override {
+    if (bounds_animator_.IsAnimating(tab_slot_view))
+      bounds_animator_.SetTargetBounds(tab_slot_view, target_bounds);
+  }
+
   bool IsDragSessionActive() const override {
     return drag_controller_ != nullptr;
   }
 
+  bool IsEndingDrag() const override {
+    return drag_controller_ == nullptr && bounds_animator_.IsAnimating();
+  }
+
+  void FinishEndingDrag() override {
+    // Finishing animations will return tabs to the TabContainer via
+    // ResetDraggingStateDelegate::AnimationEnded.
+    bounds_animator_.Complete();
+  }
+
   bool IsActiveDropTarget() const override {
     for (int i = 0; i < GetTabCount(); ++i) {
       const Tab* const tab = GetTabAt(i);
@@ -320,13 +383,6 @@
     return false;
   }
 
-  std::vector<int> GetTabXCoordinates() const override {
-    std::vector<int> results;
-    for (int i = 0; i < GetTabCount(); ++i)
-      results.push_back(ideal_bounds(i).x());
-    return results;
-  }
-
   int GetActiveTabWidth() const override {
     return tab_strip_->GetActiveTabWidth();
   }
@@ -419,7 +475,7 @@
     int x = 0;
     for (const TabSlotView* view : views) {
       const int width = view->width();
-      bounds.push_back(gfx::Rect(x, 0, width, view->height()));
+      bounds.emplace_back(x, 0, width, view->height());
       x += width - overlap;
     }
 
@@ -451,14 +507,16 @@
     tab_strip_->controller_->OnStartedDragging(
         views.size() == static_cast<size_t>(tab_strip_->GetModelCount()));
 
-    // Reset dragging state of existing tabs.
+    // No tabs should be dragging at this point.
     for (int i = 0; i < GetTabCount(); ++i)
-      GetTabAt(i)->set_dragging(false);
+      DCHECK(!GetTabAt(i)->dragging());
 
     tab_strip_->tab_container_->CompleteAnimationAndLayout();
 
-    for (TabSlotView* dragged_view : views)
+    for (TabSlotView* dragged_view : views) {
+      AddChildView(dragged_view);
       dragged_view->set_dragging(true);
+    }
 
     // If this is a header drag, start painting the group highlight.
     TabGroupHeader* header = views::AsViewClass<TabGroupHeader>(views[0]);
@@ -478,15 +536,32 @@
     tab_strip_->controller_->OnStoppedDragging();
   }
 
-  void StoppedDragging(const std::vector<TabSlotView*>& views,
-                       const std::vector<int>& initial_positions,
-                       bool completed) override {
+  void StoppedDragging(const std::vector<TabSlotView*>& views) override {
     // Let the controller know that the user stopped dragging tabs.
     tab_strip_->controller_->OnStoppedDragging();
 
-    bool is_first_view = true;
-    for (size_t i = 0; i < views.size(); ++i)
-      tab_strip_->StoppedDraggingView(views[i], &is_first_view);
+    // Animate the dragged views to their ideal positions. We'll hand them back
+    // to TabContainer when the animation ends.
+    for (TabSlotView* view : views) {
+      gfx::Rect ideal_bounds;
+
+      TabGroupHeader* header = views::AsViewClass<TabGroupHeader>(view);
+      if (header) {
+        // Disable the group highlight now that the drag is ended.
+        tab_strip_->tab_container_->group_views()[header->group().value()]
+            ->highlight()
+            ->SetVisible(false);
+        ideal_bounds = tab_strip_->ideal_bounds(header->group().value());
+      } else {
+        ideal_bounds =
+            tab_strip_->ideal_bounds(tab_strip_->GetModelIndexOf(view));
+      }
+
+      bounds_animator_.AnimateViewTo(
+          view, ideal_bounds,
+          std::make_unique<ResetDraggingStateDelegate>(
+              tab_strip_->tab_container_, view));
+    }
   }
 
   void LayoutDraggedViewsAt(const std::vector<TabSlotView*>& views,
@@ -509,9 +584,8 @@
       // consecutive animate the view into position. Do the same if the tab is
       // already animating (which means we previously caused it to animate).
       if ((initial_drag && GetIndexOf(views[i]) != consecutive_index) ||
-          tab_strip_->tab_container_->bounds_animator().IsAnimating(views[i])) {
-        tab_strip_->tab_container_->bounds_animator().SetTargetBounds(
-            views[i], new_bounds);
+          bounds_animator_.IsAnimating(views[i])) {
+        bounds_animator_.SetTargetBounds(views[i], new_bounds);
       } else {
         view->SetBoundsRect(new_bounds);
       }
@@ -520,6 +594,13 @@
     // The rightmost tab may have moved, which would change the tabstrip's
     // preferred width.
     tab_strip_->PreferredSizeChanged();
+
+    // If the dragged tabs are in a group, we need to update the bounds of the
+    // corresponding underline and header.
+    if (views[0]->group()) {
+      tab_strip_->tab_container_->UpdateTabGroupVisuals(
+          views[0]->group().value());
+    }
   }
 
   // Forces the entire tabstrip to lay out.
@@ -528,7 +609,69 @@
     tab_strip_->tab_container_->CompleteAnimationAndLayout();
   }
 
+  void PaintChildren(const views::PaintInfo& paint_info) override {
+    std::vector<ZOrderableTabContainerElement> orderable_children;
+    for (views::View* child : children())
+      orderable_children.emplace_back(child);
+
+    // Sort in ascending order by z-value. Stable sort breaks ties by child
+    // index.
+    std::stable_sort(orderable_children.begin(), orderable_children.end());
+
+    for (const ZOrderableTabContainerElement& child : orderable_children)
+      child.view()->Paint(paint_info);
+  }
+
+  void OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) override {}
+  void OnBoundsAnimatorDone(views::BoundsAnimator* animator) override {
+    // Send the Container a message to simulate a mouse moved event at the
+    // current mouse position. This tickles the Tab the mouse is currently over
+    // to show the "hot" state of the close button, or to show the hover card,
+    // etc.  Note that this is not required (and indeed may crash!) during a
+    // drag session.
+    if (!IsDragSessionActive()) {
+      // The widget can apparently be null during shutdown.
+      views::Widget* widget = GetWidget();
+      if (widget)
+        widget->SynthesizeMouseMoveEvent();
+    }
+  }
+
  private:
+  // Animates tabs after a drag has ended, then hands them back to
+  // |tab_container_|.
+  class ResetDraggingStateDelegate : public gfx::AnimationDelegate {
+   public:
+    ResetDraggingStateDelegate(TabContainer* tab_container,
+                               TabSlotView* slot_view)
+        : tab_container_(tab_container), slot_view_(slot_view) {
+      slot_view_->set_animating(true);
+    }
+    ResetDraggingStateDelegate(const ResetDraggingStateDelegate&) = delete;
+    ResetDraggingStateDelegate& operator=(const ResetDraggingStateDelegate&) =
+        delete;
+    ~ResetDraggingStateDelegate() override = default;
+
+    void AnimationProgressed(const gfx::Animation* animation) override {
+      tab_container_->OnTabSlotAnimationProgressed(slot_view_);
+    }
+
+    void AnimationEnded(const gfx::Animation* animation) override {
+      AnimationProgressed(animation);
+      slot_view_->set_animating(false);
+      slot_view_->set_dragging(false);
+      tab_container_->StoppedDraggingView(slot_view_);
+    }
+
+    void AnimationCanceled(const gfx::Animation* animation) override {
+      AnimationEnded(animation);
+    }
+
+   private:
+    const raw_ptr<TabContainer> tab_container_;
+    const raw_ptr<TabSlotView> slot_view_;
+  };
+
   gfx::Rect ideal_bounds(int i) const { return tab_strip_->ideal_bounds(i); }
 
   gfx::Rect ideal_bounds(tab_groups::TabGroupId group) const {
@@ -711,6 +854,9 @@
 
   const raw_ptr<TabStrip> tab_strip_;
 
+  // Responsible for animating tabs during drag sessions.
+  views::BoundsAnimator bounds_animator_;
+
   // The controller for a drag initiated from a Tab. Valid for the lifetime of
   // the drag session.
   std::unique_ptr<TabDragController> drag_controller_;
@@ -721,19 +867,23 @@
   base::WeakPtrFactory<TabDragContext> weak_factory_{this};
 };
 
+BEGIN_METADATA(TabStrip, TabDragContextImpl, views::View);
+END_METADATA
+
 ///////////////////////////////////////////////////////////////////////////////
 // TabStrip, public:
 
 TabStrip::TabStrip(std::unique_ptr<TabStripController> controller)
     : controller_(std::move(controller)),
       hover_card_controller_(std::make_unique<TabHoverCardController>(this)),
-      drag_context_(std::make_unique<TabDragContextImpl>(this)),
-      tab_container_(AddChildView(
+      drag_context_(AddChildView(std::make_unique<TabDragContextImpl>(this))),
+      tab_container_(AddChildViewAt(
           std::make_unique<TabContainer>(controller_.get(),
                                          hover_card_controller_.get(),
                                          drag_context_.get(),
                                          this,
-                                         this))) {
+                                         this),
+          0)) {
   // TODO(pbos): This is probably incorrect, the background of individual tabs
   // depend on their selected state. This should probably be pushed down into
   // tabs.
@@ -753,9 +903,11 @@
   // destruction.
   drag_context_->DestroyDragController();
 
-  // The child tabs may call back to us from their destructors. Delete them so
-  // that if they call back we aren't in a weird state.
-  RemoveAllChildViews();
+  // |tab_container_|'s tabs may call back to us or to |drag_context_| from
+  // their destructors. Delete them first so that if they call back we aren't in
+  // a weird state.
+  RemoveChildViewT(tab_container_);
+  RemoveChildViewT(drag_context_);
 
   CHECK(!IsInObserverList());
 }
@@ -1176,7 +1328,7 @@
 }
 
 bool TabStrip::IsAnimating() const {
-  return tab_container_->bounds_animator().IsAnimating();
+  return tab_container_->IsAnimating();
 }
 
 void TabStrip::StopAnimating(bool layout) {
@@ -1591,11 +1743,6 @@
   return controller_->IsGroupCollapsed(group);
 }
 
-absl::optional<int> TabStrip::GetLastTabInGroup(
-    const tab_groups::TabGroupId& group) const {
-  return controller_->GetLastTabInGroup(group);
-}
-
 SkColor TabStrip::GetPaintedGroupColor(
     const tab_groups::TabGroupColorId& color_id) const {
   return GetColorProvider()->GetColor(
@@ -1630,6 +1777,9 @@
     // With tab scrolling, our bounds are driven solely by our TabContainer.
     SetBoundsRect(tab_container_->bounds());
   }
+
+  // drag_context_ isn't part of normal layout since it overlays the tabstrip.
+  drag_context_->Layout();
 }
 
 void TabStrip::ChildPreferredSizeChanged(views::View* child) {
@@ -1708,7 +1858,8 @@
     // our container and |tab_container_|'s min/preferred sizes.
     // With tab scrolling enabled, we instead ignore the size of our container
     // and simply match TabContainer's size unconditionally.
-    SetLayoutManager(std::make_unique<views::FillLayout>());
+    SetLayoutManager(std::make_unique<views::FillLayout>())
+        ->SetChildViewIgnoredByLayout(drag_context_, true);
   }
 }
 
@@ -1821,36 +1972,6 @@
   controller_->CloseTab(model_index);
 }
 
-void TabStrip::StoppedDraggingView(TabSlotView* view, bool* is_first_view) {
-  // TODO(1305016): Could this work better? It's structured very weirdly. Maybe
-  // batching? Its usage in TabStripTest doesn't make that much sense to me,
-  // either.
-
-  if (view &&
-      view->GetTabSlotViewType() == TabSlotView::ViewType::kTabGroupHeader) {
-    view->set_dragging(false);
-    // Disable the group highlight now that the drag is ended.
-    tab_container_->group_views()[view->group().value()]
-        ->highlight()
-        ->SetVisible(false);
-    return;
-  }
-
-  int tab_data_index = GetModelIndexOf(view);
-  if (tab_data_index == -1) {
-    // The tab was removed before the drag completed. Don't do anything.
-    return;
-  }
-
-  if (*is_first_view) {
-    *is_first_view = false;
-    // Animate the view back to its correct position.
-    tab_container_->StartBasicAnimation();
-  }
-
-  tab_container_->StartResetDragAnimation(tab_data_index);
-}
-
 void TabStrip::UpdateContrastRatioValues() {
   // There may be no controller in unit tests, and the call to
   // GetTabBackgroundColor() below requires one, so bail early if it is absent.
@@ -2026,19 +2147,6 @@
   return tab_container_->layout_helper()->group_header_ideal_bounds().at(group);
 }
 
-bool TabStrip::OnMouseDragged(const ui::MouseEvent& event) {
-  ContinueDrag(this, event);
-  return true;
-}
-
-void TabStrip::OnMouseReleased(const ui::MouseEvent& event) {
-  EndDrag(END_DRAG_COMPLETE);
-}
-
-void TabStrip::OnMouseCaptureLost() {
-  EndDrag(END_DRAG_CAPTURE_LOST);
-}
-
 void TabStrip::OnMouseEntered(const ui::MouseEvent& event) {
   mouse_entered_tabstrip_time_ = base::TimeTicks::Now();
 }
@@ -2057,26 +2165,11 @@
 
 void TabStrip::OnGestureEvent(ui::GestureEvent* event) {
   switch (event->type()) {
-    case ui::ET_GESTURE_SCROLL_END:
-    case ui::ET_SCROLL_FLING_START:
-    case ui::ET_GESTURE_END:
-      EndDrag(END_DRAG_COMPLETE);
-      break;
-
     case ui::ET_GESTURE_LONG_TAP: {
-      EndDrag(END_DRAG_CANCEL);
       tab_container_->HandleLongTap(event);
       break;
     }
 
-    case ui::ET_GESTURE_SCROLL_UPDATE:
-      ContinueDrag(this, *event);
-      break;
-
-    case ui::ET_GESTURE_TAP_DOWN:
-      EndDrag(END_DRAG_CANCEL);
-      break;
-
     case ui::ET_GESTURE_TAP: {
       const int active_index = GetActiveIndex();
       DCHECK_NE(-1, active_index);
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index 4d4d157..4eea7ec 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -299,8 +299,6 @@
   tab_groups::TabGroupColorId GetGroupColorId(
       const tab_groups::TabGroupId& group) const override;
   bool IsGroupCollapsed(const tab_groups::TabGroupId& group) const override;
-  absl::optional<int> GetLastTabInGroup(
-      const tab_groups::TabGroupId& group) const override;
   SkColor GetPaintedGroupColor(
       const tab_groups::TabGroupColorId& color_id) const override;
   void ShiftGroupLeft(const tab_groups::TabGroupId& group) override;
@@ -383,10 +381,6 @@
   // Closes the tab at |model_index|.
   void CloseTabInternal(int model_index, CloseTabSource source);
 
-  // Invoked from StoppedDraggingTabs to cleanup |view|. If |view| is known
-  // |is_first_view| is set to true.
-  void StoppedDraggingView(TabSlotView* view, bool* is_first_view);
-
   // Computes and stores values derived from contrast ratios.
   void UpdateContrastRatioValues();
 
@@ -407,9 +401,6 @@
   const gfx::Rect& ideal_bounds(tab_groups::TabGroupId group) const;
 
   // views::View:
-  bool OnMouseDragged(const ui::MouseEvent& event) override;
-  void OnMouseReleased(const ui::MouseEvent& event) override;
-  void OnMouseCaptureLost() override;
   void OnMouseEntered(const ui::MouseEvent& event) override;
   void OnMouseExited(const ui::MouseEvent& event) override;
   void AddedToWidget() override;
@@ -443,7 +434,7 @@
 
   std::unique_ptr<TabHoverCardController> hover_card_controller_;
 
-  std::unique_ptr<TabDragContextImpl> drag_context_;
+  raw_ptr<TabDragContextImpl> drag_context_;
 
   // The View parent for the tabs and the various group views.
   raw_ptr<TabContainer> tab_container_;
diff --git a/chrome/browser/ui/views/tabs/tab_strip_controller.h b/chrome/browser/ui/views/tabs/tab_strip_controller.h
index 83d34f6b..721a047 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_strip_controller.h
@@ -174,12 +174,6 @@
   virtual absl::optional<int> GetFirstTabInGroup(
       const tab_groups::TabGroupId& group) const = 0;
 
-  // Gets the last tab index in |group|, or nullopt if the group is
-  // currently empty. This is always safe to call unlike
-  // ListTabsInGroup().
-  virtual absl::optional<int> GetLastTabInGroup(
-      const tab_groups::TabGroupId& group) const = 0;
-
   // Returns the range of tabs in the given |group|. This must not be
   // called during intermediate states where the group is not
   // contiguous. For example, if tabs elsewhere in the tab strip are
diff --git a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
index af1e0cff..5abe81b7 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
@@ -180,11 +180,8 @@
   int GetInactiveTabWidth() { return tab_strip_->GetInactiveTabWidth(); }
 
   // End any outstanding drag and animate tabs back to their ideal bounds.
-  void StopDragging(TabSlotView* view) {
-    // Passing false for |is_first_view| results in running the post-drag
-    // animation unconditionally.
-    bool is_first_view = false;
-    tab_strip_->StoppedDraggingView(view, &is_first_view);
+  void StopDragging(const std::vector<TabSlotView*> views) {
+    tab_strip_->GetDragContext()->StoppedDragging(views);
   }
 
   std::vector<views::View*> GetChildViews() {
@@ -498,19 +495,18 @@
   const int min_active_width = TabStyleViews::GetMinimumActiveWidth();
 
   int dragged_tab_index = tab_strip_->GetActiveIndex();
-  EXPECT_GE(tab_strip_->tab_at(dragged_tab_index)->bounds().width(),
+  ASSERT_GE(tab_strip_->tab_at(dragged_tab_index)->bounds().width(),
             min_active_width);
 
   // Mark the active tab as being dragged.
   Tab* dragged_tab = tab_strip_->tab_at(dragged_tab_index);
-  dragged_tab->set_dragging(true);
-
-  gfx::AnimationContainerTestApi test_api(bounds_animator()->container());
+  tab_strip_->GetDragContext()->StartedDragging({dragged_tab});
 
   // Ending the drag triggers the tabstrip to begin animating this tab back
   // to its ideal bounds.
-  StopDragging(dragged_tab);
-  EXPECT_TRUE(bounds_animator()->IsAnimating(dragged_tab));
+  ASSERT_FALSE(tab_strip_->IsAnimating());
+  StopDragging({dragged_tab});
+  EXPECT_TRUE(tab_strip_->IsAnimating());
 
   // Change the ideal bounds of the tabs mid-animation by selecting a
   // different tab.
@@ -519,8 +515,7 @@
   // Once the animation completes, the dragged tab should have animated to
   // the new ideal bounds (computed with this as an inactive tab) rather
   // than the original ones (where it's an active tab).
-  const base::TimeDelta duration = bounds_animator()->GetAnimationDuration();
-  test_api.IncrementTime(duration);
+  tab_strip_->StopAnimating(false);
 
   EXPECT_FALSE(dragged_tab->dragging());
   EXPECT_LT(dragged_tab->bounds().width(), min_active_width);
diff --git a/chrome/browser/ui/views/tabs/z_orderable_tab_container_element.cc b/chrome/browser/ui/views/tabs/z_orderable_tab_container_element.cc
new file mode 100644
index 0000000..cd32fe7
--- /dev/null
+++ b/chrome/browser/ui/views/tabs/z_orderable_tab_container_element.cc
@@ -0,0 +1,51 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/tabs/z_orderable_tab_container_element.h"
+#include "base/bits.h"
+#include "chrome/browser/ui/views/tabs/tab.h"
+
+#include "chrome/browser/ui/views/tabs/tab_group_header.h"
+#include "chrome/browser/ui/views/tabs/tab_group_highlight.h"
+#include "chrome/browser/ui/views/tabs/tab_group_underline.h"
+#include "chrome/browser/ui/views/tabs/tab_style_views.h"
+#include "ui/views/view_utils.h"
+
+// static
+float ZOrderableTabContainerElement::CalculateZValue(views::View* child) {
+  Tab* tab = views::AsViewClass<Tab>(child);
+  TabGroupHeader* header = views::AsViewClass<TabGroupHeader>(child);
+  TabGroupUnderline* underline = views::AsViewClass<TabGroupUnderline>(child);
+  TabGroupHighlight* highlight = views::AsViewClass<TabGroupHighlight>(child);
+  DCHECK_EQ(1, !!tab + !!header + !!underline + !!highlight);
+
+  // Construct a bitfield that encodes |child|'s z-value. Higher-order bits
+  // encode more important properties - see usage below for details on each.
+  // The lowest-order |num_bits_reserved_for_tab_style_z_value| bits are
+  // reserved for the factors considered by TabStyle, e.g. selection and hover
+  // state.
+  constexpr int num_bits_reserved_for_tab_style_z_value =
+      base::bits::Log2Ceiling(static_cast<int>(TabStyle::kMaximumZValue) + 1);
+  enum ZValue {
+    kActiveTab = (1u << (num_bits_reserved_for_tab_style_z_value + 1)),
+    kGroupView = (1u << num_bits_reserved_for_tab_style_z_value)
+  };
+
+  // Group highlights will keep this z-value and be drawn below everything.
+  unsigned int z_value = 0;
+
+  // The active tab is always on top.
+  if (tab && tab->IsActive())
+    z_value |= kActiveTab;
+
+  // Group headers and underlines are painted above non-active tabs.
+  if (header || underline)
+    z_value |= kGroupView;
+
+  // The non-active tabs are painted next. They are ordered by their selected
+  // or hovered state, which is animated and thus real-valued.
+  const float tab_style_z_value =
+      tab ? tab->tab_style()->GetZValue() + 1.0f : 0.0f;
+  return z_value + tab_style_z_value;
+}
diff --git a/chrome/browser/ui/views/tabs/z_orderable_tab_container_element.h b/chrome/browser/ui/views/tabs/z_orderable_tab_container_element.h
new file mode 100644
index 0000000..e373599
--- /dev/null
+++ b/chrome/browser/ui/views/tabs/z_orderable_tab_container_element.h
@@ -0,0 +1,37 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_TABS_Z_ORDERABLE_TAB_CONTAINER_ELEMENT_H_
+#define CHROME_BROWSER_UI_VIEWS_TABS_Z_ORDERABLE_TAB_CONTAINER_ELEMENT_H_
+
+namespace views {
+class View;
+}
+
+// A class that calculates a z-value for a TabContainer child view (one of a
+// tab, a tab group header, a tab group underline, or a tab group highlight).
+// Can be compared with other ZOrderableTabContainerElements to determine paint
+// order of their associated views.
+class ZOrderableTabContainerElement {
+ public:
+  explicit ZOrderableTabContainerElement(views::View* const child)
+      : child_(child), z_value_(CalculateZValue(child)) {}
+
+  bool operator<(const ZOrderableTabContainerElement& rhs) const {
+    return z_value_ < rhs.z_value_;
+  }
+
+  views::View* view() const { return child_; }
+
+ private:
+  // Determines the 'height' of |child|, which should be used to determine the
+  // paint order of TabContainer's children.  Larger z-values should be painted
+  // on top of smaller ones.
+  static float CalculateZValue(views::View* child);
+
+  views::View* child_;
+  float z_value_;
+};  // ZOrderableTabContainerElement
+
+#endif  // CHROME_BROWSER_UI_VIEWS_TABS_Z_ORDERABLE_TAB_CONTAINER_ELEMENT_H_
diff --git a/chrome/browser/ui/views/translate/translate_bubble_controller.h b/chrome/browser/ui/views/translate/translate_bubble_controller.h
index faa7a666..d05fd74 100644
--- a/chrome/browser/ui/views/translate/translate_bubble_controller.h
+++ b/chrome/browser/ui/views/translate/translate_bubble_controller.h
@@ -47,8 +47,8 @@
       const std::u16string& text_selection,
       translate::TranslateErrors::Type error_type);
 
-  // Closes the current Partial or Full Page Translate bubble, if it exists. At
-  // most one of these bubble should be non-null at any given time.
+  // Closes the current Partial or Full Page Translate bubble, if either exists.
+  // At most one of these bubbles should be non-null at any given time.
   void CloseBubble();
 
   // Returns the currently shown Full Page Translate bubble view. Returns
@@ -79,7 +79,7 @@
   raw_ptr<TranslateBubbleView> translate_bubble_view_ = nullptr;
   raw_ptr<PartialTranslateBubbleView> partial_translate_bubble_view_ = nullptr;
 
-  // Factories used to construct models for the two different translate bubbles.
+  // Factories used to construct models for the two different Translate bubbles.
   // If the factory is null, the standard implementations -
   // TranslateBubbleModelImpl and PartialTranslateBubbleModelImpl - will be
   // used.
diff --git a/chrome/browser/ui/views/translate/translate_bubble_view_interactive_uitest.cc b/chrome/browser/ui/views/translate/translate_bubble_view_interactive_uitest.cc
index d52319d00..bd6e798 100644
--- a/chrome/browser/ui/views/translate/translate_bubble_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/translate/translate_bubble_view_interactive_uitest.cc
@@ -203,20 +203,20 @@
 IN_PROC_BROWSER_TEST_P(TranslateBubbleViewUITest, ClickLanguageTab) {
   UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
 
-  // P1.Opened/Navigate to non english page > Hit on translate bubble icon.
+  // P1.Opened/Navigate to non english page > Hit on Translate bubble icon.
   GURL french_url = GURL(embedded_test_server()->GetURL("/french_page.html"));
   NavigateAndWaitForLanguageDetection(french_url, "fr");
 
   ui::InteractionSequence::Builder()
       .SetAbortedCallback(aborted.Get())
-      // The dialog view of translate bubble is different across platforms.
+      // The dialog view of Translate bubble is different across platforms.
       // On Linux/Mac it's a AlertDialog under BrowserRootView tree.
       // On Windows it's a separate LocationBarBubbleDelegateView.
       // That it's getting the root view of translate dialog via
       // GetCurrentBubble method.
       .AddStep(views::InteractionSequenceViews::WithInitialView(
           GetCurrentTranslateBubble()))
-      // V1.Verify that by default the translate bubble’s source language
+      // V1.Verify that by default the Translate bubble’s source language
       // tab is selected and highlighted.
       .AddStep(ui::InteractionSequence::StepBuilder()
                    .SetElementID(TranslateBubbleView::kSourceLanguageTab)
@@ -266,13 +266,13 @@
                          EXPECT_TRUE(source_tab->selected());
                        }))
                    .Build())
-      // P4.Tap on cancel button option in the translate bubble popup box.
+      // P4.Tap on cancel button option in the Translate bubble popup box.
       .AddStep(ui::InteractionSequence::StepBuilder()
                    .SetElementID(TranslateBubbleView::kCloseButton)
                    .SetStartCallback(base::BindOnce(ButtonClickCallBack))
                    .SetMustRemainVisible(false)
                    .Build())
-      // V4.Tapping the close button dismisses the translate bubble.
+      // V4.Tapping the close button dismisses the Translate bubble.
       .AddStep(ui::InteractionSequence::StepBuilder()
                    .SetElementID(TranslateBubbleView::kIdentifier)
                    .SetType(ui::InteractionSequence::StepType::kHidden)
@@ -293,7 +293,7 @@
       .SetAbortedCallback(aborted.Get())
       .AddStep(views::InteractionSequenceViews::WithInitialView(
           GetCurrentTranslateBubble()))
-      // P2. Click on translate bubble > Click on 3 dot menu.
+      // P2. Click on Translate bubble > Click on 3 dot menu.
       .AddStep(ui::InteractionSequence::StepBuilder()
                    .SetElementID(TranslateBubbleView::kOptionsMenuButton)
                    .SetStartCallback(base::BindOnce(ElementClickCallback))
@@ -392,7 +392,7 @@
       .SetAbortedCallback(aborted.Get())
       .AddStep(views::InteractionSequenceViews::WithInitialView(
           GetCurrentTranslateBubble()))
-      // P2. Click on translate bubble > Click on 3 dot menu.
+      // P2. Click on Translate bubble > Click on 3 dot menu.
       .AddStep(ui::InteractionSequence::StepBuilder()
                    .SetElementID(TranslateBubbleView::kOptionsMenuButton)
                    .SetStartCallback(base::BindOnce(ElementClickCallback))
@@ -511,7 +511,7 @@
           ->GetDefaultStoragePartition()
           ->GetURLLoaderFactoryForBrowserProcess());
 
-  // P1. Opened/Navigate to non english page > Hit on translate bubble icon.
+  // P1. Opened/Navigate to non english page > Hit on Translate bubble icon.
   GURL french_url = GURL(embedded_test_server()->GetURL("/french_page.html"));
   NavigateAndWaitForLanguageDetection(french_url, "fr");
 
diff --git a/chrome/browser/ui/web_applications/web_app_uninstall_browsertest.cc b/chrome/browser/ui/web_applications/web_app_uninstall_browsertest.cc
index 9c186794..fc2288b 100644
--- a/chrome/browser/ui/web_applications/web_app_uninstall_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_uninstall_browsertest.cc
@@ -17,7 +17,9 @@
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
 #include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
 #include "chrome/browser/web_applications/isolation_prefs_utils.h"
+#include "chrome/browser/web_applications/test/web_app_test_observers.h"
 #include "chrome/browser/web_applications/web_app.h"
+#include "chrome/browser/web_applications/web_app_command_manager.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_install_finalizer.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
@@ -152,6 +154,7 @@
 
   base::RunLoop run_loop;
   bool quit_run_loop = false;
+  bool uninstall_delegate_called = false;
 
   // Trigger app uninstall without waiting for result.
   WebAppProvider* const provider = WebAppProvider::GetForTest(profile());
@@ -165,10 +168,7 @@
         quit_run_loop = true;
       }));
 
-  // Validate that uninstalling flag is set
-  auto* app = provider->registrar().GetAppById(app_id);
-  EXPECT_TRUE(app);
-  EXPECT_TRUE(app->is_uninstalling());
+  EXPECT_EQ(1u, provider->command_manager().GetCommandCountForTesting());
 
   // Trigger second uninstall call and wait for result.
   provider->install_finalizer().UninstallWebApp(
@@ -178,6 +178,23 @@
           run_loop.Quit();
         quit_run_loop = true;
       }));
+
+  EXPECT_EQ(2u, provider->command_manager().GetCommandCountForTesting());
+
+  WebAppInstallManagerObserverAdapter install_observer(
+      &provider->install_manager());
+  install_observer.SetWebAppWillBeUninstalledDelegate(
+      base::BindLambdaForTesting([&](const AppId& uninstall_app_id) {
+        EXPECT_EQ(app_id, uninstall_app_id);
+        EXPECT_FALSE(uninstall_delegate_called);
+
+        // Validate that uninstalling flag is set
+        auto* app = provider->registrar().GetAppById(app_id);
+        EXPECT_TRUE(app);
+        EXPECT_TRUE(app->is_uninstalling());
+        uninstall_delegate_called = true;
+      }));
+
   run_loop.Run();
   EXPECT_FALSE(provider->registrar().IsInstalled(app_id));
 }
diff --git a/chrome/browser/ui/webui/bookmarks/bookmarks_message_handler.cc b/chrome/browser/ui/webui/bookmarks/bookmarks_message_handler.cc
index 98a699f..6f128a825 100644
--- a/chrome/browser/ui/webui/bookmarks/bookmarks_message_handler.cc
+++ b/chrome/browser/ui/webui/bookmarks/bookmarks_message_handler.cc
@@ -17,12 +17,12 @@
 BookmarksMessageHandler::~BookmarksMessageHandler() {}
 
 void BookmarksMessageHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "getIncognitoAvailability",
       base::BindRepeating(
           &BookmarksMessageHandler::HandleGetIncognitoAvailability,
           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "getCanEditBookmarks",
       base::BindRepeating(&BookmarksMessageHandler::HandleGetCanEditBookmarks,
                           base::Unretained(this)));
@@ -51,9 +51,9 @@
 }
 
 void BookmarksMessageHandler::HandleGetIncognitoAvailability(
-    const base::ListValue* args) {
-  CHECK_EQ(1U, args->GetListDeprecated().size());
-  const base::Value& callback_id = args->GetListDeprecated()[0];
+    const base::Value::List& args) {
+  CHECK_EQ(1U, args.size());
+  const base::Value& callback_id = args[0];
 
   AllowJavascript();
 
@@ -72,9 +72,9 @@
 }
 
 void BookmarksMessageHandler::HandleGetCanEditBookmarks(
-    const base::ListValue* args) {
-  CHECK_EQ(1U, args->GetListDeprecated().size());
-  const base::Value& callback_id = args->GetListDeprecated()[0];
+    const base::Value::List& args) {
+  CHECK_EQ(1U, args.size());
+  const base::Value& callback_id = args[0];
 
   AllowJavascript();
 
diff --git a/chrome/browser/ui/webui/bookmarks/bookmarks_message_handler.h b/chrome/browser/ui/webui/bookmarks/bookmarks_message_handler.h
index 5819ecea..ec77cf64 100644
--- a/chrome/browser/ui/webui/bookmarks/bookmarks_message_handler.h
+++ b/chrome/browser/ui/webui/bookmarks/bookmarks_message_handler.h
@@ -8,10 +8,6 @@
 #include "components/prefs/pref_change_registrar.h"
 #include "content/public/browser/web_ui_message_handler.h"
 
-namespace base {
-class ListValue;
-}
-
 class BookmarksMessageHandler : public content::WebUIMessageHandler {
  public:
   BookmarksMessageHandler();
@@ -23,11 +19,11 @@
 
  private:
   int GetIncognitoAvailability();
-  void HandleGetIncognitoAvailability(const base::ListValue* args);
+  void HandleGetIncognitoAvailability(const base::Value::List& args);
   void UpdateIncognitoAvailability();
 
   bool CanEditBookmarks();
-  void HandleGetCanEditBookmarks(const base::ListValue* args);
+  void HandleGetCanEditBookmarks(const base::Value::List& args);
   void UpdateCanEditBookmarks();
 
   // content::WebUIMessageHandler:
diff --git a/chrome/browser/ui/webui/certificate_provisioning_ui_handler.cc b/chrome/browser/ui/webui/certificate_provisioning_ui_handler.cc
index bfb5614..a74008f 100644
--- a/chrome/browser/ui/webui/certificate_provisioning_ui_handler.cc
+++ b/chrome/browser/ui/webui/certificate_provisioning_ui_handler.cc
@@ -167,15 +167,15 @@
 
 void CertificateProvisioningUiHandler::RegisterMessages() {
   // Passing base::Unretained(this) to
-  // web_ui()->RegisterDeprecatedMessageCallback is fine because in chrome Web
+  // web_ui()->RegisterMessageCallback is fine because in chrome Web
   // UI, web_ui() has acquired ownership of |this| and maintains the life time
   // of |this| accordingly.
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "refreshCertificateProvisioningProcessses",
       base::BindRepeating(&CertificateProvisioningUiHandler::
                               HandleRefreshCertificateProvisioningProcesses,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "triggerCertificateProvisioningProcessUpdate",
       base::BindRepeating(&CertificateProvisioningUiHandler::
                               HandleTriggerCertificateProvisioningProcessUpdate,
@@ -199,20 +199,18 @@
 }
 
 void CertificateProvisioningUiHandler::
-    HandleRefreshCertificateProvisioningProcesses(const base::ListValue* args) {
-  CHECK_EQ(0U, args->GetListDeprecated().size());
+    HandleRefreshCertificateProvisioningProcesses(
+        const base::Value::List& args) {
+  CHECK_EQ(0U, args.size());
   AllowJavascript();
   RefreshCertificateProvisioningProcesses();
 }
 
 void CertificateProvisioningUiHandler::
     HandleTriggerCertificateProvisioningProcessUpdate(
-        const base::ListValue* args) {
-  CHECK_EQ(1U, args->GetListDeprecated().size());
-  if (!args->is_list()) {
-    return;
-  }
-  const base::Value& cert_profile_id = args->GetListDeprecated()[0];
+        const base::Value::List& args) {
+  CHECK_EQ(1U, args.size());
+  const base::Value& cert_profile_id = args[0];
   if (!cert_profile_id.is_string()) {
     return;
   }
diff --git a/chrome/browser/ui/webui/certificate_provisioning_ui_handler.h b/chrome/browser/ui/webui/certificate_provisioning_ui_handler.h
index 488cc17e..958f56a 100644
--- a/chrome/browser/ui/webui/certificate_provisioning_ui_handler.h
+++ b/chrome/browser/ui/webui/certificate_provisioning_ui_handler.h
@@ -60,7 +60,7 @@
   // the UI when it loads.
   // |args| is expected to be empty.
   void HandleRefreshCertificateProvisioningProcesses(
-      const base::ListValue* args);
+      const base::Value::List& args);
 
   // Trigger an update / refresh on a certificate provisioning process.
   // |args| is expected to contain two arguments:
@@ -69,7 +69,7 @@
   // index 1 is a boolean specifying whether the process is a user-specific
   // (false) or a device-wide (true) certificate provisioning process.
   void HandleTriggerCertificateProvisioningProcessUpdate(
-      const base::ListValue* args);
+      const base::Value::List& args);
 
   // Send the list of certificate provisioning processes to the UI.
   void RefreshCertificateProvisioningProcesses();
diff --git a/chrome/browser/ui/webui/certificate_viewer_webui.cc b/chrome/browser/ui/webui/certificate_viewer_webui.cc
index 3c6f7a1..3ee056b 100644
--- a/chrome/browser/ui/webui/certificate_viewer_webui.cc
+++ b/chrome/browser/ui/webui/certificate_viewer_webui.cc
@@ -363,12 +363,12 @@
 }
 
 void CertificateViewerDialogHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "exportCertificate",
       base::BindRepeating(
           &CertificateViewerDialogHandler::HandleExportCertificate,
           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "requestCertificateFields",
       base::BindRepeating(
           &CertificateViewerDialogHandler::HandleRequestCertificateFields,
@@ -376,8 +376,8 @@
 }
 
 void CertificateViewerDialogHandler::HandleExportCertificate(
-    const base::ListValue* args) {
-  int cert_index = GetCertificateIndex(args->GetListDeprecated()[0].GetInt());
+    const base::Value::List& args) {
+  int cert_index = GetCertificateIndex(args[0].GetInt());
   if (cert_index < 0)
     return;
 
@@ -395,10 +395,10 @@
 }
 
 void CertificateViewerDialogHandler::HandleRequestCertificateFields(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
   AllowJavascript();
-  const base::Value& callback_id = args->GetListDeprecated()[0];
-  int cert_index = GetCertificateIndex(args->GetListDeprecated()[1].GetInt());
+  const base::Value& callback_id = args[0];
+  int cert_index = GetCertificateIndex(args[1].GetInt());
   if (cert_index < 0)
     return;
 
@@ -487,13 +487,13 @@
                      .Build())
           .Build());
 
-  base::ListValue root_list;
+  base::Value::List root_list;
   root_list.Append(
       base::Value::FromUniquePtrValue(CertNodeBuilder(model.GetTitle())
                                           .Child(contents_builder.Build())
                                           .Build()));
   // Send certificate information to javascript.
-  ResolveJavascriptCallback(callback_id, root_list);
+  ResolveJavascriptCallback(callback_id, base::Value(std::move(root_list)));
 }
 
 int CertificateViewerDialogHandler::GetCertificateIndex(
diff --git a/chrome/browser/ui/webui/certificate_viewer_webui.h b/chrome/browser/ui/webui/certificate_viewer_webui.h
index 6073870e..0f02919 100644
--- a/chrome/browser/ui/webui/certificate_viewer_webui.h
+++ b/chrome/browser/ui/webui/certificate_viewer_webui.h
@@ -108,14 +108,14 @@
   // chain.
   //
   // The input is an integer index to the certificate in the chain to export.
-  void HandleExportCertificate(const base::ListValue* args);
+  void HandleExportCertificate(const base::Value::List& args);
 
   // Gets the details for a specific certificate in the certificate chain.
   // Responds with a tree structure containing the fields and values for certain
   // nodes.
   //
   // The input is an integer index to the certificate in the chain to view.
-  void HandleRequestCertificateFields(const base::ListValue* args);
+  void HandleRequestCertificateFields(const base::Value::List& args);
 
   // Helper function to get the certificate index. Returns -1 if the index is
   // out of range.
diff --git a/chrome/browser/ui/webui/chromeos/arc_power_control/arc_power_control_handler.cc b/chrome/browser/ui/webui/chromeos/arc_power_control/arc_power_control_handler.cc
index b8e2b33..f9b580af 100644
--- a/chrome/browser/ui/webui/chromeos/arc_power_control/arc_power_control_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/arc_power_control/arc_power_control_handler.cc
@@ -124,22 +124,22 @@
 }
 
 void ArcPowerControlHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "ready", base::BindRepeating(&ArcPowerControlHandler::HandleReady,
                                    base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setWakefulnessMode",
       base::BindRepeating(&ArcPowerControlHandler::HandleSetWakefulnessMode,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setThrottling",
       base::BindRepeating(&ArcPowerControlHandler::HandleSetThrottling,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "startTracing",
       base::BindRepeating(&ArcPowerControlHandler::HandleStartTracing,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "stopTracing",
       base::BindRepeating(&ArcPowerControlHandler::HandleStopTracing,
                           base::Unretained(this)));
@@ -174,7 +174,7 @@
   }
 }
 
-void ArcPowerControlHandler::HandleReady(const base::ListValue* args) {
+void ArcPowerControlHandler::HandleReady(const base::Value::List& args) {
   arc::mojom::PowerInstance* power_instance = ARC_GET_INSTANCE_FOR_METHOD(
       arc::ArcServiceManager::Get()->arc_bridge_service()->power(),
       GetWakefulnessMode);
@@ -195,15 +195,15 @@
 }
 
 void ArcPowerControlHandler::HandleSetWakefulnessMode(
-    const base::ListValue* args) {
-  DCHECK_EQ(1U, args->GetListDeprecated().size());
+    const base::Value::List& args) {
+  DCHECK_EQ(1U, args.size());
 
   if (!power_control_enabled_) {
     LOG(ERROR) << "Power control is not enabled";
     return;
   }
 
-  if (!args->GetListDeprecated()[0].is_string()) {
+  if (!args[0].is_string()) {
     LOG(ERROR) << "Invalid input";
     return;
   }
@@ -212,7 +212,7 @@
       arc::ArcServiceManager::Get()->arc_bridge_service()->power();
   DCHECK(power);
 
-  const std::string mode = args->GetListDeprecated()[0].GetString();
+  const std::string mode = args[0].GetString();
   if (mode == kWakenessfullWakeUp) {
     if (wakefulness_mode_ == arc::mojom::WakefulnessMode::ASLEEP) {
       arc::mojom::PowerInstance* const power_instance =
@@ -240,15 +240,16 @@
   }
 }
 
-void ArcPowerControlHandler::HandleSetThrottling(const base::ListValue* args) {
-  DCHECK_EQ(1U, args->GetListDeprecated().size());
+void ArcPowerControlHandler::HandleSetThrottling(
+    const base::Value::List& args) {
+  DCHECK_EQ(1U, args.size());
 
   if (!power_control_enabled_) {
     LOG(ERROR) << "Power control is not enabled";
     return;
   }
 
-  if (!args->GetListDeprecated()[0].is_string()) {
+  if (!args[0].is_string()) {
     LOG(ERROR) << "Invalid input";
     return;
   }
@@ -261,7 +262,7 @@
     return;
   }
 
-  const std::string mode = args->GetListDeprecated()[0].GetString();
+  const std::string mode = args[0].GetString();
   if (mode == kThrottlingDisable) {
     observer->SetActive(true);
     observer->SetEnforced(true);
@@ -277,13 +278,13 @@
   }
 }
 
-void ArcPowerControlHandler::HandleStartTracing(const base::ListValue* args) {
-  DCHECK(!args->GetListDeprecated().size());
+void ArcPowerControlHandler::HandleStartTracing(const base::Value::List& args) {
+  DCHECK(!args.size());
   StartTracing();
 }
 
-void ArcPowerControlHandler::HandleStopTracing(const base::ListValue* args) {
-  DCHECK(!args->GetListDeprecated().size());
+void ArcPowerControlHandler::HandleStopTracing(const base::Value::List& args) {
+  DCHECK(!args.size());
   StopTracing();
 }
 
diff --git a/chrome/browser/ui/webui/chromeos/arc_power_control/arc_power_control_handler.h b/chrome/browser/ui/webui/chromeos/arc_power_control/arc_power_control_handler.h
index 84ad07f..2068e28b 100644
--- a/chrome/browser/ui/webui/chromeos/arc_power_control/arc_power_control_handler.h
+++ b/chrome/browser/ui/webui/chromeos/arc_power_control/arc_power_control_handler.h
@@ -53,11 +53,11 @@
 
  private:
   // Handlers for calls from JS.
-  void HandleReady(const base::ListValue* args);
-  void HandleSetWakefulnessMode(const base::ListValue* args);
-  void HandleSetThrottling(const base::ListValue* args);
-  void HandleStartTracing(const base::ListValue* args);
-  void HandleStopTracing(const base::ListValue* args);
+  void HandleReady(const base::Value::List& args);
+  void HandleSetWakefulnessMode(const base::Value::List& args);
+  void HandleSetThrottling(const base::Value::List& args);
+  void HandleStartTracing(const base::Value::List& args);
+  void HandleStopTracing(const base::Value::List& args);
 
   void StartTracing();
   void StopTracing();
diff --git a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.cc b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.cc
index 23ba50b..3d82e0c 100644
--- a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.cc
@@ -43,7 +43,7 @@
 MultideviceLogsHandler::~MultideviceLogsHandler() = default;
 
 void MultideviceLogsHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "getMultideviceLogMessages",
       base::BindRepeating(&MultideviceLogsHandler::HandleGetLogMessages,
                           base::Unretained(this)));
@@ -57,14 +57,15 @@
   observation_.Reset();
 }
 
-void MultideviceLogsHandler::HandleGetLogMessages(const base::ListValue* args) {
+void MultideviceLogsHandler::HandleGetLogMessages(
+    const base::Value::List& args) {
   AllowJavascript();
-  const base::Value& callback_id = args->GetListDeprecated()[0];
-  base::Value list(base::Value::Type::LIST);
+  const base::Value& callback_id = args[0];
+  base::Value::List list;
   for (const auto& log : *LogBuffer::GetInstance()->logs()) {
     list.Append(LogMessageToDictionary(log));
   }
-  ResolveJavascriptCallback(callback_id, list);
+  ResolveJavascriptCallback(callback_id, base::Value(std::move(list)));
 }
 
 void MultideviceLogsHandler::OnLogBufferCleared() {
diff --git a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.h b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.h
index 5591e83..66f3548 100644
--- a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.h
+++ b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.h
@@ -7,12 +7,9 @@
 
 #include "ash/components/multidevice/logging/log_buffer.h"
 #include "base/scoped_observation.h"
+#include "base/values.h"
 #include "content/public/browser/web_ui_message_handler.h"
 
-namespace base {
-class ListValue;
-}  // namespace base
-
 namespace chromeos {
 
 namespace multidevice {
@@ -38,10 +35,10 @@
 
  private:
   // Message handler callback that returns the Log Buffer in dictionary form.
-  void HandleGetLogMessages(const base::ListValue* args);
+  void HandleGetLogMessages(const base::Value::List& args);
 
   // Message handler callback that clears the Log Buffer.
-  void ClearLogBuffer(const base::ListValue* args);
+  void ClearLogBuffer(const base::Value::List& args);
 
   base::ScopedObservation<LogBuffer, LogBuffer::Observer> observation_{this};
 };
diff --git a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc
index aa782eb4..4a453f8 100644
--- a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.cc
@@ -152,78 +152,78 @@
 }
 
 void MultidevicePhoneHubHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setFakePhoneHubManagerEnabled",
       base::BindRepeating(
           &MultidevicePhoneHubHandler::HandleEnableFakePhoneHubManager,
           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setFeatureStatus",
       base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetFeatureStatus,
                           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setShowOnboardingFlow",
       base::BindRepeating(
           &MultidevicePhoneHubHandler::HandleSetShowOnboardingFlow,
           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setFakePhoneName",
       base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetFakePhoneName,
                           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setFakePhoneStatus",
       base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetFakePhoneStatus,
                           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setBrowserTabs",
       base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetBrowserTabs,
                           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setNotification",
       base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetNotification,
                           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "removeNotification",
       base::BindRepeating(&MultidevicePhoneHubHandler::HandleRemoveNotification,
                           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "enableDnd",
       base::BindRepeating(&MultidevicePhoneHubHandler::HandleEnableDnd,
                           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setFindMyDeviceStatus",
       base::BindRepeating(
           &MultidevicePhoneHubHandler::HandleSetFindMyDeviceStatus,
           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setTetherStatus",
       base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetTetherStatus,
                           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "resetShouldShowOnboardingUi",
       base::BindRepeating(
           &MultidevicePhoneHubHandler::HandleResetShouldShowOnboardingUi,
           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "resetHasMultideviceFeatureSetupUiBeenDismissed",
       base::BindRepeating(
           &MultidevicePhoneHubHandler::
               HandleResetHasMultideviceFeatureSetupUiBeenDismissed,
           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setFakeCameraRoll",
       base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetFakeCameraRoll,
                           base::Unretained(this)));
@@ -304,8 +304,8 @@
   FireWebUIListener("camera-roll-ui-view-state-updated", camera_roll_dict);
 }
 
-void MultidevicePhoneHubHandler::HandleEnableDnd(const base::ListValue* args) {
-  const auto& list = args->GetListDeprecated();
+void MultidevicePhoneHubHandler::HandleEnableDnd(
+    const base::Value::List& list) {
   CHECK(!list.empty());
   const bool enabled = list[0].GetBool();
   PA_LOG(VERBOSE) << "Setting Do Not Disturb state to " << enabled;
@@ -315,8 +315,7 @@
 }
 
 void MultidevicePhoneHubHandler::HandleSetFindMyDeviceStatus(
-    const base::ListValue* args) {
-  const auto& list = args->GetListDeprecated();
+    const base::Value::List& list) {
   CHECK_GE(list.size(), 1u);
   int status_as_int = list[0].GetInt();
 
@@ -328,8 +327,7 @@
 }
 
 void MultidevicePhoneHubHandler::HandleSetTetherStatus(
-    const base::ListValue* args) {
-  const auto& list = args->GetListDeprecated();
+    const base::Value::List& list) {
   CHECK_GE(list.size(), 1u);
   int status_as_int = list[0].GetInt();
 
@@ -363,9 +361,8 @@
 }
 
 void MultidevicePhoneHubHandler::HandleEnableFakePhoneHubManager(
-    const base::ListValue* args) {
+    const base::Value::List& list) {
   AllowJavascript();
-  const auto& list = args->GetListDeprecated();
   CHECK(!list.empty());
   const bool enabled = list[0].GetBool();
   if (enabled) {
@@ -376,8 +373,7 @@
 }
 
 void MultidevicePhoneHubHandler::HandleSetFeatureStatus(
-    const base::ListValue* args) {
-  const auto& list = args->GetListDeprecated();
+    const base::Value::List& list) {
   CHECK_GE(list.size(), 1u);
   int feature_as_int = list[0].GetInt();
 
@@ -387,8 +383,7 @@
 }
 
 void MultidevicePhoneHubHandler::HandleSetShowOnboardingFlow(
-    const base::ListValue* args) {
-  const auto& list = args->GetListDeprecated();
+    const base::Value::List& list) {
   CHECK(!list.empty());
   const bool show_onboarding_flow = list[0].GetBool();
   PA_LOG(VERBOSE) << "Setting show onboarding flow to " << show_onboarding_flow;
@@ -397,8 +392,7 @@
 }
 
 void MultidevicePhoneHubHandler::HandleSetFakePhoneName(
-    const base::ListValue* args) {
-  base::Value::ConstListView args_list = args->GetListDeprecated();
+    const base::Value::List& args_list) {
   CHECK_GE(args_list.size(), 1u);
   std::u16string phone_name = base::UTF8ToUTF16(args_list[0].GetString());
   fake_phone_hub_manager_->mutable_phone_model()->SetPhoneName(phone_name);
@@ -406,8 +400,8 @@
 }
 
 void MultidevicePhoneHubHandler::HandleSetFakePhoneStatus(
-    const base::ListValue* args) {
-  const base::Value& phones_status_value = args->GetListDeprecated()[0];
+    const base::Value::List& args) {
+  const base::Value& phones_status_value = args[0];
   CHECK(phones_status_value.is_dict());
 
   absl::optional<int> mobile_status_as_int =
@@ -468,8 +462,8 @@
 }
 
 void MultidevicePhoneHubHandler::HandleSetBrowserTabs(
-    const base::ListValue* args) {
-  const base::Value& browser_tab_status_value = args->GetListDeprecated()[0];
+    const base::Value::List& args) {
+  const base::Value& browser_tab_status_value = args[0];
   CHECK(browser_tab_status_value.is_dict());
   const base::DictionaryValue* browser_tab_status_dict =
       static_cast<const base::DictionaryValue*>(&browser_tab_status_value);
@@ -504,8 +498,8 @@
 }
 
 void MultidevicePhoneHubHandler::HandleSetNotification(
-    const base::ListValue* args) {
-  const base::Value& notification_data_value = args->GetListDeprecated()[0];
+    const base::Value::List& args) {
+  const base::Value& notification_data_value = args[0];
   CHECK(notification_data_value.is_dict());
 
   absl::optional<int> id = notification_data_value.GetDict().FindInt("id");
@@ -580,8 +574,7 @@
 }
 
 void MultidevicePhoneHubHandler::HandleRemoveNotification(
-    const base::ListValue* args) {
-  const auto& list = args->GetListDeprecated();
+    const base::Value::List& list) {
   CHECK_GE(list.size(), 1u);
   int notification_id = list[0].GetInt();
   fake_phone_hub_manager_->fake_notification_manager()->RemoveNotification(
@@ -590,7 +583,7 @@
 }
 
 void MultidevicePhoneHubHandler::HandleResetShouldShowOnboardingUi(
-    const base::ListValue* args) {
+    const base::Value::List& list) {
   PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
   prefs->SetBoolean(phonehub::prefs::kHideOnboardingUi, false);
   PA_LOG(VERBOSE) << "Reset kHideOnboardingUi pref";
@@ -598,15 +591,15 @@
 
 void MultidevicePhoneHubHandler::
     HandleResetHasMultideviceFeatureSetupUiBeenDismissed(
-        const base::ListValue* args) {
+        const base::Value::List& list) {
   PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
   prefs->SetBoolean(phonehub::prefs::kHasDismissedSetupRequiredUi, false);
   PA_LOG(VERBOSE) << "Reset kHasDismissedSetupRequiredUi pref";
 }
 
 void MultidevicePhoneHubHandler::HandleSetFakeCameraRoll(
-    const base::ListValue* args) {
-  const base::Value& camera_roll_dict = args->GetListDeprecated()[0];
+    const base::Value::List& args) {
+  const base::Value& camera_roll_dict = args[0];
   CHECK(camera_roll_dict.is_dict());
 
   absl::optional<bool> is_camera_roll_enabled =
diff --git a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h
index 7bfb7b44..68427e9 100644
--- a/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h
+++ b/chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h
@@ -62,21 +62,21 @@
 
   void EnableRealPhoneHubManager();
   void EnableFakePhoneHubManager();
-  void HandleEnableFakePhoneHubManager(const base::ListValue* args);
-  void HandleSetFeatureStatus(const base::ListValue* args);
-  void HandleSetShowOnboardingFlow(const base::ListValue* args);
-  void HandleSetFakePhoneName(const base::ListValue* args);
-  void HandleSetFakePhoneStatus(const base::ListValue* args);
-  void HandleSetBrowserTabs(const base::ListValue* args);
-  void HandleSetNotification(const base::ListValue* args);
-  void HandleRemoveNotification(const base::ListValue* args);
-  void HandleEnableDnd(const base::ListValue* args);
-  void HandleSetFindMyDeviceStatus(const base::ListValue* args);
-  void HandleSetTetherStatus(const base::ListValue* args);
-  void HandleResetShouldShowOnboardingUi(const base::ListValue* args);
+  void HandleEnableFakePhoneHubManager(const base::Value::List& args);
+  void HandleSetFeatureStatus(const base::Value::List& args);
+  void HandleSetShowOnboardingFlow(const base::Value::List& args);
+  void HandleSetFakePhoneName(const base::Value::List& args);
+  void HandleSetFakePhoneStatus(const base::Value::List& args);
+  void HandleSetBrowserTabs(const base::Value::List& args);
+  void HandleSetNotification(const base::Value::List& args);
+  void HandleRemoveNotification(const base::Value::List& args);
+  void HandleEnableDnd(const base::Value::List& args);
+  void HandleSetFindMyDeviceStatus(const base::Value::List& args);
+  void HandleSetTetherStatus(const base::Value::List& args);
+  void HandleResetShouldShowOnboardingUi(const base::Value::List& args);
   void HandleResetHasMultideviceFeatureSetupUiBeenDismissed(
-      const base::ListValue* args);
-  void HandleSetFakeCameraRoll(const base::ListValue* args);
+      const base::Value::List& args);
+  void HandleSetFakeCameraRoll(const base::Value::List& args);
 
   void AddObservers();
   void RemoveObservers();
diff --git a/chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_handler.cc b/chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_handler.cc
index 75f0e27b..1fa51a5 100644
--- a/chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_handler.cc
@@ -24,11 +24,11 @@
 MultideviceSetupHandler::~MultideviceSetupHandler() = default;
 
 void MultideviceSetupHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "getProfileInfo",
       base::BindRepeating(&MultideviceSetupHandler::HandleGetProfileInfo,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "openMultiDeviceSettings",
       base::BindRepeating(
           &MultideviceSetupHandler::HandleOpenMultiDeviceSettings,
@@ -36,11 +36,11 @@
 }
 
 void MultideviceSetupHandler::HandleGetProfileInfo(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
   AllowJavascript();
 
-  DCHECK(!args->GetListDeprecated().empty());
-  std::string callback_id = args->GetListDeprecated()[0].GetString();
+  DCHECK(!args.empty());
+  std::string callback_id = args[0].GetString();
 
   const user_manager::User* user =
       chromeos::ProfileHelper::Get()->GetUserByProfile(
@@ -58,8 +58,8 @@
 }
 
 void MultideviceSetupHandler::HandleOpenMultiDeviceSettings(
-    const base::ListValue* args) {
-  DCHECK(args->GetListDeprecated().empty());
+    const base::Value::List& args) {
+  DCHECK(args.empty());
   chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
       Profile::FromWebUI(web_ui()),
       chromeos::settings::mojom::kMultiDeviceFeaturesSubpagePath);
diff --git a/chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_handler.h b/chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_handler.h
index a4c64b1..1db39cf 100644
--- a/chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_handler.h
+++ b/chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_handler.h
@@ -25,8 +25,8 @@
   // content::WebUIMessageHandler:
   void RegisterMessages() override;
 
-  void HandleGetProfileInfo(const base::ListValue* args);
-  void HandleOpenMultiDeviceSettings(const base::ListValue* args);
+  void HandleGetProfileInfo(const base::Value::List& args);
+  void HandleOpenMultiDeviceSettings(const base::Value::List& args);
 };
 
 }  // namespace multidevice_setup
diff --git a/chrome/browser/ui/webui/chromeos/network_logs_message_handler.cc b/chrome/browser/ui/webui/chromeos/network_logs_message_handler.cc
index 117f6243..1c86872 100644
--- a/chrome/browser/ui/webui/chromeos/network_logs_message_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/network_logs_message_handler.cc
@@ -66,10 +66,10 @@
 
 void NetworkLogsMessageHandler::RegisterMessages() {
   out_dir_ = GetDownloadsDirectory(web_ui());
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "storeLogs", base::BindRepeating(&NetworkLogsMessageHandler::OnStoreLogs,
                                        base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "setShillDebugging",
       base::BindRepeating(&NetworkLogsMessageHandler::OnSetShillDebugging,
                           base::Unretained(this)));
@@ -84,10 +84,10 @@
   ResolveJavascriptCallback(base::Value(callback_id), response);
 }
 
-void NetworkLogsMessageHandler::OnStoreLogs(const base::ListValue* list) {
-  CHECK_EQ(2u, list->GetListDeprecated().size());
-  std::string callback_id = list->GetListDeprecated()[0].GetString();
-  const base::Value& options = list->GetListDeprecated()[1];
+void NetworkLogsMessageHandler::OnStoreLogs(const base::Value::List& list) {
+  CHECK_EQ(2u, list.size());
+  std::string callback_id = list[0].GetString();
+  const base::Value& options = list[1];
   AllowJavascript();
 
   if (GetBoolOrFalse(options, "systemLogs")) {
@@ -179,10 +179,10 @@
 }
 
 void NetworkLogsMessageHandler::OnSetShillDebugging(
-    const base::ListValue* list) {
-  CHECK_EQ(2u, list->GetListDeprecated().size());
-  std::string callback_id = list->GetListDeprecated()[0].GetString();
-  std::string subsystem = list->GetListDeprecated()[1].GetString();
+    const base::Value::List& list) {
+  CHECK_EQ(2u, list.size());
+  std::string callback_id = list[0].GetString();
+  std::string subsystem = list[1].GetString();
   AllowJavascript();
   chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->SetDebugMode(
       subsystem,
diff --git a/chrome/browser/ui/webui/chromeos/network_logs_message_handler.h b/chrome/browser/ui/webui/chromeos/network_logs_message_handler.h
index 3623d37c6..0b4cc16 100644
--- a/chrome/browser/ui/webui/chromeos/network_logs_message_handler.h
+++ b/chrome/browser/ui/webui/chromeos/network_logs_message_handler.h
@@ -30,7 +30,7 @@
   void Respond(const std::string& callback_id,
                const std::string& result,
                bool is_error);
-  void OnStoreLogs(const base::ListValue* list);
+  void OnStoreLogs(const base::Value::List& list);
   void OnWriteSystemLogs(const std::string& callback_id,
                          base::Value&& options,
                          absl::optional<base::FilePath> syslogs_path);
@@ -43,7 +43,7 @@
                           base::Value&& options);
   void OnWritePolicies(const std::string& callback_id, bool result);
   void OnWriteSystemLogsCompleted(const std::string& callback_id);
-  void OnSetShillDebugging(const base::ListValue* list);
+  void OnSetShillDebugging(const base::Value::List& list);
   void OnSetShillDebuggingCompleted(const std::string& callback_id,
                                     bool succeeded);
 
diff --git a/chrome/browser/ui/webui/chromeos/onc_import_message_handler.cc b/chrome/browser/ui/webui/chromeos/onc_import_message_handler.cc
index 9c76401..8232b50 100644
--- a/chrome/browser/ui/webui/chromeos/onc_import_message_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/onc_import_message_handler.cc
@@ -46,7 +46,7 @@
 OncImportMessageHandler::~OncImportMessageHandler() = default;
 
 void OncImportMessageHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "importONC", base::BindRepeating(&OncImportMessageHandler::OnImportONC,
                                        base::Unretained(this)));
 }
@@ -60,10 +60,10 @@
   ResolveJavascriptCallback(base::Value(callback_id), response);
 }
 
-void OncImportMessageHandler::OnImportONC(const base::ListValue* list) {
-  CHECK_EQ(2u, list->GetListDeprecated().size());
-  std::string callback_id = list->GetListDeprecated()[0].GetString();
-  std::string onc_blob = list->GetListDeprecated()[1].GetString();
+void OncImportMessageHandler::OnImportONC(const base::Value::List& list) {
+  CHECK_EQ(2u, list.size());
+  std::string callback_id = list[0].GetString();
+  std::string onc_blob = list[1].GetString();
   AllowJavascript();
 
   // TODO(https://crbug.com/1186373): Pass the `NssCertDatabaseGetter` to
diff --git a/chrome/browser/ui/webui/chromeos/onc_import_message_handler.h b/chrome/browser/ui/webui/chromeos/onc_import_message_handler.h
index 834850e6..3e63b99 100644
--- a/chrome/browser/ui/webui/chromeos/onc_import_message_handler.h
+++ b/chrome/browser/ui/webui/chromeos/onc_import_message_handler.h
@@ -8,12 +8,9 @@
 #include <string>
 
 #include "base/memory/weak_ptr.h"
+#include "base/values.h"
 #include "content/public/browser/web_ui_message_handler.h"
 
-namespace base {
-class ListValue;
-}
-
 namespace net {
 class NSSCertDatabase;
 }
@@ -38,7 +35,7 @@
   void Respond(const std::string& callback_id,
                const std::string& result,
                bool is_error);
-  void OnImportONC(const base::ListValue* list);
+  void OnImportONC(const base::Value::List& list);
   void ImportONCToNSSDB(const std::string& callback_id,
                         const std::string& onc_blob,
                         net::NSSCertDatabase* nssdb);
diff --git a/chrome/browser/ui/webui/chromeos/power_ui.cc b/chrome/browser/ui/webui/chromeos/power_ui.cc
index 69f9620..8c2e516 100644
--- a/chrome/browser/ui/webui/chromeos/power_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/power_ui.cc
@@ -48,15 +48,14 @@
   void RegisterMessages() override;
 
  private:
-  void OnGetBatteryChargeData(const base::ListValue* value);
-  void OnGetCpuIdleData(const base::ListValue* value);
-  void OnGetCpuFreqData(const base::ListValue* value);
-  void OnGetProcessUsageData(const base::ListValue* value);
-  void GetJsStateOccupancyData(
+  void OnGetBatteryChargeData(const base::Value::List& value);
+  void OnGetCpuIdleData(const base::Value::List& value);
+  void OnGetCpuFreqData(const base::Value::List& value);
+  void OnGetProcessUsageData(const base::Value::List& value);
+  base::Value::List GetJsStateOccupancyData(
       const std::vector<CpuDataCollector::StateOccupancySampleDeque>& data,
-      const std::vector<std::string>& state_names,
-      base::ListValue* js_data);
-  void GetJsSystemResumedData(base::ListValue* value);
+      const std::vector<std::string>& state_names);
+  base::Value::List GetJsSystemResumedData();
 };
 
 PowerMessageHandler::PowerMessageHandler() {
@@ -66,30 +65,31 @@
 }
 
 void PowerMessageHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       kRequestBatteryChargeDataCallback,
       base::BindRepeating(&PowerMessageHandler::OnGetBatteryChargeData,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       kRequestCpuIdleDataCallback,
       base::BindRepeating(&PowerMessageHandler::OnGetCpuIdleData,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       kRequestCpuFreqDataCallback,
       base::BindRepeating(&PowerMessageHandler::OnGetCpuFreqData,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       kRequestProcessUsageDataCallback,
       base::BindRepeating(&PowerMessageHandler::OnGetProcessUsageData,
                           base::Unretained(this)));
 }
 
-void PowerMessageHandler::OnGetBatteryChargeData(const base::ListValue* args) {
+void PowerMessageHandler::OnGetBatteryChargeData(
+    const base::Value::List& args) {
   AllowJavascript();
 
   const base::circular_deque<PowerDataCollector::PowerSupplySample>&
       power_supply = PowerDataCollector::Get()->power_supply_data();
-  base::ListValue js_power_supply_data;
+  base::Value::List js_power_supply_data;
   for (size_t i = 0; i < power_supply.size(); ++i) {
     const PowerDataCollector::PowerSupplySample& sample = power_supply[i];
     base::Value::Dict element;
@@ -101,17 +101,16 @@
     js_power_supply_data.Append(base::Value(std::move(element)));
   }
 
-  base::ListValue js_system_resumed_data;
-  GetJsSystemResumedData(&js_system_resumed_data);
+  base::Value::List js_system_resumed_data = GetJsSystemResumedData();
 
-  base::DictionaryValue data;
-  data.SetKey("powerSupplyData", std::move(js_power_supply_data));
-  data.SetKey("systemResumedData", std::move(js_system_resumed_data));
-  const base::Value& callback_id = args->GetListDeprecated()[0];
-  ResolveJavascriptCallback(callback_id, data);
+  base::Value::Dict data;
+  data.Set("powerSupplyData", std::move(js_power_supply_data));
+  data.Set("systemResumedData", std::move(js_system_resumed_data));
+  const base::Value& callback_id = args[0];
+  ResolveJavascriptCallback(callback_id, base::Value(std::move(data)));
 }
 
-void PowerMessageHandler::OnGetCpuIdleData(const base::ListValue* args) {
+void PowerMessageHandler::OnGetCpuIdleData(const base::Value::List& args) {
   AllowJavascript();
 
   const CpuDataCollector& cpu_data_collector =
@@ -121,20 +120,19 @@
       cpu_data_collector.cpu_idle_state_data();
   const std::vector<std::string>& idle_state_names =
       cpu_data_collector.cpu_idle_state_names();
-  base::ListValue js_idle_data;
-  GetJsStateOccupancyData(idle_data, idle_state_names, &js_idle_data);
+  base::Value::List js_idle_data =
+      GetJsStateOccupancyData(idle_data, idle_state_names);
 
-  base::ListValue js_system_resumed_data;
-  GetJsSystemResumedData(&js_system_resumed_data);
+  base::Value::List js_system_resumed_data = GetJsSystemResumedData();
 
-  base::DictionaryValue data;
-  data.SetKey("idleStateData", std::move(js_idle_data));
-  data.SetKey("systemResumedData", std::move(js_system_resumed_data));
-  const base::Value& callback_id = args->GetListDeprecated()[0];
-  ResolveJavascriptCallback(callback_id, data);
+  base::Value::Dict data;
+  data.Set("idleStateData", std::move(js_idle_data));
+  data.Set("systemResumedData", std::move(js_system_resumed_data));
+  const base::Value& callback_id = args[0];
+  ResolveJavascriptCallback(callback_id, base::Value(std::move(data)));
 }
 
-void PowerMessageHandler::OnGetCpuFreqData(const base::ListValue* args) {
+void PowerMessageHandler::OnGetCpuFreqData(const base::Value::List& args) {
   AllowJavascript();
 
   const CpuDataCollector& cpu_data_collector =
@@ -144,29 +142,28 @@
       cpu_data_collector.cpu_freq_state_data();
   const std::vector<std::string>& freq_state_names =
       cpu_data_collector.cpu_freq_state_names();
-  base::ListValue js_freq_data;
-  GetJsStateOccupancyData(freq_data, freq_state_names, &js_freq_data);
+  base::Value::List js_freq_data =
+      GetJsStateOccupancyData(freq_data, freq_state_names);
 
-  base::ListValue js_system_resumed_data;
-  GetJsSystemResumedData(&js_system_resumed_data);
+  base::Value::List js_system_resumed_data = GetJsSystemResumedData();
 
-  base::DictionaryValue data;
-  data.SetKey("freqStateData", std::move(js_freq_data));
-  data.SetKey("systemResumedData", std::move(js_system_resumed_data));
-  const base::Value& callback_id = args->GetListDeprecated()[0];
-  ResolveJavascriptCallback(callback_id, data);
+  base::Value::Dict data;
+  data.Set("freqStateData", std::move(js_freq_data));
+  data.Set("systemResumedData", std::move(js_system_resumed_data));
+  const base::Value& callback_id = args[0];
+  ResolveJavascriptCallback(callback_id, base::Value(std::move(data)));
 }
 
-void PowerMessageHandler::OnGetProcessUsageData(const base::ListValue* args) {
+void PowerMessageHandler::OnGetProcessUsageData(const base::Value::List& args) {
   AllowJavascript();
-  CHECK_EQ(1U, args->GetListDeprecated().size());
+  CHECK_EQ(1U, args.size());
 
-  const base::Value& callback_id = args->GetListDeprecated()[0];
+  const base::Value& callback_id = args[0];
 
   const std::vector<ProcessDataCollector::ProcessUsageData>& process_list =
       ProcessDataCollector::Get()->GetProcessUsages();
 
-  base::ListValue js_process_usages;
+  base::Value::List js_process_usages;
   for (const auto& process_info : process_list) {
     base::Value::Dict element;
     element.Set("pid", process_info.process_data.pid);
@@ -177,11 +174,12 @@
     js_process_usages.Append(base::Value(std::move(element)));
   }
 
-  ResolveJavascriptCallback(callback_id, js_process_usages);
+  ResolveJavascriptCallback(callback_id,
+                            base::Value(std::move(js_process_usages)));
 }
 
-void PowerMessageHandler::GetJsSystemResumedData(base::ListValue *data) {
-  DCHECK(data);
+base::Value::List PowerMessageHandler::GetJsSystemResumedData() {
+  base::Value::List data;
 
   const base::circular_deque<PowerDataCollector::SystemResumedSample>&
       system_resumed = PowerDataCollector::Get()->system_resumed_data();
@@ -191,14 +189,15 @@
     element.Set("sleepDuration", sample.sleep_duration.InMillisecondsF());
     element.Set("time", sample.time.ToJsTime());
 
-    data->Append(base::Value(std::move(element)));
+    data.Append(base::Value(std::move(element)));
   }
+  return data;
 }
 
-void PowerMessageHandler::GetJsStateOccupancyData(
+base::Value::List PowerMessageHandler::GetJsStateOccupancyData(
     const std::vector<CpuDataCollector::StateOccupancySampleDeque>& data,
-    const std::vector<std::string>& state_names,
-    base::ListValue *js_data) {
+    const std::vector<std::string>& state_names) {
+  base::Value::List js_data;
   for (unsigned int cpu = 0; cpu < data.size(); ++cpu) {
     const CpuDataCollector::StateOccupancySampleDeque& sample_deque = data[cpu];
     base::Value::List js_sample_list;
@@ -217,8 +216,9 @@
 
       js_sample_list.Append(std::move(js_sample));
     }
-    js_data->Append(base::Value(std::move(js_sample_list)));
+    js_data.Append(base::Value(std::move(js_sample_list)));
   }
+  return js_data;
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/webui/chromeos/set_time_ui.cc b/chrome/browser/ui/webui/chromeos/set_time_ui.cc
index 039a4d4..ebbc75e4 100644
--- a/chrome/browser/ui/webui/chromeos/set_time_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/set_time_ui.cc
@@ -54,19 +54,19 @@
 
   // WebUIMessageHandler:
   void RegisterMessages() override {
-    web_ui()->RegisterDeprecatedMessageCallback(
+    web_ui()->RegisterMessageCallback(
         "setTimePageReady",
         base::BindRepeating(&SetTimeMessageHandler::OnPageReady,
                             base::Unretained(this)));
-    web_ui()->RegisterDeprecatedMessageCallback(
+    web_ui()->RegisterMessageCallback(
         "setTimeInSeconds",
         base::BindRepeating(&SetTimeMessageHandler::OnSetTime,
                             base::Unretained(this)));
-    web_ui()->RegisterDeprecatedMessageCallback(
+    web_ui()->RegisterMessageCallback(
         "setTimezone",
         base::BindRepeating(&SetTimeMessageHandler::OnSetTimezone,
                             base::Unretained(this)));
-    web_ui()->RegisterDeprecatedMessageCallback(
+    web_ui()->RegisterMessageCallback(
         "doneClicked", base::BindRepeating(&SetTimeMessageHandler::DoneClicked,
                                            base::Unretained(this)));
   }
@@ -82,7 +82,7 @@
   }
 
  private:
-  void OnPageReady(const base::ListValue* args) { AllowJavascript(); }
+  void OnPageReady(const base::Value::List& args) { AllowJavascript(); }
 
   // SystemClockClient::Observer:
   void SystemClockUpdated() override {
@@ -102,35 +102,34 @@
   // Handler for Javascript call to set the system clock when the user sets a
   // new time. Expects the time as the number of seconds since the Unix
   // epoch, treated as a double.
-  void OnSetTime(const base::ListValue* args) {
-    double seconds = args->GetListDeprecated()[0].GetDouble();
+  void OnSetTime(const base::Value::List& args) {
+    double seconds = args[0].GetDouble();
     SystemClockClient::Get()->SetTime(static_cast<int64_t>(seconds));
   }
 
   // Handler for Javascript call to change the system time zone when the user
   // selects a new time zone. Expects the time zone ID as a string, as it
   // appears in the time zone option values.
-  void OnSetTimezone(const base::ListValue* args) {
-    if (args->GetListDeprecated().empty() ||
-        !args->GetListDeprecated()[0].is_string()) {
+  void OnSetTimezone(const base::Value::List& args) {
+    if (args.empty() || !args[0].is_string()) {
       NOTREACHED();
       return;
     }
-    std::string timezone_id = args->GetListDeprecated()[0].GetString();
+    std::string timezone_id = args[0].GetString();
 
     Profile* profile = Profile::FromWebUI(web_ui());
     DCHECK(profile);
     system::SetTimezoneFromUI(profile, timezone_id);
   }
 
-  void DoneClicked(const base::ListValue* args) {
+  void DoneClicked(const base::Value::List& args) {
     if (!parent_access::ParentAccessService::IsApprovalRequired(
             ash::SupervisedAction::kUpdateClock)) {
       OnParentAccessValidation(true);
       return;
     }
 
-    double seconds = args->GetListDeprecated()[0].GetDouble();
+    double seconds = args[0].GetDouble();
     AccountId account_id;
     bool is_user_logged_in = user_manager::UserManager::Get()->IsUserLoggedIn();
     if (is_user_logged_in) {
diff --git a/chrome/browser/ui/webui/chromeos/slow_ui.cc b/chrome/browser/ui/webui/chromeos/slow_ui.cc
index 6e945d4..f57da45 100644
--- a/chrome/browser/ui/webui/chromeos/slow_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/slow_ui.cc
@@ -74,9 +74,9 @@
   void UpdatePage();
 
   // Handlers for JS WebUI messages.
-  void HandleDisable(const base::ListValue* args);
-  void HandleEnable(const base::ListValue* args);
-  void LoadComplete(const base::ListValue* args);
+  void HandleDisable(const base::Value::List& args);
+  void HandleEnable(const base::Value::List& args);
+  void LoadComplete(const base::Value::List& args);
 
   Profile* profile_;
   std::unique_ptr<PrefChangeRegistrar> user_pref_registrar_;
@@ -93,13 +93,13 @@
 }
 
 void SlowHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       kJsApiDisableTracing,
       base::BindRepeating(&SlowHandler::HandleDisable, base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       kJsApiEnableTracing,
       base::BindRepeating(&SlowHandler::HandleEnable, base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       kJsApiLoadComplete,
       base::BindRepeating(&SlowHandler::LoadComplete, base::Unretained(this)));
 }
@@ -114,17 +114,17 @@
   user_pref_registrar_->RemoveAll();
 }
 
-void SlowHandler::HandleDisable(const base::ListValue* args) {
+void SlowHandler::HandleDisable(const base::Value::List& args) {
   PrefService* pref_service = profile_->GetPrefs();
   pref_service->SetBoolean(prefs::kPerformanceTracingEnabled, false);
 }
 
-void SlowHandler::HandleEnable(const base::ListValue* args) {
+void SlowHandler::HandleEnable(const base::Value::List& args) {
   PrefService* pref_service = profile_->GetPrefs();
   pref_service->SetBoolean(prefs::kPerformanceTracingEnabled, true);
 }
 
-void SlowHandler::LoadComplete(const base::ListValue* args) {
+void SlowHandler::LoadComplete(const base::Value::List& args) {
   AllowJavascript();
   UpdatePage();
 }
diff --git a/chrome/browser/ui/webui/chromeos/sync/os_sync_handler.cc b/chrome/browser/ui/webui/chromeos/sync/os_sync_handler.cc
index 1abd0c80..b0e35bb 100644
--- a/chrome/browser/ui/webui/chromeos/sync/os_sync_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/sync/os_sync_handler.cc
@@ -36,19 +36,19 @@
 }
 
 void OSSyncHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "DidNavigateToOsSyncPage",
       base::BindRepeating(&OSSyncHandler::HandleDidNavigateToOsSyncPage,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "DidNavigateAwayFromOsSyncPage",
       base::BindRepeating(&OSSyncHandler::HandleDidNavigateAwayFromOsSyncPage,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "OsSyncPrefsDispatch",
       base::BindRepeating(&OSSyncHandler::HandleOsSyncPrefsDispatch,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "SetOsSyncDatatypes",
       base::BindRepeating(&OSSyncHandler::HandleSetOsSyncDatatypes,
                           base::Unretained(this)));
@@ -67,24 +67,25 @@
     PushSyncPrefs();
 }
 
-void OSSyncHandler::HandleDidNavigateToOsSyncPage(const base::ListValue* args) {
+void OSSyncHandler::HandleDidNavigateToOsSyncPage(
+    const base::Value::List& args) {
   HandleOsSyncPrefsDispatch(args);
 }
 
-void OSSyncHandler::HandleOsSyncPrefsDispatch(const base::ListValue* args) {
+void OSSyncHandler::HandleOsSyncPrefsDispatch(const base::Value::List& args) {
   AllowJavascript();
 
   PushSyncPrefs();
 }
 
 void OSSyncHandler::HandleDidNavigateAwayFromOsSyncPage(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
   // TODO(https://crbug.com/1278325): Remove this.
 }
 
-void OSSyncHandler::HandleSetOsSyncDatatypes(const base::ListValue* args) {
-  CHECK_EQ(1u, args->GetListDeprecated().size());
-  const base::Value& result_value = args->GetListDeprecated()[0];
+void OSSyncHandler::HandleSetOsSyncDatatypes(const base::Value::List& args) {
+  CHECK_EQ(1u, args.size());
+  const base::Value& result_value = args[0];
   CHECK(result_value.is_dict());
   const base::DictionaryValue& result =
       base::Value::AsDictionaryValue(result_value);
diff --git a/chrome/browser/ui/webui/chromeos/sync/os_sync_handler.h b/chrome/browser/ui/webui/chromeos/sync/os_sync_handler.h
index 4c2609b..ccbb7b7e 100644
--- a/chrome/browser/ui/webui/chromeos/sync/os_sync_handler.h
+++ b/chrome/browser/ui/webui/chromeos/sync/os_sync_handler.h
@@ -5,15 +5,12 @@
 #ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_SYNC_OS_SYNC_HANDLER_H_
 #define CHROME_BROWSER_UI_WEBUI_CHROMEOS_SYNC_OS_SYNC_HANDLER_H_
 
+#include "base/values.h"
 #include "components/sync/driver/sync_service_observer.h"
 #include "content/public/browser/web_ui_message_handler.h"
 
 class Profile;
 
-namespace base {
-class ListValue;
-}  // namespace base
-
 namespace syncer {
 class SyncService;
 }  // namespace syncer
@@ -39,10 +36,10 @@
   void OnStateChanged(syncer::SyncService* service) override;
 
   // Callbacks from the page. Visible for testing.
-  void HandleDidNavigateToOsSyncPage(const base::ListValue* args);
-  void HandleDidNavigateAwayFromOsSyncPage(const base::ListValue* args);
-  void HandleOsSyncPrefsDispatch(const base::ListValue* args);
-  void HandleSetOsSyncDatatypes(const base::ListValue* args);
+  void HandleDidNavigateToOsSyncPage(const base::Value::List& args);
+  void HandleDidNavigateAwayFromOsSyncPage(const base::Value::List& args);
+  void HandleOsSyncPrefsDispatch(const base::Value::List& args);
+  void HandleSetOsSyncDatatypes(const base::Value::List& args);
 
   void SetWebUIForTest(content::WebUI* web_ui);
 
diff --git a/chrome/browser/ui/webui/chromeos/sync/os_sync_handler_unittest.cc b/chrome/browser/ui/webui/chromeos/sync/os_sync_handler_unittest.cc
index d1fd71a..1843d9c 100644
--- a/chrome/browser/ui/webui/chromeos/sync/os_sync_handler_unittest.cc
+++ b/chrome/browser/ui/webui/chromeos/sync/os_sync_handler_unittest.cc
@@ -170,7 +170,7 @@
 };
 
 TEST_F(OsSyncHandlerTest, OsSyncPrefsSentOnNavigateToPage) {
-  handler_->HandleDidNavigateToOsSyncPage(nullptr);
+  handler_->HandleDidNavigateToOsSyncPage(base::Value::List());
 
   ASSERT_EQ(1U, web_ui_.call_data().size());
   const TestWebUI::CallData& call_data = *web_ui_.call_data().back();
@@ -184,7 +184,7 @@
   sync_service_->SetTransportState(SyncService::TransportState::START_DEFERRED);
 
   // Navigate to the page.
-  handler_->HandleDidNavigateToOsSyncPage(nullptr);
+  handler_->HandleDidNavigateToOsSyncPage(base::Value::List());
 
   // No data is sent yet, because the engine is not initialized.
   EXPECT_EQ(0U, web_ui_.call_data().size());
@@ -211,11 +211,11 @@
 }
 
 TEST_F(OsSyncHandlerTest, TestSyncEverything) {
-  base::ListValue list_args;
+  base::Value::List list_args;
   list_args.Append(CreateOsSyncPrefs(SYNC_ALL_OS_TYPES,
                                      UserSelectableOsTypeSet::All(),
                                      /*wallpaper_enabled=*/true));
-  handler_->HandleSetOsSyncDatatypes(&list_args);
+  handler_->HandleSetOsSyncDatatypes(list_args);
   EXPECT_TRUE(user_settings_->IsSyncAllOsTypesEnabled());
 }
 
@@ -224,30 +224,30 @@
 TEST_F(OsSyncHandlerTest, TestSyncIndividualTypes) {
   for (UserSelectableOsType type : UserSelectableOsTypeSet::All()) {
     UserSelectableOsTypeSet types = {type};
-    base::ListValue list_args;
+    base::Value::List list_args;
     list_args.Append(CreateOsSyncPrefs(CHOOSE_WHAT_TO_SYNC, types,
                                        /*wallpaper_enabled=*/false));
 
-    handler_->HandleSetOsSyncDatatypes(&list_args);
+    handler_->HandleSetOsSyncDatatypes(list_args);
     EXPECT_FALSE(user_settings_->IsSyncAllOsTypesEnabled());
     EXPECT_EQ(types, user_settings_->GetSelectedOsTypes());
   }
 
   // Special case for wallpaper.
-  base::ListValue list_args;
+  base::Value::List list_args;
   list_args.Append(CreateOsSyncPrefs(CHOOSE_WHAT_TO_SYNC, /*types=*/{},
                                      /*wallpaper_enabled=*/true));
-  handler_->HandleSetOsSyncDatatypes(&list_args);
+  handler_->HandleSetOsSyncDatatypes(list_args);
   EXPECT_FALSE(user_settings_->IsSyncAllOsTypesEnabled());
   EXPECT_TRUE(GetWallperEnabledPref());
 }
 
 TEST_F(OsSyncHandlerTest, TestSyncAllManually) {
-  base::ListValue list_args;
+  base::Value::List list_args;
   list_args.Append(CreateOsSyncPrefs(CHOOSE_WHAT_TO_SYNC,
                                      UserSelectableOsTypeSet::All(),
                                      /*wallpaper_enabled=*/true));
-  handler_->HandleSetOsSyncDatatypes(&list_args);
+  handler_->HandleSetOsSyncDatatypes(list_args);
   EXPECT_FALSE(user_settings_->IsSyncAllOsTypesEnabled());
   EXPECT_EQ(UserSelectableOsTypeSet::All(),
             user_settings_->GetSelectedOsTypes());
@@ -258,7 +258,7 @@
   user_settings_->SetSelectedOsTypes(/*sync_all_os_types=*/true,
                                      UserSelectableOsTypeSet::All());
   SetWallperEnabledPref(true);
-  handler_->HandleDidNavigateToOsSyncPage(nullptr);
+  handler_->HandleDidNavigateToOsSyncPage(base::Value::List());
 
   const DictionaryValue* dictionary;
   ExpectOsSyncPrefsSent(&dictionary);
@@ -275,7 +275,7 @@
   user_settings_->SetSelectedOsTypes(/*sync_all_os_types=*/false,
                                      UserSelectableOsTypeSet::All());
   SetWallperEnabledPref(true);
-  handler_->HandleDidNavigateToOsSyncPage(nullptr);
+  handler_->HandleDidNavigateToOsSyncPage(base::Value::List());
 
   const DictionaryValue* dictionary;
   ExpectOsSyncPrefsSent(&dictionary);
@@ -288,7 +288,7 @@
   for (UserSelectableOsType type : UserSelectableOsTypeSet::All()) {
     UserSelectableOsTypeSet types(type);
     user_settings_->SetSelectedOsTypes(/*sync_all_os_types=*/false, types);
-    handler_->HandleDidNavigateToOsSyncPage(nullptr);
+    handler_->HandleDidNavigateToOsSyncPage(base::Value::List());
 
     const DictionaryValue* dictionary;
     ExpectOsSyncPrefsSent(&dictionary);
@@ -299,7 +299,7 @@
   // Special case for wallpaper.
   user_settings_->SetSelectedOsTypes(/*sync_all_os_types=*/false, /*types=*/{});
   SetWallperEnabledPref(true);
-  handler_->HandleDidNavigateToOsSyncPage(nullptr);
+  handler_->HandleDidNavigateToOsSyncPage(base::Value::List());
   const DictionaryValue* dictionary;
   ExpectOsSyncPrefsSent(&dictionary);
   CheckConfigDataTypeArguments(dictionary, CHOOSE_WHAT_TO_SYNC, /*types=*/{},
diff --git a/chrome/browser/ui/webui/chromeos/sys_internals/sys_internals_message_handler.cc b/chrome/browser/ui/webui/chromeos/sys_internals/sys_internals_message_handler.cc
index 6421ab5f..395417d 100644
--- a/chrome/browser/ui/webui/chromeos/sys_internals/sys_internals_message_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/sys_internals/sys_internals_message_handler.cc
@@ -204,18 +204,17 @@
 SysInternalsMessageHandler::~SysInternalsMessageHandler() {}
 
 void SysInternalsMessageHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "getSysInfo",
       base::BindRepeating(&SysInternalsMessageHandler::HandleGetSysInfo,
                           base::Unretained(this)));
 }
 
-void SysInternalsMessageHandler::HandleGetSysInfo(const base::ListValue* args) {
+void SysInternalsMessageHandler::HandleGetSysInfo(
+    const base::Value::List& list) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(args);
 
   AllowJavascript();
-  base::Value::ConstListView list = args->GetListDeprecated();
   if (list.size() != 1 || !list[0].is_string()) {
     NOTREACHED();
     return;
diff --git a/chrome/browser/ui/webui/chromeos/sys_internals/sys_internals_message_handler.h b/chrome/browser/ui/webui/chromeos/sys_internals/sys_internals_message_handler.h
index f79ea300..7a90f67 100644
--- a/chrome/browser/ui/webui/chromeos/sys_internals/sys_internals_message_handler.h
+++ b/chrome/browser/ui/webui/chromeos/sys_internals/sys_internals_message_handler.h
@@ -28,7 +28,7 @@
  private:
   // Handle the Javascript message |getSysInfo|. The message is sent to get
   // system information.
-  void HandleGetSysInfo(const base::ListValue* args);
+  void HandleGetSysInfo(const base::Value::List& args);
 
   // The callback function to handle the returning data.
   //
diff --git a/chrome/browser/ui/webui/components/components_handler.cc b/chrome/browser/ui/webui/components/components_handler.cc
index 36086851..8f2e4dd0 100644
--- a/chrome/browser/ui/webui/components/components_handler.cc
+++ b/chrome/browser/ui/webui/components/components_handler.cc
@@ -35,17 +35,17 @@
 ComponentsHandler::~ComponentsHandler() = default;
 
 void ComponentsHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "requestComponentsData",
       base::BindRepeating(&ComponentsHandler::HandleRequestComponentsData,
                           base::Unretained(this)));
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "checkUpdate", base::BindRepeating(&ComponentsHandler::HandleCheckUpdate,
                                          base::Unretained(this)));
 
 #if BUILDFLAG(IS_CHROMEOS)
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "crosUrlComponentsRedirect",
       base::BindRepeating(&ComponentsHandler::HandleCrosUrlComponentsRedirect,
                           base::Unretained(this)));
@@ -61,9 +61,9 @@
 }
 
 void ComponentsHandler::HandleRequestComponentsData(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
   AllowJavascript();
-  const base::Value& callback_id = args->GetListDeprecated()[0];
+  const base::Value& callback_id = args[0];
 
   base::DictionaryValue result;
   result.GetDict().Set("components",
@@ -85,17 +85,17 @@
 // TODO(shrikant): We need to make this button available based on current
 // state e.g. If component state is currently updating then we need to disable
 // button. (https://code.google.com/p/chromium/issues/detail?id=272540)
-void ComponentsHandler::HandleCheckUpdate(const base::ListValue* args) {
-  if (args->GetListDeprecated().size() != 1) {
+void ComponentsHandler::HandleCheckUpdate(const base::Value::List& args) {
+  if (args.size() != 1) {
     NOTREACHED();
     return;
   }
 
-  if (!args->GetListDeprecated()[0].is_string()) {
+  if (!args[0].is_string()) {
     NOTREACHED();
     return;
   }
-  const std::string& component_id = args->GetListDeprecated()[0].GetString();
+  const std::string& component_id = args[0].GetString();
 
   OnDemandUpdate(component_id);
 }
@@ -175,7 +175,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS)
 void ComponentsHandler::HandleCrosUrlComponentsRedirect(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   lacros_url_handling::NavigateInAsh(GURL(chrome::kOsUIComponentsURL));
 #else
diff --git a/chrome/browser/ui/webui/components/components_handler.h b/chrome/browser/ui/webui/components/components_handler.h
index 05205dc..9dc70fc 100644
--- a/chrome/browser/ui/webui/components/components_handler.h
+++ b/chrome/browser/ui/webui/components/components_handler.h
@@ -15,10 +15,6 @@
 #include "components/update_client/update_client.h"
 #include "content/public/browser/web_ui_message_handler.h"
 
-namespace base {
-class ListValue;
-}
-
 // The handler for Javascript messages for the chrome://components/ page.
 class ComponentsHandler : public content::WebUIMessageHandler,
                           public component_updater::ServiceObserver {
@@ -35,17 +31,17 @@
   void OnJavascriptDisallowed() override;
 
   // Callback for the "requestComponentsData" message.
-  void HandleRequestComponentsData(const base::ListValue* args);
+  void HandleRequestComponentsData(const base::Value::List& args);
 
   // Callback for the "checkUpdate" message.
-  void HandleCheckUpdate(const base::ListValue* args);
+  void HandleCheckUpdate(const base::Value::List& args);
 
   // ServiceObserver implementation.
   void OnEvent(Events event, const std::string& id) override;
 
 #if BUILDFLAG(IS_CHROMEOS)
   // Callback for the "crosUrlComponentsRedirect" message.
-  void HandleCrosUrlComponentsRedirect(const base::ListValue* args);
+  void HandleCrosUrlComponentsRedirect(const base::Value::List& args);
 #endif
 
  private:
diff --git a/chrome/browser/ui/webui/components/components_handler_unittest.cc b/chrome/browser/ui/webui/components/components_handler_unittest.cc
index d86e93b..3531345 100644
--- a/chrome/browser/ui/webui/components/components_handler_unittest.cc
+++ b/chrome/browser/ui/webui/components/components_handler_unittest.cc
@@ -27,8 +27,8 @@
 
   {
     TestComponentsHandler handler(&mock_service);
-    base::ListValue args;
+    base::Value::List args;
     args.Append("unused");
-    handler.HandleRequestComponentsData(&args);
+    handler.HandleRequestComponentsData(args);
   }
 }
diff --git a/chrome/browser/ui/webui/device_log_ui.cc b/chrome/browser/ui/webui/device_log_ui.cc
index fde9d08..80eb6a3 100644
--- a/chrome/browser/ui/webui/device_log_ui.cc
+++ b/chrome/browser/ui/webui/device_log_ui.cc
@@ -36,25 +36,25 @@
 
   // WebUIMessageHandler implementation.
   void RegisterMessages() override {
-    web_ui()->RegisterDeprecatedMessageCallback(
+    web_ui()->RegisterMessageCallback(
         "getLog", base::BindRepeating(&DeviceLogMessageHandler::GetLog,
                                       base::Unretained(this)));
-    web_ui()->RegisterDeprecatedMessageCallback(
+    web_ui()->RegisterMessageCallback(
         "clearLog", base::BindRepeating(&DeviceLogMessageHandler::ClearLog,
                                         base::Unretained(this)));
   }
 
  private:
-  void GetLog(const base::ListValue* value) {
+  void GetLog(const base::Value::List& value) {
     AllowJavascript();
-    std::string callback_id = value->GetListDeprecated()[0].GetString();
+    std::string callback_id = value[0].GetString();
     base::Value data(device_event_log::GetAsString(
         device_event_log::NEWEST_FIRST, "json", "",
         device_event_log::LOG_LEVEL_DEBUG, 0));
     ResolveJavascriptCallback(base::Value(callback_id), data);
   }
 
-  void ClearLog(const base::ListValue* value) const {
+  void ClearLog(const base::Value::List& value) const {
     device_event_log::ClearAll();
   }
 };
diff --git a/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.cc b/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.cc
index b17f35f8..5dca25de 100644
--- a/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.cc
+++ b/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.cc
@@ -26,17 +26,17 @@
 }
 
 void DownloadInternalsUIMessageHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "getServiceStatus",
       base::BindRepeating(
           &DownloadInternalsUIMessageHandler::HandleGetServiceStatus,
           weak_ptr_factory_.GetWeakPtr()));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "getServiceDownloads",
       base::BindRepeating(
           &DownloadInternalsUIMessageHandler::HandleGetServiceDownloads,
           weak_ptr_factory_.GetWeakPtr()));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "startDownload",
       base::BindRepeating(
           &DownloadInternalsUIMessageHandler::HandleStartDownload,
@@ -89,26 +89,25 @@
 }
 
 void DownloadInternalsUIMessageHandler::HandleGetServiceStatus(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
   AllowJavascript();
-  const base::Value& callback_id = args->GetListDeprecated()[0];
+  const base::Value& callback_id = args[0];
   ResolveJavascriptCallback(callback_id,
                             download_service_->GetLogger()->GetServiceStatus());
 }
 
 void DownloadInternalsUIMessageHandler::HandleGetServiceDownloads(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
   AllowJavascript();
-  const base::Value& callback_id = args->GetListDeprecated()[0];
+  const base::Value& callback_id = args[0];
   ResolveJavascriptCallback(
       callback_id, download_service_->GetLogger()->GetServiceDownloads());
 }
 
 void DownloadInternalsUIMessageHandler::HandleStartDownload(
-    const base::ListValue* args) {
-  CHECK_GT(args->GetListDeprecated().size(), 1u)
-      << "Missing argument download URL.";
-  GURL url = GURL(args->GetListDeprecated()[1].GetString());
+    const base::Value::List& args) {
+  CHECK_GT(args.size(), 1u) << "Missing argument download URL.";
+  GURL url = GURL(args[1].GetString());
   if (!url.is_valid()) {
     LOG(WARNING) << "Can't parse download URL, try to enter a valid URL.";
     return;
diff --git a/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.h b/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.h
index 2671c81..96cec17 100644
--- a/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.h
+++ b/chrome/browser/ui/webui/download_internals/download_internals_ui_message_handler.h
@@ -42,11 +42,11 @@
 
  private:
   // Get the current DownloadService and sub component statuses.
-  void HandleGetServiceStatus(const base::ListValue* args);
-  void HandleGetServiceDownloads(const base::ListValue* args);
+  void HandleGetServiceStatus(const base::Value::List& args);
+  void HandleGetServiceDownloads(const base::Value::List& args);
 
   // Starts a background download.
-  void HandleStartDownload(const base::ListValue* args);
+  void HandleStartDownload(const base::Value::List& args);
 
   raw_ptr<download::BackgroundDownloadService> download_service_;
 
diff --git a/chrome/browser/ui/webui/feedback/feedback_handler.cc b/chrome/browser/ui/webui/feedback/feedback_handler.cc
index 0ec0144..22a60fb9 100644
--- a/chrome/browser/ui/webui/feedback/feedback_handler.cc
+++ b/chrome/browser/ui/webui/feedback/feedback_handler.cc
@@ -55,40 +55,42 @@
 FeedbackHandler::~FeedbackHandler() = default;
 
 void FeedbackHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "showDialog", base::BindRepeating(&FeedbackHandler::HandleShowDialog,
                                         base::Unretained(this)));
 #if BUILDFLAG(IS_CHROMEOS)
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "showAssistantLogsInfo",
       base::BindRepeating(&FeedbackHandler::HandleShowAssistantLogsInfo,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "showBluetoothLogsInfo",
       base::BindRepeating(&FeedbackHandler::HandleShowBluetoothLogsInfo,
                           base::Unretained(this)));
 #endif  // BUILDFLAG(IS_CHROMEOS)
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "showSystemInfo",
       base::BindRepeating(&FeedbackHandler::HandleShowSystemInfo,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "showMetrics", base::BindRepeating(&FeedbackHandler::HandleShowMetrics,
                                          base::Unretained(this)));
 }
 
-void FeedbackHandler::HandleShowDialog(const base::ListValue* args) {
+void FeedbackHandler::HandleShowDialog(const base::Value::List& args) {
   dialog_->Show();
 }
 
 #if BUILDFLAG(IS_CHROMEOS)
-void FeedbackHandler::HandleShowAssistantLogsInfo(const base::ListValue* args) {
+void FeedbackHandler::HandleShowAssistantLogsInfo(
+    const base::Value::List& args) {
   ShowChildPage(Profile::FromWebUI(web_ui()), dialog_,
                 ChildPageURL("html/assistant_logs_info.html"), std::u16string(),
                 /*dialog_width=*/400, /*dialog_height=*/120,
                 /*can_resize=*/false, /*can_minimize=*/false);
 }
-void FeedbackHandler::HandleShowBluetoothLogsInfo(const base::ListValue* args) {
+void FeedbackHandler::HandleShowBluetoothLogsInfo(
+    const base::Value::List& args) {
   ShowChildPage(Profile::FromWebUI(web_ui()), dialog_,
                 ChildPageURL("html/bluetooth_logs_info.html"), std::u16string(),
                 /*dialog_width=*/400, /*dialog_height=*/120,
@@ -96,13 +98,13 @@
 }
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
-void FeedbackHandler::HandleShowSystemInfo(const base::ListValue* args) {
+void FeedbackHandler::HandleShowSystemInfo(const base::Value::List& args) {
   ShowChildPage(Profile::FromWebUI(web_ui()), dialog_,
                 ChildPageURL("html/sys_info.html"),
                 l10n_util::GetStringUTF16(IDS_FEEDBACK_SYSINFO_PAGE_TITLE));
 }
 
-void FeedbackHandler::HandleShowMetrics(const base::ListValue* args) {
+void FeedbackHandler::HandleShowMetrics(const base::Value::List& args) {
   ShowChildPage(Profile::FromWebUI(web_ui()), dialog_,
                 GURL("chrome://histograms"), std::u16string());
 }
diff --git a/chrome/browser/ui/webui/feedback/feedback_handler.h b/chrome/browser/ui/webui/feedback/feedback_handler.h
index 0628b66b..0f233b3 100644
--- a/chrome/browser/ui/webui/feedback/feedback_handler.h
+++ b/chrome/browser/ui/webui/feedback/feedback_handler.h
@@ -10,10 +10,6 @@
 #include "chrome/browser/ui/webui/feedback/feedback_dialog.h"
 #include "content/public/browser/web_ui_message_handler.h"
 
-namespace base {
-class ListValue;
-}  // namespace base
-
 class FeedbackHandler : public content::WebUIMessageHandler {
  public:
   explicit FeedbackHandler(const FeedbackDialog* dialog);
@@ -25,13 +21,13 @@
   void RegisterMessages() override;
 
  private:
-  void HandleShowDialog(const base::ListValue* args);
+  void HandleShowDialog(const base::Value::List& args);
 #if BUILDFLAG(IS_CHROMEOS)
-  void HandleShowAssistantLogsInfo(const base::ListValue* args);
-  void HandleShowBluetoothLogsInfo(const base::ListValue* args);
+  void HandleShowAssistantLogsInfo(const base::Value::List& args);
+  void HandleShowBluetoothLogsInfo(const base::Value::List& args);
 #endif  // BUILDFLAG(IS_CHROMEOS)
-  void HandleShowMetrics(const base::ListValue* args);
-  void HandleShowSystemInfo(const base::ListValue* args);
+  void HandleShowMetrics(const base::Value::List& args);
+  void HandleShowSystemInfo(const base::Value::List& args);
 
   raw_ptr<const FeedbackDialog> dialog_;
 };
diff --git a/chrome/browser/ui/webui/gcm_internals_ui.cc b/chrome/browser/ui/webui/gcm_internals_ui.cc
index 6c275a9..cd9e56f 100644
--- a/chrome/browser/ui/webui/gcm_internals_ui.cc
+++ b/chrome/browser/ui/webui/gcm_internals_ui.cc
@@ -51,10 +51,10 @@
                      const gcm::GCMClient::GCMStatistics* stats);
 
   // Request all of the GCM related infos through gcm profile service.
-  void RequestAllInfo(const base::ListValue* args);
+  void RequestAllInfo(const base::Value::List& args);
 
   // Enables/disables GCM activity recording through gcm profile service.
-  void SetRecording(const base::ListValue* args);
+  void SetRecording(const base::Value::List& args);
 
   // Callback function of the request for all gcm related infos.
   void RequestGCMStatisticsFinished(const gcm::GCMClient::GCMStatistics& args);
@@ -78,9 +78,8 @@
 }
 
 void GcmInternalsUIMessageHandler::RequestAllInfo(
-    const base::ListValue* args) {
+    const base::Value::List& list) {
   AllowJavascript();
-  const auto& list = args->GetListDeprecated();
   if (list.size() != 1) {
     NOTREACHED();
     return;
@@ -105,8 +104,7 @@
   }
 }
 
-void GcmInternalsUIMessageHandler::SetRecording(const base::ListValue* args) {
-  const auto& list = args->GetListDeprecated();
+void GcmInternalsUIMessageHandler::SetRecording(const base::Value::List& list) {
   if (list.size() != 1) {
     NOTREACHED();
     return;
@@ -142,11 +140,11 @@
 void GcmInternalsUIMessageHandler::RegisterMessages() {
   // It is safe to use base::Unretained here, since web_ui owns this message
   // handler.
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       gcm_driver::kGetGcmInternalsInfo,
       base::BindRepeating(&GcmInternalsUIMessageHandler::RequestAllInfo,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       gcm_driver::kSetGcmInternalsRecording,
       base::BindRepeating(&GcmInternalsUIMessageHandler::SetRecording,
                           base::Unretained(this)));
diff --git a/chrome/browser/ui/webui/identity_internals_ui.cc b/chrome/browser/ui/webui/identity_internals_ui.cc
index 7dbf630..ca623fa 100644
--- a/chrome/browser/ui/webui/identity_internals_ui.cc
+++ b/chrome/browser/ui/webui/identity_internals_ui.cc
@@ -79,12 +79,12 @@
   // Gets all of the tokens stored in IdentityAPI token cache and returns them
   // to the caller using Javascript callback function
   // |identity_internals.returnTokens()|.
-  void GetInfoForAllTokens(const base::ListValue* args);
+  void GetInfoForAllTokens(const base::Value::List& args);
 
   // Initiates revoking of the token, based on the extension ID and token
   // passed as entries in the |args| list. Updates the caller of completion
   // using Javascript callback function |identity_internals.tokenRevokeDone()|.
-  void RevokeToken(const base::ListValue* args);
+  void RevokeToken(const base::Value::List& args);
 
   // A vector of token revokers that are currently revoking tokens.
   std::vector<std::unique_ptr<IdentityInternalsTokenRevoker>> token_revokers_;
@@ -225,8 +225,8 @@
 }
 
 void IdentityInternalsUIMessageHandler::GetInfoForAllTokens(
-    const base::ListValue* args) {
-  const std::string& callback_id = args->GetListDeprecated()[0].GetString();
+    const base::Value::List& args) {
+  const std::string& callback_id = args[0].GetString();
   CHECK(!callback_id.empty());
 
   AllowJavascript();
@@ -248,32 +248,29 @@
 }
 
 void IdentityInternalsUIMessageHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "identityInternalsGetTokens",
       base::BindRepeating(
           &IdentityInternalsUIMessageHandler::GetInfoForAllTokens,
           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "identityInternalsRevokeToken",
       base::BindRepeating(&IdentityInternalsUIMessageHandler::RevokeToken,
                           base::Unretained(this)));
 }
 
 void IdentityInternalsUIMessageHandler::RevokeToken(
-    const base::ListValue* args) {
-  const auto& list = args->GetListDeprecated();
+    const base::Value::List& list) {
   const std::string& callback_id = list[0].GetString();
   CHECK(!callback_id.empty());
   std::string extension_id;
   std::string access_token;
   if (!list.empty() && list[kRevokeTokenExtensionOffset].is_string()) {
-    extension_id =
-        args->GetListDeprecated()[kRevokeTokenExtensionOffset].GetString();
+    extension_id = list[kRevokeTokenExtensionOffset].GetString();
   }
   if (list.size() > kRevokeTokenTokenOffset &&
       list[kRevokeTokenTokenOffset].is_string()) {
-    access_token =
-        args->GetListDeprecated()[kRevokeTokenTokenOffset].GetString();
+    access_token = list[kRevokeTokenTokenOffset].GetString();
   }
 
   token_revokers_.push_back(std::make_unique<IdentityInternalsTokenRevoker>(
diff --git a/chrome/browser/ui/webui/internals/lens/lens_internals_ui_message_handler.cc b/chrome/browser/ui/webui/internals/lens/lens_internals_ui_message_handler.cc
index cdeb3277..97888cc 100644
--- a/chrome/browser/ui/webui/internals/lens/lens_internals_ui_message_handler.cc
+++ b/chrome/browser/ui/webui/internals/lens/lens_internals_ui_message_handler.cc
@@ -61,20 +61,19 @@
   base::android::Java2dStringArrayTo2dStringVector(env, j_debug_data,
                                                    &debug_data);
 
-  std::vector<base::Value> debug_data_as_vector_of_values;
+  base::Value::List debug_data_as_vector_of_values;
   for (auto const& row : debug_data) {
-    std::vector<base::Value> row_as_list_storage;
+    base::Value::List row_as_list_storage;
     // Convert to base::Value array.
     for (std::u16string const& cell_string : row) {
-      row_as_list_storage.emplace_back(base::Value(cell_string));
+      row_as_list_storage.Append(cell_string);
     }
-    debug_data_as_vector_of_values.emplace_back(
-        base::Value(row_as_list_storage));
+    debug_data_as_vector_of_values.Append(std::move(row_as_list_storage));
   }
 
   const base::Value& callback_id = args[0];
-  ResolveJavascriptCallback(callback_id,
-                            base::Value(debug_data_as_vector_of_values));
+  ResolveJavascriptCallback(
+      callback_id, base::Value(std::move(debug_data_as_vector_of_values)));
 }
 
 void LensInternalsUIMessageHandler::HandleStopDebugMode(
diff --git a/chrome/browser/ui/webui/managed_ui_handler.cc b/chrome/browser/ui/webui/managed_ui_handler.cc
index 1bd3236..f9da8625 100644
--- a/chrome/browser/ui/webui/managed_ui_handler.cc
+++ b/chrome/browser/ui/webui/managed_ui_handler.cc
@@ -53,13 +53,14 @@
 }
 
 void ManagedUIHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "observeManagedUI",
       base::BindRepeating(&ManagedUIHandler::HandleObserveManagedUI,
                           base::Unretained(this)));
 }
 
-void ManagedUIHandler::HandleObserveManagedUI(const base::ListValue* /*args*/) {
+void ManagedUIHandler::HandleObserveManagedUI(
+    const base::Value::List& /*args*/) {
   AllowJavascript();
   AddObservers();
 }
diff --git a/chrome/browser/ui/webui/managed_ui_handler.h b/chrome/browser/ui/webui/managed_ui_handler.h
index cd07abc..84e0e37 100644
--- a/chrome/browser/ui/webui/managed_ui_handler.h
+++ b/chrome/browser/ui/webui/managed_ui_handler.h
@@ -56,7 +56,7 @@
   void OnJavascriptDisallowed() override;
 
   // Handles the "observeManagedUI" message. No arguments.
-  void HandleObserveManagedUI(const base::ListValue* args);
+  void HandleObserveManagedUI(const base::Value::List& args);
 
   // Add/remove observers on the PolicyService.
   void AddObservers();
diff --git a/chrome/browser/ui/webui/management/management_ui_handler.cc b/chrome/browser/ui/webui/management/management_ui_handler.cc
index 7be5bc4..18eeaaa8 100644
--- a/chrome/browser/ui/webui/management/management_ui_handler.cc
+++ b/chrome/browser/ui/webui/management/management_ui_handler.cc
@@ -287,9 +287,9 @@
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-std::vector<base::Value> GetPermissionsForExtension(
+base::Value::List GetPermissionsForExtension(
     scoped_refptr<const extensions::Extension> extension) {
-  std::vector<base::Value> permission_messages;
+  base::Value::List permission_messages;
   // Only consider force installed extensions
   if (!extensions::Manifest::IsPolicyLocation(extension->location()))
     return permission_messages;
@@ -305,7 +305,7 @@
           permissions);
 
   for (const auto& message : messages)
-    permission_messages.push_back(base::Value(message.message()));
+    permission_messages.Append(message.message());
 
   return permission_messages;
 }
@@ -314,7 +314,7 @@
   base::Value::List powerful_extensions;
 
   for (const auto& extension : extensions) {
-    std::vector<base::Value> permission_messages =
+    base::Value::List permission_messages =
         GetPermissionsForExtension(extension);
 
     // Only show extension on page if there is at least one permission
diff --git a/chrome/browser/ui/webui/media/webrtc_logs_ui.cc b/chrome/browser/ui/webui/media/webrtc_logs_ui.cc
index 008ef35d..575407bf 100644
--- a/chrome/browser/ui/webui/media/webrtc_logs_ui.cc
+++ b/chrome/browser/ui/webui/media/webrtc_logs_ui.cc
@@ -104,7 +104,7 @@
   using WebRtcEventLogManager = webrtc_event_logging::WebRtcEventLogManager;
 
   // Asynchronously fetches the list of upload WebRTC logs. Called from JS.
-  void HandleRequestWebRtcLogs(const base::ListValue* args);
+  void HandleRequestWebRtcLogs(const base::Value::List& args);
 
   // Asynchronously load WebRTC text logs.
   void LoadWebRtcTextLogs(const std::string& callback_id);
@@ -185,15 +185,15 @@
 void WebRtcLogsDOMHandler::RegisterMessages() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "requestWebRtcLogsList",
       base::BindRepeating(&WebRtcLogsDOMHandler::HandleRequestWebRtcLogs,
                           base::Unretained(this)));
 }
 
 void WebRtcLogsDOMHandler::HandleRequestWebRtcLogs(
-    const base::ListValue* args) {
-  std::string callback_id = args->GetListDeprecated()[0].GetString();
+    const base::Value::List& args) {
+  std::string callback_id = args[0].GetString();
   AllowJavascript();
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   LoadWebRtcTextLogs(callback_id);
diff --git a/chrome/browser/ui/webui/memory_internals_ui.cc b/chrome/browser/ui/webui/memory_internals_ui.cc
index 98ec33f..b789161 100644
--- a/chrome/browser/ui/webui/memory_internals_ui.cc
+++ b/chrome/browser/ui/webui/memory_internals_ui.cc
@@ -139,16 +139,16 @@
   void RegisterMessages() override;
 
   // Callback for the "requestProcessList" message.
-  void HandleRequestProcessList(const base::ListValue* args);
+  void HandleRequestProcessList(const base::Value::List& args);
 
   // Callback for the "saveDump" message.
-  void HandleSaveDump(const base::ListValue* args);
+  void HandleSaveDump(const base::Value::List& args);
 
   // Callback for the "reportProcess" message.
-  void HandleReportProcess(const base::ListValue* args);
+  void HandleReportProcess(const base::Value::List& args);
 
   // Callback for the "startProfiling" message.
-  void HandleStartProfiling(const base::ListValue* args);
+  void HandleStartProfiling(const base::Value::List& args);
 
  private:
   void ReturnProcessListOnUIThread(const std::string& callback_id,
@@ -186,28 +186,28 @@
 void MemoryInternalsDOMHandler::RegisterMessages() {
   // Unretained should be OK here since this class is bound to the lifetime of
   // the WebUI.
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "requestProcessList",
       base::BindRepeating(&MemoryInternalsDOMHandler::HandleRequestProcessList,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "saveDump",
       base::BindRepeating(&MemoryInternalsDOMHandler::HandleSaveDump,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "reportProcess",
       base::BindRepeating(&MemoryInternalsDOMHandler::HandleReportProcess,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "startProfiling",
       base::BindRepeating(&MemoryInternalsDOMHandler::HandleStartProfiling,
                           base::Unretained(this)));
 }
 
 void MemoryInternalsDOMHandler::HandleRequestProcessList(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
   AllowJavascript();
-  std::string callback_id = args->GetListDeprecated()[0].GetString();
+  std::string callback_id = args[0].GetString();
 
   std::vector<base::Value> result;
 
@@ -239,7 +239,7 @@
       weak_factory_.GetWeakPtr(), callback_id, std::move(result)));
 }
 
-void MemoryInternalsDOMHandler::HandleSaveDump(const base::ListValue* args) {
+void MemoryInternalsDOMHandler::HandleSaveDump(const base::Value::List& args) {
   base::FilePath default_file = base::FilePath().AppendASCII(
       base::StringPrintf("trace_with_heap_dump.json.gz"));
 
@@ -275,17 +275,17 @@
 }
 
 void MemoryInternalsDOMHandler::HandleReportProcess(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
   // TODO(etienneb): Delete the use of this method.
 }
 
 void MemoryInternalsDOMHandler::HandleStartProfiling(
-    const base::ListValue* args) {
+    const base::Value::List& args) {
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
-  if (!args->is_list() || args->GetListDeprecated().size() != 1)
+  if (args.size() != 1)
     return;
 
-  base::ProcessId pid = args->GetListDeprecated()[0].GetInt();
+  base::ProcessId pid = args[0].GetInt();
   heap_profiling::Supervisor* supervisor =
       heap_profiling::Supervisor::GetInstance();
   if (supervisor->HasStarted()) {
@@ -304,10 +304,10 @@
   // This function will be called with the child processes that are not
   // renderers. It will fill in the browser and renderer processes on the UI
   // thread (RenderProcessHost is UI-thread only) and return the full list.
-  std::vector<base::Value> process_list;
+  base::Value::List process_list;
 
   // Add browser process.
-  process_list.push_back(MakeProcessInfo(base::GetCurrentProcId(), "Browser"));
+  process_list.Append(MakeProcessInfo(base::GetCurrentProcId(), "Browser"));
 
   // Append renderer processes.
   auto iter = content::RenderProcessHost::AllHostsIterator();
@@ -317,37 +317,37 @@
       if (renderer_pid != 0) {
         // TODO(brettw) make a better description of the process, maybe see
         // what TaskManager does to get the page title.
-        process_list.push_back(MakeProcessInfo(renderer_pid, "Renderer"));
+        process_list.Append(MakeProcessInfo(renderer_pid, "Renderer"));
       }
     }
     iter.Advance();
   }
 
   // Append all child processes collected on the IO thread.
-  process_list.insert(process_list.end(),
-                      std::make_move_iterator(std::begin(children)),
-                      std::make_move_iterator(std::end(children)));
+  for (auto& child : children)
+    process_list.Append(std::move(child));
 
   // Sort profiled_pids to allow binary_search in the loop.
   std::sort(profiled_pids.begin(), profiled_pids.end());
 
   // Append whether each process is being profiled.
   for (base::Value& value : process_list) {
-    DCHECK_EQ(value.GetListDeprecated().size(), 2u);
+    DCHECK_EQ(value.GetList().size(), 2u);
 
     base::ProcessId pid =
-        static_cast<base::ProcessId>(value.GetListDeprecated()[0].GetInt());
+        static_cast<base::ProcessId>(value.GetList()[0].GetInt());
     bool is_profiled =
         std::binary_search(profiled_pids.begin(), profiled_pids.end(), pid);
     value.Append(is_profiled);
   }
 
   // Pass the results in a dictionary.
-  base::Value result(base::Value::Type::DICTIONARY);
-  result.SetKey("message", base::Value(GetMessageString()));
-  result.SetKey("processes", base::Value(std::move(process_list)));
+  base::Value::Dict result;
+  result.Set("message", GetMessageString());
+  result.Set("processes", std::move(process_list));
 
-  ResolveJavascriptCallback(base::Value(callback_id), result);
+  ResolveJavascriptCallback(base::Value(callback_id),
+                            base::Value(std::move(result)));
 }
 
 void MemoryInternalsDOMHandler::FileSelected(const base::FilePath& path,
diff --git a/chrome/browser/ui/webui/nacl_ui.cc b/chrome/browser/ui/webui/nacl_ui.cc
index 92291be5..ed5fa10 100644
--- a/chrome/browser/ui/webui/nacl_ui.cc
+++ b/chrome/browser/ui/webui/nacl_ui.cc
@@ -90,7 +90,7 @@
 
  private:
   // Callback for the "requestNaClInfo" message.
-  void HandleRequestNaClInfo(const base::ListValue* args);
+  void HandleRequestNaClInfo(const base::Value::List& args);
 
   // Callback for the NaCl plugin information.
   void OnGotPlugins(const std::vector<content::WebPluginInfo>& plugins);
@@ -147,7 +147,7 @@
 NaClDomHandler::~NaClDomHandler() = default;
 
 void NaClDomHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "requestNaClInfo",
       base::BindRepeating(&NaClDomHandler::HandleRequestNaClInfo,
                           base::Unretained(this)));
@@ -290,10 +290,10 @@
   AddLineBreak(list);
 }
 
-void NaClDomHandler::HandleRequestNaClInfo(const base::ListValue* args) {
+void NaClDomHandler::HandleRequestNaClInfo(const base::Value::List& args) {
   CHECK(callback_id_.empty());
-  CHECK_EQ(1U, args->GetListDeprecated().size());
-  callback_id_ = args->GetListDeprecated()[0].GetString();
+  CHECK_EQ(1U, args.size());
+  callback_id_ = args[0].GetString();
 
   if (!has_plugin_info_) {
     PluginService::GetInstance()->GetPlugins(base::BindOnce(
diff --git a/chrome/browser/ui/webui/nearby_internals/nearby_internals_contact_handler.cc b/chrome/browser/ui/webui/nearby_internals/nearby_internals_contact_handler.cc
index 50ca67b..6014b980 100644
--- a/chrome/browser/ui/webui/nearby_internals/nearby_internals_contact_handler.cc
+++ b/chrome/browser/ui/webui/nearby_internals/nearby_internals_contact_handler.cc
@@ -17,10 +17,10 @@
 
 namespace {
 
-std::string FormatAsJSON(const base::Value& value) {
+std::string FormatListAsJSON(const base::Value::List& list) {
   std::string json;
   base::JSONWriter::WriteWithOptions(
-      value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
+      list, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
   return json;
 }
 
@@ -55,23 +55,22 @@
                    *did_contacts_change_since_last_upload);
   }
   if (allowed_contact_ids) {
-    base::Value::ListStorage allowed_ids_list;
+    base::Value::List allowed_ids_list;
     allowed_ids_list.reserve(allowed_contact_ids->size());
     for (const auto& contact_id : *allowed_contact_ids) {
-      allowed_ids_list.push_back(base::Value(contact_id));
+      allowed_ids_list.Append(contact_id);
     }
     dictionary.Set(kContactMessageAllowedIdsKey,
-                   FormatAsJSON(base::Value(std::move(allowed_ids_list))));
+                   FormatListAsJSON(allowed_ids_list));
   }
   if (contacts) {
-    base::Value::ListStorage contact_list;
+    base::Value::List contact_list;
     contact_list.reserve(contacts->size());
     for (const auto& contact : *contacts)
-      contact_list.push_back(
-          base::Value(ContactRecordToReadableDictionary(contact)));
+      contact_list.Append(ContactRecordToReadableDictionary(contact));
 
     dictionary.Set(kContactMessageContactRecordKey,
-                   FormatAsJSON(base::Value(std::move(contact_list))));
+                   FormatListAsJSON(contact_list));
   }
   if (num_unreachable_contacts_filtered_out.has_value()) {
     dictionary.Set(kContactMessageNumUnreachableContactsKey,
diff --git a/chrome/browser/ui/webui/nearby_internals/nearby_internals_ui_trigger_handler.cc b/chrome/browser/ui/webui/nearby_internals/nearby_internals_ui_trigger_handler.cc
index 424c3ae4..3166853 100644
--- a/chrome/browser/ui/webui/nearby_internals/nearby_internals_ui_trigger_handler.cc
+++ b/chrome/browser/ui/webui/nearby_internals/nearby_internals_ui_trigger_handler.cc
@@ -192,14 +192,14 @@
 // argument to JavaScript functions.
 base::Value ShareTargetMapToList(
     const base::flat_map<std::string, ShareTarget>& id_to_share_target_map) {
-  base::Value::ListStorage share_target_list;
+  base::Value::List share_target_list;
   share_target_list.reserve(id_to_share_target_map.size());
 
   for (const auto& it : id_to_share_target_map) {
-    share_target_list.push_back(ShareTargetToDictionary(it.second));
+    share_target_list.Append(ShareTargetToDictionary(it.second));
   }
 
-  return base::Value(share_target_list);
+  return base::Value(std::move(share_target_list));
 }
 
 // Converts |transfer_metadata| to a raw dictionary value used as a JSON
diff --git a/chrome/browser/ui/webui/net_export_ui.cc b/chrome/browser/ui/webui/net_export_ui.cc
index c296f40..5ecdd50f 100644
--- a/chrome/browser/ui/webui/net_export_ui.cc
+++ b/chrome/browser/ui/webui/net_export_ui.cc
@@ -98,11 +98,11 @@
   void RegisterMessages() override;
 
   // Messages
-  void OnEnableNotifyUIWithState(const base::ListValue* list);
-  void OnStartNetLog(const base::ListValue* list);
-  void OnStopNetLog(const base::ListValue* list);
-  void OnSendNetLog(const base::ListValue* list);
-  void OnShowFile(const base::ListValue* list);
+  void OnEnableNotifyUIWithState(const base::Value::List& list);
+  void OnStartNetLog(const base::Value::List& list);
+  void OnStopNetLog(const base::Value::List& list);
+  void OnSendNetLog(const base::Value::List& list);
+  void OnShowFile(const base::Value::List& list);
 
   // ui::SelectFileDialog::Listener implementation.
   void FileSelected(const base::FilePath& path,
@@ -179,23 +179,23 @@
 void NetExportMessageHandler::RegisterMessages() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       net_log::kEnableNotifyUIWithStateHandler,
       base::BindRepeating(&NetExportMessageHandler::OnEnableNotifyUIWithState,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       net_log::kStartNetLogHandler,
       base::BindRepeating(&NetExportMessageHandler::OnStartNetLog,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       net_log::kStopNetLogHandler,
       base::BindRepeating(&NetExportMessageHandler::OnStopNetLog,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       net_log::kSendNetLogHandler,
       base::BindRepeating(&NetExportMessageHandler::OnSendNetLog,
                           base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       net_log::kShowFile,
       base::BindRepeating(&NetExportMessageHandler::OnShowFile,
                           base::Unretained(this)));
@@ -205,7 +205,7 @@
 // After this function, NotifyUIWithState() will be called on all |file_writer_|
 // state changes.
 void NetExportMessageHandler::OnEnableNotifyUIWithState(
-    const base::ListValue* list) {
+    const base::Value::List& list) {
   AllowJavascript();
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!state_observation_manager_.IsObserving()) {
@@ -214,11 +214,9 @@
   NotifyUIWithState(file_writer_->GetState());
 }
 
-void NetExportMessageHandler::OnStartNetLog(const base::ListValue* list) {
+void NetExportMessageHandler::OnStartNetLog(const base::Value::List& params) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  base::Value::ConstListView params = list->GetListDeprecated();
-
   // Determine the capture mode.
   capture_mode_ = net::NetLogCaptureMode::kDefault;
   if (!params.empty() && params[0].is_string()) {
@@ -245,7 +243,7 @@
   }
 }
 
-void NetExportMessageHandler::OnStopNetLog(const base::ListValue* list) {
+void NetExportMessageHandler::OnStopNetLog(const base::Value::List& list) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   base::Value::Dict ui_thread_polled_data;
@@ -263,13 +261,13 @@
   file_writer_->StopNetLog(std::move(ui_thread_polled_data));
 }
 
-void NetExportMessageHandler::OnSendNetLog(const base::ListValue* list) {
+void NetExportMessageHandler::OnSendNetLog(const base::Value::List& list) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   file_writer_->GetFilePathToCompletedLog(
       base::BindOnce(&NetExportMessageHandler::SendEmail));
 }
 
-void NetExportMessageHandler::OnShowFile(const base::ListValue* list) {
+void NetExportMessageHandler::OnShowFile(const base::Value::List& list) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   file_writer_->GetFilePathToCompletedLog(
       base::BindOnce(&NetExportMessageHandler::ShowFileInShell, AsWeakPtr()));
diff --git a/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc b/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
index f7e394db8..c31fa691 100644
--- a/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
+++ b/chrome/browser/ui/webui/offline/offline_internals_ui_message_handler.cc
@@ -151,22 +151,20 @@
 void OfflineInternalsUIMessageHandler::HandleStoredPagesCallback(
     std::string callback_id,
     const offline_pages::MultipleOfflinePageItemResult& pages) {
-  std::vector<base::Value> results;
+  base::Value::List results;
   for (const auto& page : pages) {
-    base::Value offline_page(base::Value::Type::DICTIONARY);
-    offline_page.SetStringKey("onlineUrl", page.url.spec());
-    offline_page.SetStringKey("namespace", page.client_id.name_space);
-    offline_page.SetDoubleKey("size", page.file_size);
-    offline_page.SetStringKey("id", std::to_string(page.offline_id));
-    offline_page.SetStringKey("filePath", page.file_path.MaybeAsASCII());
-    offline_page.SetDoubleKey("creationTime", page.creation_time.ToJsTime());
-    offline_page.SetDoubleKey("lastAccessTime",
-                              page.last_access_time.ToJsTime());
-    offline_page.SetIntKey("accessCount", page.access_count);
-    offline_page.SetStringKey("originalUrl",
-                              page.original_url_if_different.spec());
-    offline_page.SetStringKey("requestOrigin", page.request_origin);
-    results.push_back(std::move(offline_page));
+    base::Value::Dict offline_page;
+    offline_page.Set("onlineUrl", page.url.spec());
+    offline_page.Set("namespace", page.client_id.name_space);
+    offline_page.Set("size", static_cast<int>(page.file_size));
+    offline_page.Set("id", std::to_string(page.offline_id));
+    offline_page.Set("filePath", page.file_path.MaybeAsASCII());
+    offline_page.Set("creationTime", page.creation_time.ToJsTime());
+    offline_page.Set("lastAccessTime", page.last_access_time.ToJsTime());
+    offline_page.Set("accessCount", page.access_count);
+    offline_page.Set("originalUrl", page.original_url_if_different.spec());
+    offline_page.Set("requestOrigin", page.request_origin);
+    results.Append(std::move(offline_page));
   }
   // Sort by creation order.
   std::sort(results.begin(), results.end(), [](const auto& a, const auto& b) {
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler_unittest.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler_unittest.cc
index 550e6de3..45a3422 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler_unittest.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler_unittest.cc
@@ -1086,9 +1086,9 @@
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   AddToDenyList(mojom::PrinterType::kExtension);
 #else
-  base::Value::ListStorage deny_list;
-  deny_list.push_back(base::Value("extension"));
-  prefs()->Set(prefs::kPrinterTypeDenyList, base::Value(std::move(deny_list)));
+  base::Value::List deny_list;
+  deny_list.Append("extension");
+  prefs()->SetList(prefs::kPrinterTypeDenyList, std::move(deny_list));
 #endif
   Initialize();
 
@@ -1174,10 +1174,10 @@
   AddToDenyList(mojom::PrinterType::kLocal);
   AddToDenyList(mojom::PrinterType::kPdf);
 #else
-  base::Value::ListStorage deny_list;
-  deny_list.push_back(base::Value("local"));
-  deny_list.push_back(base::Value("pdf"));
-  prefs()->Set(prefs::kPrinterTypeDenyList, base::Value(std::move(deny_list)));
+  base::Value::List deny_list;
+  deny_list.Append("local");
+  deny_list.Append("pdf");
+  prefs()->SetList(prefs::kPrinterTypeDenyList, std::move(deny_list));
 #endif
   Initialize();
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_section.cc b/chrome/browser/ui/webui/settings/chromeos/device_section.cc
index f748c8f1..9edb5d2 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_section.cc
@@ -1240,6 +1240,7 @@
 void DeviceSection::AddDevicePointersStrings(
     content::WebUIDataSource* html_source) {
   static constexpr webui::LocalizedString kPointersStrings[] = {
+      {"audioTitle", IDS_SETTINGS_AUDIO_TITLE},
       {"mouseTitle", IDS_SETTINGS_MOUSE_TITLE},
       {"pointingStickTitle", IDS_SETTINGS_POINTING_STICK_TITLE},
       {"touchpadTitle", IDS_SETTINGS_TOUCHPAD_TITLE},
@@ -1293,6 +1294,9 @@
   html_source->AddBoolean("allowTouchpadHapticClickSettings",
                           base::FeatureList::IsEnabled(
                               ::features::kAllowTouchpadHapticClickSettings));
+  html_source->AddBoolean(
+      "enableAudioSettingsPage",
+      base::FeatureList::IsEnabled(ash::features::kAudioSettingsPage));
 }
 
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc b/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc
index 127cfd096..2331486 100644
--- a/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc
+++ b/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc
@@ -186,21 +186,21 @@
     const std::string& webui_callback_id,
     const std::vector<site_engagement::ImportantSitesUtil::ImportantDomainInfo>&
         installed_apps) {
-  std::vector<base::Value> installed_apps_list;
+  base::Value::List installed_apps_list;
   for (const auto& info : installed_apps) {
-    base::Value entry(base::Value::Type::DICTIONARY);
+    base::Value::Dict entry;
     // Used to get favicon in ClearBrowsingDataDialog and display URL next to
     // app name in the dialog.
-    entry.SetStringKey(kRegisterableDomainField, info.registerable_domain);
+    entry.Set(kRegisterableDomainField, info.registerable_domain);
     // The |reason_bitfield| is only passed to Javascript to be logged
     // from |HandleClearBrowsingData|.
-    entry.SetIntKey(kReasonBitfieldField, info.reason_bitfield);
+    entry.Set(kReasonBitfieldField, info.reason_bitfield);
     // Initially all sites are selected for deletion.
-    entry.SetBoolKey(kIsCheckedField, true);
+    entry.Set(kIsCheckedField, true);
     // User friendly name for the installed app.
     DCHECK(info.app_name);
-    entry.SetStringKey(kAppName, info.app_name.value());
-    installed_apps_list.push_back(std::move(entry));
+    entry.Set(kAppName, info.app_name.value());
+    installed_apps_list.Append(std::move(entry));
   }
   ResolveJavascriptCallback(base::Value(webui_callback_id),
                             base::Value(std::move(installed_apps_list)));
diff --git a/chrome/browser/ui/webui/system_info_ui.cc b/chrome/browser/ui/webui/system_info_ui.cc
index 8aacda5..0b2e16b 100644
--- a/chrome/browser/ui/webui/system_info_ui.cc
+++ b/chrome/browser/ui/webui/system_info_ui.cc
@@ -87,7 +87,7 @@
 
   // Callback for the "requestSystemInfo" message. This asynchronously requests
   // system info and eventually returns it to the front end.
-  void HandleRequestSystemInfo(const base::ListValue* args);
+  void HandleRequestSystemInfo(const base::Value::List& args);
 
   void OnSystemInfo(std::unique_ptr<SystemLogsResponse> sys_info);
 
@@ -111,15 +111,15 @@
 }
 
 void SystemInfoHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "requestSystemInfo",
       base::BindRepeating(&SystemInfoHandler::HandleRequestSystemInfo,
                           base::Unretained(this)));
 }
 
-void SystemInfoHandler::HandleRequestSystemInfo(const base::ListValue* args) {
+void SystemInfoHandler::HandleRequestSystemInfo(const base::Value::List& args) {
   AllowJavascript();
-  callback_id_ = args->GetListDeprecated()[0].GetString();
+  callback_id_ = args[0].GetString();
 
   system_logs::SystemLogsFetcher* fetcher =
       system_logs::BuildAboutSystemLogsFetcher();
diff --git a/chrome/browser/ui/webui/webui_webview_browsertest.cc b/chrome/browser/ui/webui/webui_webview_browsertest.cc
index ce74466..97b1fc55 100644
--- a/chrome/browser/ui/webui/webui_webview_browsertest.cc
+++ b/chrome/browser/ui/webui/webui_webview_browsertest.cc
@@ -40,7 +40,7 @@
  public:
   WebUIMessageListener(content::WebUI* web_ui, const std::string& message)
       : message_loop_(new content::MessageLoopRunner) {
-    web_ui->RegisterDeprecatedMessageCallback(
+    web_ui->RegisterMessageCallback(
         message,
         base::BindRepeating(&WebUIMessageListener::HandleMessage, AsWeakPtr()));
   }
@@ -54,7 +54,7 @@
   }
 
  private:
-  void HandleMessage(const base::ListValue* test_result) {
+  void HandleMessage(const base::Value::List& test_result) {
     message_loop_->Quit();
   }
 
diff --git a/chrome/browser/android/usb/web_usb_chooser_android.cc b/chrome/browser/usb/android/web_usb_chooser_android.cc
similarity index 92%
rename from chrome/browser/android/usb/web_usb_chooser_android.cc
rename to chrome/browser/usb/android/web_usb_chooser_android.cc
index 21a5ccb..b162d154 100644
--- a/chrome/browser/android/usb/web_usb_chooser_android.cc
+++ b/chrome/browser/usb/android/web_usb_chooser_android.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/android/usb/web_usb_chooser_android.h"
+#include "chrome/browser/usb/android/web_usb_chooser_android.h"
 
 #include <utility>
 
diff --git a/chrome/browser/android/usb/web_usb_chooser_android.h b/chrome/browser/usb/android/web_usb_chooser_android.h
similarity index 83%
rename from chrome/browser/android/usb/web_usb_chooser_android.h
rename to chrome/browser/usb/android/web_usb_chooser_android.h
index 0e5b993..14e17533 100644
--- a/chrome/browser/android/usb/web_usb_chooser_android.h
+++ b/chrome/browser/usb/android/web_usb_chooser_android.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ANDROID_USB_WEB_USB_CHOOSER_ANDROID_H_
-#define CHROME_BROWSER_ANDROID_USB_WEB_USB_CHOOSER_ANDROID_H_
+#ifndef CHROME_BROWSER_USB_ANDROID_WEB_USB_CHOOSER_ANDROID_H_
+#define CHROME_BROWSER_USB_ANDROID_WEB_USB_CHOOSER_ANDROID_H_
 
 #include <memory>
 
@@ -34,4 +34,4 @@
   std::unique_ptr<UsbChooserDialogAndroid> dialog_;
 };
 
-#endif  // CHROME_BROWSER_ANDROID_USB_WEB_USB_CHOOSER_ANDROID_H_
+#endif  // CHROME_BROWSER_USB_ANDROID_WEB_USB_CHOOSER_ANDROID_H_
diff --git a/chrome/browser/usb/chrome_usb_delegate.cc b/chrome/browser/usb/chrome_usb_delegate.cc
index 04493117..7aedd97 100644
--- a/chrome/browser/usb/chrome_usb_delegate.cc
+++ b/chrome/browser/usb/chrome_usb_delegate.cc
@@ -237,8 +237,7 @@
 
 void ChromeUsbDelegate::GetDevices(
     RenderFrameHost& frame,
-    base::OnceCallback<void(std::vector<device::mojom::UsbDeviceInfoPtr>)>
-        callback) {
+    blink::mojom::WebUsbService::GetDevicesCallback callback) {
   GetChooserContext(frame)->GetDevices(std::move(callback));
 }
 
diff --git a/chrome/browser/usb/chrome_usb_delegate.h b/chrome/browser/usb/chrome_usb_delegate.h
index c8632ad..047cc23 100644
--- a/chrome/browser/usb/chrome_usb_delegate.h
+++ b/chrome/browser/usb/chrome_usb_delegate.h
@@ -12,6 +12,7 @@
 #include "chrome/browser/usb/usb_chooser_context.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/usb_chooser.h"
+#include "content/public/browser/usb_delegate.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "services/device/public/mojom/usb_device.mojom-forward.h"
@@ -21,50 +22,44 @@
 #include "url/origin.h"
 
 class ChromeUsbDelegate
-    : public permissions::ObjectPermissionContextBase::PermissionObserver,
+    : public content::UsbDelegate,
+      public permissions::ObjectPermissionContextBase::PermissionObserver,
       public UsbChooserContext::DeviceObserver {
  public:
-  class Observer : public base::CheckedObserver {
-   public:
-    virtual void OnDeviceAdded(const device::mojom::UsbDeviceInfo& device) = 0;
-    virtual void OnDeviceRemoved(
-        const device::mojom::UsbDeviceInfo& device) = 0;
-    virtual void OnDeviceManagerConnectionError() = 0;
-    virtual void OnPermissionRevoked(const url::Origin& origin) = 0;
-  };
-
   ChromeUsbDelegate();
   ChromeUsbDelegate(ChromeUsbDelegate&&) = delete;
   ChromeUsbDelegate& operator=(ChromeUsbDelegate&) = delete;
   ~ChromeUsbDelegate() override;
 
+  // content::UsbDelegate:
   void AdjustProtectedInterfaceClasses(content::RenderFrameHost& frame,
-                                       std::vector<uint8_t>& classes);
-  virtual std::unique_ptr<content::UsbChooser> RunChooser(
+                                       std::vector<uint8_t>& classes) override;
+  std::unique_ptr<content::UsbChooser> RunChooser(
       content::RenderFrameHost& frame,
       std::vector<device::mojom::UsbDeviceFilterPtr> filters,
-      blink::mojom::WebUsbService::GetPermissionCallback callback);
-  bool CanRequestDevicePermission(content::RenderFrameHost& frame);
+      blink::mojom::WebUsbService::GetPermissionCallback callback) override;
+  bool CanRequestDevicePermission(content::RenderFrameHost& frame) override;
   void RevokeDevicePermissionWebInitiated(
       content::RenderFrameHost& frame,
-      const device::mojom::UsbDeviceInfo& device);
+      const device::mojom::UsbDeviceInfo& device) override;
   const device::mojom::UsbDeviceInfo* GetDeviceInfo(
       content::RenderFrameHost& frame,
-      const std::string& guid);
+      const std::string& guid) override;
   bool HasDevicePermission(content::RenderFrameHost& frame,
-                           const device::mojom::UsbDeviceInfo& device);
+                           const device::mojom::UsbDeviceInfo& device) override;
   void GetDevices(
       content::RenderFrameHost& frame,
-      base::OnceCallback<void(std::vector<device::mojom::UsbDeviceInfoPtr>)>
-          callback);
+      blink::mojom::WebUsbService::GetDevicesCallback callback) override;
   void GetDevice(
       content::RenderFrameHost& frame,
       const std::string& guid,
       base::span<const uint8_t> blocked_interface_classes,
       mojo::PendingReceiver<device::mojom::UsbDevice> device_receiver,
-      mojo::PendingRemote<device::mojom::UsbDeviceClient> device_client);
-  void AddObserver(content::RenderFrameHost& frame, Observer* observer);
-  void RemoveObserver(Observer* observer);
+      mojo::PendingRemote<device::mojom::UsbDeviceClient> device_client)
+      override;
+  void AddObserver(content::RenderFrameHost& frame,
+                   Observer* observer) override;
+  void RemoveObserver(Observer* observer) override;
 
   // permissions::ObjectPermissionContextBase::PermissionObserver:
   void OnPermissionRevoked(const url::Origin& origin) override;
diff --git a/chrome/browser/usb/web_usb_chooser.cc b/chrome/browser/usb/web_usb_chooser.cc
index 1388d8d..e1e1f6f 100644
--- a/chrome/browser/usb/web_usb_chooser.cc
+++ b/chrome/browser/usb/web_usb_chooser.cc
@@ -10,7 +10,7 @@
 #include "chrome/browser/usb/usb_chooser_controller.h"
 
 #if BUILDFLAG(IS_ANDROID)
-#include "chrome/browser/android/usb/web_usb_chooser_android.h"
+#include "chrome/browser/usb/android/web_usb_chooser_android.h"
 #else
 #include "chrome/browser/usb/web_usb_chooser_desktop.h"
 #endif  // BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 1c0b773..243924e 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -30,6 +30,8 @@
     "commands/web_app_command.h",
     "commands/web_app_install_command.cc",
     "commands/web_app_install_command.h",
+    "commands/web_app_uninstall_command.cc",
+    "commands/web_app_uninstall_command.h",
     "daily_metrics_helper.cc",
     "daily_metrics_helper.h",
     "extension_status_utils.h",
@@ -179,8 +181,6 @@
     "web_app_translation_manager.h",
     "web_app_ui_manager.cc",
     "web_app_ui_manager.h",
-    "web_app_uninstall_job.cc",
-    "web_app_uninstall_job.h",
     "web_app_url_loader.cc",
     "web_app_url_loader.h",
     "web_app_utils.cc",
@@ -478,6 +478,7 @@
     "commands/install_from_sync_command_unittest.cc",
     "commands/install_isolated_app_command_unittest.cc",
     "commands/run_on_os_login_command_unittest.cc",
+    "commands/web_app_uninstall_command_unittest.cc",
     "daily_metrics_helper_unittest.cc",
     "external_install_options_unittest.cc",
     "externally_managed_app_manager_impl_unittest.cc",
@@ -511,7 +512,6 @@
     "web_app_registrar_unittest.cc",
     "web_app_sync_bridge_unittest.cc",
     "web_app_translation_manager_unittest.cc",
-    "web_app_uninstall_job_unittest.cc",
     "web_app_unittest.cc",
     "web_app_url_loader_unittest.cc",
     "web_app_utils_unittest.cc",
diff --git a/chrome/browser/web_applications/commands/install_from_sync_command.cc b/chrome/browser/web_applications/commands/install_from_sync_command.cc
index 3d38c84..b748eca0 100644
--- a/chrome/browser/web_applications/commands/install_from_sync_command.cc
+++ b/chrome/browser/web_applications/commands/install_from_sync_command.cc
@@ -18,7 +18,6 @@
 #include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
-#include "chrome/browser/web_applications/web_app_uninstall_job.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
 #include "components/webapps/browser/install_result_code.h"
 #include "components/webapps/browser/installable/installable_metrics.h"
diff --git a/chrome/browser/web_applications/commands/web_app_uninstall_command.cc b/chrome/browser/web_applications/commands/web_app_uninstall_command.cc
new file mode 100644
index 0000000..3d001ff
--- /dev/null
+++ b/chrome/browser/web_applications/commands/web_app_uninstall_command.cc
@@ -0,0 +1,195 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/web_applications/commands/web_app_uninstall_command.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/isolation_prefs_utils.h"
+#include "chrome/browser/web_applications/web_app.h"
+#include "chrome/browser/web_applications/web_app_command_manager.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
+#include "chrome/browser/web_applications/web_app_id.h"
+#include "chrome/browser/web_applications/web_app_install_finalizer.h"
+#include "chrome/browser/web_applications/web_app_install_manager.h"
+#include "chrome/browser/web_applications/web_app_registrar.h"
+#include "chrome/browser/web_applications/web_app_registry_update.h"
+#include "chrome/browser/web_applications/web_app_sync_bridge.h"
+#include "chrome/browser/web_applications/web_app_translation_manager.h"
+#include "components/webapps/browser/installable/installable_metrics.h"
+#include "components/webapps/browser/uninstall_result_code.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace web_app {
+
+WebAppUninstallCommand::WebAppUninstallCommand(
+    const AppId& app_id,
+    const url::Origin& app_origin,
+    Profile* profile,
+    OsIntegrationManager* os_integration_manager,
+    WebAppSyncBridge* sync_bridge,
+    WebAppIconManager* icon_manager,
+    WebAppRegistrar* registrar,
+    WebAppInstallManager* install_manager,
+    WebAppInstallFinalizer* install_finalizer,
+    WebAppTranslationManager* translation_manager,
+    webapps::WebappUninstallSource source,
+    UninstallWebAppCallback callback)
+    : WebAppCommand(WebAppCommandLock::CreateForAppLock({app_id})),
+      app_id_(app_id),
+      app_origin_(app_origin),
+      source_(source),
+      callback_(std::move(callback)),
+      os_integration_manager_(os_integration_manager),
+      sync_bridge_(sync_bridge),
+      icon_manager_(icon_manager),
+      registrar_(registrar),
+      install_manager_(install_manager),
+      install_finalizer_(install_finalizer),
+      translation_manager_(translation_manager),
+      profile_prefs_(profile->GetPrefs()) {}
+
+WebAppUninstallCommand::~WebAppUninstallCommand() = default;
+
+void WebAppUninstallCommand::Start() {
+  if (!registrar_->GetAppById(app_id_)) {
+    Abort(webapps::UninstallResultCode::kNoAppToUninstall);
+    return;
+  }
+
+  DCHECK(state_ == State::kNotStarted);
+  state_ = State::kPendingDataDeletion;
+
+  // Note: It is supported to re-start an uninstall on startup, so
+  // `is_uninstalling()` is not checked. It is a class invariant that there can
+  // never be more than one uninstall task operating on the same web app at the
+  // same time.
+  {
+    ScopedRegistryUpdate update(sync_bridge_);
+    WebApp* app = update->UpdateApp(app_id_);
+    DCHECK(app);
+    app->SetIsUninstalling(true);
+  }
+  install_manager_->NotifyWebAppWillBeUninstalled(app_id_);
+
+  RemoveAppIsolationState(profile_prefs_, app_origin_);
+
+  // Uninstall any sub-apps the app has.
+  // TODO(phillis): Fix this command to get locks for all sub-app ids as well.
+  // https://crbug.com/1341337
+  std::vector<AppId> sub_app_ids = registrar_->GetAllSubAppIds(app_id_);
+  num_pending_sub_app_uninstalls_ = sub_app_ids.size();
+  for (const AppId& sub_app_id : sub_app_ids) {
+    if (registrar_->GetAppById(sub_app_id) == nullptr)
+      continue;
+    install_finalizer_->UninstallExternalWebApp(
+        sub_app_id, WebAppManagement::Type::kSubApp,
+        webapps::WebappUninstallSource::kSubApp,
+        base::BindOnce(&WebAppUninstallCommand::OnSubAppUninstalled,
+                       weak_factory_.GetWeakPtr()));
+  }
+
+  os_integration_manager_->UninstallAllOsHooks(
+      app_id_, base::BindOnce(&WebAppUninstallCommand::OnOsHooksUninstalled,
+                              weak_factory_.GetWeakPtr()));
+  icon_manager_->DeleteData(
+      app_id_, base::BindOnce(&WebAppUninstallCommand::OnIconDataDeleted,
+                              weak_factory_.GetWeakPtr()));
+
+  translation_manager_->DeleteTranslations(
+      app_id_, base::BindOnce(&WebAppUninstallCommand::OnTranslationDataDeleted,
+                              weak_factory_.GetWeakPtr()));
+}
+
+void WebAppUninstallCommand::Abort(webapps::UninstallResultCode code) {
+  if (!callback_)
+    return;
+  SignalCompletionAndSelfDestruct(CommandResult::kFailure,
+                                  base::BindOnce(std::move(callback_), code));
+}
+
+void WebAppUninstallCommand::OnSubAppUninstalled(
+    webapps::UninstallResultCode code) {
+  errors_ = errors_ || (code != webapps::UninstallResultCode::kSuccess);
+  num_pending_sub_app_uninstalls_--;
+  DCHECK_GE(num_pending_sub_app_uninstalls_, 0u);
+  MaybeFinishUninstall();
+}
+
+void WebAppUninstallCommand::OnOsHooksUninstalled(OsHooksErrors errors) {
+  DCHECK(state_ == State::kPendingDataDeletion);
+  hooks_uninstalled_ = true;
+  // TODO(https://crbug.com/1293234): Remove after flakiness is solved.
+  DLOG_IF(ERROR, errors.any())
+      << "OS integration errors for " << app_id_ << ": " << errors.to_string();
+  base::UmaHistogramBoolean("WebApp.Uninstall.OsHookSuccess", errors.none());
+  errors_ = errors_ || errors.any();
+  MaybeFinishUninstall();
+}
+
+void WebAppUninstallCommand::OnIconDataDeleted(bool success) {
+  DCHECK(state_ == State::kPendingDataDeletion);
+  app_data_deleted_ = true;
+  // TODO(https://crbug.com/1293234): Remove after flakiness is solved.
+  DLOG_IF(ERROR, !success) << "Error deleting icon data for " << app_id_;
+  base::UmaHistogramBoolean("WebApp.Uninstall.IconDataSuccess", success);
+  errors_ = errors_ || !success;
+  MaybeFinishUninstall();
+}
+
+void WebAppUninstallCommand::OnTranslationDataDeleted(bool success) {
+  DCHECK(state_ == State::kPendingDataDeletion);
+  translation_data_deleted_ = true;
+  errors_ = errors_ || !success;
+  MaybeFinishUninstall();
+}
+
+void WebAppUninstallCommand::MaybeFinishUninstall() {
+  DCHECK(state_ == State::kPendingDataDeletion);
+  if (!hooks_uninstalled_ || !app_data_deleted_ ||
+      num_pending_sub_app_uninstalls_ > 0 || !translation_data_deleted_) {
+    return;
+  }
+  DCHECK_EQ(num_pending_sub_app_uninstalls_, 0u);
+  state_ = State::kDone;
+
+  base::UmaHistogramBoolean("WebApp.Uninstall.Result", !errors_);
+
+  webapps::InstallableMetrics::TrackUninstallEvent(source_);
+  {
+    DCHECK_NE(registrar_->GetAppById(app_id_), nullptr);
+    ScopedRegistryUpdate update(sync_bridge_);
+    update->DeleteApp(app_id_);
+  }
+  install_manager_->NotifyWebAppUninstalled(app_id_);
+
+  SignalCompletionAndSelfDestruct(
+      errors_ ? CommandResult::kFailure : CommandResult::kSuccess,
+      base::BindOnce(std::move(callback_),
+                     errors_ ? webapps::UninstallResultCode::kError
+                             : webapps::UninstallResultCode::kSuccess));
+}
+
+void WebAppUninstallCommand::OnSyncSourceRemoved() {
+  // TODO(crbug.com/1320086): remove after uninstall from sync is async.
+  Abort(webapps::UninstallResultCode::kNoAppToUninstall);
+  return;
+}
+
+void WebAppUninstallCommand::OnShutdown() {
+  Abort(webapps::UninstallResultCode::kError);
+  return;
+}
+
+base::Value WebAppUninstallCommand::ToDebugValue() const {
+  return base::Value(base::StringPrintf(
+      "WebAppUninstallCommand %d, app_id_: %s", id(), app_id_.c_str()));
+}
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/commands/web_app_uninstall_command.h b/chrome/browser/web_applications/commands/web_app_uninstall_command.h
new file mode 100644
index 0000000..3c53d08
--- /dev/null
+++ b/chrome/browser/web_applications/commands/web_app_uninstall_command.h
@@ -0,0 +1,100 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_WEB_APPLICATIONS_COMMANDS_WEB_APP_UNINSTALL_COMMAND_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_COMMANDS_WEB_APP_UNINSTALL_COMMAND_H_
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/web_applications/commands/web_app_command.h"
+#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
+#include "chrome/browser/web_applications/web_app_id.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
+
+class Profile;
+class PrefService;
+
+namespace webapps {
+enum class UninstallResultCode;
+enum class WebappUninstallSource;
+}  // namespace webapps
+
+namespace web_app {
+
+class OsIntegrationManager;
+class WebAppIconManager;
+class WebAppInstallManager;
+class WebAppInstallFinalizer;
+class WebAppRegistrar;
+class WebAppSyncBridge;
+class WebAppTranslationManager;
+
+// Uninstall the web app.
+class WebAppUninstallCommand : public WebAppCommand {
+ public:
+  using UninstallWebAppCallback =
+      base::OnceCallback<void(webapps::UninstallResultCode)>;
+
+  WebAppUninstallCommand(const AppId& app_id,
+                         const url::Origin& app_origin,
+                         Profile* profile,
+                         OsIntegrationManager* os_integration_manager,
+                         WebAppSyncBridge* sync_bridge,
+                         WebAppIconManager* icon_manager,
+                         WebAppRegistrar* registrar,
+                         WebAppInstallManager* install_manager,
+                         WebAppInstallFinalizer* install_finalizer,
+                         WebAppTranslationManager* translation_manager,
+                         webapps::WebappUninstallSource source,
+                         UninstallWebAppCallback callback);
+  ~WebAppUninstallCommand() override;
+
+  void Start() override;
+  void OnSyncSourceRemoved() override;
+  void OnShutdown() override;
+
+  base::Value ToDebugValue() const override;
+
+ private:
+  void Abort(webapps::UninstallResultCode code);
+  void OnSubAppUninstalled(webapps::UninstallResultCode code);
+  void OnOsHooksUninstalled(OsHooksErrors errors);
+  void OnIconDataDeleted(bool success);
+  void OnTranslationDataDeleted(bool success);
+  void MaybeFinishUninstall();
+
+  enum class State {
+    kNotStarted = 0,
+    kPendingDataDeletion = 1,
+    kDone = 2,
+  } state_ = State::kNotStarted;
+
+  AppId app_id_;
+  url::Origin app_origin_;
+  webapps::WebappUninstallSource source_;
+  UninstallWebAppCallback callback_;
+
+  raw_ptr<OsIntegrationManager> os_integration_manager_;
+  raw_ptr<WebAppSyncBridge> sync_bridge_;
+  raw_ptr<WebAppIconManager> icon_manager_;
+  raw_ptr<WebAppRegistrar> registrar_;
+  raw_ptr<WebAppInstallManager> install_manager_;
+  raw_ptr<WebAppInstallFinalizer> install_finalizer_;
+  raw_ptr<WebAppTranslationManager> translation_manager_;
+  raw_ptr<PrefService> profile_prefs_;
+
+  size_t num_pending_sub_app_uninstalls_;
+
+  bool app_data_deleted_ = false;
+  bool translation_data_deleted_ = false;
+  bool hooks_uninstalled_ = false;
+  bool errors_ = false;
+
+  base::WeakPtrFactory<WebAppUninstallCommand> weak_factory_{this};
+};
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_COMMANDS_WEB_APP_INSTALL_COMMAND_H_
diff --git a/chrome/browser/web_applications/commands/web_app_uninstall_command_unittest.cc b/chrome/browser/web_applications/commands/web_app_uninstall_command_unittest.cc
new file mode 100644
index 0000000..e0ab2ed
--- /dev/null
+++ b/chrome/browser/web_applications/commands/web_app_uninstall_command_unittest.cc
@@ -0,0 +1,203 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/web_applications/commands/web_app_uninstall_command.h"
+
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/test/bind.h"
+#include "base/test/gmock_callback_support.h"
+#include "base/test/task_environment.h"
+#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
+#include "chrome/browser/web_applications/test/mock_file_utils_wrapper.h"
+#include "chrome/browser/web_applications/test/mock_os_integration_manager.h"
+#include "chrome/browser/web_applications/test/test_file_utils.h"
+#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
+#include "chrome/browser/web_applications/test/web_app_test.h"
+#include "chrome/browser/web_applications/test/web_app_test_utils.h"
+#include "chrome/browser/web_applications/web_app.h"
+#include "chrome/browser/web_applications/web_app_command_manager.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
+#include "chrome/browser/web_applications/web_app_registry_update.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/webapps/browser/uninstall_result_code.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/origin.h"
+
+namespace web_app {
+namespace {
+
+class WebAppUninstallCommandTest : public WebAppTest {
+ public:
+  WebAppUninstallCommandTest() = default;
+
+  void SetUp() override {
+    WebAppTest::SetUp();
+
+    FakeWebAppProvider* provider = FakeWebAppProvider::Get(profile());
+    provider->SetDefaultFakeSubsystems();
+    file_utils_wrapper_ =
+        base::MakeRefCounted<testing::StrictMock<MockFileUtilsWrapper>>();
+    provider->SetIconManager(
+        std::make_unique<WebAppIconManager>(profile(), file_utils_wrapper_));
+    provider->SetRunSubsystemStartupTasks(true);
+    test::AwaitStartWebAppProviderAndSubsystems(profile());
+  }
+
+  void TearDown() override {
+    file_utils_wrapper_ = nullptr;
+    WebAppTest::TearDown();
+  }
+
+  WebAppProvider* provider() { return WebAppProvider::GetForTest(profile()); }
+
+  testing::StrictMock<MockOsIntegrationManager> os_integration_manager_;
+  scoped_refptr<testing::StrictMock<MockFileUtilsWrapper>> file_utils_wrapper_;
+};
+
+TEST_F(WebAppUninstallCommandTest, SimpleUninstall) {
+  auto web_app = test::CreateWebApp(GURL("https://www.example.com"),
+                                    WebAppManagement::kSync);
+  AppId app_id = web_app->app_id();
+  {
+    ScopedRegistryUpdate update(&provider()->sync_bridge());
+    update->CreateApp(std::move(web_app));
+  }
+
+  OsHooksErrors result;
+  EXPECT_CALL(os_integration_manager_, UninstallAllOsHooks(app_id, testing::_))
+      .WillOnce(base::test::RunOnceCallback<1>(result));
+
+  base::FilePath deletion_path = GetManifestResourcesDirectoryForApp(
+      GetWebAppsRootDirectory(profile()), app_id);
+
+  EXPECT_CALL(*file_utils_wrapper_, DeleteFileRecursively(deletion_path))
+      .WillOnce(testing::Return(true));
+
+  base::RunLoop loop;
+  provider()->command_manager().ScheduleCommand(
+      std::make_unique<WebAppUninstallCommand>(
+          app_id, url::Origin(), profile(), &os_integration_manager_,
+          &provider()->sync_bridge(), &provider()->icon_manager(),
+          &provider()->registrar(), &provider()->install_manager(),
+          &provider()->install_finalizer(), &provider()->translation_manager(),
+          webapps::WebappUninstallSource::kAppMenu,
+          base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
+            EXPECT_EQ(webapps::UninstallResultCode::kSuccess, code);
+            loop.Quit();
+          })));
+
+  loop.Run();
+  EXPECT_EQ(provider()->registrar().GetAppById(app_id), nullptr);
+}
+
+TEST_F(WebAppUninstallCommandTest, FailedDataDelete) {
+  auto web_app = test::CreateWebApp(GURL("https://www.example.com"),
+                                    WebAppManagement::kSync);
+  AppId app_id = web_app->app_id();
+  {
+    ScopedRegistryUpdate update(&provider()->sync_bridge());
+    update->CreateApp(std::move(web_app));
+  }
+
+  OsHooksErrors result;
+  EXPECT_CALL(os_integration_manager_, UninstallAllOsHooks(app_id, testing::_))
+      .WillOnce(base::test::RunOnceCallback<1>(result));
+
+  base::FilePath deletion_path = GetManifestResourcesDirectoryForApp(
+      GetWebAppsRootDirectory(profile()), app_id);
+
+  EXPECT_CALL(*file_utils_wrapper_, DeleteFileRecursively(deletion_path))
+      .WillOnce(testing::Return(false));
+
+  base::RunLoop loop;
+  provider()->command_manager().ScheduleCommand(
+      std::make_unique<WebAppUninstallCommand>(
+          app_id, url::Origin(), profile(), &os_integration_manager_,
+          &provider()->sync_bridge(), &provider()->icon_manager(),
+          &provider()->registrar(), &provider()->install_manager(),
+          &provider()->install_finalizer(), &provider()->translation_manager(),
+          webapps::WebappUninstallSource::kAppMenu,
+          base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
+            EXPECT_EQ(webapps::UninstallResultCode::kError, code);
+            loop.Quit();
+          })));
+
+  loop.Run();
+  EXPECT_EQ(provider()->registrar().GetAppById(app_id), nullptr);
+}
+
+TEST_F(WebAppUninstallCommandTest, FailedOsHooks) {
+  auto web_app = test::CreateWebApp(GURL("https://www.example.com"),
+                                    WebAppManagement::kSync);
+  AppId app_id = web_app->app_id();
+  {
+    ScopedRegistryUpdate update(&provider()->sync_bridge());
+    update->CreateApp(std::move(web_app));
+  }
+
+  OsHooksErrors result;
+  result.set(true);
+  EXPECT_CALL(os_integration_manager_, UninstallAllOsHooks(app_id, testing::_))
+      .WillOnce(base::test::RunOnceCallback<1>(result));
+
+  base::FilePath deletion_path = GetManifestResourcesDirectoryForApp(
+      GetWebAppsRootDirectory(profile()), app_id);
+
+  EXPECT_CALL(*file_utils_wrapper_, DeleteFileRecursively(deletion_path))
+      .WillOnce(testing::Return(true));
+
+  base::RunLoop loop;
+  provider()->command_manager().ScheduleCommand(
+      std::make_unique<WebAppUninstallCommand>(
+          app_id, url::Origin(), profile(), &os_integration_manager_,
+          &provider()->sync_bridge(), &provider()->icon_manager(),
+          &provider()->registrar(), &provider()->install_manager(),
+          &provider()->install_finalizer(), &provider()->translation_manager(),
+          webapps::WebappUninstallSource::kAppMenu,
+          base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
+            EXPECT_EQ(webapps::UninstallResultCode::kError, code);
+            loop.Quit();
+          })));
+
+  loop.Run();
+  EXPECT_EQ(provider()->registrar().GetAppById(app_id), nullptr);
+}
+
+TEST_F(WebAppUninstallCommandTest, UninstallNonExistentApp) {
+  auto web_app = test::CreateWebApp(GURL("https://www.example.com"),
+                                    WebAppManagement::kSync);
+  AppId app_id = web_app->app_id();
+
+  EXPECT_CALL(os_integration_manager_, UninstallAllOsHooks(app_id, testing::_))
+      .Times(0);
+
+  base::FilePath deletion_path = GetManifestResourcesDirectoryForApp(
+      GetWebAppsRootDirectory(profile()), app_id);
+
+  EXPECT_CALL(*file_utils_wrapper_, DeleteFileRecursively(deletion_path))
+      .Times(0);
+
+  base::RunLoop loop;
+  provider()->command_manager().ScheduleCommand(
+      std::make_unique<WebAppUninstallCommand>(
+          app_id, url::Origin(), profile(), &os_integration_manager_,
+          &provider()->sync_bridge(), &provider()->icon_manager(),
+          &provider()->registrar(), &provider()->install_manager(),
+          &provider()->install_finalizer(), &provider()->translation_manager(),
+          webapps::WebappUninstallSource::kAppMenu,
+          base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
+            EXPECT_EQ(webapps::UninstallResultCode::kNoAppToUninstall, code);
+            loop.Quit();
+          })));
+
+  loop.Run();
+  EXPECT_EQ(provider()->registrar().GetAppById(app_id), nullptr);
+}
+
+}  // namespace
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/extensions/externally_managed_app_install_task_unittest.cc b/chrome/browser/web_applications/extensions/externally_managed_app_install_task_unittest.cc
index 5f71656..753fc16 100644
--- a/chrome/browser/web_applications/extensions/externally_managed_app_install_task_unittest.cc
+++ b/chrome/browser/web_applications/extensions/externally_managed_app_install_task_unittest.cc
@@ -177,11 +177,6 @@
         }));
   }
 
-  void UninstallFromSync(const std::vector<AppId>& web_apps,
-                         RepeatingUninstallCallback callback) override {
-    NOTREACHED();
-  }
-
   void FinalizeUpdate(const WebAppInstallInfo& web_app_info,
                       InstallFinalizedCallback callback) override {
     NOTREACHED();
diff --git a/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_app_definition_utils_unittest.cc b/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_app_definition_utils_unittest.cc
index aae1ce2..e9b67165 100644
--- a/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_app_definition_utils_unittest.cc
+++ b/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_app_definition_utils_unittest.cc
@@ -19,8 +19,9 @@
   ~PreinstalledWebAppUtilsTest() override = default;
 };
 
-// https://crbug.com/1198780 tracks test failures on Linux and ChromeOS.
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+// https://crbug.com/1198780 tracks test failures due to memory smashing on
+// Linux, ChromeOS, and the Mac.
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC)
 #define MAYBE_GetTranslatedName DISABLED_GetTranslatedName
 #else
 #define MAYBE_GetTranslatedName GetTranslatedName
diff --git a/chrome/browser/web_applications/test/fake_install_finalizer.cc b/chrome/browser/web_applications/test/fake_install_finalizer.cc
index 92a2cef8..cad6669 100644
--- a/chrome/browser/web_applications/test/fake_install_finalizer.cc
+++ b/chrome/browser/web_applications/test/fake_install_finalizer.cc
@@ -77,12 +77,6 @@
                      }));
 }
 
-void FakeInstallFinalizer::UninstallFromSync(
-    const std::vector<AppId>& web_apps,
-    RepeatingUninstallCallback callback) {
-  NOTREACHED();
-}
-
 bool FakeInstallFinalizer::CanUserUninstallWebApp(const AppId& app_id) const {
   NOTIMPLEMENTED();
   return false;
diff --git a/chrome/browser/web_applications/test/fake_install_finalizer.h b/chrome/browser/web_applications/test/fake_install_finalizer.h
index 8b1826b..99c21b3 100644
--- a/chrome/browser/web_applications/test/fake_install_finalizer.h
+++ b/chrome/browser/web_applications/test/fake_install_finalizer.h
@@ -42,8 +42,6 @@
       WebAppManagement::Type source,
       webapps::WebappUninstallSource uninstall_surface,
       UninstallWebAppCallback callback) override;
-  void UninstallFromSync(const std::vector<AppId>& web_apps,
-                         RepeatingUninstallCallback callback) override;
   bool CanUserUninstallWebApp(const AppId& app_id) const override;
   void UninstallWebApp(const AppId& app_id,
                        webapps::WebappUninstallSource uninstall_source,
diff --git a/chrome/browser/web_applications/test/fake_web_app_registry_controller.cc b/chrome/browser/web_applications/test/fake_web_app_registry_controller.cc
index 890a85da..d8d83a2 100644
--- a/chrome/browser/web_applications/test/fake_web_app_registry_controller.cc
+++ b/chrome/browser/web_applications/test/fake_web_app_registry_controller.cc
@@ -127,7 +127,7 @@
                                                              callback);
   } else {
     for (const AppId& web_app : web_apps) {
-      callback.Run(web_app, /*uninstalled=*/true);
+      callback.Run(web_app, webapps::UninstallResultCode::kSuccess);
     }
   }
 }
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc
index 1fa0180..aef5d1c 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -24,6 +24,7 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/ash/system_web_apps/types/system_web_app_data.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/commands/web_app_uninstall_command.h"
 #include "chrome/browser/web_applications/isolation_prefs_utils.h"
 #include "chrome/browser/web_applications/manifest_update_task.h"
 #include "chrome/browser/web_applications/os_integration/web_app_shortcuts_menu.h"
@@ -31,6 +32,7 @@
 #include "chrome/browser/web_applications/user_display_mode.h"
 #include "chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs.h"
 #include "chrome/browser/web_applications/web_app.h"
+#include "chrome/browser/web_applications/web_app_command_manager.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_icon_generator.h"
 #include "chrome/browser/web_applications/web_app_icon_manager.h"
@@ -43,7 +45,6 @@
 #include "chrome/browser/web_applications/web_app_sync_bridge.h"
 #include "chrome/browser/web_applications/web_app_translation_manager.h"
 #include "chrome/browser/web_applications/web_app_ui_manager.h"
-#include "chrome/browser/web_applications/web_app_uninstall_job.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
@@ -248,12 +249,12 @@
 
   // Check that the source was from a known 'user' or allowed ones such
   // as kMigration.
+  // WebappUninstallSource::kSync should not be included in this list.
   DCHECK(
       webapp_uninstall_source == webapps::WebappUninstallSource::kUnknown ||
       webapp_uninstall_source == webapps::WebappUninstallSource::kAppMenu ||
       webapp_uninstall_source == webapps::WebappUninstallSource::kAppsPage ||
       webapp_uninstall_source == webapps::WebappUninstallSource::kOsSettings ||
-      webapp_uninstall_source == webapps::WebappUninstallSource::kSync ||
       webapp_uninstall_source ==
           webapps::WebappUninstallSource::kAppManagement ||
       webapp_uninstall_source == webapps::WebappUninstallSource::kMigration ||
@@ -286,49 +287,15 @@
   UninstallWebAppInternal(app_id, webapp_uninstall_source, std::move(callback));
 }
 
-void WebAppInstallFinalizer::UninstallFromSync(
-    const std::vector<AppId>& web_apps,
-    RepeatingUninstallCallback callback) {
-  DCHECK(started_);
-
-  for (auto& app_id : web_apps) {
-    if (base::Contains(pending_uninstalls_, app_id)) {
-      continue;
-    }
-    auto uninstall_task = std::make_unique<WebAppUninstallJob>(
-        os_integration_manager_, sync_bridge_, icon_manager_, registrar_,
-        install_manager_, this, translation_manager_, profile_->GetPrefs());
-    uninstall_task->Start(
-        app_id,
-        url::Origin::Create(registrar_->GetAppById(app_id)->start_url()),
-        webapps::WebappUninstallSource::kSync,
-        base::BindOnce(&WebAppInstallFinalizer::OnUninstallComplete,
-                       weak_ptr_factory_.GetWeakPtr(), app_id,
-                       webapps::WebappUninstallSource::kSync,
-                       base::BindOnce(callback, app_id)));
-    pending_uninstalls_[app_id] = std::move(uninstall_task);
-  }
-}
-
 void WebAppInstallFinalizer::RetryIncompleteUninstalls(
     const base::flat_set<AppId>& apps_to_uninstall) {
   for (const AppId& app_id : apps_to_uninstall) {
-    if (base::Contains(pending_uninstalls_, app_id))
-      continue;
-    auto uninstall_task = std::make_unique<WebAppUninstallJob>(
-        os_integration_manager_, sync_bridge_, icon_manager_, registrar_,
-        install_manager_, this, translation_manager_, profile_->GetPrefs());
-    const WebApp* web_app = registrar_->GetAppById(app_id);
-    if (!web_app)
-      continue;
-    uninstall_task->Start(
-        app_id, url::Origin::Create(web_app->start_url()),
-        webapps::WebappUninstallSource::kStartupCleanup,
-        base::BindOnce(&WebAppInstallFinalizer::OnUninstallComplete,
-                       weak_ptr_factory_.GetWeakPtr(), app_id,
-                       webapps::WebappUninstallSource::kStartupCleanup,
-                       base::DoNothing()));
-    pending_uninstalls_[app_id] = std::move(uninstall_task);
+    command_manager_->ScheduleCommand(std::make_unique<WebAppUninstallCommand>(
+        app_id,
+        url::Origin::Create(registrar_->GetAppById(app_id)->start_url()),
+        profile_, os_integration_manager_, sync_bridge_, icon_manager_,
+        registrar_, install_manager_, this, translation_manager_,
+        webapps::WebappUninstallSource::kStartupCleanup, base::DoNothing()));
   }
 }
 
@@ -388,7 +355,6 @@
 }
 
 void WebAppInstallFinalizer::Shutdown() {
-  pending_uninstalls_.clear();
   started_ = false;
 }
 
@@ -405,7 +371,8 @@
     OsIntegrationManager* os_integration_manager,
     WebAppIconManager* icon_manager,
     WebAppPolicyManager* policy_manager,
-    WebAppTranslationManager* translation_manager) {
+    WebAppTranslationManager* translation_manager,
+    WebAppCommandManager* command_manager) {
   install_manager_ = install_manager;
   registrar_ = registrar;
   ui_manager_ = ui_manager;
@@ -414,50 +381,18 @@
   icon_manager_ = icon_manager;
   policy_manager_ = policy_manager;
   translation_manager_ = translation_manager;
-}
-
-std::vector<AppId> WebAppInstallFinalizer::GetPendingUninstallsForTesting()
-    const {
-  std::vector<AppId> ids;
-  for (const auto& [key, _] : pending_uninstalls_) {
-    ids.push_back(key);
-  }
-  return ids;
+  command_manager_ = command_manager;
 }
 
 void WebAppInstallFinalizer::UninstallWebAppInternal(
     const AppId& app_id,
     webapps::WebappUninstallSource uninstall_source,
     UninstallWebAppCallback callback) {
-  if (registrar_->GetAppById(app_id) == nullptr ||
-      base::Contains(pending_uninstalls_, app_id)) {
-    std::move(callback).Run(webapps::UninstallResultCode::kNoAppToUninstall);
-    return;
-  }
-  auto uninstall_task = std::make_unique<WebAppUninstallJob>(
-      os_integration_manager_, sync_bridge_, icon_manager_, registrar_,
-      install_manager_, this, translation_manager_, profile_->GetPrefs());
-  uninstall_task->Start(
+  command_manager_->ScheduleCommand(std::make_unique<WebAppUninstallCommand>(
       app_id, url::Origin::Create(registrar_->GetAppById(app_id)->start_url()),
-      uninstall_source,
-      base::BindOnce(&WebAppInstallFinalizer::OnUninstallComplete,
-                     weak_ptr_factory_.GetWeakPtr(), app_id, uninstall_source,
-                     std::move(callback)));
-  pending_uninstalls_[app_id] = std::move(uninstall_task);
-}
-
-void WebAppInstallFinalizer::OnUninstallComplete(
-    AppId app_id,
-    webapps::WebappUninstallSource source,
-    UninstallWebAppCallback callback,
-    webapps::UninstallResultCode code) {
-  DCHECK(base::Contains(pending_uninstalls_, app_id));
-  pending_uninstalls_.erase(app_id);
-  if (source == webapps::WebappUninstallSource::kSync) {
-    base::UmaHistogramBoolean("Webapp.SyncInitiatedUninstallResult",
-                              code == webapps::UninstallResultCode::kSuccess);
-  }
-  std::move(callback).Run(code);
+      profile_, os_integration_manager_, sync_bridge_, icon_manager_,
+      registrar_, install_manager_, this, translation_manager_,
+      uninstall_source, std::move(callback)));
 }
 
 void WebAppInstallFinalizer::UninstallExternalWebAppOrRemoveSource(
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.h b/chrome/browser/web_applications/web_app_install_finalizer.h
index b720eb0..a57c7c0a 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.h
+++ b/chrome/browser/web_applications/web_app_install_finalizer.h
@@ -19,7 +19,6 @@
 #include "chrome/browser/web_applications/web_app_constants.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
-#include "chrome/browser/web_applications/web_app_uninstall_job.h"
 #include "components/webapps/browser/install_result_code.h"
 #include "components/webapps/browser/installable/installable_metrics.h"
 #include "third_party/skia/include/core/SkBitmap.h"
@@ -41,7 +40,7 @@
 class WebAppPolicyManager;
 class WebAppRegistrar;
 class WebAppTranslationManager;
-class WebAppUninstallJob;
+class WebAppCommandManager;
 
 // An finalizer for the installation process, represents the last step.
 // Takes WebAppInstallInfo as input, writes data to disk (e.g icons, shortcuts)
@@ -127,19 +126,6 @@
   virtual void RetryIncompleteUninstalls(
       const base::flat_set<AppId>& apps_to_uninstall);
 
-  // Sync-initiated uninstall. Copied from WebAppInstallSyncInstallDelegate.
-  // Called before the web apps are removed from the registry by sync. This:
-  // * Begins process of uninstalling OS hooks, which initially requires the
-  //   registrar to still contain the web app data.
-  // * Notifies observers of WebAppWillBeUninstalled.
-  // After the app data is fully deleted & os hooks uninstalled:
-  // * Notifies observers of WebAppUninstalled.
-  // * `callback` is called.
-  // The registrar is expected to be synchronously updated after this function
-  // call to remove the given `web_apps`.
-  virtual void UninstallFromSync(const std::vector<AppId>& web_apps,
-                                 RepeatingUninstallCallback callback);
-
   virtual bool CanUserUninstallWebApp(const AppId& app_id) const;
 
   virtual bool CanReparentTab(const AppId& app_id, bool shortcut_created) const;
@@ -157,7 +143,8 @@
                      OsIntegrationManager* os_integration_manager,
                      WebAppIconManager* icon_manager,
                      WebAppPolicyManager* policy_manager,
-                     WebAppTranslationManager* translation_manager);
+                     WebAppTranslationManager* translation_manager,
+                     WebAppCommandManager* command_manager);
 
   virtual void SetRemoveSourceCallbackForTesting(
       base::RepeatingCallback<void(const AppId&)>);
@@ -172,18 +159,12 @@
                                   bool is_placeholder,
                                   GURL install_url);
 
-  std::vector<AppId> GetPendingUninstallsForTesting() const;
-
  private:
   using CommitCallback = base::OnceCallback<void(bool success)>;
 
   void UninstallWebAppInternal(const AppId& app_id,
                                webapps::WebappUninstallSource uninstall_surface,
                                UninstallWebAppCallback callback);
-  void OnUninstallComplete(AppId app_id,
-                           webapps::WebappUninstallSource uninstall_surface,
-                           UninstallWebAppCallback callback,
-                           webapps::UninstallResultCode code);
   void UninstallExternalWebAppOrRemoveSource(
       const AppId& app_id,
       WebAppManagement::Type install_source,
@@ -250,13 +231,11 @@
   raw_ptr<WebAppIconManager> icon_manager_ = nullptr;
   raw_ptr<WebAppPolicyManager> policy_manager_ = nullptr;
   raw_ptr<WebAppTranslationManager> translation_manager_ = nullptr;
+  raw_ptr<WebAppCommandManager> command_manager_ = nullptr;
 
   const raw_ptr<Profile> profile_;
   bool started_ = false;
 
-  base::flat_map<AppId, std::unique_ptr<WebAppUninstallJob>>
-      pending_uninstalls_;
-
   base::RepeatingCallback<void(const AppId& app_id)>
       install_source_removed_callback_for_testing_;
 
diff --git a/chrome/browser/web_applications/web_app_install_manager.cc b/chrome/browser/web_applications/web_app_install_manager.cc
index 3f424a7..eba8ccc 100644
--- a/chrome/browser/web_applications/web_app_install_manager.cc
+++ b/chrome/browser/web_applications/web_app_install_manager.cc
@@ -19,6 +19,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/commands/install_from_sync_command.h"
+#include "chrome/browser/web_applications/commands/web_app_uninstall_command.h"
 #include "chrome/browser/web_applications/install_bounce_metric.h"
 #include "chrome/browser/web_applications/user_display_mode.h"
 #include "chrome/browser/web_applications/web_app.h"
@@ -106,11 +107,17 @@
     WebAppRegistrar* registrar,
     OsIntegrationManager* os_integration_manager,
     WebAppCommandManager* command_manager,
-    WebAppInstallFinalizer* finalizer) {
+    WebAppInstallFinalizer* finalizer,
+    WebAppIconManager* icon_manager,
+    WebAppSyncBridge* sync_bridge,
+    WebAppTranslationManager* translation_manager) {
   registrar_ = registrar;
   os_integration_manager_ = os_integration_manager;
   command_manager_ = command_manager;
   finalizer_ = finalizer;
+  icon_manager_ = icon_manager;
+  sync_bridge_ = sync_bridge;
+  translation_manager_ = translation_manager;
 }
 
 void WebAppInstallManager::LoadWebAppAndCheckManifest(
@@ -220,15 +227,15 @@
   if (!started_)
     return;
 
-  finalizer_->UninstallFromSync(
-      std::move(web_apps),
-      base::BindRepeating(
-          [](RepeatingUninstallCallback callback, const web_app::AppId& app_id,
-             webapps::UninstallResultCode code) {
-            callback.Run(app_id,
-                         code == webapps::UninstallResultCode::kSuccess);
-          },
-          std::move(callback)));
+  for (auto& app_id : web_apps) {
+    command_manager_->ScheduleCommand(std::make_unique<WebAppUninstallCommand>(
+        app_id,
+        url::Origin::Create(registrar_->GetAppById(app_id)->start_url()),
+        profile_, os_integration_manager_, sync_bridge_, icon_manager_,
+        registrar_, this, finalizer_, translation_manager_,
+        webapps::WebappUninstallSource::kSync,
+        base::BindOnce(callback, app_id)));
+  }
 }
 
 void WebAppInstallManager::RetryIncompleteUninstalls(
diff --git a/chrome/browser/web_applications/web_app_install_manager.h b/chrome/browser/web_applications/web_app_install_manager.h
index a942d51d..9ca8f02 100644
--- a/chrome/browser/web_applications/web_app_install_manager.h
+++ b/chrome/browser/web_applications/web_app_install_manager.h
@@ -43,6 +43,9 @@
 class WebAppInstallTask;
 class WebAppRegistrar;
 class OsIntegrationManager;
+class WebAppSyncBridge;
+class WebAppTranslationManager;
+class WebAppIconManager;
 
 // TODO(loyso): Unify the API and merge similar InstallWebAppZZZZ functions.
 class WebAppInstallManager final : public SyncInstallDelegate {
@@ -58,7 +61,10 @@
   void SetSubsystems(WebAppRegistrar* registrar,
                      OsIntegrationManager* os_integration_manager,
                      WebAppCommandManager* command_manager,
-                     WebAppInstallFinalizer* finalizer);
+                     WebAppInstallFinalizer* finalizer,
+                     WebAppIconManager* icon_manager,
+                     WebAppSyncBridge* sync_bridge,
+                     WebAppTranslationManager* translation_manager);
 
   // Loads |web_app_url| in a new WebContents and determines whether it has a
   // valid manifest. Calls |callback| with results.
@@ -187,6 +193,9 @@
   raw_ptr<OsIntegrationManager> os_integration_manager_ = nullptr;
   raw_ptr<WebAppInstallFinalizer> finalizer_ = nullptr;
   raw_ptr<WebAppCommandManager> command_manager_ = nullptr;
+  raw_ptr<WebAppSyncBridge> sync_bridge_ = nullptr;
+  raw_ptr<WebAppTranslationManager> translation_manager_ = nullptr;
+  raw_ptr<WebAppIconManager> icon_manager_ = nullptr;
 
   // All owned tasks.
   using Tasks = base::flat_set<std::unique_ptr<WebAppInstallTask>,
diff --git a/chrome/browser/web_applications/web_app_install_manager_unittest.cc b/chrome/browser/web_applications/web_app_install_manager_unittest.cc
index d9b90d3..4633b845 100644
--- a/chrome/browser/web_applications/web_app_install_manager_unittest.cc
+++ b/chrome/browser/web_applications/web_app_install_manager_unittest.cc
@@ -44,7 +44,6 @@
 #include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/browser/web_applications/web_app_sync_bridge.h"
-#include "chrome/browser/web_applications/web_app_uninstall_job.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/webapps/browser/install_result_code.h"
@@ -116,8 +115,9 @@
     install_manager_ = std::make_unique<WebAppInstallManager>(profile());
     install_manager_->SetSubsystems(
         &registrar(), &controller().os_integration_manager(),
-        &fake_registry_controller_->command_manager(),
-        install_finalizer_.get());
+        &fake_registry_controller_->command_manager(), install_finalizer_.get(),
+        icon_manager_.get(), &fake_registry_controller_->sync_bridge(),
+        &fake_registry_controller_->translation_manager());
 
     auto test_url_loader = std::make_unique<TestWebAppUrlLoader>();
 
@@ -133,7 +133,8 @@
         &fake_registry_controller_->sync_bridge(),
         &fake_registry_controller_->os_integration_manager(),
         icon_manager_.get(), policy_manager_.get(),
-        &fake_registry_controller_->translation_manager());
+        &fake_registry_controller_->translation_manager(),
+        &fake_registry_controller_->command_manager());
   }
 
   void TearDown() override {
@@ -428,12 +429,12 @@
             std::move(apps_to_uninstall),
             base::BindLambdaForTesting(
                 [&, callback](const AppId& uninstalled_app_id,
-                              bool uninstalled) {
+                              webapps::UninstallResultCode code) {
                   EXPECT_EQ(uninstalled_app_id, app_id);
-                  EXPECT_TRUE(uninstalled);
+                  EXPECT_EQ(code, webapps::UninstallResultCode::kSuccess);
                   event_order.push_back(Event::kUninstallFromSync_Callback);
                   run_loop.Quit();
-                  callback.Run(uninstalled_app_id, uninstalled);
+                  callback.Run(uninstalled_app_id, code);
                 }));
       }));
 
@@ -489,12 +490,12 @@
             std::move(apps_to_uninstall),
             base::BindLambdaForTesting(
                 [&, callback](const AppId& uninstalled_app_id,
-                              bool uninstalled) {
+                              webapps::UninstallResultCode code) {
                   EXPECT_EQ(uninstalled_app_id, app_id);
-                  EXPECT_TRUE(uninstalled);
+                  EXPECT_EQ(code, webapps::UninstallResultCode::kSuccess);
                   event_order.push_back(Event::kUninstallFromSync_Callback);
                   run_loop.Quit();
-                  callback.Run(uninstalled_app_id, uninstalled);
+                  callback.Run(uninstalled_app_id, code);
                 }));
       }));
 
diff --git a/chrome/browser/web_applications/web_app_install_task_unittest.cc b/chrome/browser/web_applications/web_app_install_task_unittest.cc
index 226554d..7f82566 100644
--- a/chrome/browser/web_applications/web_app_install_task_unittest.cc
+++ b/chrome/browser/web_applications/web_app_install_task_unittest.cc
@@ -117,7 +117,8 @@
         &fake_registry_controller_->sync_bridge(),
         &fake_os_integration_manager(), icon_manager_.get(),
         policy_manager_.get(),
-        &fake_registry_controller_->translation_manager());
+        &fake_registry_controller_->translation_manager(),
+        &fake_registry_controller_->command_manager());
 
     url_loader_ = std::make_unique<TestWebAppUrlLoader>();
     controller().Init();
diff --git a/chrome/browser/web_applications/web_app_provider.cc b/chrome/browser/web_applications/web_app_provider.cc
index 3a9edfa..9bb4e0b4 100644
--- a/chrome/browser/web_applications/web_app_provider.cc
+++ b/chrome/browser/web_applications/web_app_provider.cc
@@ -290,10 +290,12 @@
   install_finalizer_->SetSubsystems(
       install_manager_.get(), registrar_.get(), ui_manager_.get(),
       sync_bridge_.get(), os_integration_manager_.get(), icon_manager_.get(),
-      web_app_policy_manager_.get(), translation_manager_.get());
+      web_app_policy_manager_.get(), translation_manager_.get(),
+      command_manager_.get());
   install_manager_->SetSubsystems(
       registrar_.get(), os_integration_manager_.get(), command_manager_.get(),
-      install_finalizer_.get());
+      install_finalizer_.get(), icon_manager_.get(), sync_bridge_.get(),
+      translation_manager_.get());
   manifest_update_manager_->SetSubsystems(
       install_manager_.get(), registrar_.get(), icon_manager_.get(),
       ui_manager_.get(), install_finalizer_.get(),
diff --git a/chrome/browser/web_applications/web_app_sync_bridge.cc b/chrome/browser/web_applications/web_app_sync_bridge.cc
index d385c573..0112251 100644
--- a/chrome/browser/web_applications/web_app_sync_bridge.cc
+++ b/chrome/browser/web_applications/web_app_sync_bridge.cc
@@ -549,10 +549,14 @@
   std::move(callback).Run(success);
 }
 
-void WebAppSyncBridge::WebAppUninstalled(const AppId& app, bool uninstalled) {
-  // In the case `uninstalled` is false, the AppId should still be removed from
-  // the set, since uninstall failures are not yet handled, and there are no
-  // uninstall retry attempts.
+void WebAppSyncBridge::OnWebAppUninstallComplete(
+    const AppId& app,
+    webapps::UninstallResultCode code) {
+  base::UmaHistogramBoolean("Webapp.SyncInitiatedUninstallResult",
+                            code == webapps::UninstallResultCode::kSuccess);
+  // In the case where code indicated a failure, the AppId should still be
+  // removed from the set, since uninstall failures are not yet handled, and
+  // there are no uninstall retry attempts.
   apps_in_sync_uninstall_.erase(app);
 }
 
@@ -681,7 +685,7 @@
     command_manager_->NotifySyncSourceRemoved(apps_to_delete);
     install_delegate_->UninstallFromSync(
         apps_to_delete,
-        base::BindRepeating(&WebAppSyncBridge::WebAppUninstalled,
+        base::BindRepeating(&WebAppSyncBridge::OnWebAppUninstallComplete,
                             weak_ptr_factory_.GetWeakPtr()));
   }
 
diff --git a/chrome/browser/web_applications/web_app_sync_bridge.h b/chrome/browser/web_applications/web_app_sync_bridge.h
index 712943f3..f9e8df0 100644
--- a/chrome/browser/web_applications/web_app_sync_bridge.h
+++ b/chrome/browser/web_applications/web_app_sync_bridge.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "components/sync/model/entity_change.h"
 #include "components/sync/model/model_type_sync_bridge.h"
+#include "components/webapps/browser/uninstall_result_code.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
@@ -162,6 +163,8 @@
   void UpdateRegistrar(std::unique_ptr<RegistryUpdateData> update_data);
 
   // Useful for identifying apps that have not yet been fully uninstalled.
+  // TODO(phillis): Remove apps_in_sync_uninstall_ and
+  // GetAppsInSyncUninstallForTest. https://crbug.com/1341354
   std::set<AppId> apps_in_sync_uninstall_;
 
   // Update the remote sync server.
@@ -172,7 +175,8 @@
                         Registry registry,
                         std::unique_ptr<syncer::MetadataBatch> metadata_batch);
   void OnDataWritten(CommitCallback callback, bool success);
-  void WebAppUninstalled(const AppId& app, bool uninstalled);
+  void OnWebAppUninstallComplete(const AppId& app,
+                                 webapps::UninstallResultCode code);
 
   void ReportErrorToChangeProcessor(const syncer::ModelError& error);
 
diff --git a/chrome/browser/web_applications/web_app_sync_bridge_unittest.cc b/chrome/browser/web_applications/web_app_sync_bridge_unittest.cc
index 1aaf8fc4..678070fd 100644
--- a/chrome/browser/web_applications/web_app_sync_bridge_unittest.cc
+++ b/chrome/browser/web_applications/web_app_sync_bridge_unittest.cc
@@ -580,7 +580,8 @@
             ScopedRegistryUpdate update(&sync_bridge());
             update->DeleteApp(app_to_uninstall);
           }
-          callback.Run(app_to_uninstall, true);
+          callback.Run(app_to_uninstall,
+                       webapps::UninstallResultCode::kSuccess);
         }
 
         barrier_closure.Run();
diff --git a/chrome/browser/web_applications/web_app_sync_install_delegate.h b/chrome/browser/web_applications/web_app_sync_install_delegate.h
index e96dfd7..e774a58 100644
--- a/chrome/browser/web_applications/web_app_sync_install_delegate.h
+++ b/chrome/browser/web_applications/web_app_sync_install_delegate.h
@@ -12,6 +12,7 @@
 #include "base/containers/flat_set.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "components/webapps/browser/install_result_code.h"
+#include "components/webapps/browser/uninstall_result_code.h"
 
 namespace web_app {
 
@@ -27,7 +28,8 @@
       base::RepeatingCallback<void(const AppId& app_id,
                                    webapps::InstallResultCode code)>;
   using RepeatingUninstallCallback =
-      base::RepeatingCallback<void(const AppId& app_id, bool uninstalled)>;
+      base::RepeatingCallback<void(const AppId& app_id,
+                                   webapps::UninstallResultCode code)>;
 
   // |web_apps| are already registered and owned by the registrar.
   virtual void InstallWebAppsAfterSync(std::vector<WebApp*> web_apps,
diff --git a/chrome/browser/web_applications/web_app_uninstall_job.cc b/chrome/browser/web_applications/web_app_uninstall_job.cc
deleted file mode 100644
index 0fdb904..0000000
--- a/chrome/browser/web_applications/web_app_uninstall_job.cc
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/web_applications/web_app_uninstall_job.h"
-
-#include "base/bind.h"
-#include "base/logging.h"
-#include "base/metrics/histogram_functions.h"
-#include "chrome/browser/web_applications/isolation_prefs_utils.h"
-#include "chrome/browser/web_applications/web_app.h"
-#include "chrome/browser/web_applications/web_app_icon_manager.h"
-#include "chrome/browser/web_applications/web_app_install_finalizer.h"
-#include "chrome/browser/web_applications/web_app_install_manager.h"
-#include "chrome/browser/web_applications/web_app_registrar.h"
-#include "chrome/browser/web_applications/web_app_registry_update.h"
-#include "chrome/browser/web_applications/web_app_sync_bridge.h"
-#include "chrome/browser/web_applications/web_app_translation_manager.h"
-#include "components/webapps/browser/installable/installable_metrics.h"
-#include "components/webapps/browser/uninstall_result_code.h"
-
-namespace web_app {
-
-WebAppUninstallJob::WebAppUninstallJob(
-    OsIntegrationManager* os_integration_manager,
-    WebAppSyncBridge* sync_bridge,
-    WebAppIconManager* icon_manager,
-    WebAppRegistrar* registrar,
-    WebAppInstallManager* install_manager,
-    WebAppInstallFinalizer* install_finalizer,
-    WebAppTranslationManager* translation_manager,
-    PrefService* profile_prefs)
-    : os_integration_manager_(os_integration_manager),
-      sync_bridge_(sync_bridge),
-      icon_manager_(icon_manager),
-      registrar_(registrar),
-      install_manager_(install_manager),
-      install_finalizer_(install_finalizer),
-      translation_manager_(translation_manager),
-      profile_prefs_(profile_prefs) {}
-
-WebAppUninstallJob::~WebAppUninstallJob() = default;
-
-void WebAppUninstallJob::Start(const AppId& app_id,
-                               const url::Origin& app_origin,
-                               webapps::WebappUninstallSource source,
-                               UninstallCallback callback) {
-  DCHECK(install_manager_);
-
-  app_id_ = app_id;
-  source_ = source;
-  callback_ = std::move(callback);
-  DCHECK(state_ == State::kNotStarted);
-  state_ = State::kPendingDataDeletion;
-
-  // Note: It is supported to re-start an uninstall on startup, so
-  // `is_uninstalling()` is not checked. It is a class invariant that there can
-  // never be more than one uninstall task operating on the same web app at the
-  // same time.
-  {
-    ScopedRegistryUpdate update(sync_bridge_);
-    WebApp* app = update->UpdateApp(app_id);
-    DCHECK(app);
-    app->SetIsUninstalling(true);
-  }
-  install_manager_->NotifyWebAppWillBeUninstalled(app_id);
-
-  RemoveAppIsolationState(profile_prefs_, app_origin);
-
-  // Uninstall any sub-apps the app has.
-  std::vector<AppId> sub_app_ids = registrar_->GetAllSubAppIds(app_id_);
-  num_pending_sub_app_uninstalls_ = sub_app_ids.size();
-  for (const AppId& sub_app_id : sub_app_ids) {
-    if (registrar_->GetAppById(sub_app_id) == nullptr)
-      continue;
-    install_finalizer_->UninstallExternalWebApp(
-        sub_app_id, WebAppManagement::Type::kSubApp,
-        webapps::WebappUninstallSource::kSubApp,
-        base::BindOnce(&WebAppUninstallJob::OnSubAppUninstalled,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
-
-  os_integration_manager_->UninstallAllOsHooks(
-      app_id_, base::BindOnce(&WebAppUninstallJob::OnOsHooksUninstalled,
-                              weak_ptr_factory_.GetWeakPtr()));
-  icon_manager_->DeleteData(
-      app_id, base::BindOnce(&WebAppUninstallJob::OnIconDataDeleted,
-                             weak_ptr_factory_.GetWeakPtr()));
-
-  translation_manager_->DeleteTranslations(
-      app_id, base::BindOnce(&WebAppUninstallJob::OnTranslationDataDeleted,
-                             weak_ptr_factory_.GetWeakPtr()));
-}
-
-void WebAppUninstallJob::OnSubAppUninstalled(
-    webapps::UninstallResultCode code) {
-  errors_ = errors_ || (code != webapps::UninstallResultCode::kSuccess);
-  num_pending_sub_app_uninstalls_--;
-  DCHECK_GE(num_pending_sub_app_uninstalls_, 0u);
-  MaybeFinishUninstall();
-}
-
-void WebAppUninstallJob::OnOsHooksUninstalled(OsHooksErrors errors) {
-  DCHECK(state_ == State::kPendingDataDeletion);
-  hooks_uninstalled_ = true;
-  // TODO(https://crbug.com/1293234): Remove after flakiness is solved.
-  DLOG_IF(ERROR, errors.any())
-      << "OS integration errors for " << app_id_ << ": " << errors.to_string();
-  base::UmaHistogramBoolean("WebApp.Uninstall.OsHookSuccess", errors.none());
-  errors_ = errors_ || errors.any();
-  MaybeFinishUninstall();
-}
-
-void WebAppUninstallJob::OnIconDataDeleted(bool success) {
-  DCHECK(state_ == State::kPendingDataDeletion);
-  app_data_deleted_ = true;
-  // TODO(https://crbug.com/1293234): Remove after flakiness is solved.
-  DLOG_IF(ERROR, !success) << "Error deleting icon data for " << app_id_;
-  base::UmaHistogramBoolean("WebApp.Uninstall.IconDataSuccess", success);
-  errors_ = errors_ || !success;
-  MaybeFinishUninstall();
-}
-
-void WebAppUninstallJob::OnTranslationDataDeleted(bool success) {
-  DCHECK(state_ == State::kPendingDataDeletion);
-  translation_data_deleted_ = true;
-  errors_ = errors_ || !success;
-  MaybeFinishUninstall();
-}
-
-void WebAppUninstallJob::MaybeFinishUninstall() {
-  DCHECK(state_ == State::kPendingDataDeletion);
-  if (!hooks_uninstalled_ || !app_data_deleted_ ||
-      num_pending_sub_app_uninstalls_ > 0 || !translation_data_deleted_) {
-    return;
-  }
-  DCHECK_EQ(num_pending_sub_app_uninstalls_, 0u);
-  state_ = State::kDone;
-
-  base::UmaHistogramBoolean("WebApp.Uninstall.Result", !errors_);
-
-  webapps::InstallableMetrics::TrackUninstallEvent(source_);
-  {
-    DCHECK_NE(registrar_->GetAppById(app_id_), nullptr);
-    ScopedRegistryUpdate update(sync_bridge_);
-    update->DeleteApp(app_id_);
-  }
-  install_manager_->NotifyWebAppUninstalled(app_id_);
-  std::move(callback_).Run(errors_ ? webapps::UninstallResultCode::kError
-                                   : webapps::UninstallResultCode::kSuccess);
-}
-
-}  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_uninstall_job.h b/chrome/browser/web_applications/web_app_uninstall_job.h
deleted file mode 100644
index 7d21712..0000000
--- a/chrome/browser/web_applications/web_app_uninstall_job.h
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_UNINSTALL_JOB_H_
-#define CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_UNINSTALL_JOB_H_
-
-#include "base/callback.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
-#include "chrome/browser/web_applications/web_app_id.h"
-#include "url/origin.h"
-
-class PrefService;
-
-namespace webapps {
-enum class UninstallResultCode;
-enum class WebappUninstallSource;
-}
-
-namespace web_app {
-
-class OsIntegrationManager;
-class WebAppIconManager;
-class WebAppInstallManager;
-class WebAppInstallFinalizer;
-class WebAppRegistrar;
-class WebAppSyncBridge;
-class WebAppTranslationManager;
-
-// Uninstalls a given web app by:
-// 1) Unregistering OS hooks.
-// 2) Deleting the app from the database.
-// 3) Deleting data on disk.
-// Extra invariants:
-// * There is never more than one uninstall task operating on the same app at
-//   the same time.
-// TODO(https://crbug.com/1162477): Make the database delete happen last.
-class WebAppUninstallJob {
- public:
-  using UninstallCallback =
-      base::OnceCallback<void(webapps::UninstallResultCode)>;
-
-  WebAppUninstallJob(OsIntegrationManager* os_integration_manager,
-                     WebAppSyncBridge* sync_bridge,
-                     WebAppIconManager* icon_manager,
-                     WebAppRegistrar* registrar,
-                     WebAppInstallManager* install_manager,
-                     WebAppInstallFinalizer* install_finalizer,
-                     WebAppTranslationManager* translation_manager,
-                     PrefService* profile_prefs);
-  ~WebAppUninstallJob();
-
-  // The given `app_id` must correspond to an app in the `registrar`.
-  // This modifies the app to set `is_uninstalling()` to true, and delete the
-  // app from the registry after uninstallation is complete.
-  void Start(const AppId& app_id,
-             const url::Origin& app_origin,
-             webapps::WebappUninstallSource source,
-             UninstallCallback callback);
-
- private:
-  void OnSubAppUninstalled(webapps::UninstallResultCode code);
-  void OnOsHooksUninstalled(OsHooksErrors errors);
-  void OnIconDataDeleted(bool success);
-  void OnTranslationDataDeleted(bool success);
-  void MaybeFinishUninstall();
-
-  enum class State {
-    kNotStarted = 0,
-    kPendingDataDeletion = 1,
-    kDone = 2,
-  } state_ = State::kNotStarted;
-
-  raw_ptr<OsIntegrationManager> os_integration_manager_;
-  raw_ptr<WebAppSyncBridge> sync_bridge_;
-  raw_ptr<WebAppIconManager> icon_manager_;
-  raw_ptr<WebAppRegistrar> registrar_;
-  raw_ptr<WebAppInstallManager> install_manager_;
-  raw_ptr<WebAppInstallFinalizer> install_finalizer_;
-  raw_ptr<WebAppTranslationManager> translation_manager_;
-  raw_ptr<PrefService> profile_prefs_;
-
-  AppId app_id_;
-  webapps::WebappUninstallSource source_;
-  UninstallCallback callback_;
-  size_t num_pending_sub_app_uninstalls_;
-
-  bool app_data_deleted_ = false;
-  bool translation_data_deleted_ = false;
-  bool hooks_uninstalled_ = false;
-  bool errors_ = false;
-
-  base::WeakPtrFactory<WebAppUninstallJob> weak_ptr_factory_{this};
-};
-
-}  // namespace web_app
-
-#endif  // CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_UNINSTALL_JOB_H_
diff --git a/chrome/browser/web_applications/web_app_uninstall_job_unittest.cc b/chrome/browser/web_applications/web_app_uninstall_job_unittest.cc
deleted file mode 100644
index 06624315..0000000
--- a/chrome/browser/web_applications/web_app_uninstall_job_unittest.cc
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/web_applications/web_app_uninstall_job.h"
-
-#include <memory>
-
-#include "base/memory/scoped_refptr.h"
-#include "base/test/bind.h"
-#include "base/test/gmock_callback_support.h"
-#include "base/test/task_environment.h"
-#include "chrome/browser/web_applications/test/fake_web_app_database_factory.h"
-#include "chrome/browser/web_applications/test/fake_web_app_registry_controller.h"
-#include "chrome/browser/web_applications/test/mock_file_utils_wrapper.h"
-#include "chrome/browser/web_applications/test/mock_os_integration_manager.h"
-#include "chrome/browser/web_applications/test/test_file_utils.h"
-#include "chrome/browser/web_applications/test/web_app_test.h"
-#include "chrome/browser/web_applications/test/web_app_test_utils.h"
-#include "chrome/browser/web_applications/web_app.h"
-#include "chrome/browser/web_applications/web_app_constants.h"
-#include "chrome/browser/web_applications/web_app_icon_manager.h"
-#include "chrome/browser/web_applications/web_app_install_finalizer.h"
-#include "chrome/browser/web_applications/web_app_install_manager.h"
-#include "chrome/browser/web_applications/web_app_utils.h"
-#include "components/prefs/testing_pref_service.h"
-#include "components/webapps/browser/installable/installable_metrics.h"
-#include "components/webapps/browser/uninstall_result_code.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "url/origin.h"
-
-namespace web_app {
-namespace {
-
-class WebAppUninstallJobTest : public WebAppTest {
- public:
-  WebAppUninstallJobTest() = default;
-
-  void SetUp() override {
-    WebAppTest::SetUp();
-
-    fake_registry_controller_ =
-        std::make_unique<FakeWebAppRegistryController>();
-
-    controller().SetUp(profile());
-
-    install_manager_ = std::make_unique<WebAppInstallManager>(profile());
-
-    file_utils_wrapper_ =
-        base::MakeRefCounted<testing::StrictMock<MockFileUtilsWrapper>>();
-    icon_manager_ =
-        std::make_unique<WebAppIconManager>(profile(), file_utils_wrapper_);
-    icon_manager_->SetSubsystems(&controller().registrar(), &install_manager());
-  }
-
-  void TearDown() override {
-    file_utils_wrapper_ = nullptr;
-    WebAppTest::TearDown();
-  }
-
-  FakeWebAppRegistryController& controller() {
-    return *fake_registry_controller_;
-  }
-
-  WebAppInstallManager& install_manager() const { return *install_manager_; }
-
-  WebAppInstallFinalizer& install_finalizer() const {
-    return *install_finalizer_;
-  }
-
-  testing::StrictMock<MockOsIntegrationManager> os_integration_manager_;
-  std::unique_ptr<WebAppInstallManager> install_manager_;
-  std::unique_ptr<WebAppInstallFinalizer> install_finalizer_;
-  std::unique_ptr<FakeWebAppRegistryController> fake_registry_controller_;
-  std::unique_ptr<WebAppIconManager> icon_manager_;
-  scoped_refptr<testing::StrictMock<MockFileUtilsWrapper>> file_utils_wrapper_;
-};
-
-TEST_F(WebAppUninstallJobTest, SimpleUninstall) {
-  Registry registry;
-  auto web_app = test::CreateWebApp(GURL("https://www.example.com"),
-                                    WebAppManagement::kSync);
-  AppId id = web_app->app_id();
-  registry.emplace(id, std::move(web_app));
-  controller().database_factory().WriteRegistry(registry);
-  controller().Init();
-
-  WebAppUninstallJob task(&os_integration_manager_, &controller().sync_bridge(),
-                          icon_manager_.get(), &controller().registrar(),
-                          &install_manager(), &install_finalizer(),
-                          &controller().translation_manager(),
-                          profile()->GetPrefs());
-
-  OsHooksErrors result;
-  EXPECT_CALL(os_integration_manager_, UninstallAllOsHooks(id, testing::_))
-      .WillOnce(base::test::RunOnceCallback<1>(result));
-
-  base::FilePath deletion_path = GetManifestResourcesDirectoryForApp(
-      GetWebAppsRootDirectory(profile()), id);
-
-  EXPECT_CALL(*file_utils_wrapper_, DeleteFileRecursively(deletion_path))
-      .WillOnce(testing::Return(true));
-
-  base::RunLoop loop;
-  task.Start(id, url::Origin(), webapps::WebappUninstallSource::kAppMenu,
-             base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
-               EXPECT_EQ(webapps::UninstallResultCode::kSuccess, code);
-               loop.Quit();
-             }));
-  loop.Run();
-
-  EXPECT_EQ(controller().registrar().GetAppById(id), nullptr);
-}
-
-TEST_F(WebAppUninstallJobTest, FailedDataDelete) {
-  Registry registry;
-  auto web_app = test::CreateWebApp(GURL("https://www.example.com"),
-                                    WebAppManagement::kSync);
-  AppId id = web_app->app_id();
-  registry.emplace(id, std::move(web_app));
-  controller().database_factory().WriteRegistry(registry);
-  controller().Init();
-
-  WebAppUninstallJob task(&os_integration_manager_, &controller().sync_bridge(),
-                          icon_manager_.get(), &controller().registrar(),
-                          &install_manager(), &install_finalizer(),
-                          &controller().translation_manager(),
-                          profile()->GetPrefs());
-
-  OsHooksErrors result;
-  EXPECT_CALL(os_integration_manager_, UninstallAllOsHooks(id, testing::_))
-      .WillOnce(base::test::RunOnceCallback<1>(result));
-
-  base::FilePath deletion_path = GetManifestResourcesDirectoryForApp(
-      GetWebAppsRootDirectory(profile()), id);
-
-  EXPECT_CALL(*file_utils_wrapper_, DeleteFileRecursively(deletion_path))
-      .WillOnce(testing::Return(false));
-
-  base::RunLoop loop;
-  task.Start(id, url::Origin(), webapps::WebappUninstallSource::kAppMenu,
-             base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
-               EXPECT_EQ(webapps::UninstallResultCode::kError, code);
-               loop.Quit();
-             }));
-  loop.Run();
-
-  EXPECT_EQ(controller().registrar().GetAppById(id), nullptr);
-}
-
-TEST_F(WebAppUninstallJobTest, FailedOsHooks) {
-  Registry registry;
-  auto web_app = test::CreateWebApp(GURL("https://www.example.com"),
-                                    WebAppManagement::kSync);
-  AppId id = web_app->app_id();
-  registry.emplace(id, std::move(web_app));
-  controller().database_factory().WriteRegistry(registry);
-  controller().Init();
-
-  WebAppUninstallJob task(&os_integration_manager_, &controller().sync_bridge(),
-                          icon_manager_.get(), &controller().registrar(),
-                          &install_manager(), &install_finalizer(),
-                          &controller().translation_manager(),
-                          profile()->GetPrefs());
-
-  OsHooksErrors result;
-  result.set(true);
-  EXPECT_CALL(os_integration_manager_, UninstallAllOsHooks(id, testing::_))
-      .WillOnce(base::test::RunOnceCallback<1>(result));
-
-  base::FilePath deletion_path = GetManifestResourcesDirectoryForApp(
-      GetWebAppsRootDirectory(profile()), id);
-
-  EXPECT_CALL(*file_utils_wrapper_, DeleteFileRecursively(deletion_path))
-      .WillOnce(testing::Return(true));
-
-  base::RunLoop loop;
-  task.Start(id, url::Origin(), webapps::WebappUninstallSource::kAppMenu,
-             base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
-               EXPECT_EQ(webapps::UninstallResultCode::kError, code);
-               loop.Quit();
-             }));
-  loop.Run();
-
-  EXPECT_EQ(controller().registrar().GetAppById(id), nullptr);
-}
-
-}  // namespace
-}  // namespace web_app
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index e12faf6..289b792 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1656676699-d95bb0fc102352638ce15efc505d9b58b99195fc.profdata
+chrome-linux-main-1656741156-6760c329323fb6a0786f5f43ec915db5c7242a87.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 7467be6..3d61b3ad 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1656676699-8be33b750f7f7d60c93f27aa585cc233d4b16d68.profdata
+chrome-mac-arm-main-1656741156-1c48f24646a0595d0ec0be68d374f5b981f4857a.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index c6cefb02..4326939 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1656676699-bba5103d292294be8db6d7f00331b6d7881c6353.profdata
+chrome-mac-main-1656741156-2e6caf79fcb8b782bbfb87995cb3b148742e4b95.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 5a91fc63..9c45d19b 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1656676699-4618a10486995f0ce2953c3d208f928f7b20fbe9.profdata
+chrome-win32-main-1656741156-a8ada058b715205ec5d50c5d7c991b8062afe65c.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 960559b..06b95ed 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1656687579-2ba6f8291cff57c6f07de00e340583d4dc2270e2.profdata
+chrome-win64-main-1656741156-f2ab040434ace8266b3fe5325233a879e19617d6.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index c00fcdb0..41625e9 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -1192,6 +1192,6 @@
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 const base::Feature kUseWebAppDBInsteadOfExternalPrefs{
-    "UseWebAppDBInsteadOfExternalPrefs", base::FEATURE_DISABLED_BY_DEFAULT};
+    "UseWebAppDBInsteadOfExternalPrefs", base::FEATURE_ENABLED_BY_DEFAULT};
 
 }  // namespace features
diff --git a/chrome/common/extensions/api/gcm.json b/chrome/common/extensions/api/gcm.json
index aefee4b..ba225d3 100644
--- a/chrome/common/extensions/api/gcm.json
+++ b/chrome/common/extensions/api/gcm.json
@@ -5,7 +5,7 @@
 [
   {
     "namespace": "gcm",
-    "description": "Use <code>chrome.gcm</code> to enable apps and extensions to send and receive messages through the <a href='http://developer.android.com/google/gcm/'>Google Cloud Messaging Service</a>.",
+    "description": "Use <code>chrome.gcm</code> to enable apps and extensions to send and receive messages through <a href='https://firebase.google.com/docs/cloud-messaging/'>Firebase Cloud Messaging</a> (FCM).",
     "properties": {
       "MAX_MESSAGE_SIZE": {
         "value": 4096,
@@ -16,7 +16,7 @@
       {
         "name": "register",
         "type": "function",
-        "description": "Registers the application with GCM. The registration ID will be returned by the <code>callback</code>. If <code>register</code> is called again with the same list of <code>senderIds</code>, the same registration ID will be returned.",
+        "description": "Registers the application with FCM. The registration ID will be returned by the <code>callback</code>. If <code>register</code> is called again with the same list of <code>senderIds</code>, the same registration ID will be returned.",
         "parameters": [
           {
             "name": "senderIds",
@@ -37,7 +37,7 @@
               {
                 "name": "registrationId",
                 "type": "string",
-                "description": "A registration ID assigned to the application by the GCM."
+                "description": "A registration ID assigned to the application by the FCM."
               }
             ]
           }
@@ -46,7 +46,7 @@
       {
         "name": "unregister",
         "type": "function",
-        "description": "Unregisters the application from GCM.",
+        "description": "Unregisters the application from FCM.",
         "parameters": [
           {
             "name": "callback",
@@ -64,12 +64,12 @@
           {
             "name": "message",
             "type": "object",
-            "description": "A message to send to the other party via GCM.",
+            "description": "A message to send to the other party via FCM.",
             "properties": {
               "destinationId": {
                 "type": "string",
                 "minLength": 1,
-                "description": "The ID of the server to send the message to as assigned by <a href='https://code.google.com/apis/console'>Google API Console</a>."
+                "description": "The ID of the server to send the message to as assigned by <a href='https://console.cloud.google.com/apis/dashboard'>Google API Console</a>."
               },
               "messageId": {
                 "type": "string",
@@ -81,7 +81,7 @@
                 "minimum": 0,
                 "maximum": 86400,
                 "optional": true,
-                "description": "Time-to-live of the message in seconds. If it is not possible to send the message within that time, an onSendError event will be raised. A time-to-live of 0 indicates that the message should be sent immediately or fail if it's not possible. The maximum and a default value of time-to-live is 86400 seconds (1 day)."
+                "description": "Time-to-live of the message in seconds. If it is not possible to send the message within that time, an onSendError event will be raised. A time-to-live of 0 indicates that the message should be sent immediately or fail if it's not possible. The default value of time-to-live is 86,400 seconds (1 day) and the maximum value is 2,419,200 seconds (28 days)."
               },
               "data": {
                 "type": "object",
@@ -113,12 +113,12 @@
       {
         "name": "onMessage",
         "type": "function",
-        "description": "Fired when a message is received through GCM.",
+        "description": "Fired when a message is received through FCM.",
         "parameters": [
           {
             "name": "message",
             "type": "object",
-            "description": "A message received from another party via GCM.",
+            "description": "A message received from another party via FCM.",
             "properties": {
               "data": {
                 "type": "object",
@@ -136,7 +136,7 @@
               "collapseKey": {
                 "type": "string",
                 "optional": true,
-                "description": "The collapse key of a message. See <a href='cloudMessaging#collapsible_messages'>Collapsible Messages</a> section of Cloud Messaging documentation for details."
+                "description": "The collapse key of a message. See the <a href='https://firebase.google.com/docs/cloud-messaging/concept-options#collapsible_and_non-collapsible_messages'>Non-collapsible and collapsible messages</a> for details."
               }
             }
           }
@@ -145,17 +145,16 @@
       {
         "name": "onMessagesDeleted",
         "type": "function",
-        "description": "Fired when a GCM server had to delete messages sent by an app server to the application. See <a href='cloudMessaging#messages_deleted_event'>Messages deleted event</a> section of Cloud Messaging documentation for details on handling this event."
+        "description": "Fired when a FCM server had to delete messages sent by an app server to the application. See <a href='https://firebase.google.com/docs/cloud-messaging/concept-options#lifetime'>Lifetime of a message</a> for details on handling this event."
       },
       {
         "name": "onSendError",
         "type": "function",
-        "description": "Fired when it was not possible to send a message to the GCM server.",
+        "description": "Fired when it was not possible to send a message to the FCM server.",
         "parameters": [
           {
             "name": "error",
             "type": "object",
-            "description": "An error related to sending a message raised by GCM.",
             "properties": {
               "errorMessage": {
                 "type": "string",
@@ -175,7 +174,7 @@
                 "description": "Additional details related to the error, when available."
               }
             },
-            "description": "An error that occured while trying to send the message either in Chrome or on the GCM server. Application can retry sending the message with a reasonable backoff and possibly longer time-to-live."
+            "description": "An error that occured while trying to send the message either in Chrome or on the FCM server. Application can retry sending the message with a reasonable backoff and possibly longer time-to-live."
           }
         ]
       }
diff --git a/chrome/common/logging_chrome.cc b/chrome/common/logging_chrome.cc
index 0f733bd..2bf9783e 100644
--- a/chrome/common/logging_chrome.cc
+++ b/chrome/common/logging_chrome.cc
@@ -289,29 +289,11 @@
 }
 
 base::FilePath GetSessionLogDir(const base::CommandLine& command_line) {
-  base::FilePath log_dir;
-  std::string log_dir_str;
+  std::string log_dir;
   std::unique_ptr<base::Environment> env(base::Environment::Create());
-  if (env->GetVar(env_vars::kSessionLogDir, &log_dir_str) &&
-      !log_dir_str.empty()) {
-    log_dir = base::FilePath(log_dir_str);
-  } else if (command_line.HasSwitch(ash::switches::kLoginProfile)) {
-    base::PathService::Get(chrome::DIR_USER_DATA, &log_dir);
-    base::FilePath profile_dir;
-    std::string login_profile_value =
-        command_line.GetSwitchValueASCII(ash::switches::kLoginProfile);
-    if (login_profile_value == chrome::kLegacyProfileDir ||
-        login_profile_value == chrome::kTestUserProfileDir) {
-      profile_dir = base::FilePath(login_profile_value);
-    } else {
-      // We could not use g_browser_process > profile_helper() here.
-      std::string profile_dir_str = chrome::kProfileDirPrefix;
-      profile_dir_str.append(login_profile_value);
-      profile_dir = base::FilePath(profile_dir_str);
-    }
-    log_dir = log_dir.Append(profile_dir);
-  }
-  return log_dir;
+  if (!env->GetVar(env_vars::kSessionLogDir, &log_dir))
+    NOTREACHED();
+  return base::FilePath(log_dir);
 }
 
 base::FilePath GetSessionLogFile(const base::CommandLine& command_line) {
diff --git a/chrome/credential_provider/build/make_setup.py b/chrome/credential_provider/build/make_setup.py
index c825fb82..2da6b280 100755
--- a/chrome/credential_provider/build/make_setup.py
+++ b/chrome/credential_provider/build/make_setup.py
@@ -58,7 +58,7 @@
   Returns:
     The executable command to run the 7zip compressor.
   """
-  return (os.path.join(src_path, r'third_party\lzma_sdk\7zr.exe')
+  return (os.path.join(src_path, r'third_party\lzma_sdk\bin\7zr.exe')
           if sys.platform == 'win32' else '7zr')
 
 def GetCmdLine(command, sz_fn, gcp_7z_fn):
diff --git a/chrome/credential_provider/setup/BUILD.gn b/chrome/credential_provider/setup/BUILD.gn
index eb708c8..82aa7e5 100644
--- a/chrome/credential_provider/setup/BUILD.gn
+++ b/chrome/credential_provider/setup/BUILD.gn
@@ -61,7 +61,7 @@
   deps = [
     ":setup_resources",
     ":version",
-    "//third_party/lzma_sdk/Util/SfxSetup:7zS2_source",
+    "//third_party/lzma_sdk/C/Util/SfxSetup:7zS2_source",
   ]
   configs += [ "//build/config/win:windowed" ]
 }
diff --git a/chrome/installer/mini_installer/BUILD.gn b/chrome/installer/mini_installer/BUILD.gn
index 51fa0d7f..d7ac881 100644
--- a/chrome/installer/mini_installer/BUILD.gn
+++ b/chrome/installer/mini_installer/BUILD.gn
@@ -317,7 +317,7 @@
       "--out",
       "$target_name.exe",
       "--path_7za",
-      "../../third_party/lzma_sdk/Executable",
+      "../../third_party/lzma_sdk/bin",
     ]
     deps = [
       ":mini_installer",
diff --git a/chrome/installer/test/alternate_version_generator.cc b/chrome/installer/test/alternate_version_generator.cc
index 280bb294..4f9c33a18 100644
--- a/chrome/installer/test/alternate_version_generator.cc
+++ b/chrome/installer/test/alternate_version_generator.cc
@@ -59,7 +59,7 @@
 namespace {
 
 const wchar_t k7zaExe[] = L"7za.exe";
-const wchar_t k7zaPathRelative[] = L"..\\..\\third_party\\lzma_sdk\\Executable";
+const wchar_t k7zaPathRelative[] = L"..\\..\\third_party\\lzma_sdk\\bin";
 const wchar_t kB7[] = L"B7";
 const wchar_t kBl[] = L"BL";
 const wchar_t kChromeBin[] = L"Chrome-bin";
diff --git a/chrome/installer/util/lzma_util.cc b/chrome/installer/util/lzma_util.cc
index 3d21b9a3..77a302c 100644
--- a/chrome/installer/util/lzma_util.cc
+++ b/chrome/installer/util/lzma_util.cc
@@ -22,10 +22,10 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 extern "C" {
-#include "third_party/lzma_sdk/7z.h"
-#include "third_party/lzma_sdk/7zAlloc.h"
-#include "third_party/lzma_sdk/7zCrc.h"
-#include "third_party/lzma_sdk/7zFile.h"
+#include "third_party/lzma_sdk/C/7z.h"
+#include "third_party/lzma_sdk/C/7zAlloc.h"
+#include "third_party/lzma_sdk/C/7zCrc.h"
+#include "third_party/lzma_sdk/C/7zFile.h"
 }
 
 namespace {
diff --git a/chrome/services/file_util/xz_file_extractor.cc b/chrome/services/file_util/xz_file_extractor.cc
index f3cd166..71ee175 100644
--- a/chrome/services/file_util/xz_file_extractor.cc
+++ b/chrome/services/file_util/xz_file_extractor.cc
@@ -13,9 +13,9 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "third_party/lzma_sdk/7zCrc.h"
-#include "third_party/lzma_sdk/Xz.h"
-#include "third_party/lzma_sdk/XzCrc64.h"
+#include "third_party/lzma_sdk/C/7zCrc.h"
+#include "third_party/lzma_sdk/C/Xz.h"
+#include "third_party/lzma_sdk/C/XzCrc64.h"
 
 namespace {
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 26658b5..f5d96cf 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2864,6 +2864,7 @@
         "../browser/extensions/api/messaging/service_worker_messaging_apitest.cc",
         "../browser/extensions/api/metrics_private/metrics_apitest.cc",
         "../browser/extensions/api/module/module_apitest.cc",
+        "../browser/extensions/api/offscreen/offscreen_document_manager_browsertest.cc",
         "../browser/extensions/api/page_capture/page_capture_apitest.cc",
         "../browser/extensions/api/passwords_private/passwords_private_apitest.cc",
         "../browser/extensions/api/permissions/permissions_apitest.cc",
@@ -5306,7 +5307,6 @@
     "../browser/resource_coordinator/tab_load_tracker_unittest.cc",
     "../browser/resources_util_unittest.cc",
     "../browser/security_events/security_event_recorder_impl_unittest.cc",
-    "../browser/segmentation_platform/default_model/query_tiles_model_unittest.cc",
     "../browser/segmentation_platform/model_provider_factory_impl_unittest.cc",
     "../browser/segmentation_platform/segmentation_platform_profile_observer_unittest.cc",
     "../browser/signin/e2e_tests/test_accounts_util_unittest.cc",
@@ -5317,9 +5317,6 @@
     "../browser/search_engines/template_url_parser_unittest.cc",
     "../browser/search_engines/template_url_service_sync_unittest.cc",
     "../browser/search_engines/template_url_service_unittest.cc",
-    "../browser/segmentation_platform/default_model/feed_user_segment_unittest.cc",
-    "../browser/segmentation_platform/default_model/low_user_engagement_model_unittest.cc",
-    "../browser/segmentation_platform/default_model/price_tracking_action_model_unittest.cc",
     "../browser/services_unittest.cc",
     "../browser/sessions/chrome_serialized_navigation_driver_unittest.cc",
     "../browser/sessions/session_common_utils_unittest.cc",
@@ -5898,7 +5895,7 @@
       "../browser/safe_browsing/chrome_cleaner/srt_field_trial_win_unittest.cc",
       "../browser/shell_integration_win_unittest.cc",
       "../browser/ui/startup/credential_provider_signin_info_fetcher_win_unittest.cc",
-      "../browser/ui/views/frame/windows_10_caption_button_unittest.cc",
+      "../browser/ui/views/frame/windows_caption_button_unittest.cc",
       "../browser/ui/views/try_chrome_dialog_win/button_layout_unittest.cc",
       "../browser/ui/views/uninstall_view_unittest.cc",
       "../browser/ui/webui/version/version_handler_win_unittest.cc",
@@ -6811,6 +6808,7 @@
       # NTP is in native code on Android.
       "../browser/new_tab_page/chrome_colors/chrome_colors_service_unittest.cc",
       "../browser/new_tab_page/modules/drive/drive_service_unittest.cc",
+      "../browser/new_tab_page/modules/feed/feed_handler_unittest.cc",
       "../browser/new_tab_page/modules/photos/photos_service_unittest.cc",
       "../browser/new_tab_page/modules/safe_browsing/safe_browsing_handler_unittest.cc",
       "../browser/new_tab_page/modules/task_module/task_module_service_unittest.cc",
@@ -6840,7 +6838,6 @@
       sources += [ "../browser/process_singleton_posix_unittest.cc" ]
     }
     if (is_linux) {
-      sources += [ "../browser/themes/theme_service_linux_unittest.cc" ]
       deps += [ "//ui/views/linux_ui:linux_ui_factory" ]
     }
     if (use_ozone) {
@@ -6855,6 +6852,7 @@
       "//chrome/browser/media/router:test_support",
       "//chrome/browser/media/router/discovery:discovery",
       "//chrome/browser/media/router/discovery/access_code:access_code_sink_service",
+      "//chrome/browser/new_tab_page/modules/feed:mojo_bindings",
       "//chrome/browser/resource_coordinator:intervention_policy_database_proto",
       "//chrome/browser/resource_coordinator:tab_manager_features",
       "//chrome/browser/resource_coordinator:tab_metrics_event_proto",
@@ -8460,6 +8458,7 @@
       "../browser/ui/views/tabs/fake_tab_slot_controller.h",
       "../browser/ui/views/tabs/overflow_view_unittest.cc",
       "../browser/ui/views/tabs/tab_container_unittest.cc",
+      "../browser/ui/views/tabs/tab_group_views_unittest.cc",
       "../browser/ui/views/tabs/tab_hover_card_bubble_view_unittest.cc",
       "../browser/ui/views/tabs/tab_hover_card_controller_unittest.cc",
       "../browser/ui/views/tabs/tab_hover_card_metrics_unittest.cc",
diff --git a/chrome/test/base/test_browser_window.cc b/chrome/test/base/test_browser_window.cc
index a3e8d87..12f4299 100644
--- a/chrome/test/base/test_browser_window.cc
+++ b/chrome/test/base/test_browser_window.cc
@@ -121,7 +121,7 @@
       {ui::ColorProviderManager::ColorMode::kLight,
        ui::ColorProviderManager::ContrastMode::kNormal,
        ui::ColorProviderManager::SystemTheme::kDefault,
-       ui::ColorProviderManager::FrameType::kChromium, nullptr});
+       ui::ColorProviderManager::FrameType::kChromium});
 }
 
 ui::ElementContext TestBrowserWindow::GetElementContext() {
diff --git a/chrome/test/base/test_chrome_web_ui_controller_factory.cc b/chrome/test/base/test_chrome_web_ui_controller_factory.cc
index 0827243e..c6c6391 100644
--- a/chrome/test/base/test_chrome_web_ui_controller_factory.cc
+++ b/chrome/test/base/test_chrome_web_ui_controller_factory.cc
@@ -65,8 +65,7 @@
                : ChromeWebUIControllerFactory::CreateWebUIControllerForURL(
                      web_ui, webui_url);
   // Add an empty callback since managed-footnote always sends this message.
-  web_ui->RegisterDeprecatedMessageCallback("observeManagedUI",
-                                            base::DoNothing());
+  web_ui->RegisterMessageCallback("observeManagedUI", base::DoNothing());
   content::URLDataSource::Add(profile,
                               std::make_unique<TestDataSource>("webui"));
 
diff --git a/chrome/test/base/test_theme_provider.cc b/chrome/test/base/test_theme_provider.cc
index 0d23c6f..7218ae86 100644
--- a/chrome/test/base/test_theme_provider.cc
+++ b/chrome/test/base/test_theme_provider.cc
@@ -15,11 +15,6 @@
   return nullptr;
 }
 
-SkColor TestThemeProvider::GetColor(int id) const {
-  auto it = colors_.find(id);
-  return it != colors_.end() ? it->second : gfx::kPlaceholderColor;
-}
-
 color_utils::HSL TestThemeProvider::GetTint(int id) const {
   return color_utils::HSL();
 }
diff --git a/chrome/test/base/test_theme_provider.h b/chrome/test/base/test_theme_provider.h
index de099d08..eca5c108 100644
--- a/chrome/test/base/test_theme_provider.h
+++ b/chrome/test/base/test_theme_provider.h
@@ -17,7 +17,6 @@
 
   // ui::ThemeProvider:
   gfx::ImageSkia* GetImageSkiaNamed(int id) const override;
-  SkColor GetColor(int id) const override;
   color_utils::HSL GetTint(int id) const override;
   int GetDisplayProperty(int id) const override;
   bool ShouldUseNativeFrame() const override;
diff --git a/chrome/test/base/web_ui_browser_test.cc b/chrome/test/base/web_ui_browser_test.cc
index 220c4458..e4b566d67 100644
--- a/chrome/test/base/web_ui_browser_test.cc
+++ b/chrome/test/base/web_ui_browser_test.cc
@@ -113,11 +113,10 @@
   ~WebUITestMessageHandler() override = default;
 
   // Receives testResult messages.
-  void HandleTestResult(const base::ListValue* test_result) {
+  void HandleTestResult(const base::Value::List& list) {
     // To ensure this gets done, do this before ASSERT* calls.
     RunQuitClosure();
 
-    const auto& list = test_result->GetListDeprecated();
     ASSERT_FALSE(list.empty());
     const bool test_succeeded = list[0].is_bool() && list[0].GetBool();
     std::string message;
@@ -131,7 +130,7 @@
 
   // content::WebUIMessageHandler:
   void RegisterMessages() override {
-    web_ui()->RegisterDeprecatedMessageCallback(
+    web_ui()->RegisterMessageCallback(
         "testResult",
         base::BindRepeating(&WebUITestMessageHandler::HandleTestResult,
                             base::Unretained(this)));
diff --git a/chrome/test/base/web_ui_browser_test_browsertest.cc b/chrome/test/base/web_ui_browser_test_browsertest.cc
index e47c39f26..75e4e0b 100644
--- a/chrome/test/base/web_ui_browser_test_browsertest.cc
+++ b/chrome/test/base/web_ui_browser_test_browsertest.cc
@@ -137,33 +137,33 @@
     AsyncWebUIMessageHandler& operator=(const AsyncWebUIMessageHandler&) =
         delete;
 
-    MOCK_METHOD1(HandleTestContinues, void(const base::ListValue*));
-    MOCK_METHOD1(HandleTestFails, void(const base::ListValue*));
-    MOCK_METHOD1(HandleTestPasses, void(const base::ListValue*));
+    MOCK_METHOD1(HandleTestContinues, void(const base::Value::List&));
+    MOCK_METHOD1(HandleTestFails, void(const base::Value::List&));
+    MOCK_METHOD1(HandleTestPasses, void(const base::Value::List&));
 
    private:
     void RegisterMessages() override {
-      web_ui()->RegisterDeprecatedMessageCallback(
+      web_ui()->RegisterMessageCallback(
           "startAsyncTest",
           base::BindRepeating(&AsyncWebUIMessageHandler::HandleStartAsyncTest,
                               base::Unretained(this)));
-      web_ui()->RegisterDeprecatedMessageCallback(
+      web_ui()->RegisterMessageCallback(
           "testContinues",
           base::BindRepeating(&AsyncWebUIMessageHandler::HandleTestContinues,
                               base::Unretained(this)));
-      web_ui()->RegisterDeprecatedMessageCallback(
+      web_ui()->RegisterMessageCallback(
           "testFails",
           base::BindRepeating(&AsyncWebUIMessageHandler::HandleTestFails,
                               base::Unretained(this)));
-      web_ui()->RegisterDeprecatedMessageCallback(
+      web_ui()->RegisterMessageCallback(
           "testPasses",
           base::BindRepeating(&AsyncWebUIMessageHandler::HandleTestPasses,
                               base::Unretained(this)));
     }
 
     // Starts the test in |list_value|[0] with the runAsync wrapper.
-    void HandleStartAsyncTest(const base::ListValue* list_value) {
-      const base::Value& test_name = list_value->GetListDeprecated()[0];
+    void HandleStartAsyncTest(const base::Value::List& list_value) {
+      const base::Value& test_name = list_value[0];
       web_ui()->CallJavascriptFunctionUnsafe("runAsync", test_name);
     }
   };
diff --git a/chrome/test/data/webui/async_gen.cc b/chrome/test/data/webui/async_gen.cc
index 8056683..1de3803 100644
--- a/chrome/test/data/webui/async_gen.cc
+++ b/chrome/test/data/webui/async_gen.cc
@@ -20,18 +20,17 @@
     ~AsyncWebUIMessageHandler() {}
 
 void WebUIBrowserAsyncGenTest::AsyncWebUIMessageHandler::HandleCallJS(
-    const base::ListValue* list_value) {
-  base::Value::ConstListView list_view = list_value->GetListDeprecated();
-  ASSERT_TRUE(0u < list_view.size() && list_view[0].is_string());
-  std::string call_js = list_view[0].GetString();
+    const base::Value::List& list_value) {
+  ASSERT_TRUE(0u < list_value.size() && list_value[0].is_string());
+  std::string call_js = list_value[0].GetString();
   web_ui()->CallJavascriptFunctionUnsafe(call_js);
 }
 
 void WebUIBrowserAsyncGenTest::AsyncWebUIMessageHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "callJS", base::BindRepeating(&AsyncWebUIMessageHandler::HandleCallJS,
                                     base::Unretained(this)));
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "tearDown", base::BindRepeating(&AsyncWebUIMessageHandler::HandleTearDown,
                                       base::Unretained(this)));
 }
diff --git a/chrome/test/data/webui/async_gen.h b/chrome/test/data/webui/async_gen.h
index a67d3c7..1b9bf67b 100644
--- a/chrome/test/data/webui/async_gen.h
+++ b/chrome/test/data/webui/async_gen.h
@@ -8,10 +8,6 @@
 #include "content/public/browser/web_ui_message_handler.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
-namespace base {
-class ListValue;
-}  // namespace base
-
 // C++ support class for javascript-generated asynchronous tests.
 class WebUIBrowserAsyncGenTest : public WebUIBrowserTest {
  public:
@@ -28,10 +24,10 @@
     AsyncWebUIMessageHandler();
     ~AsyncWebUIMessageHandler() override;
 
-    MOCK_METHOD1(HandleTearDown, void(const base::ListValue*));
+    MOCK_METHOD1(HandleTearDown, void(const base::Value::List&));
 
    private:
-    void HandleCallJS(const base::ListValue* list_value);
+    void HandleCallJS(const base::Value::List& list_value);
 
     // WebUIMessageHandler implementation.
     void RegisterMessages() override;
diff --git a/chrome/test/data/webui/chrome_send_browsertest.cc b/chrome/test/data/webui/chrome_send_browsertest.cc
index 95d2a01..52f8585f5 100644
--- a/chrome/test/data/webui/chrome_send_browsertest.cc
+++ b/chrome/test/data/webui/chrome_send_browsertest.cc
@@ -23,7 +23,7 @@
     ~ChromeSendWebUIMessageHandler() {}
 
 void ChromeSendWebUITest::ChromeSendWebUIMessageHandler::RegisterMessages() {
-  web_ui()->RegisterDeprecatedMessageCallback(
+  web_ui()->RegisterMessageCallback(
       "checkSend",
       base::BindRepeating(&ChromeSendWebUIMessageHandler::HandleCheckSend,
                           base::Unretained(this)));
diff --git a/chrome/test/data/webui/chrome_send_browsertest.h b/chrome/test/data/webui/chrome_send_browsertest.h
index b52fc756..8ac5b77 100644
--- a/chrome/test/data/webui/chrome_send_browsertest.h
+++ b/chrome/test/data/webui/chrome_send_browsertest.h
@@ -26,7 +26,7 @@
     ChromeSendWebUIMessageHandler();
     ~ChromeSendWebUIMessageHandler() override;
 
-    MOCK_METHOD1(HandleCheckSend, void(const base::ListValue*));
+    MOCK_METHOD1(HandleCheckSend, void(const base::Value::List&));
 
    private:
     void RegisterMessages() override;
diff --git a/chrome/test/data/webui/chromeos/os_feedback_ui/share_data_page_test.js b/chrome/test/data/webui/chromeos/os_feedback_ui/share_data_page_test.js
index 24cbaf72..991691c 100644
--- a/chrome/test/data/webui/chromeos/os_feedback_ui/share_data_page_test.js
+++ b/chrome/test/data/webui/chromeos/os_feedback_ui/share_data_page_test.js
@@ -10,6 +10,7 @@
 import {FeedbackContext} from 'chrome://os-feedback/feedback_types.js';
 import {ShareDataPageElement} from 'chrome://os-feedback/share_data_page.js';
 import {mojoString16ToString, stringToMojoString16} from 'chrome://resources/ash/common/mojo_utils.js';
+import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {assertArrayEquals, assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
@@ -356,6 +357,39 @@
     assertEquals(imgUrl, screenshotImage.src);
   });
 
+
+  // Test that clicking the screenshot will open preview dialog and set the
+  // focus on the close dialog icon button.
+  test('screenshotPreview', async () => {
+    await initializePage();
+    page.feedbackContext = fakeFeedbackContext;
+    page.screenshotUrl = fakeImageUrl;
+    assertEquals(fakeImageUrl, getElement('#screenshotImage').src);
+
+    const closeDialogButton = getElement('#closeDialogButton');
+    // The preview dialog's close icon button is not visible.
+    assertFalse(isVisible(closeDialogButton));
+
+    // The screenshot is displayed as an image button.
+    const imageButton = /** @type {!Element} */ (getElement('#imageButton'));
+    const imageClickPromise = eventToPromise('click', imageButton);
+    imageButton.click();
+    await imageClickPromise;
+
+    // The preview dialog's close icon button is visible now.
+    assertTrue(isVisible(closeDialogButton));
+    // The preview dialog's close icon button is focused.
+    assertEquals(closeDialogButton, getDeepActiveElement());
+
+    // Press enter should close the preview dialog.
+    closeDialogButton.dispatchEvent(
+        new KeyboardEvent('keydown', {key: 'Enter'}));
+    await flushTasks();
+
+    // The preview dialog's close icon button is not visible now.
+    assertFalse(isVisible(closeDialogButton));
+  });
+
   /**
    * Test that when when the send button is clicked, the getAttachedFile has
    * been called.
diff --git a/chrome/test/data/webui/nearby_share/shared/nearby_page_template_test.js b/chrome/test/data/webui/nearby_share/shared/nearby_page_template_test.js
index cba11501..0ecf82d 100644
--- a/chrome/test/data/webui/nearby_share/shared/nearby_page_template_test.js
+++ b/chrome/test/data/webui/nearby_share/shared/nearby_page_template_test.js
@@ -70,8 +70,11 @@
 
     /** @type {boolean} */
     let cancelTriggered = false;
-    element.addEventListener(
-        element.cancelButtonEventName, () => cancelTriggered = true);
+    // Nearby Share app always expects |event.detail| to be defined
+    element.addEventListener(element.cancelButtonEventName, event => {
+      cancelTriggered = true;
+      assertTrue(!!event.detail);
+    });
     element.shadowRoot.querySelector('#cancelButton').click();
     assertTrue(cancelTriggered);
 
diff --git a/chrome/test/data/webui/new_tab_page/customize_modules_test.ts b/chrome/test/data/webui/new_tab_page/customize_modules_test.ts
index 6fa4982..f375b69 100644
--- a/chrome/test/data/webui/new_tab_page/customize_modules_test.ts
+++ b/chrome/test/data/webui/new_tab_page/customize_modules_test.ts
@@ -35,6 +35,7 @@
                 new ModuleDescriptor(id, name, () => Promise.resolve(null))));
     const customizeModules = document.createElement('ntp-customize-modules');
     document.body.appendChild(customizeModules);
+    assertStyle(customizeModules.$.container, 'display', 'none');
     callbackRouterRemote.setDisabledModules(
         allDisabled,
         modules.filter(({disabled}) => disabled).map(({id}) => id));
@@ -365,4 +366,11 @@
     // Assert.
     assertEquals(0, subToggleRows.length);
   });
+
+  test('should show modules after loaded', async () => {
+    const customizeModules = await createCustomizeModules(true, [
+      {id: 'foo', name: 'foo name', disabled: false},
+    ]);
+    assertNotStyle(customizeModules.$.container, 'display', 'none');
+  });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/device_page_tests.js b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
index 0cce273..628b9fa 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
@@ -659,6 +659,33 @@
     assertTrue(isVisible(devicePage.shadowRoot.querySelector('#keyboardRow')));
     assertTrue(isVisible(devicePage.shadowRoot.querySelector('#displayRow')));
 
+    // enableAudioSettingsPage feature flag by default is turned off.
+    assertFalse(isVisible(devicePage.shadowRoot.querySelector('#audioRow')));
+
+    webUIListenerCallback('has-mouse-changed', false);
+    assertTrue(isVisible(devicePage.shadowRoot.querySelector('#pointersRow')));
+
+    webUIListenerCallback('has-pointing-stick-changed', false);
+    assertTrue(isVisible(devicePage.shadowRoot.querySelector('#pointersRow')));
+
+    webUIListenerCallback('has-touchpad-changed', false);
+    assertFalse(isVisible(devicePage.shadowRoot.querySelector('#pointersRow')));
+
+    webUIListenerCallback('has-mouse-changed', true);
+    assertTrue(isVisible(devicePage.shadowRoot.querySelector('#pointersRow')));
+  });
+
+  test('audio row visibility', async function() {
+    loadTimeData.overrideValues({
+      enableAudioSettingsPage: true,
+    });
+
+    await init();
+    assertTrue(isVisible(devicePage.shadowRoot.querySelector('#pointersRow')));
+    assertTrue(isVisible(devicePage.shadowRoot.querySelector('#keyboardRow')));
+    assertTrue(isVisible(devicePage.shadowRoot.querySelector('#displayRow')));
+    assertTrue(isVisible(devicePage.shadowRoot.querySelector('#audioRow')));
+
     webUIListenerCallback('has-mouse-changed', false);
     assertTrue(isVisible(devicePage.shadowRoot.querySelector('#pointersRow')));
 
diff --git a/chrome/tools/build/win/create_installer_archive.py b/chrome/tools/build/win/create_installer_archive.py
index 68e4706..dfa3ee4 100755
--- a/chrome/tools/build/win/create_installer_archive.py
+++ b/chrome/tools/build/win/create_installer_archive.py
@@ -173,7 +173,7 @@
 def GetLZMAExec(build_dir):
   if sys.platform == 'win32':
     lzma_exec = os.path.join(build_dir, "..", "..", "third_party",
-                             "lzma_sdk", "Executable", "7za.exe")
+                             "lzma_sdk", "bin", "7za.exe")
   else:
     lzma_exec = '7zr'  # Use system 7zr.
   return lzma_exec
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index 837f3c4..ee7d5e9 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -412,9 +412,8 @@
 };
 
 DISPID GetDispId(Microsoft::WRL::ComPtr<IDispatch> dispatch,
-                 const std::wstring& of_name) {
+                 std::wstring name) {
   DISPID id = 0;
-  std::wstring name = of_name;
   LPOLESTR name_ptr = &name[0];
   EXPECT_HRESULT_SUCCEEDED(dispatch->GetIDsOfNames(IID_NULL, &name_ptr, 1,
                                                    LOCALE_USER_DEFAULT, &id));
diff --git a/chrome/updater/win/BUILD.gn b/chrome/updater/win/BUILD.gn
index bb76ea2..db20e3f 100644
--- a/chrome/updater/win/BUILD.gn
+++ b/chrome/updater/win/BUILD.gn
@@ -70,7 +70,7 @@
   sources = [
     "$root_out_dir/certificate_tag.exe",
     "//chrome/tools/build/win/resedit.py",
-    "//third_party/lzma_sdk/7zr.exe",
+    "//third_party/lzma_sdk/bin/7zr.exe",
     "signing/sign.py",
   ]
 
diff --git a/chrome/updater/win/app_command_runner.cc b/chrome/updater/win/app_command_runner.cc
index 07da5f36..8c17eb6 100644
--- a/chrome/updater/win/app_command_runner.cc
+++ b/chrome/updater/win/app_command_runner.cc
@@ -275,12 +275,11 @@
 
 HRESULT AppCommandRunner::GetAppCommandFormatComponents(
     UpdaterScope scope,
-    const std::wstring& command_format,
+    std::wstring command_format,
     base::FilePath& executable,
     std::vector<std::wstring>& parameters) {
-  std::wstring fmt = command_format;
   int num_args = 0;
-  ScopedLocalAlloc args(::CommandLineToArgvW(&fmt[0], &num_args));
+  ScopedLocalAlloc args(::CommandLineToArgvW(&command_format[0], &num_args));
   if (!args.is_valid() || num_args < 1)
     return E_INVALIDARG;
 
diff --git a/chrome/updater/win/app_command_runner.h b/chrome/updater/win/app_command_runner.h
index b59d593..4b0bdb13 100644
--- a/chrome/updater/win/app_command_runner.h
+++ b/chrome/updater/win/app_command_runner.h
@@ -66,7 +66,7 @@
   // command line can be either hardcoded or placeholders from `%1` to `%9`.
   static HRESULT GetAppCommandFormatComponents(
       UpdaterScope scope,
-      const std::wstring& command_format,
+      std::wstring command_format,
       base::FilePath& executable,
       std::vector<std::wstring>& parameters);
 
diff --git a/chrome/updater/win/installer/create_installer_archive.py b/chrome/updater/win/installer/create_installer_archive.py
index eca0c4b3..e218a83 100644
--- a/chrome/updater/win/installer/create_installer_archive.py
+++ b/chrome/updater/win/installer/create_installer_archive.py
@@ -88,7 +88,7 @@
 def GetLZMAExec(build_dir):
     if sys.platform == 'win32':
         lzma_exec = os.path.join(build_dir, "..", "..", "third_party",
-                                 "lzma_sdk", "Executable", "7za.exe")
+                                 "lzma_sdk", "bin", "7za.exe")
     else:
         lzma_exec = '7zr'  # Use system 7zr.
     return lzma_exec
diff --git a/chromecast/media/cma/backend/android/volume_control_android.cc b/chromecast/media/cma/backend/android/volume_control_android.cc
index fd3c336..39c23ae 100644
--- a/chromecast/media/cma/backend/android/volume_control_android.cc
+++ b/chromecast/media/cma/backend/android/volume_control_android.cc
@@ -198,16 +198,7 @@
               << " mute=" << muted_[type];
   }
 
-  if (!is_single_volume_) {
-    // The kOther content type should not have any type-wide volume control or
-    // mute (volume control for kOther is per-stream only). Therefore, ensure
-    // that the global volume and mute state fo kOther is initialized correctly
-    // (100% volume, and not muted).
-    SetVolumeOnThread(VolumeChangeSource::kAutomatic, AudioContentType::kOther,
-                      1.0f, false /* from_android */);
-    SetMutedOnThread(VolumeChangeSource::kAutomatic, AudioContentType::kOther,
-                     false, false /* from_android */);
-  }
+  // kOther is not used on Android, so we don't attempt to set it to 100% here.
 
   initialize_complete_event_.Signal();
 }
@@ -281,14 +272,6 @@
 void VolumeControlAndroid::ReportVolumeChangeOnThread(AudioContentType type,
                                                       float level) {
   DCHECK(thread_.task_runner()->BelongsToCurrentThread());
-  if (!is_single_volume_ && type == AudioContentType::kOther) {
-    // Volume for AudioContentType::kOther should stay at 1.0.
-    Java_VolumeControl_setVolume(base::android::AttachCurrentThread(),
-                                 j_volume_control_, static_cast<int>(type),
-                                 1.0f);
-    return;
-  }
-
   SetVolumeOnThread(VolumeChangeSource::kUser, type, level,
                     true /* from android */);
 }
@@ -296,14 +279,6 @@
 void VolumeControlAndroid::ReportMuteChangeOnThread(AudioContentType type,
                                                     bool muted) {
   DCHECK(thread_.task_runner()->BelongsToCurrentThread());
-  if (!is_single_volume_ && type == AudioContentType::kOther) {
-    // Mute state for AudioContentType::kOther should always be false.
-    Java_VolumeControl_setMuted(base::android::AttachCurrentThread(),
-                                j_volume_control_, static_cast<int>(type),
-                                false);
-    return;
-  }
-
   SetMutedOnThread(VolumeChangeSource::kUser, type, muted,
                    true /* from_android */);
 }
diff --git a/chromecast/media/cma/backend/cplay/cplay.cc b/chromecast/media/cma/backend/cplay/cplay.cc
index 4a2e5f6..988b92c 100644
--- a/chromecast/media/cma/backend/cplay/cplay.cc
+++ b/chromecast/media/cma/backend/cplay/cplay.cc
@@ -335,6 +335,7 @@
   FilterGroup* input_group = pipeline->GetInputGroup(params.device_id);
   CHECK(input_group);
   MixerInput mixer_input(&input_source, input_group);
+  mixer_input.Initialize();
 
   // Set volume.
   std::string contents;
diff --git a/chromeos/ash/components/dbus/rgbkbd/rgbkbd_client.cc b/chromeos/ash/components/dbus/rgbkbd/rgbkbd_client.cc
index da77420..5af6f9e 100644
--- a/chromeos/ash/components/dbus/rgbkbd/rgbkbd_client.cc
+++ b/chromeos/ash/components/dbus/rgbkbd/rgbkbd_client.cc
@@ -26,11 +26,6 @@
   RgbkbdClientImpl() = default;
   RgbkbdClientImpl(const RgbkbdClientImpl&) = delete;
   RgbkbdClientImpl& operator=(const RgbkbdClientImpl&) = delete;
-  ~RgbkbdClientImpl() override {
-    for (auto& observer : observers_) {
-      observer.OnShutdown();
-    }
-  }
 
   void GetRgbKeyboardCapabilities(
       GetRgbKeyboardCapabilitiesCallback callback) override {
diff --git a/chromeos/ash/components/dbus/rgbkbd/rgbkbd_client.h b/chromeos/ash/components/dbus/rgbkbd/rgbkbd_client.h
index a4f7be5..1e71cf79 100644
--- a/chromeos/ash/components/dbus/rgbkbd/rgbkbd_client.h
+++ b/chromeos/ash/components/dbus/rgbkbd/rgbkbd_client.h
@@ -30,7 +30,6 @@
     ~Observer() override = default;
     virtual void OnCapabilityUpdatedForTesting(
         rgbkbd::RgbKeyboardCapabilities capability) = 0;
-    virtual void OnShutdown() = 0;
   };
 
   void AddObserver(Observer* observer);
diff --git a/chromeos/network/network_device_handler_impl.cc b/chromeos/network/network_device_handler_impl.cc
index eaa4a217..6fb8429 100644
--- a/chromeos/network/network_device_handler_impl.cc
+++ b/chromeos/network/network_device_handler_impl.cc
@@ -35,6 +35,8 @@
 
 namespace {
 
+const char kDefaultSimPin[] = "1111";
+
 std::string GetErrorNameForShillError(const std::string& shill_error_name) {
   if (shill_error_name == shill::kErrorResultFailure ||
       shill_error_name == shill::kErrorResultInvalidArguments) {
@@ -217,16 +219,34 @@
     base::OnceClosure callback,
     network_handler::ErrorCallback error_callback) {
   NET_LOG(USER) << "Device.UnblockPin: " << device_path;
+  // A SIM PIN must be provided along with the PUK in order to PUK unblock a
+  // SIM. If admins have blocked SIM PIN locking, a PIN still must be provided,
+  // so th default SIM |kDefaultSimPin| is used.
+  const std::string pin = allow_cellular_sim_lock_ ? new_pin : kDefaultSimPin;
   ShillDeviceClient::Get()->UnblockPin(
-      dbus::ObjectPath(device_path), puk, new_pin,
-      base::BindOnce(&HandleSimPinOperationSuccess,
-                     CellularMetricsLogger::SimPinOperation::kUnblock,
+      dbus::ObjectPath(device_path), puk, pin,
+      base::BindOnce(&NetworkDeviceHandlerImpl::OnUnblockPinSuccess,
+                     weak_ptr_factory_.GetWeakPtr(), device_path, pin,
                      std::move(callback)),
       base::BindOnce(&HandleSimPinOperationFailure,
                      CellularMetricsLogger::SimPinOperation::kUnblock,
                      device_path, std::move(error_callback)));
 }
 
+void NetworkDeviceHandlerImpl::OnUnblockPinSuccess(
+    const std::string& device_path,
+    const std::string& pin,
+    base::OnceClosure callback) {
+  if (allow_cellular_sim_lock_) {
+    HandleSimPinOperationSuccess(
+        CellularMetricsLogger::SimPinOperation::kUnblock, std::move(callback));
+    return;
+  }
+
+  // Disable the SIM PIN lock setting.
+  RequirePin(device_path, false, pin, std::move(callback), base::DoNothing());
+}
+
 void NetworkDeviceHandlerImpl::ChangePin(
     const std::string& device_path,
     const std::string& old_pin,
diff --git a/chromeos/network/network_device_handler_impl.h b/chromeos/network/network_device_handler_impl.h
index 942221a..ceb6ae69 100644
--- a/chromeos/network/network_device_handler_impl.h
+++ b/chromeos/network/network_device_handler_impl.h
@@ -170,6 +170,11 @@
   // Resets MAC address source property for secondary USB Ethernet devices.
   void ResetMacAddressSourceForSecondaryUsbEthernetDevices() const;
 
+  // On a successful SIM PUK unblock.
+  void OnUnblockPinSuccess(const std::string& device_path,
+                           const std::string& pin,
+                           base::OnceClosure callback);
+
   // Get the DeviceState for the wifi device, if any.
   const DeviceState* GetWifiDeviceState();
 
diff --git a/chromeos/network/network_device_handler_unittest.cc b/chromeos/network/network_device_handler_unittest.cc
index 87686f5..7693fa01 100644
--- a/chromeos/network/network_device_handler_unittest.cc
+++ b/chromeos/network/network_device_handler_unittest.cc
@@ -510,6 +510,19 @@
   histogram_tester.ExpectBucketCount(
       CellularMetricsLogger::kSimPinUnblockSuccessHistogram,
       CellularMetricsLogger::SimPinOperationResult::kErrorUnknown, 1);
+
+  histogram_tester.ExpectTotalCount(
+      CellularMetricsLogger::kSimPinRemoveLockSuccessHistogram, 0);
+
+  // Test that if SIM PIN locking is prohibited, PUK unblocking a SIM will
+  // also disable the SIM PIN lock setting.
+  network_device_handler_->SetAllowCellularSimLock(
+      /*allow_cellular_sim_lock=*/false);
+  network_device_handler_->UnblockPin(kDefaultCellularDevicePath, kPin, kPuk,
+                                      GetSuccessCallback(), GetErrorCallback());
+  base::RunLoop().RunUntilIdle();
+  histogram_tester.ExpectTotalCount(
+      CellularMetricsLogger::kSimPinRemoveLockSuccessHistogram, 1);
 }
 
 TEST_F(NetworkDeviceHandlerTest, ChangePin) {
diff --git a/chromeos/tast_control.gni b/chromeos/tast_control.gni
index a6d53f9..3a8dc386 100644
--- a/chromeos/tast_control.gni
+++ b/chromeos/tast_control.gni
@@ -267,10 +267,12 @@
   # http://b/236312054
   "crostini.AppGeditFilesharing",
   "crostini.AppGeditFilesharing.stable",
+  "crostini.AppGeditFilesharing.clamshell_stable",
 
   # http://b/236710501
   "crostini.AppAndroidStudio.stable",
   "crostini.AppGeditUnshareFolder.stable",
+  "crostini.AppGeditUnshareFolder.clamshell_stable",
 
   # http://crbug.com/1339132
   "crostini.AppEclipse",
diff --git a/components/desks_storage/core/desk_sync_bridge.cc b/components/desks_storage/core/desk_sync_bridge.cc
index 2cd83b9..39a65d2 100644
--- a/components/desks_storage/core/desk_sync_bridge.cc
+++ b/components/desks_storage/core/desk_sync_bridge.cc
@@ -295,9 +295,15 @@
                                    &app_launch_info->tab_group_infos.value());
       }
 
-      if (app.app().browser_app_window().has_show_as_app())
+      if (app.app().browser_app_window().has_show_as_app()) {
         app_launch_info->app_type_browser =
             app.app().browser_app_window().show_as_app();
+      }
+
+      if (app.app().browser_app_window().has_first_non_pinned_tab_index()) {
+        app_launch_info->first_non_pinned_tab_index =
+            app.app().browser_app_window().first_non_pinned_tab_index();
+      }
 
       break;
     case sync_pb::WorkspaceDeskSpecifics_AppOneOf::AppCase::kChromeApp:
@@ -485,6 +491,11 @@
     FillBrowserAppTabGroupInfos(app_restore_data->tab_group_infos.value(),
                                 out_browser_app_window);
   }
+
+  if (app_restore_data->first_non_pinned_tab_index.has_value()) {
+    out_browser_app_window->set_first_non_pinned_tab_index(
+        app_restore_data->first_non_pinned_tab_index.value());
+  }
 }
 
 // Fill `out_window_bounds` with information from `bounds`.
diff --git a/components/desks_storage/core/desk_sync_bridge_unittest.cc b/components/desks_storage/core/desk_sync_bridge_unittest.cc
index 8653f6b..54f7e10 100644
--- a/components/desks_storage/core/desk_sync_bridge_unittest.cc
+++ b/components/desks_storage/core/desk_sync_bridge_unittest.cc
@@ -126,7 +126,7 @@
     "2\",\"title\":\"Example2\"}],\"tab_groups\":[{\"range_"
     "start\":1,\"range_end\":2,\"title\":\"sample_tab_"
     "group\",\"color\":\"GREY\",\"is_collapsed\":false}],\"active_tab_index\":"
-    "1,\"window_id\":0,"
+    "1,\"first_non_pinned_tab_index\":1,\"window_id\":0,"
     "\"display_id\":\"100\",\"pre_minimized_window_state\":\"NORMAL\"}]}},"
     "{\"version\":1,\"uuid\":\"" +
     base::StringPrintf(kUuidFormat, 9) +
@@ -139,7 +139,8 @@
     "\"url\":\"https://google.com\",\"title\":\"Example "
     "2\"},{\"url\":\"https://"
     "gmail.com.com/"
-    "2\",\"title\":\"Example2\"}],\"active_tab_index\":1,\"window_id\":0,"
+    "2\",\"title\":\"Example2\"}],\"active_tab_index\":1,\"first_non_pinned_"
+    "tab_index\":1,\"window_id\":0,"
     "\"display_id\":\"100\",\"pre_minimized_window_state\":\"NORMAL\"}]}}]";
 
 void FillDefaultBrowserAppWindow(WorkspaceDeskSpecifics_App* app,
@@ -151,6 +152,7 @@
   }
 
   app_window->set_active_tab_index(number_of_tabs - 1);
+  app_window->set_first_non_pinned_tab_index(number_of_tabs - 1);
 
   WindowBound* window_bound = app->mutable_window_bound();
   window_bound->set_left(110);
diff --git a/components/desks_storage/core/desk_template_conversion.cc b/components/desks_storage/core/desk_template_conversion.cc
index de7df92..1b6ad3f5 100644
--- a/components/desks_storage/core/desk_template_conversion.cc
+++ b/components/desks_storage/core/desk_template_conversion.cc
@@ -55,6 +55,7 @@
 constexpr char kDeskTypeUnknown[] = "UNKNOWN";
 constexpr char kDisplayId[] = "display_id";
 constexpr char kEventFlag[] = "event_flag";
+constexpr char kFirstNonPinnedTabIndex[] = "first_non_pinned_tab_index";
 constexpr char kIsAppTypeBrowser[] = "is_app";
 constexpr char kLaunchContainer[] = "launch_container";
 constexpr char kLaunchContainerWindow[] = "LAUNCH_CONTAINER_WINDOW";
@@ -456,6 +457,10 @@
     if (GetInt(app, kActiveTabIndex, &active_tab_index))
       app_launch_info->active_tab_index = active_tab_index;
 
+    int first_non_pinned_tab_index;
+    if (GetInt(app, kFirstNonPinnedTabIndex, &first_non_pinned_tab_index))
+      app_launch_info->first_non_pinned_tab_index = first_non_pinned_tab_index;
+
     // Fill in the URL list
     app_launch_info->urls.emplace();
     const base::Value* tabs = app.FindKeyOfType(kTabs, base::Value::Type::LIST);
@@ -889,6 +894,11 @@
                     base::Value(app->active_tab_index.value()));
   }
 
+  if (app->first_non_pinned_tab_index.has_value()) {
+    app_data.SetKey(kFirstNonPinnedTabIndex,
+                    base::Value(app->first_non_pinned_tab_index.value()));
+  }
+
   if (app->app_type_browser.has_value()) {
     app_data.SetKey(kIsAppTypeBrowser,
                     base::Value(app->app_type_browser.value()));
diff --git a/components/desks_storage/core/desk_template_conversion_unittests.cc b/components/desks_storage/core/desk_template_conversion_unittests.cc
index 45db2233..fd69f30d 100644
--- a/components/desks_storage/core/desk_template_conversion_unittests.cc
+++ b/components/desks_storage/core/desk_template_conversion_unittests.cc
@@ -112,9 +112,10 @@
   EXPECT_EQ(ali->window_id.value(), 0);
   EXPECT_TRUE(ali->display_id.has_value());
   EXPECT_EQ(ali->display_id.value(), 100L);
-  // Can't test active_tab_index since GetAppLaunchInfo returns an entry without
-  // it
-  // https://osscs.corp.google.com/chromium/chromium/src/+/main:components/app_restore/app_restore_data.cc
+  EXPECT_TRUE(ali->active_tab_index.has_value());
+  EXPECT_EQ(ali->active_tab_index.value(), 1);
+  EXPECT_TRUE(ali->first_non_pinned_tab_index.has_value());
+  EXPECT_EQ(ali->first_non_pinned_tab_index.value(), 1);
   EXPECT_TRUE(ali->urls.has_value());
   EXPECT_EQ(ali->urls.value()[0].spec(), kBrowserUrl1);
   EXPECT_EQ(ali->urls.value()[1].spec(), kBrowserUrl2);
diff --git a/components/desks_storage/core/desk_test_util.h b/components/desks_storage/core/desk_test_util.h
index 4c7718a96..ffd3674 100644
--- a/components/desks_storage/core/desk_test_util.h
+++ b/components/desks_storage/core/desk_test_util.h
@@ -44,7 +44,7 @@
     "2\"}],\"tab_groups\":[{\"first_"
     "index\":1,\"last_index\":2,\"title\":\"sample_tab_"
     "group\",\"color\":\"GREY\",\"is_collapsed\":false}],\"active_tab_index\":"
-    "1,\"window_id\":0,"
+    "1,\"first_non_pinned_tab_index\":1,\"window_id\":0,"
     "\"display_id\":\"100\",\"event_flag\":0,\"pre_minimized_window_state\":"
     "\"NORMAL\"}]}}";
 
@@ -80,7 +80,7 @@
     "\"}],\"tab_groups\":[{\"first_"
     "index\":1,\"last_index\":2,\"title\":\"sample_tab_"
     "group\",\"color\":\"GREY\",\"is_collapsed\":false}],\"active_tab_index\":"
-    "1,\"window_id\":0,"
+    "1,\"first_non_pinned_tab_index\":1,\"window_id\":0,"
     "\"display_id\":\"100\",\"event_flag\":0,\"pre_minimized_window_state\":"
     "\"NORMAL\"}]}}";
 
diff --git a/components/exo/shell_surface.cc b/components/exo/shell_surface.cc
index 58ec66f7..c38f9d3 100644
--- a/components/exo/shell_surface.cc
+++ b/components/exo/shell_surface.cc
@@ -426,7 +426,10 @@
     // being notified of the change before |this|.
     UpdateShadow();
 
-    Configure();
+    // A window state change will send a configuration event. Avoid sending
+    // two configuration events for the same change.
+    if (!window_state_is_changing_)
+      Configure();
   }
 }
 
@@ -436,6 +439,7 @@
 void ShellSurface::OnPreWindowStateTypeChange(
     ash::WindowState* window_state,
     chromeos::WindowStateType old_type) {
+  window_state_is_changing_ = true;
   chromeos::WindowStateType new_type = window_state->GetStateType();
   if (chromeos::IsMinimizedWindowStateType(old_type) ||
       chromeos::IsMinimizedWindowStateType(new_type)) {
@@ -469,14 +473,16 @@
 void ShellSurface::OnPostWindowStateTypeChange(
     ash::WindowState* window_state,
     chromeos::WindowStateType old_type) {
-  chromeos::WindowStateType new_type = window_state->GetStateType();
-  // For exo-client using client-side decoration, window-state information is
-  // needed to toggle the maximize and restore buttons. When the window is
-  // restored, we show a maximized button; otherwise we show a restore button.
-  if (chromeos::IsMaximizedOrFullscreenOrPinnedWindowStateType(old_type) ||
-      chromeos::IsMaximizedOrFullscreenOrPinnedWindowStateType(new_type)) {
-    Configure();
-  }
+  // Send the new state to the exo-client when the state changes. This is
+  // important for client presentation. For example exo-client using client-side
+  // decoration, window-state information is needed to toggle the maximize and
+  // restore buttons. When the window is restored, we show a maximized button;
+  // otherwise we show a restore button.
+  //
+  // Note that configuration events on bounds change is suppressed during state
+  // change, because it is assumed that a configuration event will always be
+  // sent at the end of a state change.
+  Configure();
 
   if (widget_) {
     UpdateWidgetBounds();
@@ -485,6 +491,7 @@
 
   // Re-enable animations if they were disabled in pre state change handler.
   animations_disabler_.reset();
+  window_state_is_changing_ = false;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/components/exo/shell_surface.h b/components/exo/shell_surface.h
index 76e6476..e938b7a 100644
--- a/components/exo/shell_surface.h
+++ b/components/exo/shell_surface.h
@@ -196,6 +196,7 @@
   int pending_resize_component_ = HTCAPTION;
   ui::WindowShowState initial_show_state_ = ui::SHOW_STATE_DEFAULT;
   bool ignore_window_bounds_changes_ = false;
+  bool window_state_is_changing_ = false;
 
   base::ObserverList<ShellSurfaceObserver> observers_;
 };
diff --git a/components/exo/shell_surface_unittest.cc b/components/exo/shell_surface_unittest.cc
index 2cfc699a..61739a6 100644
--- a/components/exo/shell_surface_unittest.cc
+++ b/components/exo/shell_surface_unittest.cc
@@ -2371,4 +2371,69 @@
             shell_surface->GetWidget()->GetWindowBoundsInScreen());
 }
 
+TEST_F(ShellSurfaceTest, PostWindowChangeCallback) {
+  chromeos::WindowStateType state_type = chromeos::WindowStateType::kDefault;
+  auto test_callback = base::BindRepeating(
+      [](chromeos::WindowStateType* state_type, const gfx::Rect&,
+         chromeos::WindowStateType new_type, bool, bool,
+         const gfx::Vector2d&) -> uint32_t {
+        *state_type = new_type;
+        return 0;
+      },
+      &state_type);
+
+  std::unique_ptr<ShellSurface> shell_surface =
+      test::ShellSurfaceBuilder({256, 256}).BuildShellSurface();
+
+  shell_surface->set_configure_callback(test_callback);
+
+  auto* state = ash::WindowState::Get(
+      shell_surface->GetWidget()->GetNativeWindow()->GetToplevelWindow());
+
+  // Make sure we are in a non-snapped state before testing state change.
+  ASSERT_FALSE(state->IsSnapped());
+
+  auto snap_event = std::make_unique<ash::WMEvent>(ash::WM_EVENT_SNAP_PRIMARY);
+
+  // Trigger a snap event, this should cause a configure event.
+  state->OnWMEvent(snap_event.get());
+
+  EXPECT_EQ(state_type, chromeos::WindowStateType::kPrimarySnapped);
+}
+
+// A single configuration event should be sent when both the bounds and the
+// window state change.
+TEST_F(ShellSurfaceTest, ConfigureOnlySentOnceForBoundsAndWindowStateChange) {
+  int times_configured = 0;
+  auto test_callback = base::BindRepeating(
+      [](int* times_configured, const gfx::Rect&,
+         chromeos::WindowStateType new_type, bool, bool,
+         const gfx::Vector2d&) -> uint32_t {
+        ++(*times_configured);
+        return 0;
+      },
+      &times_configured);
+
+  std::unique_ptr<ShellSurface> shell_surface =
+      test::ShellSurfaceBuilder({1, 1}).BuildShellSurface();
+
+  shell_surface->set_configure_callback(test_callback);
+
+  auto* state = ash::WindowState::Get(
+      shell_surface->GetWidget()->GetNativeWindow()->GetToplevelWindow());
+
+  // Make sure we are in normal mode. Maximizing from this state should result
+  // in BOTH the bounds and state changing.
+  ASSERT_TRUE(state->IsNormalStateType());
+
+  auto maximize_event = std::make_unique<ash::WMEvent>(ash::WM_EVENT_MAXIMIZE);
+
+  // Trigger a snap event, this should cause a configure event.
+  state->OnWMEvent(maximize_event.get());
+
+  // The bounds change event should have been suppressed because the window
+  // state is changing.
+  EXPECT_EQ(times_configured, 1);
+}
+
 }  // namespace exo
diff --git a/components/exo/wayland/protocol/aura-shell.xml b/components/exo/wayland/protocol/aura-shell.xml
index d621134..66ec90d 100644
--- a/components/exo/wayland/protocol/aura-shell.xml
+++ b/components/exo/wayland/protocol/aura-shell.xml
@@ -24,7 +24,7 @@
     DEALINGS IN THE SOFTWARE.
   </copyright>
 
-  <interface name="zaura_shell" version="35">
+  <interface name="zaura_shell" version="36">
     <description summary="aura_shell">
       The global interface exposing aura shell capabilities is used to
       instantiate an interface extension for a wl_surface object.
@@ -649,7 +649,7 @@
     </event>
   </interface>
 
-  <interface name="zaura_toplevel" version="35">
+  <interface name="zaura_toplevel" version="36">
     <description summary="aura shell interface to the toplevel shell">
       An interface to the toplevel shell, which allows the
       client to access shell specific functionality.
@@ -725,6 +725,26 @@
       <arg name="states" type="array"/>
     </event>
 
+    <enum name="state">
+      <description summary="supplemental aura states to xdg states">
+        The states that are contained here are supplemental to the states
+        defined in the XDG shell and specific aura windows.
+      </description>
+
+      <!-- Offset by 100 to prevent collision with new XDG states. -->
+      <entry name="immersive" value="100" since="36">
+        <description summary="immersive mode with hidden title bar and shelf">
+          User can access system UIs such as the shelf and window frame
+          by pointing to, or swiping over, the screen edge.
+        </description>
+      </entry>
+      <entry name="minimized" value="101" since="36">
+        <description summary="surface is minized">
+          The window has been minimized.
+        </description>
+      </entry>
+    </enum>
+
     <event name="origin_change" since="29">
       <description summary="window origin change">
         A notification sent when the window origin has changed. Unlike a configure,
diff --git a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
index 232bc11..06ecabd 100644
--- a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
+++ b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
@@ -83,7 +83,7 @@
         FeatureConstants.VIDEO_TUTORIAL_NTP_SEARCH_FEATURE,
         FeatureConstants.VIDEO_TUTORIAL_NTP_SUMMARY_FEATURE,
         FeatureConstants.VIDEO_TUTORIAL_NTP_VOICE_SEARCH_FEATURE,
-        FeatureConstants.VIDEO_TUTORIAL_TRY_NOW_FEATURE})
+        FeatureConstants.VIDEO_TUTORIAL_TRY_NOW_FEATURE, FeatureConstants.PRICE_DROP_NTP_FEATURE})
 @Retention(RetentionPolicy.SOURCE)
 public @interface FeatureConstants {
     String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_NEW_TAB_FEATURE =
@@ -386,4 +386,9 @@
      * An IPH feature to inform users about the Webnotes Stylize feature in Sharing Hub.
      */
     String SHARING_HUB_WEBNOTES_STYLIZE_FEATURE = "IPH_SharingHubWebnotesStylize";
+
+    /**
+     * An IPH feature to inform users that a price drop has occurred in any of their open tabs
+     */
+    String PRICE_DROP_NTP_FEATURE = "IPH_PriceDropNTP";
 }
diff --git a/components/feature_engagement/public/feature_constants.cc b/components/feature_engagement/public/feature_constants.cc
index 8988eca..8806257 100644
--- a/components/feature_engagement/public/feature_constants.cc
+++ b/components/feature_engagement/public/feature_constants.cc
@@ -211,6 +211,8 @@
     "IPH_PageInfoStoreInfo", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kIPHPreviewsOmniboxUIFeature{
     "IPH_PreviewsOmniboxUI", base::FEATURE_ENABLED_BY_DEFAULT};
+const base::Feature kIPHPriceDropNTPFeature{"IPH_PriceDropNTP",
+                                            base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kIPHShoppingListMenuItemFeature{
     "IPH_ShoppingListMenuItem", base::FEATURE_ENABLED_BY_DEFAULT};
 const base::Feature kIPHTabGroupsQuicklyComparePagesFeature{
diff --git a/components/feature_engagement/public/feature_constants.h b/components/feature_engagement/public/feature_constants.h
index 8ce5c31..3885ffd 100644
--- a/components/feature_engagement/public/feature_constants.h
+++ b/components/feature_engagement/public/feature_constants.h
@@ -128,6 +128,7 @@
 extern const base::Feature kIPHPageInfoFeature;
 extern const base::Feature kIPHPageInfoStoreInfoFeature;
 extern const base::Feature kIPHPreviewsOmniboxUIFeature;
+extern const base::Feature kIPHPriceDropNTPFeature;
 extern const base::Feature kIPHQuietNotificationPromptsFeature;
 extern const base::Feature kIPHReadLaterContextMenuFeature;
 extern const base::Feature kIPHReadLaterAppMenuBookmarkThisPageFeature;
diff --git a/components/feature_engagement/public/feature_list.cc b/components/feature_engagement/public/feature_list.cc
index ec02fc1b..e4f72ddc 100644
--- a/components/feature_engagement/public/feature_list.cc
+++ b/components/feature_engagement/public/feature_list.cc
@@ -75,6 +75,7 @@
     &kIPHPageInfoFeature,
     &kIPHPageInfoStoreInfoFeature,
     &kIPHPreviewsOmniboxUIFeature,
+    &kIPHPriceDropNTPFeature,
     &kIPHPwaInstallAvailableFeature,
     &kIPHQuietNotificationPromptsFeature,
     &kIPHReadLaterContextMenuFeature,
diff --git a/components/feature_engagement/public/feature_list.h b/components/feature_engagement/public/feature_list.h
index 449f2dd6..f3f4bce 100644
--- a/components/feature_engagement/public/feature_list.h
+++ b/components/feature_engagement/public/feature_list.h
@@ -147,6 +147,7 @@
 DEFINE_VARIATION_PARAM(kIPHPageInfoFeature, "IPH_PageInfo");
 DEFINE_VARIATION_PARAM(kIPHPageInfoStoreInfoFeature, "IPH_PageInfoStoreInfo");
 DEFINE_VARIATION_PARAM(kIPHPreviewsOmniboxUIFeature, "IPH_PreviewsOmniboxUI");
+DEFINE_VARIATION_PARAM(kIPHPriceDropNTPFeature, "IPH_PriceDropNTP");
 DEFINE_VARIATION_PARAM(kIPHPwaInstallAvailableFeature,
                        "IPH_PwaInstallAvailableFeature");
 DEFINE_VARIATION_PARAM(kIPHQuietNotificationPromptsFeature,
@@ -316,6 +317,7 @@
         VARIATION_ENTRY(kIPHPageInfoFeature),
         VARIATION_ENTRY(kIPHPageInfoStoreInfoFeature),
         VARIATION_ENTRY(kIPHPreviewsOmniboxUIFeature),
+        VARIATION_ENTRY(kIPHPriceDropNTPFeature),
         VARIATION_ENTRY(kIPHPwaInstallAvailableFeature),
         VARIATION_ENTRY(kIPHQuietNotificationPromptsFeature),
         VARIATION_ENTRY(kIPHReadLaterContextMenuFeature),
diff --git a/components/feed/core/proto/v2/store.proto b/components/feed/core/proto/v2/store.proto
index fe29b3d..d49208c 100644
--- a/components/feed/core/proto/v2/store.proto
+++ b/components/feed/core/proto/v2/store.proto
@@ -70,11 +70,8 @@
   repeated uint32 content_hashes = 11;
   // Root EventID provided by the server.
   bytes root_event_id = 12;
-  // The unix timestamp in milliseconds that the feed response is produced on
-  // the server. This is returned from the server and based on server clock.
-  int64 last_server_response_time_millis = 15;
 
-  reserved 3, 5;
+  reserved 3, 5, 15;
 }
 
 // Data that doesn't belong to a stream.
@@ -105,6 +102,10 @@
     // The unix timestamp in milliseconds that the stream was fetched last time.
     int64 last_fetch_time_millis = 6;
 
+    // The unix timestamp in milliseconds that the feed response is produced on
+    // the server. This is returned from the server and based on server clock.
+    int64 last_server_response_time_millis = 7;
+
     reserved 2;
   }
 
diff --git a/components/feed/core/v2/api_test/feed_api_stream_unittest.cc b/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
index 91747fd..df9fad7 100644
--- a/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
+++ b/components/feed/core/v2/api_test/feed_api_stream_unittest.cc
@@ -1882,8 +1882,7 @@
   for (bool waa_on : {true, false}) {
     for (bool privacy_notice_fulfilled : {true, false}) {
       response_translator_.InjectResponse(MakeTypicalNextPageState(
-          page++, kTestTimeEpoch, kTestTimeEpoch, signed_in, waa_on,
-          privacy_notice_fulfilled));
+          page++, kTestTimeEpoch, signed_in, waa_on, privacy_notice_fulfilled));
       stream_->LoadMore(surface, base::DoNothing());
       WaitForIdleTaskQueue();
       EXPECT_TRUE(surface.update->logging_parameters().logging_enabled());
@@ -2699,6 +2698,7 @@
     RefreshResponseData response;
     response.model_update_request = MakeTypicalInitialModelState();
     response.web_and_app_activity_enabled = true;
+    response.last_fetch_timestamp = base::Time::Now();
     response_translator_.InjectResponse(std::move(response));
   }
   TestForYouSurface surface(stream_.get());
@@ -2720,6 +2720,7 @@
     RefreshResponseData response;
     response.model_update_request = MakeTypicalInitialModelState();
     response.discover_personalization_enabled = true;
+    response.last_fetch_timestamp = base::Time::Now();
     response_translator_.InjectResponse(std::move(response));
   }
   TestForYouSurface surface(stream_.get());
@@ -3081,8 +3082,11 @@
   task_environment_.AdvanceClock(base::Seconds(200));
 
   // Load the initial page.
-  response_translator_.InjectResponse(
-      MakeTypicalInitialModelState(0, client_timestamp, server_timestamp));
+  RefreshResponseData response;
+  response.model_update_request = MakeTypicalInitialModelState();
+  response.last_fetch_timestamp = client_timestamp;
+  response.server_response_sent_timestamp = server_timestamp;
+  response_translator_.InjectResponse(std::move(response));
   TestForYouSurface surface(stream_.get());
   WaitForIdleTaskQueue();
 
@@ -3128,8 +3132,11 @@
                                kTestInfoCardType1, 1);
 
   // Refresh the page so that a feed query including the info card tracking
-  // states is sent.
+  // states is sent. Call "CreateStream()" before the refresh to simulate
+  // Chrome restart. This is used to test that info card tracking states are
+  // sent in the initial page load when stream model is not loaded yet.
   response_translator_.InjectResponse(MakeTypicalRefreshModelState());
+  CreateStream();
   stream_->ManualRefresh(kForYouStream, base::DoNothing());
   WaitForIdleTaskQueue();
 
diff --git a/components/feed/core/v2/api_test/feed_api_test.cc b/components/feed/core/v2/api_test/feed_api_test.cc
index 34b5e846..003f070b 100644
--- a/components/feed/core/v2/api_test/feed_api_test.cc
+++ b/components/feed/core/v2/api_test/feed_api_test.cc
@@ -35,6 +35,7 @@
 #include "components/feed/core/proto/v2/xsurface.pb.h"
 #include "components/feed/core/shared_prefs/pref_names.h"
 #include "components/feed/core/v2/config.h"
+#include "components/feed/core/v2/feedstore_util.h"
 #include "components/feed/core/v2/prefs.h"
 #include "components/feed/core/v2/test/callback_receiver.h"
 #include "components/feed/core/v2/test/proto_printer.h"
@@ -780,7 +781,7 @@
     bool loaded_new_content_from_network,
     base::TimeDelta stored_content_age,
     const ContentStats& content_stats,
-    const RequestMetadata& request_metadata,
+    ContentOrder content_order,
     std::unique_ptr<LoadLatencyTimes> latencies) {
   load_stream_from_store_status = load_from_store_status;
   load_stream_status = final_status;
@@ -789,7 +790,7 @@
   MetricsReporter::OnLoadStream(
       stream_type, load_from_store_status, final_status, is_initial_load,
       loaded_new_content_from_network, stored_content_age, content_stats,
-      request_metadata, std::move(latencies));
+      content_order, std::move(latencies));
 }
 void TestMetricsReporter::OnLoadMoreBegin(const StreamType& stream_type,
                                           SurfaceId surface_id) {
diff --git a/components/feed/core/v2/api_test/feed_api_test.h b/components/feed/core/v2/api_test/feed_api_test.h
index 59406c5..96e452b 100644
--- a/components/feed/core/v2/api_test/feed_api_test.h
+++ b/components/feed/core/v2/api_test/feed_api_test.h
@@ -416,7 +416,7 @@
                     bool loaded_new_content_from_network,
                     base::TimeDelta stored_content_age,
                     const ContentStats& content_stats,
-                    const RequestMetadata& request_metadata,
+                    ContentOrder content_order,
                     std::unique_ptr<LoadLatencyTimes> latencies) override;
   void OnLoadMoreBegin(const StreamType& stream_type,
                        SurfaceId surface_id) override;
diff --git a/components/feed/core/v2/feed_stream.cc b/components/feed/core/v2/feed_stream.cc
index 7756f4b5a..8f050dcdb 100644
--- a/components/feed/core/v2/feed_stream.cc
+++ b/components/feed/core/v2/feed_stream.cc
@@ -310,7 +310,7 @@
       stream.type, result.load_from_store_status, result.final_status,
       result.load_type == LoadType::kInitialLoad,
       result.loaded_new_content_from_network, result.stored_content_age,
-      content_stats, GetRequestMetadata(stream.type, false),
+      content_stats, GetContentOrder(result.stream_type),
       std::move(result.latencies));
 
   stream.model_loading_in_progress = false;
@@ -978,14 +978,14 @@
         /*allow_expired_session_id =*/false);
   }
 
-  if (stream_type.IsWebFeed()) {
-    result.content_order = GetValidWebFeedContentOrder(*profile_prefs_);
-  }
+  result.content_order = GetContentOrder(stream_type);
 
-  if (stream->model) {
+  const feedstore::Metadata::StreamMetadata* stream_metadata =
+      FindMetadataForStream(GetMetadata(), stream_type);
+  if (stream_metadata != nullptr) {
     result.info_card_tracking_states = info_card_tracker_.GetAllStates(
-        stream->model->last_server_response_time_millis(),
-        stream->model->last_added_time_millis());
+        stream_metadata->last_server_response_time_millis(),
+        stream_metadata->last_fetch_time_millis());
   }
 
   return result;
@@ -1420,13 +1420,9 @@
           &FeedStream::ForceRefreshTask, base::Unretained(this), stream_type)));
 }
 
-ContentOrder FeedStream::GetContentOrder(const StreamType& stream_type) {
-  if (!stream_type.IsWebFeed()) {
-    NOTREACHED()
-        << "GetContentOrderFromPrefs is not supported for this stream_type "
-        << stream_type;
+ContentOrder FeedStream::GetContentOrder(const StreamType& stream_type) const {
+  if (!stream_type.IsWebFeed())
     return ContentOrder::kUnspecified;
-  }
   return GetValidWebFeedContentOrder(*profile_prefs_);
 }
 
diff --git a/components/feed/core/v2/feed_stream.h b/components/feed/core/v2/feed_stream.h
index ba13fae..149ca04 100644
--- a/components/feed/core/v2/feed_stream.h
+++ b/components/feed/core/v2/feed_stream.h
@@ -183,7 +183,7 @@
   base::Time GetLastFetchTime(const StreamType& stream_type) override;
   void SetContentOrder(const StreamType& stream_type,
                        ContentOrder content_order) override;
-  ContentOrder GetContentOrder(const StreamType& stream_type) override;
+  ContentOrder GetContentOrder(const StreamType& stream_type) const override;
   ContentOrder GetContentOrderFromPrefs(const StreamType& stream_type) override;
   void IncrementFollowedFromWebPageMenuCount() override;
 
diff --git a/components/feed/core/v2/feedstore_util.cc b/components/feed/core/v2/feedstore_util.cc
index 3fbbb4e..a6d2029f 100644
--- a/components/feed/core/v2/feedstore_util.cc
+++ b/components/feed/core/v2/feedstore_util.cc
@@ -38,6 +38,14 @@
   return base::Time::UnixEpoch() + base::Milliseconds(millis);
 }
 
+int64_t ToTimestampNanos(base::Time t) {
+  return (t - base::Time::UnixEpoch()).InNanoseconds();
+}
+
+base::Time FromTimestampMicros(int64_t micros) {
+  return base::Time::UnixEpoch() + base::Microseconds(micros);
+}
+
 void SetLastAddedTime(base::Time t, feedstore::StreamData& data) {
   data.set_last_added_time_millis(ToTimestampMillis(t));
 }
@@ -187,12 +195,13 @@
   return {};
 }
 
-void SetLastServerResponseTime(base::Time t, feedstore::StreamData& data) {
-  data.set_last_server_response_time_millis(ToTimestampMillis(t));
-}
-
-base::Time GetLastServerResponseTime(const feedstore::StreamData& data) {
-  return FromTimestampMillis(data.last_server_response_time_millis());
+void SetLastServerResponseTime(Metadata& metadata,
+                               const feed::StreamType& stream_type,
+                               const base::Time& server_time) {
+  Metadata::StreamMetadata& stream_metadata =
+      MetadataForStream(metadata, stream_type);
+  stream_metadata.set_last_server_response_time_millis(
+      ToTimestampMillis(server_time));
 }
 
 int32_t ContentHashFromPrefetchMetadata(
diff --git a/components/feed/core/v2/feedstore_util.h b/components/feed/core/v2/feedstore_util.h
index 5a0a939..a5c930a 100644
--- a/components/feed/core/v2/feedstore_util.h
+++ b/components/feed/core/v2/feedstore_util.h
@@ -31,14 +31,17 @@
 
 int64_t ToTimestampMillis(base::Time t);
 base::Time FromTimestampMillis(int64_t millis);
+int64_t ToTimestampNanos(base::Time t);
+base::Time FromTimestampMicros(int64_t millis);
 void SetLastAddedTime(base::Time t, feedstore::StreamData& data);
-void SetLastServerResponseTime(base::Time t, feedstore::StreamData& data);
+void SetLastServerResponseTime(Metadata& metadata,
+                               const feed::StreamType& stream_type,
+                               const base::Time& server_time);
 
 base::Time GetLastAddedTime(const feedstore::StreamData& data);
 base::Time GetSessionIdExpiryTime(const feedstore::Metadata& metadata);
 base::Time GetStreamViewTime(const Metadata& metadata,
                              const feed::StreamType& stream_type);
-base::Time GetLastServerResponseTime(const feedstore::StreamData& data);
 
 bool IsKnownStale(const Metadata& metadata,
                   const feed::StreamType& stream_type);
diff --git a/components/feed/core/v2/metrics_reporter.cc b/components/feed/core/v2/metrics_reporter.cc
index a413c4cb..359c377b26 100644
--- a/components/feed/core/v2/metrics_reporter.cc
+++ b/components/feed/core/v2/metrics_reporter.cc
@@ -739,7 +739,7 @@
     bool loaded_new_content_from_network,
     base::TimeDelta stored_content_age,
     const ContentStats& content_stats,
-    const RequestMetadata& request_metadata,
+    ContentOrder content_order,
     std::unique_ptr<LoadLatencyTimes> load_latencies) {
   VVLOG << "OnLoadStream load_from_store_status=" << load_from_store_status
         << " final_status=" << final_status;
@@ -786,7 +786,7 @@
     if (stream_type.IsWebFeed()) {
       base::UmaHistogramSparse(
           base::StrCat({"ContentSuggestions.Feed.WebFeed.LoadedCardCount.",
-                        ContentOrderToString(request_metadata.content_order)}),
+                        ContentOrderToString(content_order)}),
           content_stats.card_count);
     }
   }
diff --git a/components/feed/core/v2/metrics_reporter.h b/components/feed/core/v2/metrics_reporter.h
index 74541f06..38cd6aa 100644
--- a/components/feed/core/v2/metrics_reporter.h
+++ b/components/feed/core/v2/metrics_reporter.h
@@ -98,7 +98,7 @@
                             bool loaded_new_content_from_network,
                             base::TimeDelta stored_content_age,
                             const ContentStats& content_stats,
-                            const RequestMetadata& request_metadata,
+                            ContentOrder content_order,
                             std::unique_ptr<LoadLatencyTimes> load_latencies);
   virtual void OnBackgroundRefresh(const StreamType& stream_type,
                                    LoadStreamStatus final_status);
diff --git a/components/feed/core/v2/metrics_reporter_unittest.cc b/components/feed/core/v2/metrics_reporter_unittest.cc
index 664c92c..35e3833 100644
--- a/components/feed/core/v2/metrics_reporter_unittest.cc
+++ b/components/feed/core/v2/metrics_reporter_unittest.cc
@@ -37,12 +37,6 @@
     /*total_content_frame_size_bytes=*/100 * 1024,
     /*shared_state_size=*/200 * 1024};
 
-RequestMetadata DefaultRequestMetadata() {
-  RequestMetadata metadata;
-  metadata.content_order = ContentOrder::kGrouped;
-  return metadata;
-}
-
 class MetricsReporterTest : public testing::Test, MetricsReporter::Delegate {
  protected:
   void SetUp() override {
@@ -289,7 +283,7 @@
                           /*is_initial_load=*/true,
                           /*loaded_new_content_from_network=*/true,
                           /*stored_content_age=*/base::Days(5), kContentStats,
-                          DefaultRequestMetadata(),
+                          ContentOrder::kGrouped,
                           std::make_unique<LoadLatencyTimes>());
 
   histogram_.ExpectUniqueSample(
@@ -313,7 +307,7 @@
                           /*is_initial_load=*/true,
                           /*loaded_new_content_from_network=*/false,
                           /*stored_content_age=*/base::Days(5), kContentStats,
-                          DefaultRequestMetadata(),
+                          ContentOrder::kGrouped,
                           std::make_unique<LoadLatencyTimes>());
 
   histogram_.ExpectUniqueSample(
@@ -330,7 +324,7 @@
                           /*is_initial_load=*/true,
                           /*loaded_new_content_from_network=*/true,
                           /*stored_content_age=*/base::Days(5), kContentStats,
-                          DefaultRequestMetadata(),
+                          ContentOrder::kGrouped,
                           std::make_unique<LoadLatencyTimes>());
 
   histogram_.ExpectUniqueSample(
@@ -359,15 +353,12 @@
 }
 
 TEST_F(MetricsReporterTest, WebFeed_ReportsLoadStreamStatus_ReverseChron) {
-  RequestMetadata request_metadata = DefaultRequestMetadata();
-  request_metadata.content_order = ContentOrder::kReverseChron;
-
   reporter_->OnLoadStream(kWebFeedStream, LoadStreamStatus::kDataInStoreIsStale,
                           LoadStreamStatus::kLoadedFromNetwork,
                           /*is_initial_load=*/true,
                           /*loaded_new_content_from_network=*/true,
                           /*stored_content_age=*/base::Days(5), kContentStats,
-                          request_metadata,
+                          ContentOrder::kReverseChron,
                           std::make_unique<LoadLatencyTimes>());
 
   histogram_.ExpectUniqueSample(
@@ -386,7 +377,7 @@
                           /*is_initial_load=*/true,
                           /*loaded_new_content_from_network=*/true,
                           /*stored_content_age=*/base::Days(5), ContentStats(),
-                          DefaultRequestMetadata(),
+                          ContentOrder::kGrouped,
                           std::make_unique<LoadLatencyTimes>());
 
   histogram_.ExpectUniqueSample(
@@ -400,7 +391,7 @@
                           /*is_initial_load=*/true,
                           /*loaded_new_content_from_network=*/false,
                           /*stored_content_age=*/base::Days(5), kContentStats,
-                          DefaultRequestMetadata(),
+                          ContentOrder::kGrouped,
                           std::make_unique<LoadLatencyTimes>());
 
   histogram_.ExpectTotalCount("ContentSuggestions.Feed.LoadedCardCount", 0);
@@ -412,7 +403,7 @@
                           /*is_initial_load=*/false,
                           /*loaded_new_content_from_network=*/true,
                           /*stored_content_age=*/base::Days(5), kContentStats,
-                          DefaultRequestMetadata(),
+                          ContentOrder::kGrouped,
                           std::make_unique<LoadLatencyTimes>());
 
   histogram_.ExpectUniqueSample(
@@ -432,7 +423,7 @@
                           /*is_initial_load=*/true,
                           /*loaded_new_content_from_network=*/true,
                           /*stored_content_age=*/base::TimeDelta(),
-                          kContentStats, DefaultRequestMetadata(),
+                          kContentStats, ContentOrder::kGrouped,
                           std::make_unique<LoadLatencyTimes>());
 
   histogram_.ExpectUniqueSample(
@@ -451,7 +442,7 @@
                           /*is_initial_load=*/true,
                           /*loaded_new_content_from_network=*/true,
                           /*stored_content_age=*/base::Days(5), kContentStats,
-                          DefaultRequestMetadata(),
+                          ContentOrder::kGrouped,
                           std::make_unique<LoadLatencyTimes>());
 
   histogram_.ExpectUniqueTimeSample(
@@ -465,7 +456,7 @@
                           /*is_initial_load=*/true,
                           /*loaded_new_content_from_network=*/false,
                           /*stored_content_age=*/base::Days(5), kContentStats,
-                          DefaultRequestMetadata(),
+                          ContentOrder::kGrouped,
                           std::make_unique<LoadLatencyTimes>());
 
   histogram_.ExpectUniqueTimeSample(
@@ -479,13 +470,13 @@
       LoadStreamStatus::kLoadedFromStore, /*is_initial_load=*/true,
       /*loaded_new_content_from_network=*/false,
       /*stored_content_age=*/-base::Seconds(1), kContentStats,
-      DefaultRequestMetadata(), std::make_unique<LoadLatencyTimes>());
+      ContentOrder::kGrouped, std::make_unique<LoadLatencyTimes>());
   reporter_->OnLoadStream(kForYouStream, LoadStreamStatus::kDataInStoreIsStale,
                           LoadStreamStatus::kLoadedFromStore,
                           /*is_initial_load=*/true,
                           /*loaded_new_content_from_network=*/false,
                           /*stored_content_age=*/base::TimeDelta(),
-                          kContentStats, DefaultRequestMetadata(),
+                          kContentStats, ContentOrder::kGrouped,
                           std::make_unique<LoadLatencyTimes>());
   histogram_.ExpectTotalCount(
       "ContentSuggestions.Feed.ContentAgeOnLoad.NotRefreshed", 0);
@@ -505,7 +496,7 @@
                             /*is_initial_load=*/true,
                             /*loaded_new_content_from_network=*/true,
                             /*stored_content_age=*/base::TimeDelta(),
-                            kContentStats, DefaultRequestMetadata(),
+                            kContentStats, ContentOrder::kGrouped,
                             std::move(latencies));
   }
   task_environment_.FastForwardBy(base::Milliseconds(300));
diff --git a/components/feed/core/v2/protocol_translator.cc b/components/feed/core/v2/protocol_translator.cc
index 4d66982..2cf8a4df 100644
--- a/components/feed/core/v2/protocol_translator.cc
+++ b/components/feed/core/v2/protocol_translator.cc
@@ -320,12 +320,6 @@
         TranslateContentLifetime(response_metadata.content_lifetime());
   }
 
-  if (response_metadata.has_response_time_ms()) {
-    feedstore::SetLastServerResponseTime(
-        feedstore::FromTimestampMillis(response_metadata.response_time_ms()),
-        result->stream_data);
-  }
-
   const auto& chrome_response_metadata =
       response_metadata.chrome_feed_response_metadata();
   // Note that we're storing the raw proto bytes for the root event ID because
@@ -383,10 +377,12 @@
   response_data.content_lifetime = std::move(content_lifetime);
   response_data.session_id = std::move(session_id);
   response_data.experiments = std::move(experiments);
-  response_data.server_request_received_timestamp_ns =
-      feed_response->feed_response_metadata().event_id().time_usec() * 1'000;
-  response_data.server_response_sent_timestamp_ns =
-      feed_response->feed_response_metadata().response_time_ms() * 1'000'000;
+  response_data.server_request_received_timestamp =
+      feedstore::FromTimestampMicros(
+          feed_response->feed_response_metadata().event_id().time_usec());
+  response_data.server_response_sent_timestamp = feedstore::FromTimestampMillis(
+      feed_response->feed_response_metadata().response_time_ms());
+  response_data.last_fetch_timestamp = current_time;
   response_data.web_and_app_activity_enabled =
       chrome_response_metadata.web_and_app_activity_enabled();
   response_data.discover_personalization_enabled =
diff --git a/components/feed/core/v2/protocol_translator.h b/components/feed/core/v2/protocol_translator.h
index d3d290d0..c7df0e63 100644
--- a/components/feed/core/v2/protocol_translator.h
+++ b/components/feed/core/v2/protocol_translator.h
@@ -77,10 +77,13 @@
   // List of experiments from the server, if provided.
   absl::optional<Experiments> experiments;
 
-  // Server-reported network timestamps (nanoseconds). They can be compared to
+  // Server-reported network timestamps. They can be compared to
   // each other but not to client timestamps.
-  int64_t server_request_received_timestamp_ns;
-  int64_t server_response_sent_timestamp_ns;
+  base::Time server_request_received_timestamp;
+  base::Time server_response_sent_timestamp;
+
+  // The client-side timestamp that the response is fetched.
+  base::Time last_fetch_timestamp;
 
   bool web_and_app_activity_enabled = false;
   bool discover_personalization_enabled = false;
diff --git a/components/feed/core/v2/public/feed_api.h b/components/feed/core/v2/public/feed_api.h
index d5d27e7..0fee014 100644
--- a/components/feed/core/v2/public/feed_api.h
+++ b/components/feed/core/v2/public/feed_api.h
@@ -75,7 +75,7 @@
                                ContentOrder content_order) = 0;
 
   // Returns the current `ContentOrder` for `stream_type`.
-  virtual ContentOrder GetContentOrder(const StreamType& stream_type) = 0;
+  virtual ContentOrder GetContentOrder(const StreamType& stream_type) const = 0;
 
   // Gets the "raw" content order value stored in prefs. Returns `kUnspecified`
   // if the user has not selected one yet.
diff --git a/components/feed/core/v2/public/ntp_feed_content_fetcher.h b/components/feed/core/v2/public/ntp_feed_content_fetcher.h
index 5e1624d..463335e6 100644
--- a/components/feed/core/v2/public/ntp_feed_content_fetcher.h
+++ b/components/feed/core/v2/public/ntp_feed_content_fetcher.h
@@ -25,7 +25,7 @@
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
       const std::string& api_key,
       PrefService* pref_service);
-  ~NtpFeedContentFetcher();
+  virtual ~NtpFeedContentFetcher();
 
   /** Semantic article info based on PrefetchMetadata. */
   struct Article {
@@ -45,7 +45,7 @@
    * on their PrefetchMetadata, and pass them along to the callback.
    * @param callback Callback that is called with the fetch result.
    */
-  void FetchFollowingFeedArticles(
+  virtual void FetchFollowingFeedArticles(
       base::OnceCallback<void(std::vector<Article>)>);
 
   void SetFeedNetworkForTesting(std::unique_ptr<FeedNetwork> feed_network);
diff --git a/components/feed/core/v2/public/test/stub_feed_api.cc b/components/feed/core/v2/public/test/stub_feed_api.cc
index 2ba8009..c9abeb2 100644
--- a/components/feed/core/v2/public/test/stub_feed_api.cc
+++ b/components/feed/core/v2/public/test/stub_feed_api.cc
@@ -59,7 +59,7 @@
   return base::Time();
 }
 
-ContentOrder StubFeedApi::GetContentOrder(const StreamType& stream_type) {
+ContentOrder StubFeedApi::GetContentOrder(const StreamType& stream_type) const {
   return ContentOrder::kUnspecified;
 }
 
diff --git a/components/feed/core/v2/public/test/stub_feed_api.h b/components/feed/core/v2/public/test/stub_feed_api.h
index 24111adf7..46ba9e7 100644
--- a/components/feed/core/v2/public/test/stub_feed_api.h
+++ b/components/feed/core/v2/public/test/stub_feed_api.h
@@ -109,7 +109,7 @@
   base::Time GetLastFetchTime(const StreamType& stream_type) override;
   void SetContentOrder(const StreamType& stream_type,
                        ContentOrder content_order) override {}
-  ContentOrder GetContentOrder(const StreamType& stream_type) override;
+  ContentOrder GetContentOrder(const StreamType& stream_type) const override;
   ContentOrder GetContentOrderFromPrefs(const StreamType& stream_type) override;
   void IncrementFollowedFromWebPageMenuCount() override {}
 
diff --git a/components/feed/core/v2/stream_model.h b/components/feed/core/v2/stream_model.h
index f2a92a1..382d668 100644
--- a/components/feed/core/v2/stream_model.h
+++ b/components/feed/core/v2/stream_model.h
@@ -125,12 +125,6 @@
     return stream_data_.last_added_time_millis();
   }
 
-  // The server timestamp, in milliseconds from the Epoch, when the response is
-  // produced.
-  int64_t last_server_response_time_millis() const {
-    return stream_data_.last_server_response_time_millis();
-  }
-
   // Returns the full list of content in the order it should be presented.
   const std::vector<ContentRevision>& GetContentList() const {
     return content_list_;
diff --git a/components/feed/core/v2/stream_model_unittest.cc b/components/feed/core/v2/stream_model_unittest.cc
index 02fd236..2e562f8 100644
--- a/components/feed/core/v2/stream_model_unittest.cc
+++ b/components/feed/core/v2/stream_model_unittest.cc
@@ -455,7 +455,7 @@
   observer.Clear();
   store_observer.Clear();
   model.Update(MakeTypicalNextPageState(
-      2, kTestTimeEpoch, kTestTimeEpoch, true, true, true,
+      2, kTestTimeEpoch, true, true, true,
       StreamModelUpdateRequest::Source::kNetworkLoadMore));
 
   EXPECT_EQ(2UL, observer.GetUiUpdate()->shared_states.size());
diff --git a/components/feed/core/v2/tasks/load_more_task.cc b/components/feed/core/v2/tasks/load_more_task.cc
index dc89ec2..e4fcbe32 100644
--- a/components/feed/core/v2/tasks/load_more_task.cc
+++ b/components/feed/core/v2/tasks/load_more_task.cc
@@ -143,7 +143,8 @@
       !translated_response.model_update_request->stream_structures.empty();
 
   auto updated_metadata = stream_.GetMetadata();
-  SetLastFetchTime(updated_metadata, stream_type_, base::Time::Now());
+  SetLastFetchTime(updated_metadata, stream_type_,
+                   translated_response.last_fetch_timestamp);
   if (translated_response.session_id) {
     feedstore::MaybeUpdateSessionId(updated_metadata,
                                     translated_response.session_id);
diff --git a/components/feed/core/v2/tasks/load_stream_task.cc b/components/feed/core/v2/tasks/load_stream_task.cc
index 4b03cde..114776f 100644
--- a/components/feed/core/v2/tasks/load_stream_task.cc
+++ b/components/feed/core/v2/tasks/load_stream_task.cc
@@ -379,9 +379,9 @@
           *response_body, StreamModelUpdateRequest::Source::kNetworkUpdate,
           response_info.account_info, base::Time::Now());
   server_send_timestamp_ns_ =
-      response_data.server_request_received_timestamp_ns;
-  server_receive_timestamp_ns_ =
-      response_data.server_request_received_timestamp_ns;
+      feedstore::ToTimestampNanos(response_data.server_response_sent_timestamp);
+  server_receive_timestamp_ns_ = feedstore::ToTimestampMillis(
+      response_data.server_request_received_timestamp);
 
   if (!response_data.model_update_request) {
     return RequestFinished(
@@ -406,7 +406,10 @@
   MetricsReporter::NoticeCardFulfilled(fetched_content_has_notice_card);
 
   feedstore::Metadata updated_metadata = stream_.GetMetadata();
-  SetLastFetchTime(updated_metadata, options_.stream_type, base::Time::Now());
+  SetLastFetchTime(updated_metadata, options_.stream_type,
+                   response_data.last_fetch_timestamp);
+  SetLastServerResponseTime(updated_metadata, options_.stream_type,
+                            response_data.server_response_sent_timestamp);
   updated_metadata.set_web_and_app_activity_enabled(
       response_data.web_and_app_activity_enabled);
   updated_metadata.set_discover_personalization_enabled(
diff --git a/components/feed/core/v2/test/stream_builder.cc b/components/feed/core/v2/test/stream_builder.cc
index d5a2113..c28df7d 100644
--- a/components/feed/core/v2/test/stream_builder.cc
+++ b/components/feed/core/v2/test/stream_builder.cc
@@ -262,8 +262,6 @@
     AddContentHashes(initial_update->content[i], initial_update->stream_data);
   }
   feedstore::SetLastAddedTime(last_added_time, initial_update->stream_data);
-  feedstore::SetLastServerResponseTime(last_server_response_time,
-                                       initial_update->stream_data);
 
   return initial_update;
 }
@@ -306,8 +304,6 @@
   AddContentHashes(MakeContent(j), initial_update->stream_data);
 
   feedstore::SetLastAddedTime(last_added_time, initial_update->stream_data);
-  feedstore::SetLastServerResponseTime(last_server_response_time,
-                                       initial_update->stream_data);
 
   return initial_update;
 }
@@ -315,13 +311,11 @@
 std::unique_ptr<StreamModelUpdateRequest> MakeTypicalInitialModelState(
     int first_cluster_id,
     base::Time last_added_time,
-    base::Time last_server_response_time,
     bool signed_in,
     bool logging_enabled,
     bool privacy_notice_fulfilled) {
   StreamModelUpdateRequestGenerator generator;
   generator.last_added_time = last_added_time;
-  generator.last_server_response_time = last_server_response_time;
   generator.signed_in = signed_in;
   generator.logging_enabled = logging_enabled;
   generator.privacy_notice_fulfilled = privacy_notice_fulfilled;
@@ -331,12 +325,10 @@
 std::unique_ptr<StreamModelUpdateRequest> MakeTypicalRefreshModelState(
     int first_cluster_id,
     base::Time last_added_time,
-    base::Time last_server_response_time,
     bool signed_in,
     bool logging_enabled) {
   StreamModelUpdateRequestGenerator generator;
   generator.last_added_time = last_added_time;
-  generator.last_server_response_time = last_server_response_time;
   generator.signed_in = signed_in;
   generator.logging_enabled = logging_enabled;
   generator.privacy_notice_fulfilled = false;
@@ -347,14 +339,12 @@
 std::unique_ptr<StreamModelUpdateRequest> MakeTypicalNextPageState(
     int page_number,
     base::Time last_added_time,
-    base::Time last_server_response_time,
     bool signed_in,
     bool logging_enabled,
     bool privacy_notice_fulfilled,
     StreamModelUpdateRequest::Source source) {
   StreamModelUpdateRequestGenerator generator;
   generator.last_added_time = last_added_time;
-  generator.last_server_response_time = last_server_response_time;
   generator.signed_in = signed_in;
   generator.logging_enabled = logging_enabled;
   generator.privacy_notice_fulfilled = privacy_notice_fulfilled;
diff --git a/components/feed/core/v2/test/stream_builder.h b/components/feed/core/v2/test/stream_builder.h
index 44284bca..61e3bdf7 100644
--- a/components/feed/core/v2/test/stream_builder.h
+++ b/components/feed/core/v2/test/stream_builder.h
@@ -90,7 +90,6 @@
 std::unique_ptr<StreamModelUpdateRequest> MakeTypicalInitialModelState(
     int first_cluster_id = 0,
     base::Time last_added_time = kTestTimeEpoch,
-    base::Time last_server_response_time = kTestTimeEpoch,
     bool signed_in = true,
     bool logging_enabled = true,
     bool privacy_notice_fulfilled = false);
@@ -105,7 +104,6 @@
 std::unique_ptr<StreamModelUpdateRequest> MakeTypicalRefreshModelState(
     int first_cluster_id = 2,
     base::Time last_added_time = kTestTimeEpoch,
-    base::Time last_server_response_time = kTestTimeEpoch,
     bool signed_in = true,
     bool logging_enabled = true);
 // Root
@@ -116,7 +114,6 @@
 std::unique_ptr<StreamModelUpdateRequest> MakeTypicalNextPageState(
     int page_number = 2,
     base::Time last_added_time = kTestTimeEpoch,
-    base::Time last_server_response_time = kTestTimeEpoch,
     bool signed_in = true,
     bool logging_enabled = true,
     bool privacy_notice_fulfilled = true,
diff --git a/components/language/core/common/language_experiments.h b/components/language/core/common/language_experiments.h
index 839f7015..f9740ec 100644
--- a/components/language/core/common/language_experiments.h
+++ b/components/language/core/common/language_experiments.h
@@ -36,7 +36,7 @@
 // Notify sync to update data on language determined.
 extern const base::Feature kNotifySyncOnLanguageDetermined;
 
-// This feature uses the existing UI for translate bubble.
+// This feature uses the existing UI for the Full Page Translate bubble.
 extern const base::Feature kUseButtonTranslateBubbleUi;
 
 // This feature enables setting the application language on Android.
diff --git a/components/omnibox/browser/autocomplete_provider.cc b/components/omnibox/browser/autocomplete_provider.cc
index 8dae86c9..42b4c5c4 100644
--- a/components/omnibox/browser/autocomplete_provider.cc
+++ b/components/omnibox/browser/autocomplete_provider.cc
@@ -36,9 +36,10 @@
 // static
 const char* AutocompleteProvider::TypeToString(Type type) {
   // When creating a new provider, add the provider type to this function and
-  // make sure to also add the appropriate suffix entry to OmniboxProviderTime
-  // histogram_suffixes (in histogram_suffixes_list.xml) so that the run-time
-  // metrics associated with the relevant provider can be properly analyzed.
+  // make sure to also add the appropriate OmniboxProvider variant to the
+  // Omnibox.ProviderTime2 histogram (defined in omnibox/histograms.xml) so that
+  // the run-time metrics associated with the relevant provider can be properly
+  // analyzed.
   switch (type) {
     case TYPE_BOOKMARK:
       return "Bookmark";
diff --git a/components/optimization_guide/core/hints_fetcher.cc b/components/optimization_guide/core/hints_fetcher.cc
index d9b502d..63f7a7b 100644
--- a/components/optimization_guide/core/hints_fetcher.cc
+++ b/components/optimization_guide/core/hints_fetcher.cc
@@ -227,9 +227,6 @@
 
   get_hints_request.set_context(request_context_);
 
-  *get_hints_request.mutable_active_field_trials() =
-      GetActiveFieldTrialsAllowedForFetch();
-
   get_hints_request.set_locale(locale);
 
   for (const auto& url : valid_urls)
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index 2af3b793..73db1b2 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -80,11 +80,6 @@
 const base::Feature kOptimizationHints{"OptimizationHints",
                                        base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Feature flag that contains a feature param that specifies the field trials
-// that are allowed to be sent up to the Optimization Guide Server.
-const base::Feature kOptimizationHintsFieldTrials{
-    "OptimizationHintsFieldTrials", base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Enables fetching from a remote Optimization Guide Service.
 const base::Feature kRemoteOptimizationGuideFetching{
     "OptimizationHintsFetching", base::FEATURE_ENABLED_BY_DEFAULT};
@@ -448,22 +443,6 @@
       ));
 }
 
-base::flat_set<uint32_t> FieldTrialNameHashesAllowedForFetch() {
-  std::string value = base::GetFieldTrialParamValueByFeature(
-      kOptimizationHintsFieldTrials, "allowed_field_trial_names");
-  if (value.empty())
-    return {};
-
-  std::vector<std::string> allowed_field_trial_names = base::SplitString(
-      value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-  base::flat_set<uint32_t> allowed_field_trial_name_hashes;
-  for (const auto& allowed_field_trial_name : allowed_field_trial_names) {
-    allowed_field_trial_name_hashes.insert(
-        variations::HashName(allowed_field_trial_name));
-  }
-  return allowed_field_trial_name_hashes;
-}
-
 bool IsModelDownloadingEnabled() {
   return base::FeatureList::IsEnabled(kOptimizationGuideModelDownloading);
 }
diff --git a/components/optimization_guide/core/optimization_guide_features.h b/components/optimization_guide/core/optimization_guide_features.h
index e1b15dc0..f7ac917 100644
--- a/components/optimization_guide/core/optimization_guide_features.h
+++ b/components/optimization_guide/core/optimization_guide_features.h
@@ -23,7 +23,6 @@
 namespace features {
 
 extern const base::Feature kOptimizationHints;
-extern const base::Feature kOptimizationHintsFieldTrials;
 extern const base::Feature kRemoteOptimizationGuideFetching;
 extern const base::Feature kRemoteOptimizationGuideFetchingAnonymousDataConsent;
 extern const base::Feature kContextMenuPerformanceInfoAndRemoteHintFetching;
@@ -212,11 +211,6 @@
 // The default timeout for the watchdog to use if none is given by the caller.
 base::TimeDelta ModelExecutionWatchdogDefaultTimeout();
 
-// Returns a set of field trial name hashes that can be sent in the request to
-// the remote Optimization Guide Service if the client is in one of the
-// specified field trials.
-base::flat_set<uint32_t> FieldTrialNameHashesAllowedForFetch();
-
 // Whether the ability to download models is enabled.
 bool IsModelDownloadingEnabled();
 
diff --git a/components/optimization_guide/core/optimization_guide_util.cc b/components/optimization_guide/core/optimization_guide_util.cc
index 39f3957..3dca369 100644
--- a/components/optimization_guide/core/optimization_guide_util.cc
+++ b/components/optimization_guide/core/optimization_guide_util.cc
@@ -9,7 +9,6 @@
 #include "components/optimization_guide/core/optimization_guide_decision.h"
 #include "components/optimization_guide/core/optimization_guide_enums.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
-#include "components/variations/active_field_trials.h"
 #include "net/base/url_util.h"
 #include "url/url_canon.h"
 
@@ -27,39 +26,6 @@
   return true;
 }
 
-google::protobuf::RepeatedPtrField<proto::FieldTrial>
-GetActiveFieldTrialsAllowedForFetch() {
-  google::protobuf::RepeatedPtrField<proto::FieldTrial>
-      filtered_active_field_trials;
-
-  base::flat_set<uint32_t> allowed_field_trials_for_fetch =
-      features::FieldTrialNameHashesAllowedForFetch();
-  if (allowed_field_trials_for_fetch.empty())
-    return filtered_active_field_trials;
-
-  std::vector<variations::ActiveGroupId> active_field_trials;
-  variations::GetFieldTrialActiveGroupIds(/*suffix=*/"", &active_field_trials);
-  for (const auto& active_field_trial : active_field_trials) {
-    if (static_cast<size_t>(filtered_active_field_trials.size()) ==
-        allowed_field_trials_for_fetch.size()) {
-      // We've found all the field trials that we are allowed to send to the
-      // server.
-      break;
-    }
-
-    if (allowed_field_trials_for_fetch.find(active_field_trial.name) ==
-        allowed_field_trials_for_fetch.end()) {
-      // Continue if we are not allowed to send the field trial to the server.
-      continue;
-    }
-
-    proto::FieldTrial* ft_proto = filtered_active_field_trials.Add();
-    ft_proto->set_name_hash(active_field_trial.name);
-    ft_proto->set_group_hash(active_field_trial.group);
-  }
-  return filtered_active_field_trials;
-}
-
 std::string GetStringForOptimizationGuideDecision(
     OptimizationGuideDecision decision) {
   switch (decision) {
diff --git a/components/optimization_guide/core/optimization_guide_util.h b/components/optimization_guide/core/optimization_guide_util.h
index f20e3a2..8a6dd4d9 100644
--- a/components/optimization_guide/core/optimization_guide_util.h
+++ b/components/optimization_guide/core/optimization_guide_util.h
@@ -33,11 +33,6 @@
 // host that is not supported by the remote optimization guide.
 bool IsHostValidToFetchFromRemoteOptimizationGuide(const std::string& host);
 
-// Returns the set of active field trials that are allowed to be sent to the
-// remote Optimization Guide Service.
-google::protobuf::RepeatedPtrField<proto::FieldTrial>
-GetActiveFieldTrialsAllowedForFetch();
-
 // Validates that the metadata stored in |any_metadata_| is of the same type
 // and is parseable as |T|. Will return metadata if all checks pass.
 template <class T,
diff --git a/components/optimization_guide/core/page_entities_model_executor_impl_unittest.cc b/components/optimization_guide/core/page_entities_model_executor_impl_unittest.cc
index d989342..1c8c664 100644
--- a/components/optimization_guide/core/page_entities_model_executor_impl_unittest.cc
+++ b/components/optimization_guide/core/page_entities_model_executor_impl_unittest.cc
@@ -64,6 +64,13 @@
 
 class PageEntitiesModelExecutorImplTest : public testing::Test {
  public:
+  PageEntitiesModelExecutorImplTest() {
+    PageEntitiesModelExecutorConfig config;
+    // The false variation is tested in a src-internal test.
+    config.should_provide_filter_path = true;
+    SetPageEntitiesModelExecutorConfigForTesting(config);
+  }
+
   void SetUp() override {
     model_observer_tracker_ = std::make_unique<ModelObserverTracker>();
 
@@ -259,14 +266,7 @@
   EXPECT_TRUE(immediate_callback_run);
 }
 
-// TODO(https://crbug.com/1341224): Fix flakes on linux-chromeos-chrome and
-// re-enable this test.
-#if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE_CreateMissingFiles DISABLED_CreateMissingFiles
-#else
-#define MAYBE_CreateMissingFiles CreateMissingFiles
-#endif
-TEST_F(PageEntitiesModelExecutorImplTest, MAYBE_CreateMissingFiles) {
+TEST_F(PageEntitiesModelExecutorImplTest, CreateMissingFiles) {
   proto::Any any;
   proto::PageEntitiesModelMetadata metadata;
   metadata.add_slice("global");
diff --git a/components/optimization_guide/core/prediction_manager.cc b/components/optimization_guide/core/prediction_manager.cc
index 817933c4..a6a3d671 100644
--- a/components/optimization_guide/core/prediction_manager.cc
+++ b/components/optimization_guide/core/prediction_manager.cc
@@ -386,18 +386,6 @@
   // It is assumed that if we proceed past here, that a fetch will at least be
   // attempted.
 
-  std::vector<proto::FieldTrial> active_field_trials;
-  // Active field trials convey some sort of user information, so
-  // ensure that the user has opted into the right permissions before adding
-  // these fields to the request.
-  if (IsUserPermittedToFetchFromRemoteOptimizationGuide(off_the_record_,
-                                                        pref_service_)) {
-    google::protobuf::RepeatedPtrField<proto::FieldTrial> current_field_trials =
-        GetActiveFieldTrialsAllowedForFetch();
-    active_field_trials = std::vector<proto::FieldTrial>(
-        {current_field_trials.begin(), current_field_trials.end()});
-  }
-
   if (!prediction_model_fetcher_) {
     prediction_model_fetcher_ = std::make_unique<PredictionModelFetcherImpl>(
         url_loader_factory_,
@@ -434,8 +422,7 @@
 
   bool fetch_initiated =
       prediction_model_fetcher_->FetchOptimizationGuideServiceModels(
-          models_info, active_field_trials, proto::CONTEXT_BATCH_UPDATE_MODELS,
-          application_locale_,
+          models_info, proto::CONTEXT_BATCH_UPDATE_MODELS, application_locale_,
           base::BindOnce(&PredictionManager::OnModelsFetched,
                          ui_weak_ptr_factory_.GetWeakPtr(), models_info));
 
diff --git a/components/optimization_guide/core/prediction_manager_unittest.cc b/components/optimization_guide/core/prediction_manager_unittest.cc
index d4ad3d7..a424df2b 100644
--- a/components/optimization_guide/core/prediction_manager_unittest.cc
+++ b/components/optimization_guide/core/prediction_manager_unittest.cc
@@ -173,7 +173,6 @@
 
   bool FetchOptimizationGuideServiceModels(
       const std::vector<proto::ModelInfo>& models_request_info,
-      const std::vector<proto::FieldTrial>& active_field_trials,
       proto::RequestContext request_context,
       const std::string& locale,
       ModelsFetchedCallback models_fetched_callback) override {
diff --git a/components/optimization_guide/core/prediction_model_fetcher.h b/components/optimization_guide/core/prediction_model_fetcher.h
index 3c6e150f..2608a92 100644
--- a/components/optimization_guide/core/prediction_model_fetcher.h
+++ b/components/optimization_guide/core/prediction_model_fetcher.h
@@ -40,7 +40,6 @@
   // nullopt if the fetch failed or no fetch is needed. Virtualized for testing.
   virtual bool FetchOptimizationGuideServiceModels(
       const std::vector<proto::ModelInfo>& models_request_info,
-      const std::vector<proto::FieldTrial>& active_field_trials,
       proto::RequestContext request_context,
       const std::string& locale,
       ModelsFetchedCallback models_fetched_callback) = 0;
diff --git a/components/optimization_guide/core/prediction_model_fetcher_impl.cc b/components/optimization_guide/core/prediction_model_fetcher_impl.cc
index 6162e00..83c1a2a 100644
--- a/components/optimization_guide/core/prediction_model_fetcher_impl.cc
+++ b/components/optimization_guide/core/prediction_model_fetcher_impl.cc
@@ -47,7 +47,6 @@
 
 bool PredictionModelFetcherImpl::FetchOptimizationGuideServiceModels(
     const std::vector<proto::ModelInfo>& models_request_info,
-    const std::vector<proto::FieldTrial>& active_field_trials,
     proto::RequestContext request_context,
     const std::string& locale,
     ModelsFetchedCallback models_fetched_callback) {
@@ -68,9 +67,6 @@
   pending_models_request_->set_request_context(request_context);
   pending_models_request_->set_locale(locale);
 
-  *pending_models_request_->mutable_active_field_trials() = {
-      active_field_trials.begin(), active_field_trials.end()};
-
   for (const auto& model_request_info : models_request_info)
     *pending_models_request_->add_requested_models() = model_request_info;
 
diff --git a/components/optimization_guide/core/prediction_model_fetcher_impl.h b/components/optimization_guide/core/prediction_model_fetcher_impl.h
index 969af656..2f0516e6 100644
--- a/components/optimization_guide/core/prediction_model_fetcher_impl.h
+++ b/components/optimization_guide/core/prediction_model_fetcher_impl.h
@@ -44,7 +44,6 @@
   // PredictionModelFetcher implementation
   bool FetchOptimizationGuideServiceModels(
       const std::vector<proto::ModelInfo>& models_request_info,
-      const std::vector<proto::FieldTrial>& active_field_trials,
       proto::RequestContext request_context,
       const std::string& locale,
       ModelsFetchedCallback models_fetched_callback) override;
diff --git a/components/optimization_guide/core/prediction_model_fetcher_unittest.cc b/components/optimization_guide/core/prediction_model_fetcher_unittest.cc
index 501802e..2efed65 100644
--- a/components/optimization_guide/core/prediction_model_fetcher_unittest.cc
+++ b/components/optimization_guide/core/prediction_model_fetcher_unittest.cc
@@ -57,12 +57,11 @@
 
  protected:
   bool FetchModels(const std::vector<proto::ModelInfo> models_request_info,
-                   const std::vector<proto::FieldTrial>& active_field_trials,
                    proto::RequestContext request_context,
                    const std::string& locale) {
     bool status =
         prediction_model_fetcher_->FetchOptimizationGuideServiceModels(
-            models_request_info, active_field_trials, request_context, locale,
+            models_request_info, request_context, locale,
             base::BindOnce(&PredictionModelFetcherTest::OnModelsFetched,
                            base::Unretained(this)));
     RunUntilIdle();
@@ -110,7 +109,7 @@
   proto::ModelInfo model_info;
   model_info.set_optimization_target(
       proto::OptimizationTarget::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
-  EXPECT_TRUE(FetchModels({model_info}, /*active_field_trials=*/{},
+  EXPECT_TRUE(FetchModels({model_info},
                           proto::RequestContext::CONTEXT_BATCH_UPDATE_MODELS,
                           "en-US"));
   VerifyHasPendingFetchRequests();
@@ -127,7 +126,7 @@
   proto::ModelInfo model_info;
   model_info.set_optimization_target(
       proto::OptimizationTarget::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
-  EXPECT_TRUE(FetchModels({model_info}, /*active_field_trials=*/{},
+  EXPECT_TRUE(FetchModels({model_info},
                           proto::RequestContext::CONTEXT_BATCH_UPDATE_MODELS,
                           "en-US"));
   // Send a 404 to HintsFetcher.
@@ -159,7 +158,7 @@
   proto::ModelInfo model_info;
   model_info.set_optimization_target(
       proto::OptimizationTarget::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
-  EXPECT_TRUE(FetchModels({model_info}, /*active_field_trials=*/{},
+  EXPECT_TRUE(FetchModels({model_info},
                           proto::RequestContext::CONTEXT_BATCH_UPDATE_MODELS,
                           "en-US"));
   VerifyHasPendingFetchRequests();
@@ -170,9 +169,7 @@
 TEST_F(PredictionModelFetcherTest, EmptyModelInfo) {
   base::HistogramTester histogram_tester;
   std::string response_content;
-  proto::FieldTrial field_trial;
-  field_trial.set_name_hash(123);
-  EXPECT_FALSE(FetchModels(/*models_request_info=*/{}, {field_trial},
+  EXPECT_FALSE(FetchModels(/*models_request_info=*/{},
                            proto::RequestContext::CONTEXT_BATCH_UPDATE_MODELS,
                            "en-US"));
 
diff --git a/components/optimization_guide/proto/hints.proto b/components/optimization_guide/proto/hints.proto
index a17a95b..ffef327 100644
--- a/components/optimization_guide/proto/hints.proto
+++ b/components/optimization_guide/proto/hints.proto
@@ -40,6 +40,8 @@
 // Request to return a set of hints that guide what optimizations to perform
 // on those hosts.
 message GetHintsRequest {
+  reserved 6;
+
   // Information about the set of hosts to retrieve hints for.
   repeated HostInfo hosts = 1;
 
@@ -63,9 +65,6 @@
   // Context in which this request is made.
   optional RequestContext context = 3;
 
-  // The field trials that are currently active when this request is made.
-  repeated FieldTrial active_field_trials = 6;
-
   // The locale to associate with this request.
   //
   // It is the IETF language tag, defined in BCP 47. The region subtag is not
diff --git a/components/optimization_guide/proto/models.proto b/components/optimization_guide/proto/models.proto
index 666e710..4154047 100644
--- a/components/optimization_guide/proto/models.proto
+++ b/components/optimization_guide/proto/models.proto
@@ -145,7 +145,7 @@
 
 // Requests prediction models to be used for a set of optimization targets.
 message GetModelsRequest {
-  reserved 2;
+  reserved 2, 4;
 
   // Information about the requested models.
   repeated ModelInfo requested_models = 1;
@@ -155,8 +155,6 @@
   // CONTEXT_PAGE_NAVIGATION), then no model updates will be returned for the
   // requested models.
   optional RequestContext request_context = 3;
-  // The field trials that are currently active when this request is made.
-  repeated FieldTrial active_field_trials = 4;
   // The locale to associate with this request.
   //
   // It is the IETF language tag, defined in BCP 47. The region subtag is not
diff --git a/components/segmentation_platform/BUILD.gn b/components/segmentation_platform/BUILD.gn
index 1385204c..caa0073 100644
--- a/components/segmentation_platform/BUILD.gn
+++ b/components/segmentation_platform/BUILD.gn
@@ -9,6 +9,7 @@
 
   deps = [
     "//components/segmentation_platform/embedder:unit_tests",
+    "//components/segmentation_platform/embedder/default_model:unit_tests",
     "//components/segmentation_platform/internal:unit_tests",
     "//components/segmentation_platform/public:unit_tests",
   ]
diff --git a/components/segmentation_platform/components_unittests.filter b/components/segmentation_platform/components_unittests.filter
index 962a582..db2a3a76 100644
--- a/components/segmentation_platform/components_unittests.filter
+++ b/components/segmentation_platform/components_unittests.filter
@@ -5,14 +5,22 @@
 FailedUkmDatabaseTest.*
 FeatureAggregatorImplTest.*
 FeatureListQueryProcessorTest.*
+FeedUserModelTest.*
 HistogramSignalHandlerTest.*
 HistoryDelegateImplTest.*
+LowUserEngagementModelTest.*
 MetadataUtilsTest.*
 ModelExecutionManagerTest.*
 ModelExecutionSchedulerTest.*
 ModelExecutorTest.*
 OptimizationGuideSegmentationModelProviderTest.*
+PriceTrackingActionModelTest.*
 PriceTrackingInputDelegateTest.*
+QueryTilesModelTest.*
+SegmentInfoDatabaseTest.*
+SegmentResultProviderTest.*
+SegmentScoreProviderTest.*
+SegmentSelectorTest.*
 SegmentationModelExecutorTest.*
 SegmentationPlatformDummyUkmManagerTest.*
 SegmentationPlatformServiceImplEmptyConfigTest.*
@@ -20,10 +28,6 @@
 SegmentationPlatformServiceImplTest.*
 SegmentationResultPrefsTest.*
 SegmentationUkmHelperTest.*
-SegmentInfoDatabaseTest.*
-SegmentResultProviderTest.*
-SegmentScoreProviderTest.*
-SegmentSelectorTest.*
 ServiceProxyImplTest.*
 SignalDatabaseImplTest.*
 SignalFilterProcessorTest.*
@@ -34,8 +38,8 @@
 StatsTest.*
 TrainingDataCollectorImplTest.*
 UkmConfigTest.*
-UkmDatabaseBackendTest.*
 UkmDataManagerImplTest.*
+UkmDatabaseBackendTest.*
 UkmMetricsTableTest.*
 UkmObserverTest.*
 UkmUrlTableTest.*
diff --git a/components/segmentation_platform/embedder/default_model/BUILD.gn b/components/segmentation_platform/embedder/default_model/BUILD.gn
new file mode 100644
index 0000000..62e4599b
--- /dev/null
+++ b/components/segmentation_platform/embedder/default_model/BUILD.gn
@@ -0,0 +1,44 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("default_model") {
+  sources = [
+    "feed_user_segment.cc",
+    "feed_user_segment.h",
+    "low_user_engagement_model.cc",
+    "low_user_engagement_model.h",
+    "price_tracking_action_model.cc",
+    "price_tracking_action_model.h",
+    "query_tiles_model.cc",
+    "query_tiles_model.h",
+  ]
+  deps = [
+    "//base",
+    "//components/segmentation_platform/internal",
+    "//components/segmentation_platform/internal/proto",
+    "//components/segmentation_platform/public",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+
+  # IMPORTANT NOTE: When adding new tests, also remember to update the list of
+  # tests in //components/segmentation_platform/components_unittests.filter
+  sources = [
+    "feed_user_segment_unittest.cc",
+    "low_user_engagement_model_unittest.cc",
+    "price_tracking_action_model_unittest.cc",
+    "query_tiles_model_unittest.cc",
+  ]
+
+  deps = [
+    ":default_model",
+    "//base",
+    "//base/test:test_support",
+    "//components/segmentation_platform/internal",
+    "//components/segmentation_platform/internal/proto",
+    "//testing/gtest",
+  ]
+}
diff --git a/chrome/browser/segmentation_platform/default_model/feed_user_segment.cc b/components/segmentation_platform/embedder/default_model/feed_user_segment.cc
similarity index 98%
rename from chrome/browser/segmentation_platform/default_model/feed_user_segment.cc
rename to components/segmentation_platform/embedder/default_model/feed_user_segment.cc
index 2bb3866..0008f2f 100644
--- a/chrome/browser/segmentation_platform/default_model/feed_user_segment.cc
+++ b/components/segmentation_platform/embedder/default_model/feed_user_segment.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/segmentation_platform/default_model/feed_user_segment.h"
+#include "components/segmentation_platform/embedder/default_model/feed_user_segment.h"
 
 #include <array>
 
diff --git a/chrome/browser/segmentation_platform/default_model/feed_user_segment.h b/components/segmentation_platform/embedder/default_model/feed_user_segment.h
similarity index 88%
rename from chrome/browser/segmentation_platform/default_model/feed_user_segment.h
rename to components/segmentation_platform/embedder/default_model/feed_user_segment.h
index 0583ed6..c011938 100644
--- a/chrome/browser/segmentation_platform/default_model/feed_user_segment.h
+++ b/components/segmentation_platform/embedder/default_model/feed_user_segment.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_SEGMENTATION_PLATFORM_DEFAULT_MODEL_FEED_USER_SEGMENT_H_
-#define CHROME_BROWSER_SEGMENTATION_PLATFORM_DEFAULT_MODEL_FEED_USER_SEGMENT_H_
+#ifndef COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_DEFAULT_MODEL_FEED_USER_SEGMENT_H_
+#define COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_DEFAULT_MODEL_FEED_USER_SEGMENT_H_
 
 #include "components/segmentation_platform/public/model_provider.h"
 
@@ -62,4 +62,4 @@
 
 }  // namespace segmentation_platform
 
-#endif  // CHROME_BROWSER_SEGMENTATION_PLATFORM_DEFAULT_MODEL_FEED_USER_SEGMENT_H_
+#endif  // COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_DEFAULT_MODEL_FEED_USER_SEGMENT_H_
diff --git a/chrome/browser/segmentation_platform/default_model/feed_user_segment_unittest.cc b/components/segmentation_platform/embedder/default_model/feed_user_segment_unittest.cc
similarity index 97%
rename from chrome/browser/segmentation_platform/default_model/feed_user_segment_unittest.cc
rename to components/segmentation_platform/embedder/default_model/feed_user_segment_unittest.cc
index e2bb8a1..fba5304 100644
--- a/chrome/browser/segmentation_platform/default_model/feed_user_segment_unittest.cc
+++ b/components/segmentation_platform/embedder/default_model/feed_user_segment_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/segmentation_platform/default_model/feed_user_segment.h"
+#include "components/segmentation_platform/embedder/default_model/feed_user_segment.h"
 
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
diff --git a/chrome/browser/segmentation_platform/default_model/low_user_engagement_model.cc b/components/segmentation_platform/embedder/default_model/low_user_engagement_model.cc
similarity index 96%
rename from chrome/browser/segmentation_platform/default_model/low_user_engagement_model.cc
rename to components/segmentation_platform/embedder/default_model/low_user_engagement_model.cc
index 9722661e..7badd31 100644
--- a/chrome/browser/segmentation_platform/default_model/low_user_engagement_model.cc
+++ b/components/segmentation_platform/embedder/default_model/low_user_engagement_model.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/segmentation_platform/default_model/low_user_engagement_model.h"
+#include "components/segmentation_platform/embedder/default_model/low_user_engagement_model.h"
 
 #include <array>
 
diff --git a/chrome/browser/segmentation_platform/default_model/low_user_engagement_model.h b/components/segmentation_platform/embedder/default_model/low_user_engagement_model.h
similarity index 78%
rename from chrome/browser/segmentation_platform/default_model/low_user_engagement_model.h
rename to components/segmentation_platform/embedder/default_model/low_user_engagement_model.h
index 7641fcc..3517fb13 100644
--- a/chrome/browser/segmentation_platform/default_model/low_user_engagement_model.h
+++ b/components/segmentation_platform/embedder/default_model/low_user_engagement_model.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_SEGMENTATION_PLATFORM_DEFAULT_MODEL_LOW_USER_ENGAGEMENT_MODEL_H_
-#define CHROME_BROWSER_SEGMENTATION_PLATFORM_DEFAULT_MODEL_LOW_USER_ENGAGEMENT_MODEL_H_
+#ifndef COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_DEFAULT_MODEL_LOW_USER_ENGAGEMENT_MODEL_H_
+#define COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_DEFAULT_MODEL_LOW_USER_ENGAGEMENT_MODEL_H_
 
 #include "components/segmentation_platform/public/model_provider.h"
 
@@ -30,4 +30,4 @@
 
 }  // namespace segmentation_platform
 
-#endif  // CHROME_BROWSER_SEGMENTATION_PLATFORM_DEFAULT_MODEL_LOW_USER_ENGAGEMENT_MODEL_H_
+#endif  // COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_DEFAULT_MODEL_LOW_USER_ENGAGEMENT_MODEL_H_
diff --git a/chrome/browser/segmentation_platform/default_model/low_user_engagement_model_unittest.cc b/components/segmentation_platform/embedder/default_model/low_user_engagement_model_unittest.cc
similarity index 96%
rename from chrome/browser/segmentation_platform/default_model/low_user_engagement_model_unittest.cc
rename to components/segmentation_platform/embedder/default_model/low_user_engagement_model_unittest.cc
index 58df4e1..06e2a2e 100644
--- a/chrome/browser/segmentation_platform/default_model/low_user_engagement_model_unittest.cc
+++ b/components/segmentation_platform/embedder/default_model/low_user_engagement_model_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/segmentation_platform/default_model/low_user_engagement_model.h"
+#include "components/segmentation_platform/embedder/default_model/low_user_engagement_model.h"
 
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
diff --git a/chrome/browser/segmentation_platform/default_model/price_tracking_action_model.cc b/components/segmentation_platform/embedder/default_model/price_tracking_action_model.cc
similarity index 96%
rename from chrome/browser/segmentation_platform/default_model/price_tracking_action_model.cc
rename to components/segmentation_platform/embedder/default_model/price_tracking_action_model.cc
index 9dbf255..af18c1d 100644
--- a/chrome/browser/segmentation_platform/default_model/price_tracking_action_model.cc
+++ b/components/segmentation_platform/embedder/default_model/price_tracking_action_model.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/segmentation_platform/default_model/price_tracking_action_model.h"
+#include "components/segmentation_platform/embedder/default_model/price_tracking_action_model.h"
 
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "components/segmentation_platform/internal/metadata/metadata_writer.h"
diff --git a/chrome/browser/segmentation_platform/default_model/price_tracking_action_model.h b/components/segmentation_platform/embedder/default_model/price_tracking_action_model.h
similarity index 78%
rename from chrome/browser/segmentation_platform/default_model/price_tracking_action_model.h
rename to components/segmentation_platform/embedder/default_model/price_tracking_action_model.h
index 44b456f..68ce9e41 100644
--- a/chrome/browser/segmentation_platform/default_model/price_tracking_action_model.h
+++ b/components/segmentation_platform/embedder/default_model/price_tracking_action_model.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_SEGMENTATION_PLATFORM_DEFAULT_MODEL_PRICE_TRACKING_ACTION_MODEL_H_
-#define CHROME_BROWSER_SEGMENTATION_PLATFORM_DEFAULT_MODEL_PRICE_TRACKING_ACTION_MODEL_H_
+#ifndef COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_DEFAULT_MODEL_PRICE_TRACKING_ACTION_MODEL_H_
+#define COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_DEFAULT_MODEL_PRICE_TRACKING_ACTION_MODEL_H_
 
 #include "components/segmentation_platform/public/model_provider.h"
 
@@ -29,4 +29,4 @@
 
 }  // namespace segmentation_platform
 
-#endif  // CHROME_BROWSER_SEGMENTATION_PLATFORM_DEFAULT_MODEL_PRICE_TRACKING_ACTION_MODEL_H_
+#endif  // COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_DEFAULT_MODEL_PRICE_TRACKING_ACTION_MODEL_H_
diff --git a/chrome/browser/segmentation_platform/default_model/price_tracking_action_model_unittest.cc b/components/segmentation_platform/embedder/default_model/price_tracking_action_model_unittest.cc
similarity index 96%
rename from chrome/browser/segmentation_platform/default_model/price_tracking_action_model_unittest.cc
rename to components/segmentation_platform/embedder/default_model/price_tracking_action_model_unittest.cc
index cebf48a..8710f40 100644
--- a/chrome/browser/segmentation_platform/default_model/price_tracking_action_model_unittest.cc
+++ b/components/segmentation_platform/embedder/default_model/price_tracking_action_model_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/segmentation_platform/default_model/price_tracking_action_model.h"
+#include "components/segmentation_platform/embedder/default_model/price_tracking_action_model.h"
 
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
diff --git a/chrome/browser/segmentation_platform/default_model/query_tiles_model.cc b/components/segmentation_platform/embedder/default_model/query_tiles_model.cc
similarity index 97%
rename from chrome/browser/segmentation_platform/default_model/query_tiles_model.cc
rename to components/segmentation_platform/embedder/default_model/query_tiles_model.cc
index d2b41635..b8d09d72 100644
--- a/chrome/browser/segmentation_platform/default_model/query_tiles_model.cc
+++ b/components/segmentation_platform/embedder/default_model/query_tiles_model.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/segmentation_platform/default_model/query_tiles_model.h"
+#include "components/segmentation_platform/embedder/default_model/query_tiles_model.h"
 
 #include <array>
 
diff --git a/chrome/browser/segmentation_platform/default_model/query_tiles_model.h b/components/segmentation_platform/embedder/default_model/query_tiles_model.h
similarity index 78%
rename from chrome/browser/segmentation_platform/default_model/query_tiles_model.h
rename to components/segmentation_platform/embedder/default_model/query_tiles_model.h
index affa3bb..68b40c8 100644
--- a/chrome/browser/segmentation_platform/default_model/query_tiles_model.h
+++ b/components/segmentation_platform/embedder/default_model/query_tiles_model.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_SEGMENTATION_PLATFORM_DEFAULT_MODEL_QUERY_TILES_MODEL_H_
-#define CHROME_BROWSER_SEGMENTATION_PLATFORM_DEFAULT_MODEL_QUERY_TILES_MODEL_H_
+#ifndef COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_DEFAULT_MODEL_QUERY_TILES_MODEL_H_
+#define COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_DEFAULT_MODEL_QUERY_TILES_MODEL_H_
 
 #include "components/segmentation_platform/public/model_provider.h"
 
@@ -30,4 +30,4 @@
 
 }  // namespace segmentation_platform
 
-#endif  // CHROME_BROWSER_SEGMENTATION_PLATFORM_DEFAULT_MODEL_QUERY_TILES_MODEL_H_
+#endif  // COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_DEFAULT_MODEL_QUERY_TILES_MODEL_H_
diff --git a/chrome/browser/segmentation_platform/default_model/query_tiles_model_unittest.cc b/components/segmentation_platform/embedder/default_model/query_tiles_model_unittest.cc
similarity index 97%
rename from chrome/browser/segmentation_platform/default_model/query_tiles_model_unittest.cc
rename to components/segmentation_platform/embedder/default_model/query_tiles_model_unittest.cc
index 98f41aaa..7e58755 100644
--- a/chrome/browser/segmentation_platform/default_model/query_tiles_model_unittest.cc
+++ b/components/segmentation_platform/embedder/default_model/query_tiles_model_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/segmentation_platform/default_model/query_tiles_model.h"
+#include "components/segmentation_platform/embedder/default_model/query_tiles_model.h"
 
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
diff --git a/components/segmentation_platform/internal/BUILD.gn b/components/segmentation_platform/internal/BUILD.gn
index 61a07d8..55d0ad2 100644
--- a/components/segmentation_platform/internal/BUILD.gn
+++ b/components/segmentation_platform/internal/BUILD.gn
@@ -14,7 +14,7 @@
     "//chrome/browser",
     "//chrome/browser/segmentation_platform:*",
     "//chrome/test:*",
-    "//components/segmentation_platform/embedder:*",
+    "//components/segmentation_platform/embedder/*",
     "//ios/chrome/browser/segmentation_platform:*",
   ]
 
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index ab7b9f6..f3c0f220 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -1305,6 +1305,7 @@
   VISIT(active_tab_index);
   VISIT(show_as_app);
   VISIT_REP(tab_groups);
+  VISIT(first_non_pinned_tab_index);
 }
 
 VISIT_PROTO_FIELDS(
diff --git a/components/sync/protocol/workspace_desk_specifics.proto b/components/sync/protocol/workspace_desk_specifics.proto
index f7e7bea..040c4a7 100644
--- a/components/sync/protocol/workspace_desk_specifics.proto
+++ b/components/sync/protocol/workspace_desk_specifics.proto
@@ -136,6 +136,9 @@
 
     // Tab groups associated with this window.
     repeated TabGroup tab_groups = 4;
+
+    // The index of the first non-pinned tab.
+    optional int32 first_non_pinned_tab_index = 5;
   }
 
   // A Chrome App window.
diff --git a/components/sync/trusted_vault/BUILD.gn b/components/sync/trusted_vault/BUILD.gn
index 04c90f6e..bbea0ce 100644
--- a/components/sync/trusted_vault/BUILD.gn
+++ b/components/sync/trusted_vault/BUILD.gn
@@ -5,6 +5,8 @@
 if (!is_android) {
   static_library("trusted_vault") {
     sources = [
+      "degraded_recoverability_scheduler.cc",
+      "degraded_recoverability_scheduler.h",
       "download_keys_response_handler.cc",
       "download_keys_response_handler.h",
       "proto_string_bytes_conversion.cc",
diff --git a/components/sync/trusted_vault/degraded_recoverability_scheduler.cc b/components/sync/trusted_vault/degraded_recoverability_scheduler.cc
new file mode 100644
index 0000000..6e2ea12
--- /dev/null
+++ b/components/sync/trusted_vault/degraded_recoverability_scheduler.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/sync/trusted_vault/degraded_recoverability_scheduler.h"
+
+#include "base/location.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+
+namespace {
+constexpr base::TimeDelta kLongRefreshPeriod = base::Days(7);
+constexpr base::TimeDelta kShortRefreshPeriod = base::Hours(1);
+
+base::TimeDelta ComputeTimeUntilNextRefresh(
+    const base::TimeDelta& refresh_period,
+    const base::TimeTicks& last_refreshed_time) {
+  if (last_refreshed_time.is_null()) {
+    return base::TimeDelta();
+  }
+  const base::TimeDelta elapsed_time =
+      base::TimeTicks::Now() - last_refreshed_time;
+  return refresh_period - elapsed_time;
+}
+
+}  // namespace
+
+namespace syncer {
+
+DegradedRecoverabilityScheduler::DegradedRecoverabilityScheduler()
+    : current_refresh_period_(kLongRefreshPeriod) {
+  // TODO(crbug.com/1247990): read `last_refreshed_time_`, convert it to
+  // TimeTicks, and schedule next refresh.
+  NOTIMPLEMENTED();
+  Start();
+}
+
+void DegradedRecoverabilityScheduler::StartLongIntervalRefreshing() {
+  current_refresh_period_ = kLongRefreshPeriod;
+  Start();
+}
+
+void DegradedRecoverabilityScheduler::StartShortIntervalRefreshing() {
+  current_refresh_period_ = kShortRefreshPeriod;
+  Start();
+}
+
+void DegradedRecoverabilityScheduler::RefreshImmediately() {
+  // TODO(crbug.com/1247990): Currently if the timer is not running, then this
+  // means that Refresh() has just invoked. Probably this would be changed
+  // later, then we need to take care.
+  if (!next_refresh_timer_.IsRunning()) {
+    return;
+  }
+  next_refresh_timer_.FireNow();
+}
+
+void DegradedRecoverabilityScheduler::Start() {
+  next_refresh_timer_.Start(FROM_HERE,
+                            ComputeTimeUntilNextRefresh(current_refresh_period_,
+                                                        last_refreshed_time_),
+                            this, &DegradedRecoverabilityScheduler::Refresh);
+}
+
+void DegradedRecoverabilityScheduler::Refresh() {
+  // TODO(crbug.com/1247990): To be implemented, make sure the to schedule the
+  // next Refresh() after the current one is completed.
+  NOTIMPLEMENTED();
+  last_refreshed_time_ = base::TimeTicks::Now();
+  next_refresh_timer_.Start(FROM_HERE, current_refresh_period_, this,
+                            &DegradedRecoverabilityScheduler::Refresh);
+}
+
+}  // namespace syncer
diff --git a/components/sync/trusted_vault/degraded_recoverability_scheduler.h b/components/sync/trusted_vault/degraded_recoverability_scheduler.h
new file mode 100644
index 0000000..34e9891d
--- /dev/null
+++ b/components/sync/trusted_vault/degraded_recoverability_scheduler.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SYNC_TRUSTED_VAULT_DEGRADED_RECOVERABILITY_SCHEDULER_H_
+#define COMPONENTS_SYNC_TRUSTED_VAULT_DEGRADED_RECOVERABILITY_SCHEDULER_H_
+
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+
+namespace syncer {
+
+// Schedules refresh of degraded recoverability state based on the current
+// state, heuristics and last refresh time.
+class DegradedRecoverabilityScheduler {
+ public:
+  DegradedRecoverabilityScheduler();
+  DegradedRecoverabilityScheduler(const DegradedRecoverabilityScheduler&) =
+      delete;
+  DegradedRecoverabilityScheduler& operator=(
+      const DegradedRecoverabilityScheduler&) = delete;
+  ~DegradedRecoverabilityScheduler() = default;
+
+  void StartLongIntervalRefreshing();
+  void StartShortIntervalRefreshing();
+  void RefreshImmediately();
+
+ private:
+  void Start();
+  void Refresh();
+
+  // A "timer" takes care of invoking Refresh() in the future, once after a
+  // `current_refresh_period_` delay has elapsed.
+  base::OneShotTimer next_refresh_timer_;
+  base::TimeDelta current_refresh_period_;
+  // The last time Refresh has executed, it's initially null until the first
+  // Refresh() execution.
+  base::TimeTicks last_refreshed_time_;
+};
+
+}  // namespace syncer
+
+#endif  // COMPONENTS_SYNC_TRUSTED_VAULT_DEGRADED_RECOVERABILITY_SCHEDULER_H_
diff --git a/components/translate/core/browser/translate_pref_names.cc b/components/translate/core/browser/translate_pref_names.cc
index b3ee11e5..ae72e6c 100644
--- a/components/translate/core/browser/translate_pref_names.cc
+++ b/components/translate/core/browser/translate_pref_names.cc
@@ -9,9 +9,10 @@
 namespace translate {
 namespace prefs {
 
-// Boolean that is true when offering translate (i.e. the automatic translate
-// bubble) is enabled. Even when this is false, the user can force translate
-// from the right-click context menu unless translate is disabled by policy.
+// Boolean that is true when offering translate (i.e. the automatic Full Page
+// Translate bubble) is enabled. Even when this is false, the user can force
+// translate from the right-click context menu unless translate is disabled by
+// policy.
 const char kOfferTranslateEnabled[] = "translate.enabled";
 
 const char kPrefAlwaysTranslateList[] = "translate_allowlists";
diff --git a/components/translate/core/browser/translate_prefs.h b/components/translate/core/browser/translate_prefs.h
index 39d8fd6..86d8f16 100644
--- a/components/translate/core/browser/translate_prefs.h
+++ b/components/translate/core/browser/translate_prefs.h
@@ -150,11 +150,11 @@
   // the rest of the code.
   static std::string MapPreferenceName(const std::string& pref_name);
 
-  // Checks if the "offer translate" (i.e. automatic translate bubble) feature
-  // is enabled.
+  // Returns true if the "offer translate" pref is enabled (i.e. allowing for
+  // automatic Full Page Translate bubbles).
   bool IsOfferTranslateEnabled() const;
 
-  // Checks if translate is allowed by policy.
+  // Returns true if Translate is allowed by policy.
   bool IsTranslateAllowedByPolicy() const;
 
   // Sets the country that the application is run in. Determined by the
diff --git a/components/user_education/views/help_bubble_view_unittest.cc b/components/user_education/views/help_bubble_view_unittest.cc
index 8ed909a..562e90f5 100644
--- a/components/user_education/views/help_bubble_view_unittest.cc
+++ b/components/user_education/views/help_bubble_view_unittest.cc
@@ -72,7 +72,6 @@
   ~TestThemeProvider() override = default;
 
   gfx::ImageSkia* GetImageSkiaNamed(int id) const override { return nullptr; }
-  SkColor GetColor(int id) const override { return SK_ColorRED; }
   color_utils::HSL GetTint(int id) const override { return color_utils::HSL(); }
   int GetDisplayProperty(int id) const override { return 0; }
   bool ShouldUseNativeFrame() const override { return false; }
diff --git a/components/version_ui/resources/about_version.css b/components/version_ui/resources/about_version.css
index f4a8c01a..7ff50de 100644
--- a/components/version_ui/resources/about_version.css
+++ b/components/version_ui/resources/about_version.css
@@ -84,7 +84,8 @@
   vertical-align: bottom;
 }
 
-#copy-to-clipboard {
+#copy-to-clipboard,
+#copy-os-content-to-clipboard {
   -webkit-mask-image: url(chrome://resources/images/icon_copy_content.svg);
   -webkit-mask-size: cover;
   background: none;
diff --git a/components/version_ui/resources/about_version.html b/components/version_ui/resources/about_version.html
index 0e9ca97..8830e1f 100644
--- a/components/version_ui/resources/about_version.html
+++ b/components/version_ui/resources/about_version.html
@@ -87,14 +87,11 @@
             <span>$i18n{cl}</span>
           </td>
         </tr>
-<if expr="not chromeos_ash">
+<if expr="not chromeos_ash and not chromeos_lacros">
         <tr>
           <td class="label">$i18n{os_name}</td>
           <td class="version" id="os_type">
             <span>$i18n{os_type}</span>
-<if expr="chromeos_lacros">
-            (Ash <span>$i18n{ash_chrome_version}</span>)
-</if>
 <if expr="is_android">
             <span>$i18n{os_version}</span>
 </if>
@@ -138,6 +135,18 @@
           </td>
         </tr>
 </if>
+<if expr="chromeos_lacros">
+        <tr>
+          <td class="label">$i18n{os_name}</td>
+          <td class="version" id="os_type">
+            <span id="copy-os-content">
+              <span>$i18n{os_type}</span>
+              (Ash <span>$i18n{ash_chrome_version}</span>)
+            </span>
+            <button id="copy-os-content-to-clipboard" aria-label="$i18n{copy_label}"></button>
+          </td>
+        </tr>
+</if>
 <if expr="not is_ios">
         <tr><td class="label">JavaScript</td>
           <td class="version" id="js_engine">
diff --git a/components/version_ui/resources/about_version.js b/components/version_ui/resources/about_version.js
index 3e76d61..31a99b4 100644
--- a/components/version_ui/resources/about_version.js
+++ b/components/version_ui/resources/about_version.js
@@ -109,6 +109,12 @@
   navigator.clipboard.writeText($('copy-content').innerText);
 }
 
+// <if expr="chromeos_lacros">
+function copyOSContentToClipboard() {
+  navigator.clipboard.writeText($('copy-os-content').innerText);
+}
+// </if>
+
 /* All the work we do onload. */
 function onLoadWork() {
   // <if expr="chromeos_ash or is_win">
@@ -138,6 +144,11 @@
   }
 
   $('copy-to-clipboard').addEventListener('click', copyToClipboard);
+
+  // <if expr="chromeos_lacros">
+  $('copy-os-content-to-clipboard')
+      .addEventListener('click', copyOSContentToClipboard);
+  // </if>
 }
 
 document.addEventListener('DOMContentLoaded', onLoadWork);
diff --git a/components/viz/service/display/damage_frame_annotator.cc b/components/viz/service/display/damage_frame_annotator.cc
index f33796d..e951ea9 100644
--- a/components/viz/service/display/damage_frame_annotator.cc
+++ b/components/viz/service/display/damage_frame_annotator.cc
@@ -28,7 +28,7 @@
 
   annotations_.push_back(
       AnnotationData{gfx::Rect(damage_rect.size()), transform,
-                     Highlight{SkColorSetARGB(128, 255, 0, 0), 4}});
+                     Highlight{SkColor4f{1.0, 0, 0, 0.5}, 4}});
 
   AnnotateRootRenderPass(root_render_pass);
   annotations_.clear();
@@ -58,10 +58,8 @@
 
     DebugBorderDrawQuad* new_quad =
         static_cast<DebugBorderDrawQuad*>(*quad_iter);
-    // TODO(crbug.com/1308932) SkColor4f for annotation highlights
     new_quad->SetNew(new_sqs, annotation.rect, annotation.rect,
-                     SkColor4f::FromColor(annotation.highlight.color),
-                     annotation.highlight.width);
+                     annotation.highlight.color, annotation.highlight.width);
 
     ++quad_iter;
   }
diff --git a/components/viz/service/display/damage_frame_annotator.h b/components/viz/service/display/damage_frame_annotator.h
index 0fad6a14..232662e6 100644
--- a/components/viz/service/display/damage_frame_annotator.h
+++ b/components/viz/service/display/damage_frame_annotator.h
@@ -32,7 +32,7 @@
 
  private:
   struct Highlight {
-    SkColor color;
+    SkColor4f color;
     int width;
   };
 
diff --git a/components/zucchini/crc32.cc b/components/zucchini/crc32.cc
index 1c45dfe..48d1535 100644
--- a/components/zucchini/crc32.cc
+++ b/components/zucchini/crc32.cc
@@ -28,7 +28,7 @@
 }  // namespace
 
 // Minimalistic CRC-32 implementation for Zucchini usage. Adapted from LZMA SDK
-// (found at third_party/lzma_sdk/7zCrc.c), which is public domain.
+// (found at third_party/lzma_sdk/C/7zCrc.c), which is public domain.
 uint32_t CalculateCrc32(const uint8_t* first, const uint8_t* last) {
   DCHECK_GE(last, first);
 
diff --git a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
index 9f173ad7..b924b993 100644
--- a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
@@ -7010,8 +7010,8 @@
 // name update is blocked if kDisableFrameNameUpdateOnNonCurrentRenderFrameHost
 // is enabled
 //
-// TODO(https://crbug.com/1326944): Flaky on Mac.
-#if BUILDFLAG(IS_MAC)
+// TODO(https://crbug.com/1326944): Flaky on Mac and Android.
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID)
 #define MAYBE_BlockNameUpdateForPendingDelete \
   DISABLED_BlockNameUpdateForPendingDelete
 #else
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
index 5d1547c..acfea57 100644
--- a/content/browser/renderer_host/render_frame_host_manager.cc
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
@@ -1933,15 +1933,14 @@
   // BrowserContext.
   DCHECK_EQ(new_instance->GetBrowserContext(), browser_context);
 
-  // If |new_instance| is a new SiteInstance for a subframe that requires a
-  // dedicated process, set its process reuse policy so that such subframes are
-  // consolidated into existing processes for that site.
-  // TODO(crbug.com/1314749): With MPArch there may be multiple main frames and
-  // so IsMainFrame should not be used to identify subframes. Follow up to
-  // confirm correctness. Using IsOutermostMainFrame here will cause same-site
-  // fenced frames to share a process, even across tabs, aligning with Shadow
-  // DOM behavior. Determining correctness here will also involve resolving on
-  // the FF process model plan (see https://github.com/WICG/fenced-
+  // If |new_instance| is a new SiteInstance for a subframe or a fenced frame
+  // that require a dedicated process, set its process reuse policy so that such
+  // subframes and fenced frames are consolidated into existing processes for
+  // that site.
+  // TODO(crbug.com/1340662): The model described in fenced frames process
+  // isolation explainer is still in the design stage. Determining correctness
+  // here will also involve resolving on the FF process model plan (see
+  // https://github.com/WICG/fenced-
   // frame/blob/master/explainer/process_isolation.md).
   if (!frame_tree_node_->IsOutermostMainFrame() &&
       !new_instance_impl->HasProcess() &&
@@ -1954,8 +1953,7 @@
         GetContentClient()
             ->browser()
             ->ShouldEmbeddedFramesTryToReuseExistingProcess(
-                frame_tree_node_->frame_tree()
-                    ->GetMainFrame()
+                frame_tree_node_->GetParentOrOuterDocument()
                     ->GetOutermostMainFrame())) {
       new_instance_impl->set_process_reuse_policy(
           SiteInstanceImpl::ProcessReusePolicy::
diff --git a/content/browser/tracing/startup_tracing_browsertest.cc b/content/browser/tracing/startup_tracing_browsertest.cc
index 8fd46909..7b06d35 100644
--- a/content/browser/tracing/startup_tracing_browsertest.cc
+++ b/content/browser/tracing/startup_tracing_browsertest.cc
@@ -350,7 +350,13 @@
         testing::Values(OutputType::kJSON, OutputType::kProto),
         testing::Values(OutputLocation::kDirectoryWithDefaultBasename)));
 
-IN_PROC_BROWSER_TEST_P(EmergencyStopTracingTest, StopOnUIThread) {
+// Flaky; see https://crbug.com/1341341 .
+#if BUILDFLAG(IS_MAC)
+#define MAYBE_StopOnUIThread DISABLED_StopOnUIThread
+#else
+#define MAYBE_StopOnUIThread StopOnUIThread
+#endif
+IN_PROC_BROWSER_TEST_P(EmergencyStopTracingTest, MAYBE_StopOnUIThread) {
   EXPECT_TRUE(NavigateToURL(shell(), GetTestUrl("", "title1.html")));
 
   StartupTracingController::EmergencyStop();
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index f04ddcf..b1b6b18 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -397,6 +397,7 @@
     "url_loader_throttles.h",
     "usb_chooser.cc",
     "usb_chooser.h",
+    "usb_delegate.h",
     "video_capture_device_launcher.cc",
     "video_capture_device_launcher.h",
     "video_capture_service.h",
diff --git a/content/public/browser/usb_delegate.h b/content/public/browser/usb_delegate.h
new file mode 100644
index 0000000..9270307
--- /dev/null
+++ b/content/public/browser/usb_delegate.h
@@ -0,0 +1,101 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_BROWSER_USB_DELEGATE_H_
+#define CONTENT_PUBLIC_BROWSER_USB_DELEGATE_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/span.h"
+#include "base/observer_list_types.h"
+#include "content/common/content_export.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "services/device/public/mojom/usb_device.mojom-forward.h"
+#include "services/device/public/mojom/usb_enumeration_options.mojom-forward.h"
+#include "services/device/public/mojom/usb_manager.mojom-forward.h"
+#include "third_party/blink/public/mojom/usb/web_usb_service.mojom.h"
+
+namespace url {
+class Origin;
+}
+
+namespace content {
+
+class RenderFrameHost;
+class UsbChooser;
+
+// Interface provided by the content embedder to support the WebUSB API.
+class CONTENT_EXPORT UsbDelegate {
+ public:
+  class Observer : public base::CheckedObserver {
+   public:
+    virtual void OnDeviceAdded(const device::mojom::UsbDeviceInfo& device) = 0;
+    virtual void OnDeviceRemoved(
+        const device::mojom::UsbDeviceInfo& device) = 0;
+    virtual void OnDeviceManagerConnectionError() = 0;
+    virtual void OnPermissionRevoked(const url::Origin& origin) = 0;
+  };
+
+  virtual ~UsbDelegate() = default;
+
+  // Allows the embedder to modify the set of protected interface classes for
+  // the given frame.
+  virtual void AdjustProtectedInterfaceClasses(
+      RenderFrameHost& frame,
+      std::vector<uint8_t>& classes) = 0;
+
+  // Shows a chooser for the user to select a USB device.  |callback| will be
+  // run when the prompt is closed. Deleting the returned object will cancel the
+  // prompt. This method should not be called if CanRequestDevicePermission()
+  // below returned false.
+  virtual std::unique_ptr<UsbChooser> RunChooser(
+      RenderFrameHost& frame,
+      std::vector<device::mojom::UsbDeviceFilterPtr> filters,
+      blink::mojom::WebUsbService::GetPermissionCallback callback) = 0;
+
+  // Returns whether |frame| has permission to request access to a device.
+  virtual bool CanRequestDevicePermission(RenderFrameHost& frame) = 0;
+
+  virtual void RevokeDevicePermissionWebInitiated(
+      content::RenderFrameHost& frame,
+      const device::mojom::UsbDeviceInfo& device) = 0;
+
+  virtual const device::mojom::UsbDeviceInfo* GetDeviceInfo(
+      RenderFrameHost& frame,
+      const std::string& guid) = 0;
+
+  // Returns whether |frame| has permission to access |device|.
+  virtual bool HasDevicePermission(
+      RenderFrameHost& frame,
+      const device::mojom::UsbDeviceInfo& device) = 0;
+
+  // These two methods are expected to proxy to the UsbDeviceManager interface
+  // owned by the embedder.
+  //
+  // Content and the embedder must use the same connection so that the embedder
+  // can process connect/disconnect events for permissions management purposes
+  // before they are delivered to content. Otherwise race conditions are
+  // possible.
+  virtual void GetDevices(
+      RenderFrameHost& frame,
+      blink::mojom::WebUsbService::GetDevicesCallback callback) = 0;
+  virtual void GetDevice(
+      RenderFrameHost& frame,
+      const std::string& guid,
+      base::span<const uint8_t> blocked_interface_classes,
+      mojo::PendingReceiver<device::mojom::UsbDevice> device_receiver,
+      mojo::PendingRemote<device::mojom::UsbDeviceClient> device_client) = 0;
+
+  // Functions to manage the set of Observer instances registered to this
+  // object.
+  virtual void AddObserver(RenderFrameHost& frame, Observer* observer) = 0;
+  virtual void RemoveObserver(Observer* observer) = 0;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_USB_DELEGATE_H_
diff --git a/content/renderer/pepper/pepper_video_decoder_host.cc b/content/renderer/pepper/pepper_video_decoder_host.cc
index 09fc9ab..6a5aec27 100644
--- a/content/renderer/pepper/pepper_video_decoder_host.cc
+++ b/content/renderer/pepper/pepper_video_decoder_host.cc
@@ -409,12 +409,6 @@
   CHECK(it->second == PictureBufferState::ASSIGNED);
   it->second = PictureBufferState::IN_USE;
 
-  if (software_fallback_used_) {
-    media::ReportPepperVideoDecoderOutputPictureCountSW(coded_size_.height());
-  } else {
-    media::ReportPepperVideoDecoderOutputPictureCountHW(coded_size_.height());
-  }
-
   // Don't bother validating the visible rect, since the plugin process is less
   // trusted than the gpu process.
   PP_Rect visible_rect = PP_FromGfxRect(picture.visible_rect());
diff --git a/content/renderer/pepper/ppb_video_decoder_impl.cc b/content/renderer/pepper/ppb_video_decoder_impl.cc
index fe4b69c..68bdeca 100644
--- a/content/renderer/pepper/ppb_video_decoder_impl.cc
+++ b/content/renderer/pepper/ppb_video_decoder_impl.cc
@@ -269,8 +269,6 @@
   if (!GetPPP())
     return;
 
-  media::ReportPepperVideoDecoderOutputPictureCountHW(coded_size_.height());
-
   PP_Picture_Dev output;
   output.picture_buffer_id = picture.picture_buffer_id();
   output.bitstream_buffer_id = picture.bitstream_buffer_id();
diff --git a/content/test/data/accessibility/aria/aria-button-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-button-expected-uia-win.txt
index 8731c43..9247886 100644
--- a/content/test/data/accessibility/aria/aria-button-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-button-expected-uia-win.txt
@@ -1,4 +1,4 @@
-Document
+Document Value.IsReadOnly=true
 ++Button Name='Button1'
 ++Button Name='Button2' Toggle.ToggleState='On'
 ++Button Name='Button3' Toggle.ToggleState='Off'
@@ -6,13 +6,13 @@
 ++Button Name='Button5'
 ++Button Name='Complex button '
 ++++Text Name='Complex button ' IsControlElement=false
-++++Edit
+++++Edit Value.IsReadOnly=false
 ++Button Name='Complex toggle button ' Toggle.ToggleState='On'
 ++++Text Name='Complex toggle button '
-++++Edit
+++++Edit Value.IsReadOnly=false
 ++Button Name='Complex pop up button ' ExpandCollapse.ExpandCollapseState='Collapsed'
 ++++Text Name='Complex pop up button '
-++++Edit
+++++Edit Value.IsReadOnly=false
 ++Button Name='Example haspopup' ExpandCollapse.ExpandCollapseState='Collapsed'
 ++Button Name='Example haspopup' ExpandCollapse.ExpandCollapseState='Collapsed'
 ++Button Name='Example haspopup' ExpandCollapse.ExpandCollapseState='LeafNode'
diff --git a/content/test/data/accessibility/aria/aria-button.html b/content/test/data/accessibility/aria/aria-button.html
index d1623996..626e9418 100644
--- a/content/test/data/accessibility/aria/aria-button.html
+++ b/content/test/data/accessibility/aria/aria-button.html
@@ -6,6 +6,7 @@
 @WIN-ALLOW:ia2_hypertext=*
 @WIN-ALLOW:PRESSED
 @WIN-ALLOW:xml-roles:*
+@UIA-WIN-ALLOW:Value.IsReadOnly*
 @AURALINUX-ALLOW:haspopup*
 @AURALINUX-ALLOW:pressed
 @AURALINUX-ALLOW:xml-roles:*
diff --git a/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
index 4ebf136..61758e3 100644
--- a/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
@@ -173,9 +173,12 @@
 # Failing on android N5
 crbug.com/1340755 [ android android-nexus-5 no-passthrough ] ContextLost_WebGL2UnpackImageHeight [ Failure ]
 
-# Flaking on Linux FYI Release (AMD RX 5500 XT)
+# Flaking on Linux FYI Release
 crbug.com/1340081 [ linux release amd-0x7340 ] ContextLost_WebGLContextLostFromSelectElement [ RetryOnFailure ]
-
+crbug.com/1340081 [ linux release amd-0x7340 ] ContextLost_WebGLUnblockedAfterUserInitiatedReload [ RetryOnFailure ]
+crbug.com/1340081 [ linux release intel ] ContextLost_WebGLContextLostFromSelectElement [ RetryOnFailure ]
+crbug.com/1340081 [ linux release intel ] ContextLost_WebGLUnblockedAfterUserInitiatedReload [ RetryOnFailure ]
+crbug.com/1340081 [ linux release intel-0x5912 ] ContextLost_MacWebGLMultisamplingHighPowerSwitchDoesNotCrash [ RetryOnFailure ]
 
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
diff --git a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
index 50e2c36b..c5a5847c 100644
--- a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
@@ -129,9 +129,10 @@
 
 crbug.com/1321145 [ fuchsia fuchsia-board-qemu-x64 ] * [ Failure ]
 
-# Flaking on Linux FYI Release (AMD RX 5500 XT)
+# Flaking on Linux FYI Release
 crbug.com/1340081 [ linux release amd-0x7340 ] GpuProcess_canvas2d [ RetryOnFailure ]
 crbug.com/1340081 [ linux release amd-0x7340 ] GpuProcess_css3d [ RetryOnFailure ]
+crbug.com/1340081 [ linux release intel-0x5912 ] GpuProcess_canvas2d [ RetryOnFailure ]
 
 
 
diff --git a/content/test/gpu/gpu_tests/test_expectations/hardware_accelerated_feature_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/hardware_accelerated_feature_expectations.txt
index e01fee0..14376ad 100644
--- a/content/test/gpu/gpu_tests/test_expectations/hardware_accelerated_feature_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/hardware_accelerated_feature_expectations.txt
@@ -77,6 +77,9 @@
 ###################
 # Non-"Skip" expectations go here to suppress regular flakes/failures.
 
+# Flakes on Linux Release
+crbug.com/1340081 [ linux release intel-0x5912 ] HardwareAcceleratedFeature_canvas_accelerated [ RetryOnFailure ]
+
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
 #######################################################################
diff --git a/content/test/gpu/gpu_tests/test_expectations/info_collection_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/info_collection_expectations.txt
index 6aacf724..6180adb 100644
--- a/content/test/gpu/gpu_tests/test_expectations/info_collection_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/info_collection_expectations.txt
@@ -86,6 +86,9 @@
 # InfoCollection_basic is flaky on Fuchsia.
 crbug.com/1154597 [ fuchsia ] InfoCollection_basic [ RetryOnFailure ]
 
+# Flakes on Linux Release
+crbug.com/1340081 [ linux release intel-0x5912 ] InfoCollection_asan_info_surfaced [ RetryOnFailure ]
+
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
 #######################################################################
diff --git a/content/test/gpu/gpu_tests/test_expectations/maps_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/maps_expectations.txt
index 3cf8b9bf..93e3979 100644
--- a/content/test/gpu/gpu_tests/test_expectations/maps_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/maps_expectations.txt
@@ -79,6 +79,7 @@
 
 # Flakes on Linux Release (AMD RX 5500 XT)
 crbug.com/1340081 [ linux release amd-0x7340 ] Maps_maps [ Failure ]
+crbug.com/1340081 [ linux release intel ] Maps_maps [ RetryOnFailure ]
 
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
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 c04a479..cbc832bf 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -424,6 +424,9 @@
 crbug.com/982292 [ mac release intel-0x3e9b ] Pixel_BackgroundImage [ RetryOnFailure ]
 crbug.com/982292 [ mac release intel-0x3e9b ] Pixel_Video_MP4_FourColors_Rot_270 [ RetryOnFailure ]
 
+# Flaking on Linux FYI Release (AMD RX 5500 XT)
+crbug.com/1340081 [ linux release amd-0x7340 ] Pixel_ScissorTestWithPreserveDrawingBuffer [ RetryOnFailure ]
+
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
 #######################################################################
diff --git a/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
index 6ca7f5b3..d145ced 100644
--- a/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/trace_test_expectations.txt
@@ -187,6 +187,11 @@
 crbug.com/1278028 [ android android-nexus-5x ] TraceTest_Video_VP9 [ RetryOnFailure ]
 crbug.com/1278028 [ android android-nexus-5x ] TraceTest_Video_VP9_DXVA [ RetryOnFailure ]
 
+# Flaking on Linux FYI Release (AMD RX 5500 XT)
+crbug.com/1340081 [ linux release amd-0x7340 ] TraceTest_ReflectedDiv [ RetryOnFailure ]
+crbug.com/1340081 [ linux release amd-0x7340 ] TraceTest_WebGLGreenTriangle_NoAA_NoAlpha [ RetryOnFailure ]
+
+
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
 #######################################################################
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index 3bee7f2..fd62715 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -214,7 +214,6 @@
 crbug.com/1131224 conformance2/rendering/framebuffer-mismatched-attachment-targets.html [ Failure ]
 crbug.com/1108086 [ no-passthrough ] conformance2/renderbuffers/framebuffer-object-attachment.html [ Failure ]
 crbug.com/angleproject/4807 [ win angle-d3d11 passthrough ] conformance2/glsl3/switch-case.html [ Failure ]
-crbug.com/angleproject/5038 conformance/extensions/ext-color-buffer-half-float.html [ Failure ]
 crbug.com/1276153 conformance/context/context-attributes-alpha-depth-stencil-antialias.html [ Failure ]
 crbug.com/1206763 [ win intel ] conformance/rendering/clear-default-framebuffer-with-scissor-test.html [ Failure ]
 
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index da91c5c0..872f15a 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -306,7 +306,6 @@
 crbug.com/angleproject/5499 [ no-passthrough ] conformance/glsl/misc/shaders-with-name-conflicts.html [ Failure ]
 
 # Skipping new tests
-crbug.com/angleproject/5038 conformance/extensions/ext-color-buffer-half-float.html [ Failure ]
 crbug.com/1276153 conformance/context/context-attributes-alpha-depth-stencil-antialias.html [ Failure ]
 crbug.com/1206763 [ win intel ] conformance/rendering/clear-default-framebuffer-with-scissor-test.html [ Failure ]
 
@@ -432,7 +431,8 @@
 crbug.com/1152602 [ win amd-0x7340 angle-d3d11 ] conformance/canvas/drawingbuffer-static-canvas-test.html [ RetryOnFailure ]
 
 # Win / D3D9 failures
-# Skipping these two tests because they're causing assertion failures.
+# Won't investigate this failure - D3D9 backend's effectively obsolete.
+crbug.com/angleproject/5038 [ win angle-d3d9 ] conformance/extensions/ext-color-buffer-half-float.html [ Failure ]
 
 # The functions test have been persistently flaky on D3D9
 crbug.com/956134 [ win angle-d3d9 ] conformance/extensions/webgl-depth-texture.html [ Failure ]
@@ -710,6 +710,8 @@
 crbug.com/1232446 [ chromeos chromeos-board-amd64-generic ] conformance/rendering/gl-scissor-test.html [ Failure ]
 crbug.com/1271227 [ chromeos chromeos-board-amd64-generic ] conformance/attribs/gl-bindAttribLocation-aliasing.html [ RetryOnFailure ]
 
+# Failures on validating command decoder only; won't fix.
+crbug.com/angleproject/5038 [ chromeos no-passthrough ] conformance/extensions/ext-color-buffer-half-float.html [ Failure ]
 
 ###########################
 # Lacros/Wayland failures #
@@ -730,6 +732,10 @@
 crbug.com/1099959 [ swiftshader-gl no-passthrough ] WebglExtension_EXT_sRGB [ Failure ]
 crbug.com/1099959 [ swiftshader-gl no-passthrough ] WebglExtension_EXT_texture_compression_rgtc [ Failure ]
 
+# Flaky tests on Android Nexus 5
+crbug.com/1341423 [ android android-nexus-5 no-passthrough ] conformance/rendering/draw-webgl-to-canvas-2d-repeatedly.html [ RetryOnFailure ]
+crbug.com/1341423 [ android android-nexus-5 no-passthrough ] conformance/rendering/color-mask-preserved-during-implicit-clears.html [ RetryOnFailure ]
+
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
 #######################################################################
diff --git a/courgette/crc.cc b/courgette/crc.cc
index 2b86b68..2ae2bd9 100644
--- a/courgette/crc.cc
+++ b/courgette/crc.cc
@@ -11,7 +11,7 @@
 #  include "zlib.h"
 #else
 extern "C" {
-#  include "third_party/lzma_sdk/7zCrc.h"
+#include "third_party/lzma_sdk/C/7zCrc.h"
 }
 #endif
 
diff --git a/docs/webui_build_configuration.md b/docs/webui_build_configuration.md
index 5ab79cbf..fd1dc3f 100644
--- a/docs/webui_build_configuration.md
+++ b/docs/webui_build_configuration.md
@@ -46,11 +46,10 @@
           |in_folder|.
 template: html_to_wrapper only. Defaults to "polymer", set to "native" if using
           the rule to wrap the HTML of a non-Polymer Web Component.
-in_folder: html_to_wrapper only. Specifies the input folder where files are
-           located. If not specified, the current directory (of the BUILD.gn
-           file) is used.
-out_folder: html_to_wrapper only. Specifies the location to write the wrapped
-            files. If not specified, |target_gen_dir| is used.
+in_folder: Specifies the input folder where files are located. If not specified,
+           the current directory (of the BUILD.gn file) is used.
+out_folder: Specifies the location to write the wrapped files. If not specified,
+            |target_gen_dir| is used.
 minify: html_to_wrapper only. Whether to minify HTML/CSS with
         third_party/node/node_modules/html-minifier. Defaults to false.
 ```
diff --git a/extensions/browser/api/BUILD.gn b/extensions/browser/api/BUILD.gn
index 90632c2..f5d4b57 100644
--- a/extensions/browser/api/BUILD.gn
+++ b/extensions/browser/api/BUILD.gn
@@ -134,6 +134,7 @@
     "//extensions/browser/api/metrics_private",
     "//extensions/browser/api/mime_handler_private",
     "//extensions/browser/api/networking_private",
+    "//extensions/browser/api/offscreen",
     "//extensions/browser/api/power",
     "//extensions/browser/api/printer_provider",
     "//extensions/browser/api/runtime",
diff --git a/extensions/browser/api/offscreen/BUILD.gn b/extensions/browser/api/offscreen/BUILD.gn
new file mode 100644
index 0000000..88d7f57
--- /dev/null
+++ b/extensions/browser/api/offscreen/BUILD.gn
@@ -0,0 +1,23 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//extensions/buildflags/buildflags.gni")
+
+assert(enable_extensions,
+       "Cannot depend on extensions because enable_extensions=false.")
+
+source_set("offscreen") {
+  sources = [
+    "offscreen_document_manager.cc",
+    "offscreen_document_manager.h",
+  ]
+
+  deps = [
+    "//base",
+    "//components/keyed_service/content",
+    "//extensions/common",
+  ]
+
+  public_deps = [ "//extensions/browser:browser_sources" ]
+}
diff --git a/extensions/browser/api/offscreen/offscreen_document_manager.cc b/extensions/browser/api/offscreen/offscreen_document_manager.cc
new file mode 100644
index 0000000..192e5b57
--- /dev/null
+++ b/extensions/browser/api/offscreen/offscreen_document_manager.cc
@@ -0,0 +1,142 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/offscreen/offscreen_document_manager.h"
+
+#include "base/feature_list.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "extensions/browser/extension_registry_factory.h"
+#include "extensions/browser/offscreen_document_host.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/process_manager_factory.h"
+#include "extensions/common/extension_features.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace extensions {
+
+namespace {
+
+class OffscreenDocumentManagerFactory
+    : public BrowserContextKeyedServiceFactory {
+ public:
+  OffscreenDocumentManagerFactory();
+  OffscreenDocumentManagerFactory(const OffscreenDocumentManagerFactory&) =
+      delete;
+  OffscreenDocumentManagerFactory& operator=(
+      const OffscreenDocumentManagerFactory&) = delete;
+  ~OffscreenDocumentManagerFactory() override = default;
+
+  OffscreenDocumentManager* GetForBrowserContext(
+      content::BrowserContext* context);
+
+ private:
+  // BrowserContextKeyedServiceFactory:
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+};
+
+OffscreenDocumentManagerFactory::OffscreenDocumentManagerFactory()
+    : BrowserContextKeyedServiceFactory(
+          "OffscreenDocumentManager",
+          BrowserContextDependencyManager::GetInstance()) {
+  DependsOn(ExtensionRegistryFactory::GetInstance());
+  DependsOn(ProcessManagerFactory::GetInstance());
+}
+
+OffscreenDocumentManager* OffscreenDocumentManagerFactory::GetForBrowserContext(
+    content::BrowserContext* browser_context) {
+  return static_cast<OffscreenDocumentManager*>(
+      GetServiceForBrowserContext(browser_context, /*create=*/true));
+}
+
+content::BrowserContext*
+OffscreenDocumentManagerFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  // Use the `context` passed in; this service has separate instances in
+  // on-the-record and incognito.
+  return context;
+}
+
+KeyedService* OffscreenDocumentManagerFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  return new OffscreenDocumentManager(context);
+}
+
+}  // namespace
+
+// OffscreenDocumentManager::OffscreenDocumentData:
+OffscreenDocumentManager::OffscreenDocumentData::OffscreenDocumentData() =
+    default;
+OffscreenDocumentManager::OffscreenDocumentData::~OffscreenDocumentData() =
+    default;
+OffscreenDocumentManager::OffscreenDocumentData::OffscreenDocumentData(
+    OffscreenDocumentData&&) = default;
+
+// OffscreenDocumentManager:
+OffscreenDocumentManager::OffscreenDocumentManager(
+    content::BrowserContext* browser_context)
+    : browser_context_(browser_context),
+      process_manager_(ProcessManager::Get(browser_context_)) {
+  registry_observation_.Observe(ExtensionRegistry::Get(browser_context_));
+}
+
+OffscreenDocumentManager::~OffscreenDocumentManager() = default;
+
+// static
+OffscreenDocumentManager* OffscreenDocumentManager::Get(
+    content::BrowserContext* browser_context) {
+  return static_cast<OffscreenDocumentManagerFactory*>(GetFactory())
+      ->GetForBrowserContext(browser_context);
+}
+
+// static
+BrowserContextKeyedServiceFactory* OffscreenDocumentManager::GetFactory() {
+  static base::NoDestructor<OffscreenDocumentManagerFactory> g_factory;
+  return g_factory.get();
+}
+
+OffscreenDocumentHost* OffscreenDocumentManager::CreateOffscreenDocument(
+    const Extension& extension,
+    const GURL& url) {
+  DCHECK(base::FeatureList::IsEnabled(
+      extensions_features::kExtensionsOffscreenDocuments));
+  DCHECK_EQ(url::Origin::Create(url), extension.origin());
+  // Currently only a single offscreen document is supported per extension.
+  DCHECK_EQ(nullptr, GetOffscreenDocumentForExtension(extension));
+  DCHECK(!base::Contains(offscreen_documents_, extension.id()));
+
+  OffscreenDocumentData& data = offscreen_documents_[extension.id()];
+
+  scoped_refptr<content::SiteInstance> site_instance =
+      process_manager_->GetSiteInstanceForURL(url);
+  data.host = std::make_unique<OffscreenDocumentHost>(extension,
+                                                      site_instance.get(), url);
+  OffscreenDocumentHost* host = data.host.get();
+  host->CreateRendererSoon();
+
+  return host;
+}
+
+OffscreenDocumentHost*
+OffscreenDocumentManager::GetOffscreenDocumentForExtension(
+    const Extension& extension) {
+  auto iter = offscreen_documents_.find(extension.id());
+  if (iter == offscreen_documents_.end())
+    return nullptr;
+  return iter->second.host.get();
+}
+
+void OffscreenDocumentManager::OnExtensionUnloaded(
+    content::BrowserContext* browser_context,
+    const Extension* extension,
+    UnloadedExtensionReason reason) {
+  // Close any offscreen document associated with the unloaded extension.
+  offscreen_documents_.erase(extension->id());
+}
+
+}  // namespace extensions
diff --git a/extensions/browser/api/offscreen/offscreen_document_manager.h b/extensions/browser/api/offscreen/offscreen_document_manager.h
new file mode 100644
index 0000000..8e68d1b
--- /dev/null
+++ b/extensions/browser/api/offscreen/offscreen_document_manager.h
@@ -0,0 +1,91 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_OFFSCREEN_OFFSCREEN_DOCUMENT_MANAGER_H_
+#define EXTENSIONS_BROWSER_API_OFFSCREEN_OFFSCREEN_DOCUMENT_MANAGER_H_
+
+#include <map>
+#include <memory>
+
+#include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/extension_id.h"
+
+class BrowserContextKeyedServiceFactory;
+class GURL;
+
+namespace extensions {
+class Extension;
+class ProcessManager;
+class OffscreenDocumentHost;
+
+// The OffscreenDocumentManager is responsible for managing offscreen documents
+// created by extensions through the `offscreen` API.
+class OffscreenDocumentManager : public KeyedService,
+                                 public ExtensionRegistryObserver {
+ public:
+  OffscreenDocumentManager(content::BrowserContext* browser_context);
+
+  OffscreenDocumentManager(const OffscreenDocumentManager&) = delete;
+  OffscreenDocumentManager& operator=(const OffscreenDocumentManager&) = delete;
+
+  ~OffscreenDocumentManager() override;
+
+  // Returns the OffscreenDocumentManager for the given `browser_context`.
+  // Note: This class has a separate instance in incognito.
+  static OffscreenDocumentManager* Get(
+      content::BrowserContext* browser_context);
+
+  // Returns the KeyedServiceFactory for the OffscreenDocumentManager.
+  static BrowserContextKeyedServiceFactory* GetFactory();
+
+  // Creates and returns an offscreen document for the given `extension` and
+  // `url`.
+  OffscreenDocumentHost* CreateOffscreenDocument(const Extension& extension,
+                                                 const GURL& url);
+
+  // Returns the current offscreen document for the given `extension`, if one
+  // exists.
+  OffscreenDocumentHost* GetOffscreenDocumentForExtension(
+      const Extension& extension);
+
+ private:
+  struct OffscreenDocumentData {
+    // Appease Chromium clang plugin with out-of-line ctors/dtors.
+    OffscreenDocumentData();
+    OffscreenDocumentData(OffscreenDocumentData&&);
+    ~OffscreenDocumentData();
+
+    std::unique_ptr<OffscreenDocumentHost> host;
+
+    // TODO(https://crbug.com/1339382): This will need more fields to include
+    // attributes like the associated reason and justification for the
+    // document.
+  };
+
+  // ExtensionRegistry:
+  void OnExtensionUnloaded(content::BrowserContext* browser_context,
+                           const Extension* extension,
+                           UnloadedExtensionReason reason) override;
+
+  // The collection of offscreen documents, mapped to extension ID.
+  std::map<ExtensionId, OffscreenDocumentData> offscreen_documents_;
+
+  // The associated browser context.
+  raw_ptr<content::BrowserContext> browser_context_;
+
+  // The process manager for the `browser_context_`.
+  raw_ptr<ProcessManager> process_manager_;
+
+  // Observe ExtensionRegistry for extensions being unloaded.
+  base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
+      registry_observation_{this};
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_BROWSER_API_OFFSCREEN_OFFSCREEN_DOCUMENT_MANAGER_H_
diff --git a/extensions/browser/browser_context_keyed_service_factories.cc b/extensions/browser/browser_context_keyed_service_factories.cc
index 143c1a3..9fe5c75 100644
--- a/extensions/browser/browser_context_keyed_service_factories.cc
+++ b/extensions/browser/browser_context_keyed_service_factories.cc
@@ -19,6 +19,7 @@
 #include "extensions/browser/api/idle/idle_manager_factory.h"
 #include "extensions/browser/api/management/management_api.h"
 #include "extensions/browser/api/networking_private/networking_private_event_router_factory.h"
+#include "extensions/browser/api/offscreen/offscreen_document_manager.h"
 #include "extensions/browser/api/power/power_api.h"
 #include "extensions/browser/api/runtime/runtime_api.h"
 #include "extensions/browser/api/serial/serial_connection.h"
@@ -91,6 +92,7 @@
     BUILDFLAG(IS_MAC)
   NetworkingPrivateEventRouterFactory::GetInstance();
 #endif
+  OffscreenDocumentManager::GetFactory();
   PowerAPI::GetFactoryInstance();
   ProcessManagerFactory::GetInstance();
   RendererStartupHelperFactory::GetInstance();
diff --git a/gpu/command_buffer/service/service_font_manager.cc b/gpu/command_buffer/service/service_font_manager.cc
index 6f8e6ec..de46dc50 100644
--- a/gpu/command_buffer/service/service_font_manager.cc
+++ b/gpu/command_buffer/service/service_font_manager.cc
@@ -103,6 +103,11 @@
     return font_manager_->DeleteHandle(handle_id);
   }
 
+  void assertHandleValid(SkDiscardableHandleId handle_id) override {
+    CHECK(font_manager_);
+    font_manager_->AssertHandle(handle_id);
+  }
+
   void notifyCacheMiss(SkStrikeClient::CacheMissType type,
                        int fontSize) override {
     UMA_HISTOGRAM_ENUMERATION("GPU.OopRaster.GlyphCacheMiss", type,
@@ -255,6 +260,26 @@
   return true;
 }
 
+void ServiceFontManager::AssertHandle(SkDiscardableHandleId handle_id) {
+  base::AutoLock hold(lock_);
+  auto it = discardable_handle_map_.find(handle_id);
+  CHECK(it != discardable_handle_map_.end());
+
+  static crash_reporter::CrashKeyString<2> crash_key_destroyed(
+      "font_manager::destroyed");
+  crash_reporter::ScopedCrashKeyString auto_clear_destroyed(
+      &crash_key_destroyed, destroyed_ ? "1" : "0");
+  static crash_reporter::CrashKeyString<8> crash_key_ref_count(
+      "font_manager::Handle::ref_count");
+  crash_reporter::ScopedCrashKeyString auto_clear_ref_count(
+      &crash_key_ref_count, base::StringPrintf("%d", it->second.ref_count()));
+  if (destroyed_) {
+    CHECK(it->second.ref_count() > 0);
+  } else {
+    CHECK(it->second.IsLocked());
+  }
+}
+
 bool ServiceFontManager::DeleteHandle(SkDiscardableHandleId handle_id) {
   base::AutoLock hold(lock_);
   // If this method returns true, the strike associated with the handle will be
diff --git a/gpu/command_buffer/service/service_font_manager.h b/gpu/command_buffer/service/service_font_manager.h
index 400e561..abfc472 100644
--- a/gpu/command_buffer/service/service_font_manager.h
+++ b/gpu/command_buffer/service/service_font_manager.h
@@ -49,6 +49,7 @@
   bool AddHandle(SkDiscardableHandleId handle_id,
                  ServiceDiscardableHandle handle);
   bool DeleteHandle(SkDiscardableHandleId handle_id);
+  void AssertHandle(SkDiscardableHandleId handle_id);
 
   base::Lock lock_;
 
@@ -67,6 +68,7 @@
     void Lock() { ++ref_count_; }
     bool Delete() { return handle_.Delete(); }
     int ref_count() const { return ref_count_; }
+    bool IsLocked() const { return handle_.IsLockedForTesting(); }
 
    private:
     ServiceDiscardableHandle handle_;
diff --git a/ios/chrome/browser/optimization_guide/prediction_manager_browsertest.mm b/ios/chrome/browser/optimization_guide/prediction_manager_browsertest.mm
index a35adf1..8202673 100644
--- a/ios/chrome/browser/optimization_guide/prediction_manager_browsertest.mm
+++ b/ios/chrome/browser/optimization_guide/prediction_manager_browsertest.mm
@@ -172,11 +172,6 @@
             absl::nullopt, model_file_observer);
   }
 
-  void SetExpectedFieldTrialNames(
-      const base::flat_set<uint32_t>& expected_field_trial_name_hashes) {
-    expected_field_trial_name_hashes_ = expected_field_trial_name_hashes;
-  }
-
   void SetComponentUpdatesEnabled(bool enabled) {
     GetApplicationContext()->GetLocalState()->SetBoolean(
         ::prefs::kComponentUpdatesEnabled, enabled);
@@ -225,18 +220,6 @@
 
     optimization_guide::proto::GetModelsRequest models_request;
     EXPECT_TRUE(models_request.ParseFromString(request.content));
-    // Make sure we actually filter field trials appropriately.
-    EXPECT_EQ(expected_field_trial_name_hashes_.size(),
-              static_cast<size_t>(models_request.active_field_trials_size()));
-    base::flat_set<uint32_t> seen_field_trial_name_hashes;
-    for (const auto& field_trial : models_request.active_field_trials()) {
-      EXPECT_TRUE(
-          expected_field_trial_name_hashes_.find(field_trial.name_hash()) !=
-          expected_field_trial_name_hashes_.end());
-      seen_field_trial_name_hashes.insert(field_trial.name_hash());
-    }
-    EXPECT_EQ(seen_field_trial_name_hashes.size(),
-              expected_field_trial_name_hashes_.size());
 
     response->set_code(net::HTTP_OK);
     std::unique_ptr<optimization_guide::proto::GetModelsResponse>
@@ -286,7 +269,6 @@
   variations::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
       variations::VariationsIdsProvider::Mode::kUseSignedInState};
   base::test::ScopedCommandLine scoped_command_line_;
-  base::flat_set<uint32_t> expected_field_trial_name_hashes_;
   std::unique_ptr<net::EmbeddedTestServer> https_server_;
   std::unique_ptr<net::EmbeddedTestServer> models_server_;
   PredictionModelsFetcherRemoteResponseType response_type_ =
@@ -372,61 +354,6 @@
       "OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 0);
 }
 
-class PredictionManagerNoUserPermissionsTest : public PredictionManagerTest {
- public:
-  PredictionManagerNoUserPermissionsTest() {
-    // Field trials should not be sent.
-    SetExpectedFieldTrialNames({});
-  }
-
-  ~PredictionManagerNoUserPermissionsTest() override = default;
-
-  void SetUpCommandLine(base::CommandLine* cmd) override {
-    PredictionManagerTest::SetUpCommandLine(cmd);
-
-    // Remove switches that enable user permissions.
-    cmd->RemoveSwitch(optimization_guide::switches::
-                          kDisableCheckingUserPermissionsForTesting);
-  }
-
- private:
-  void InitializeFeatureList() override {
-    scoped_feature_list_.InitWithFeaturesAndParameters(
-        {
-            {optimization_guide::features::kOptimizationHints, {}},
-            {optimization_guide::features::kRemoteOptimizationGuideFetching,
-             {}},
-            {optimization_guide::features::kOptimizationTargetPrediction,
-             {{"fetch_startup_delay_ms", "2000"}}},
-            {optimization_guide::features::kOptimizationGuideModelDownloading,
-             {}},
-            {optimization_guide::features::kOptimizationHintsFieldTrials,
-             {{"allowed_field_trial_names",
-               "scoped_feature_list_trial_for_OptimizationHints,scoped_feature_"
-               "list_trial_for_OptimizationHintsFetching"}}},
-        },
-        {});
-  }
-};
-
-TEST_F(PredictionManagerNoUserPermissionsTest,
-       FieldTrialsNotPassedWhenNoUserPermissions) {
-  ModelFileObserver model_file_observer;
-  base::HistogramTester histogram_tester;
-
-  SetResponseType(
-      PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile);
-  RegisterWithKeyedService(&model_file_observer);
-
-  RetryForHistogramUntilCountReached(
-      &histogram_tester,
-      "OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
-
-  RetryForHistogramUntilCountReached(
-      &histogram_tester,
-      "OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 1);
-}
-
 class PredictionManagerModelDownloadingBrowserTest
     : public PredictionManagerTest {
  public:
@@ -462,17 +389,8 @@
              {{"fetch_startup_delay_ms", "2000"}}},
             {optimization_guide::features::kOptimizationGuideModelDownloading,
              {{"unrestricted_model_downloading", "true"}}},
-            {optimization_guide::features::kOptimizationHintsFieldTrials,
-             {{"allowed_field_trial_names",
-               "scoped_feature_list_trial_for_OptimizationHints,scoped_feature_"
-               "list_trial_for_OptimizationHintsFetching"}}},
         },
         {});
-    SetExpectedFieldTrialNames(base::flat_set<uint32_t>(
-        {variations::HashName(
-             "scoped_feature_list_trial_for_OptimizationHints"),
-         variations::HashName(
-             "scoped_feature_list_trial_for_OptimizationHintsFetching")}));
   }
 
  protected:
diff --git a/ios/chrome/browser/policy/policy_egtest.mm b/ios/chrome/browser/policy/policy_egtest.mm
index 1071c65..a3035c4 100644
--- a/ios/chrome/browser/policy/policy_egtest.mm
+++ b/ios/chrome/browser/policy/policy_egtest.mm
@@ -307,7 +307,8 @@
 
 // Tests that language detection is not performed and the tool manual trigger
 // button is disabled when the pref kOfferTranslateEnabled is set to false.
-- (void)testTranslateEnabled {
+// TODO(crbug.com/1341363): Disabled due to flakiness. Re-enabled when fixed.
+- (void)DISABLED_testTranslateEnabled {
   GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
   const GURL testURL = self.testServer->GetURL("/pony.html");
   const std::string pageText = "pony";
diff --git a/ios/chrome/browser/segmentation_platform/BUILD.gn b/ios/chrome/browser/segmentation_platform/BUILD.gn
index 960a4b2..7b14432 100644
--- a/ios/chrome/browser/segmentation_platform/BUILD.gn
+++ b/ios/chrome/browser/segmentation_platform/BUILD.gn
@@ -23,6 +23,7 @@
     "//components/optimization_guide/core",
     "//components/optimization_guide/core:features",
     "//components/prefs",
+    "//components/segmentation_platform/embedder/default_model",
     "//components/segmentation_platform/internal",
     "//components/segmentation_platform/internal/proto",
     "//components/segmentation_platform/public",
diff --git a/ios/chrome/browser/segmentation_platform/segmentation_platform_config.mm b/ios/chrome/browser/segmentation_platform/segmentation_platform_config.mm
index 6a76ed1..223e13a 100644
--- a/ios/chrome/browser/segmentation_platform/segmentation_platform_config.mm
+++ b/ios/chrome/browser/segmentation_platform/segmentation_platform_config.mm
@@ -2,18 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ios/chrome/browser/segmentation_platform/segmentation_platform_config.h"
+#import "ios/chrome/browser/segmentation_platform/segmentation_platform_config.h"
 
-#include <memory>
+#import <memory>
 
-#include "base/feature_list.h"
-#include "base/metrics/field_trial_params.h"
-#include "base/time/time.h"
-#include "components/segmentation_platform/public/config.h"
-#include "components/segmentation_platform/public/features.h"
-#include "components/segmentation_platform/public/model_provider.h"
-#include "components/segmentation_platform/public/proto/segmentation_platform.pb.h"
-#include "ios/chrome/browser/metrics/ios_chrome_metrics_service_accessor.h"
+#import "base/feature_list.h"
+#import "base/metrics/field_trial_params.h"
+#import "base/time/time.h"
+#import "components/segmentation_platform/embedder/default_model/feed_user_segment.h"
+#import "components/segmentation_platform/public/config.h"
+#import "components/segmentation_platform/public/features.h"
+#import "components/segmentation_platform/public/model_provider.h"
+#import "components/segmentation_platform/public/proto/segmentation_platform.pb.h"
+#import "ios/chrome/browser/metrics/ios_chrome_metrics_service_accessor.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -64,6 +65,10 @@
 
 std::unique_ptr<ModelProvider> GetDefaultModelProvider(
     proto::SegmentId target) {
+  if (target == proto::OPTIMIZATION_TARGET_SEGMENTATION_FEED_USER) {
+    return std::make_unique<FeedUserSegment>();
+  }
+
   // Add default models here.
   return nullptr;
 }
diff --git a/ios/chrome/browser/ui/keyboard/keyboard_commands_egtest.mm b/ios/chrome/browser/ui/keyboard/keyboard_commands_egtest.mm
index f395a7f..8100601 100644
--- a/ios/chrome/browser/ui/keyboard/keyboard_commands_egtest.mm
+++ b/ios/chrome/browser/ui/keyboard/keyboard_commands_egtest.mm
@@ -104,7 +104,8 @@
 
 // Tests that keyboard commands are not registered when the bookmark UI is
 // shown.
-- (void)testKeyboardCommandsNotRegistered_AddBookmarkPresented {
+// TODO(crbug.com/1341363): Disabled due to flakiness. Re-enabled when fixed.
+- (void)DISABLED_testKeyboardCommandsNotRegistered_AddBookmarkPresented {
   [ChromeEarlGrey waitForBookmarksToFinishLoading];
   [ChromeEarlGrey clearBookmarks];
 
diff --git a/ios/chrome/browser/ui/settings/privacy/BUILD.gn b/ios/chrome/browser/ui/settings/privacy/BUILD.gn
index d941671..5ef3344 100644
--- a/ios/chrome/browser/ui/settings/privacy/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/privacy/BUILD.gn
@@ -163,7 +163,6 @@
   sources = [ "privacy_safe_browsing_egtest.mm" ]
   deps = [
     "//base",
-    "//base/test:test_support",
     "//components/safe_browsing/core/common",
     "//components/safe_browsing/core/common:safe_browsing_prefs",
     "//components/strings:components_strings_grit",
diff --git a/ios/chrome/browser/ui/settings/privacy/privacy_safe_browsing_egtest.mm b/ios/chrome/browser/ui/settings/privacy/privacy_safe_browsing_egtest.mm
index 76020f88..dda4ac76 100644
--- a/ios/chrome/browser/ui/settings/privacy/privacy_safe_browsing_egtest.mm
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_safe_browsing_egtest.mm
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "base/test/ios/wait_util.h"
 #import "components/safe_browsing/core/common/features.h"
 #import "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #import "components/strings/grit/components_strings.h"
@@ -24,27 +23,8 @@
 using chrome_test_util::SettingsMenuPrivacyButton;
 using l10n_util::GetNSString;
 
-namespace {
-
-// Waits until the warning alert is shown.
-[[nodiscard]] bool WaitForWarningAlert(NSString* alertMessage) {
-  return base::test::ios::WaitUntilConditionOrTimeout(
-      base::test::ios::kWaitForUIElementTimeout, ^{
-        NSError* error = nil;
-        [[EarlGrey selectElementWithMatcher:grey_text(alertMessage)]
-            assertWithMatcher:grey_notNil()
-                        error:&error];
-        return (error == nil);
-      });
-}
-
-}  // namespace
-
 // Integration tests using the Privacy Safe Browsing settings screen.
-@interface PrivacySafeBrowsingTestCase : ChromeTestCase {
-  // The default value for SafeBrowsingEnabled pref.
-  BOOL _safeBrowsingEnabledPrefDefault;
-}
+@interface PrivacySafeBrowsingTestCase : ChromeTestCase
 @end
 
 @implementation PrivacySafeBrowsingTestCase
@@ -55,22 +35,12 @@
   return config;
 }
 
-- (void)setUp {
-  [super setUp];
-  // Ensure that Safe Browsing opt-out starts in its default (opted-in) state.
-  [ChromeEarlGrey setBoolValue:YES forUserPref:prefs::kSafeBrowsingEnabled];
-}
-
-- (void)tearDown {
-  [ChromeEarlGrey setBoolValue:YES forUserPref:prefs::kSafeBrowsingEnabled];
-  [super tearDown];
-}
-
 - (void)testOpenPrivacySafeBrowsingSettings {
   [self openPrivacySafeBrowsingSettings];
 }
 
-- (void)testEachSafeBrowsingOption {
+// TODO(crbug.com/1333625): Enable once activation point is fixed.
+- (void)DISABLED_testEachSafeBrowsingOption {
   [self openPrivacySafeBrowsingSettings];
 
   // Presses each of the Safe Browsing options.
@@ -81,50 +51,34 @@
   GREYAssertTrue([ChromeEarlGrey userBooleanPref:prefs::kSafeBrowsingEnhanced],
                  @"Failed to toggle-on Enhanced Safe Browsing");
 
-  [[EarlGrey selectElementWithMatcher:
-                 grey_allOf(grey_accessibilityID(
-                                kSettingsSafeBrowsingStandardProtectionCellId),
-                            grey_sufficientlyVisible(), nil)]
+  [[EarlGrey
+      selectElementWithMatcher:
+          grey_accessibilityID(kSettingsSafeBrowsingStandardProtectionCellId)]
       performAction:grey_tap()];
   GREYAssertFalse([ChromeEarlGrey userBooleanPref:prefs::kSafeBrowsingEnhanced],
                   @"Failed to toggle-off Enhanced Safe Browsing");
   GREYAssertTrue([ChromeEarlGrey userBooleanPref:prefs::kSafeBrowsingEnabled],
                  @"Failed to toggle-on Standard Safe Browsing");
 
-  // Taps "No Protection" and then the Cancel button on pop-up.
   [[EarlGrey
-      selectElementWithMatcher:grey_allOf(
-                                   grey_accessibilityID(
-                                       kSettingsSafeBrowsingNoProtectionCellId),
-                                   grey_sufficientlyVisible(), nil)]
+      selectElementWithMatcher:grey_accessibilityID(
+                                   kSettingsSafeBrowsingNoProtectionCellId)]
       performAction:grey_tap()];
-  GREYAssert(
-      WaitForWarningAlert(l10n_util::GetNSString(
-          IDS_IOS_SAFE_BROWSING_NO_PROTECTION_CONFIRMATION_DIALOG_CONFIRM)),
-      @"The No Protection pop-up did not show up");
-  [[EarlGrey
-      selectElementWithMatcher:ButtonWithAccessibilityLabelId(IDS_CANCEL)]
+  [[EarlGrey selectElementWithMatcher:grey_buttonTitle(GetNSString(IDS_CANCEL))]
       performAction:grey_tap()];
   GREYAssertFalse([ChromeEarlGrey userBooleanPref:prefs::kSafeBrowsingEnhanced],
                   @"Failed to keep Enhanced Safe Browsing off");
   GREYAssertTrue([ChromeEarlGrey userBooleanPref:prefs::kSafeBrowsingEnabled],
                  @"Failed to keep Standard Safe Browsing on");
 
-  // Taps "No Protection" and then the "Turn Off" Button on pop-up.
   [[EarlGrey
-      selectElementWithMatcher:grey_allOf(
-                                   grey_accessibilityID(
-                                       kSettingsSafeBrowsingNoProtectionCellId),
-                                   grey_sufficientlyVisible(), nil)]
+      selectElementWithMatcher:grey_accessibilityID(
+                                   kSettingsSafeBrowsingNoProtectionCellId)]
       performAction:grey_tap()];
-  GREYAssert(
-      WaitForWarningAlert(l10n_util::GetNSString(
-          IDS_IOS_SAFE_BROWSING_NO_PROTECTION_CONFIRMATION_DIALOG_CONFIRM)),
-      @"The No Protection pop-up did not show up");
   [[EarlGrey
       selectElementWithMatcher:
-          ButtonWithAccessibilityLabelId(
-              IDS_IOS_SAFE_BROWSING_NO_PROTECTION_CONFIRMATION_DIALOG_CONFIRM)]
+          grey_buttonTitle(GetNSString(
+              IDS_IOS_SAFE_BROWSING_NO_PROTECTION_CONFIRMATION_DIALOG_CONFIRM))]
       performAction:grey_tap()];
   GREYAssertFalse([ChromeEarlGrey userBooleanPref:prefs::kSafeBrowsingEnabled],
                   @"Failed to toggle-off Standard Safe Browsing");
diff --git a/ios/chrome/browser/ui/start_surface/start_surface_scene_agent.mm b/ios/chrome/browser/ui/start_surface/start_surface_scene_agent.mm
index 7ad62de..e7b59ad 100644
--- a/ios/chrome/browser/ui/start_surface/start_surface_scene_agent.mm
+++ b/ios/chrome/browser/ui/start_surface/start_surface_scene_agent.mm
@@ -206,15 +206,13 @@
   BOOL isColdStart = (level > SceneActivationLevelBackground &&
                       self.sceneState.appState.startupInformation.isColdStart);
   if (isColdStart) {
-    base::UmaHistogramCustomTimes("IOS.ColdStartBackgroundTime",
-                                  base::Minutes(timeSinceBackgroundInMinutes),
-                                  base::Seconds(0),
-                                  base::Seconds(12 * 60 /* 12 hours */), 24);
+    UMA_HISTOGRAM_CUSTOM_COUNTS("IOS.BackgroundTimeBeforeColdStart",
+                                timeSinceBackgroundInMinutes, 1,
+                                60 * 12 /* 12 hours */, 24);
   } else {
-    base::UmaHistogramCustomTimes("IOS.WarmStartBackgroundTime",
-                                  base::Minutes(timeSinceBackgroundInMinutes),
-                                  base::Seconds(0),
-                                  base::Seconds(12 * 60 /* 12 hours */), 24);
+    UMA_HISTOGRAM_CUSTOM_COUNTS("IOS.BackgroundTimeBeforeWarmStart",
+                                timeSinceBackgroundInMinutes, 1,
+                                60 * 12 /* 12 hours */, 24);
   }
 }
 
diff --git a/ios/chrome/browser/ui/start_surface/start_surface_scene_agent_unittest.mm b/ios/chrome/browser/ui/start_surface/start_surface_scene_agent_unittest.mm
index a2fa711b..2c98e028 100644
--- a/ios/chrome/browser/ui/start_surface/start_surface_scene_agent_unittest.mm
+++ b/ios/chrome/browser/ui/start_surface/start_surface_scene_agent_unittest.mm
@@ -224,10 +224,10 @@
       /*favicon_service=*/nullptr);
   SetStartSurfaceSessionObjectForSceneState(scene_state_);
 
-  histogram_tester_->ExpectTotalCount("IOS.WarmStartBackgroundTime", 0);
+  histogram_tester_->ExpectTotalCount("IOS.BackgroundTimeBeforeWarmStart", 0);
   [agent_ sceneState:scene_state_
       transitionedToActivationLevel:SceneActivationLevelForegroundActive];
-  histogram_tester_->ExpectTotalCount("IOS.WarmStartBackgroundTime", 1);
+  histogram_tester_->ExpectTotalCount("IOS.BackgroundTimeBeforeWarmStart", 1);
 }
 
 // Tests that IOS.StartSurfaceShown is correctly logged for a valid cold start
@@ -251,8 +251,8 @@
       web_state_list->GetActiveWebState(),
       /*favicon_service=*/nullptr);
 
-  histogram_tester_->ExpectTotalCount("IOS.ColdStartBackgroundTime", 0);
+  histogram_tester_->ExpectTotalCount("IOS.BackgroundTimeBeforeColdStart", 0);
   [agent_ sceneState:scene_state_
       transitionedToActivationLevel:SceneActivationLevelForegroundActive];
-  histogram_tester_->ExpectTotalCount("IOS.ColdStartBackgroundTime", 1);
+  histogram_tester_->ExpectTotalCount("IOS.BackgroundTimeBeforeColdStart", 1);
 }
diff --git a/media/BUILD.gn b/media/BUILD.gn
index c820538..325b938 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -8,6 +8,7 @@
 import("//build/config/chromecast_build.gni")
 import("//build/config/features.gni")
 import("//build/config/linux/pkg_config.gni")
+import("//build/config/logging.gni")
 import("//build/config/ui.gni")
 import("//media/cdm/library_cdm/cdm_paths.gni")
 import("//media/media_options.gni")
@@ -78,6 +79,12 @@
   if (use_cras) {
     defines += [ "USE_CRAS" ]
   }
+
+  if (!use_runtime_vlog) {
+    # Enable VLOG() on ChromeOS-Ash, otherwise disabled.
+    assert(is_chromeos_ash,
+           "Non-ChromeOS-Ash builds should not use runtime vlog")
+  }
 }
 
 # Internal grouping of the configs necessary to support sub-folders having their
diff --git a/media/base/media_util.cc b/media/base/media_util.cc
index 0993d5c..dafaad6 100644
--- a/media/base/media_util.cc
+++ b/media/base/media_util.cc
@@ -4,53 +4,12 @@
 
 #include "media/base/media_util.h"
 
-#include "base/metrics/histogram_macros.h"
-
 namespace media {
 
-namespace {
-
-// Reported to UMA server. Do not renumber or reuse values.
-enum class MediaVideoHeight {
-  k360_OrLower,
-  k480,
-  k720,
-  k1080,
-  k1440,
-  k2160_OrHigher,
-  kMaxValue = k2160_OrHigher,
-};
-
-MediaVideoHeight GetMediaVideoHeight(int height) {
-  if (height <= 400)
-    return MediaVideoHeight::k360_OrLower;
-  if (height <= 600)
-    return MediaVideoHeight::k480;
-  if (height <= 900)
-    return MediaVideoHeight::k720;
-  if (height <= 1260)
-    return MediaVideoHeight::k1080;
-  if (height <= 1800)
-    return MediaVideoHeight::k1440;
-  return MediaVideoHeight::k2160_OrHigher;
-}
-
-}  // namespace
-
 std::vector<uint8_t> EmptyExtraData() {
   return std::vector<uint8_t>();
 }
 
-void ReportPepperVideoDecoderOutputPictureCountHW(int height) {
-  UMA_HISTOGRAM_ENUMERATION("Media.PepperVideoDecoderOutputPictureCount.HW",
-                            GetMediaVideoHeight(height));
-}
-
-void ReportPepperVideoDecoderOutputPictureCountSW(int height) {
-  UMA_HISTOGRAM_ENUMERATION("Media.PepperVideoDecoderOutputPictureCount.SW",
-                            GetMediaVideoHeight(height));
-}
-
 AudioParameters::Format ConvertAudioCodecToBitstreamFormat(AudioCodec codec) {
   switch (codec) {
     case AudioCodec::kAC3:
diff --git a/media/base/media_util.h b/media/base/media_util.h
index 6c450a34..b2d3b11 100644
--- a/media/base/media_util.h
+++ b/media/base/media_util.h
@@ -19,11 +19,6 @@
 // constructed with empty extra data.
 MEDIA_EXPORT std::vector<uint8_t> EmptyExtraData();
 
-// Helpers for PPAPI UMAs. There wasn't an obvious place to put them in
-// //content/renderer/pepper.
-MEDIA_EXPORT void ReportPepperVideoDecoderOutputPictureCountHW(int height);
-MEDIA_EXPORT void ReportPepperVideoDecoderOutputPictureCountSW(int height);
-
 class MEDIA_EXPORT NullMediaLog : public media::MediaLog {
  public:
   NullMediaLog() = default;
diff --git a/media/formats/hls/tags.cc b/media/formats/hls/tags.cc
index b634262..285057b2 100644
--- a/media/formats/hls/tags.cc
+++ b/media/formats/hls/tags.cc
@@ -342,7 +342,7 @@
     }
 
     auto value = types::ParseQuotedStringWithoutSubstitution(
-        map.GetValue(XDefineTagAttribute::kValue));
+        map.GetValue(XDefineTagAttribute::kValue), /*allow_empty*/ true);
     if (value.has_error()) {
       return ParseStatus(ParseStatusCode::kMalformedTag);
     }
diff --git a/media/formats/hls/tags_unittest.cc b/media/formats/hls/tags_unittest.cc
index 0cbbfbd..eb093e7 100644
--- a/media/formats/hls/tags_unittest.cc
+++ b/media/formats/hls/tags_unittest.cc
@@ -349,6 +349,9 @@
   ErrorTest<XDefineTag>(R"(NAME="",VALUE="Foo")",
                         ParseStatusCode::kMalformedTag);
 
+  // Empty IMPORT is not allowed
+  ErrorTest<XDefineTag>(R"(IMPORT="")", ParseStatusCode::kMalformedTag);
+
   // Non-valid NAME is not allowed
   ErrorTest<XDefineTag>(R"(NAME=".FOO",VALUE="Foo")",
                         ParseStatusCode::kMalformedTag);
@@ -459,6 +462,8 @@
                            sub_buffer, ParseStatusCode::kMalformedTag);
   ErrorTest<XStreamInfTag>(R"(BANDWIDTH=1010,CODECS=123)", variable_dict,
                            sub_buffer, ParseStatusCode::kMalformedTag);
+  ErrorTest<XStreamInfTag>(R"(BANDWIDTH=1010,CODECS="")", variable_dict,
+                           sub_buffer, ParseStatusCode::kMalformedTag);
 
   // "CODECS" is subject to variable substitution
   result = OkTest<XStreamInfTag>(R"(BANDWIDTH=1010,CODECS="{$FOO},{$BAR}")",
diff --git a/media/formats/hls/types.cc b/media/formats/hls/types.cc
index e1b530cf..082fea1 100644
--- a/media/formats/hls/types.cc
+++ b/media/formats/hls/types.cc
@@ -275,15 +275,25 @@
 ParseStatus::Or<ResolvedSourceString> ParseQuotedString(
     SourceString source_str,
     const VariableDictionary& variable_dict,
-    VariableDictionary::SubstitutionBuffer& sub_buffer) {
-  return ParseQuotedStringWithoutSubstitution(source_str)
+    VariableDictionary::SubstitutionBuffer& sub_buffer,
+    bool allow_empty) {
+  return ParseQuotedStringWithoutSubstitution(source_str, allow_empty)
       .MapValue([&variable_dict, &sub_buffer](auto str) {
         return variable_dict.Resolve(str, sub_buffer);
-      });
+      })
+      .MapValue(
+          [allow_empty](auto str) -> ParseStatus::Or<ResolvedSourceString> {
+            if (!allow_empty && str.Empty()) {
+              return ParseStatusCode::kFailedToParseQuotedString;
+            } else {
+              return str;
+            }
+          });
 }
 
 ParseStatus::Or<SourceString> ParseQuotedStringWithoutSubstitution(
-    SourceString source_str) {
+    SourceString source_str,
+    bool allow_empty) {
   if (source_str.Size() < 2) {
     return ParseStatusCode::kFailedToParseQuotedString;
   }
@@ -294,7 +304,12 @@
     return ParseStatusCode::kFailedToParseQuotedString;
   }
 
-  return source_str.Substr(1, source_str.Size() - 2);
+  auto str = source_str.Substr(1, source_str.Size() - 2);
+  if (!allow_empty && str.Empty()) {
+    return ParseStatusCode::kFailedToParseQuotedString;
+  }
+
+  return str;
 }
 
 AttributeListIterator::AttributeListIterator(SourceString content)
diff --git a/media/formats/hls/types.h b/media/formats/hls/types.h
index b8cd9d51a8..2426442 100644
--- a/media/formats/hls/types.h
+++ b/media/formats/hls/types.h
@@ -90,16 +90,22 @@
 // Parses a string surrounded by double-quotes ("), returning the inner string.
 // These appear in the context of attribute-lists, and are subject to variable
 // substitution. `sub_buffer` must outlive the returned string.
+// `allow_empty` determines whether an empty quoted string is accepted, (after
+// variable substitution) which isn't the case for most attributes.
 MEDIA_EXPORT ParseStatus::Or<ResolvedSourceString> ParseQuotedString(
     SourceString source_str,
     const VariableDictionary& variable_dict,
-    VariableDictionary::SubstitutionBuffer& sub_buffer);
+    VariableDictionary::SubstitutionBuffer& sub_buffer,
+    bool allow_empty = false);
 
 // Parses a string surrounded by double-quotes ("), returning the interior
 // string. These appear in the context of attribute-lists, however certain tags
 // disallow variable substitution so this function exists to serve those.
+// `allow_empty` determines whether an empty quoted string is accepted, which
+// isn't the case for most attributes.
 MEDIA_EXPORT ParseStatus::Or<SourceString> ParseQuotedStringWithoutSubstitution(
-    SourceString source_str);
+    SourceString source_str,
+    bool allow_empty = false);
 
 // Provides an iterator-style interface over attribute-lists.
 // Since the number of attributes expected in an attribute-list for a tag varies
diff --git a/media/formats/hls/types_unittest.cc b/media/formats/hls/types_unittest.cc
index 591604f..574369e 100644
--- a/media/formats/hls/types_unittest.cc
+++ b/media/formats/hls/types_unittest.cc
@@ -458,20 +458,21 @@
 }
 
 TEST(HlsTypesTest, ParseQuotedStringWithoutSubstitution) {
-  const auto ok_test = [](base::StringPiece in, base::StringPiece expected_out,
+  const auto ok_test = [](base::StringPiece in, bool allow_empty,
+                          base::StringPiece expected_out,
                           const base::Location& from =
                               base::Location::Current()) {
     auto in_str = SourceString::CreateForTesting(in);
-    auto out = types::ParseQuotedStringWithoutSubstitution(in_str);
+    auto out = types::ParseQuotedStringWithoutSubstitution(in_str, allow_empty);
     ASSERT_TRUE(out.has_value()) << from.ToString();
     EXPECT_EQ(std::move(out).value().Str(), expected_out) << from.ToString();
   };
 
-  const auto error_test = [](base::StringPiece in,
+  const auto error_test = [](base::StringPiece in, bool allow_empty,
                              const base::Location& from =
                                  base::Location::Current()) {
     auto in_str = SourceString::CreateForTesting(in);
-    auto out = types::ParseQuotedStringWithoutSubstitution(in_str);
+    auto out = types::ParseQuotedStringWithoutSubstitution(in_str, allow_empty);
     ASSERT_TRUE(out.has_error()) << from.ToString();
     EXPECT_EQ(std::move(out).error().code(),
               ParseStatusCode::kFailedToParseQuotedString)
@@ -479,86 +480,102 @@
   };
 
   // Test some basic examples
-  ok_test("\"\"", "");
-  ok_test("\" \"", " ");
-  ok_test("\"Hello, world!\"", "Hello, world!");
+  ok_test("\"a\"", false, "a");
+  ok_test("\" \"", false, " ");
+  ok_test("\"Hello, world!\"", false, "Hello, world!");
+
+  // Empty output string is not allowed by default
+  error_test("\"\"", false);
+  ok_test("\"\"", true, "");
 
   // Interior quotes are not checked by this function
-  ok_test("\"Hello, \"World!\"\"", "Hello, \"World!\"");
+  ok_test("\"Hello, \"World!\"\"", false, "Hello, \"World!\"");
 
   // Variables are not substituted by this function, and do not trigger an error
-  ok_test("\"Hello, {$WORLD}\"", "Hello, {$WORLD}");
+  ok_test("\"Hello, {$WORLD}\"", false, "Hello, {$WORLD}");
 
   // Single-quoted string is not allowed
-  error_test("''");
-  error_test("' '");
-  error_test("'Hello, world!'");
+  error_test("''", false);
+  error_test("' '", false);
+  error_test("'Hello, world!'", false);
 
   // Missing leading/trailing quote is not allowed
-  error_test("\"");
-  error_test("\" ");
-  error_test(" \"");
-  error_test("\"Hello, world!");
-  error_test("Hello, world!\"");
+  error_test("\"", false);
+  error_test("\" ", false);
+  error_test(" \"", false);
+  error_test("\"Hello, world!", false);
+  error_test("Hello, world!\"", false);
 
-  // Empty string is not allowed
-  error_test("");
+  // Empty input string is not allowed
+  error_test("", false);
 }
 
 TEST(HlsTypesTest, ParseQuotedString) {
   VariableDictionary dict;
   EXPECT_TRUE(dict.Insert(CreateVarName("FOO"), "bar"));
-  EXPECT_TRUE(dict.Insert(CreateVarName("BAZ"), "\"foo\""));
+  EXPECT_TRUE(dict.Insert(CreateVarName("BAZ"), "foo"));
+  EXPECT_TRUE(dict.Insert(CreateVarName("EMPTY"), ""));
 
-  const auto ok_test =
-      [&dict](base::StringPiece in, base::StringPiece expected_out,
-              const base::Location& from = base::Location::Current()) {
-        auto in_str = SourceString::CreateForTesting(in);
-        VariableDictionary::SubstitutionBuffer sub_buffer;
-        auto out = types::ParseQuotedString(in_str, dict, sub_buffer);
-        ASSERT_TRUE(out.has_value()) << from.ToString();
-        EXPECT_EQ(std::move(out).value().Str(), expected_out)
-            << from.ToString();
-      };
+  const auto ok_test = [&dict](base::StringPiece in, bool allow_empty,
+                               base::StringPiece expected_out,
+                               const base::Location& from =
+                                   base::Location::Current()) {
+    auto in_str = SourceString::CreateForTesting(in);
+    VariableDictionary::SubstitutionBuffer sub_buffer;
+    auto out = types::ParseQuotedString(in_str, dict, sub_buffer, allow_empty);
+    ASSERT_TRUE(out.has_value()) << from.ToString();
+    EXPECT_EQ(std::move(out).value().Str(), expected_out) << from.ToString();
+  };
 
-  const auto error_test = [&dict](base::StringPiece in,
+  const auto error_test = [&dict](base::StringPiece in, bool allow_empty,
                                   ParseStatusCode expected_error,
                                   const base::Location& from =
                                       base::Location::Current()) {
     auto in_str = SourceString::CreateForTesting(in);
     VariableDictionary::SubstitutionBuffer sub_buffer;
-    auto out = types::ParseQuotedString(in_str, dict, sub_buffer);
+    auto out = types::ParseQuotedString(in_str, dict, sub_buffer, allow_empty);
     ASSERT_TRUE(out.has_error()) << from.ToString();
     EXPECT_EQ(std::move(out).error().code(), expected_error) << from.ToString();
   };
 
   // Test some basic examples
-  ok_test("\"\"", "");
-  ok_test("\" \"", " ");
-  ok_test("\"Hello, world!\"", "Hello, world!");
+  ok_test("\"a\"", false, "a");
+  ok_test("\" \"", false, " ");
+  ok_test("\"Hello, world!\"", false, "Hello, world!");
+
+  // Empty output string is not allowed by default (before or after
+  // variable substitution)
+  error_test("\"\"", false, ParseStatusCode::kFailedToParseQuotedString);
+  ok_test("\"\"", true, "");
+  error_test("\"{$EMPTY}\"", false,
+             ParseStatusCode::kFailedToParseQuotedString);
+  ok_test("\"{$EMPTY}\"", true, "");
 
   // Interior quotes are not checked by this function
-  ok_test("\"Hello, \"World!\"\"", "Hello, \"World!\"");
+  ok_test("\"Hello, \"World!\"\"", false, "Hello, \"World!\"");
 
-  // Variables ARE substituted by this function
-  ok_test("\"Hello, {$FOO}\"", "Hello, bar");
-  ok_test("\"Hello, {$BAZ}\"", "Hello, \"foo\"");
-  error_test("\"Hello, {$foo}\"", ParseStatusCode::kVariableUndefined);
+  // Variables are substituted by this function
+  ok_test("\"Hello, {$FOO}\"", false, "Hello, bar");
+  ok_test("\"Hello, {$BAZ}\"", false, "Hello, foo");
+  error_test("\"Hello, {$foo}\"", false, ParseStatusCode::kVariableUndefined);
 
   // Single-quoted string is not allowed
-  error_test("''", ParseStatusCode::kFailedToParseQuotedString);
-  error_test("' '", ParseStatusCode::kFailedToParseQuotedString);
-  error_test("'Hello, world!'", ParseStatusCode::kFailedToParseQuotedString);
+  error_test("''", false, ParseStatusCode::kFailedToParseQuotedString);
+  error_test("' '", false, ParseStatusCode::kFailedToParseQuotedString);
+  error_test("'Hello, world!'", false,
+             ParseStatusCode::kFailedToParseQuotedString);
 
   // Missing leading/trailing quote is not allowed
-  error_test("\"", ParseStatusCode::kFailedToParseQuotedString);
-  error_test("\" ", ParseStatusCode::kFailedToParseQuotedString);
-  error_test(" \"", ParseStatusCode::kFailedToParseQuotedString);
-  error_test("\"Hello, world!", ParseStatusCode::kFailedToParseQuotedString);
-  error_test("Hello, world!\"", ParseStatusCode::kFailedToParseQuotedString);
+  error_test("\"", false, ParseStatusCode::kFailedToParseQuotedString);
+  error_test("\" ", false, ParseStatusCode::kFailedToParseQuotedString);
+  error_test(" \"", false, ParseStatusCode::kFailedToParseQuotedString);
+  error_test("\"Hello, world!", false,
+             ParseStatusCode::kFailedToParseQuotedString);
+  error_test("Hello, world!\"", false,
+             ParseStatusCode::kFailedToParseQuotedString);
 
-  // Empty string is not allowed
-  error_test("", ParseStatusCode::kFailedToParseQuotedString);
+  // Empty input string is not allowed
+  error_test("", false, ParseStatusCode::kFailedToParseQuotedString);
 }
 
 TEST(HlsTypesTest, ParseDecimalResolution) {
diff --git a/media/gpu/v4l2/v4l2_image_processor_backend.cc b/media/gpu/v4l2/v4l2_image_processor_backend.cc
index 3dbd184..cee8471 100644
--- a/media/gpu/v4l2/v4l2_image_processor_backend.cc
+++ b/media/gpu/v4l2/v4l2_image_processor_backend.cc
@@ -7,6 +7,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <poll.h>
+#include <stdio.h>
 #include <string.h>
 #include <sys/eventfd.h>
 #include <sys/ioctl.h>
@@ -29,6 +30,7 @@
 #include "media/gpu/chromeos/platform_video_frame_utils.h"
 #include "media/gpu/macros.h"
 #include "media/gpu/v4l2/v4l2_utils.h"
+#include "media/gpu/video_frame_mapper_factory.h"
 
 namespace media {
 
@@ -821,6 +823,8 @@
   ProcessJobsTask();
 }
 
+int num_dumped_frames = 0;
+
 void V4L2ImageProcessorBackend::Dequeue() {
   DVLOGF(4);
   DCHECK_CALLED_ON_VALID_SEQUENCE(backend_sequence_checker_);
@@ -891,6 +895,33 @@
         return;
     }
 
+    auto frame_mapper = VideoFrameMapperFactory::CreateMapper(
+        output_frame->format(), output_frame->storage_type(), true);
+    if (frame_mapper && output_frame->format() == PIXEL_FORMAT_NV12) {
+      output_frame = frame_mapper->Map(std::move(output_frame), PROT_READ);
+      char filename[256];
+      snprintf(filename, 256, "/usr/local/out_frame%d", num_dumped_frames++);
+      FILE* out_file = fopen(filename, "w+");
+      for (int i = 0; i < output_frame->visible_rect().width() *
+                              output_frame->visible_rect().height();
+           i++)
+        fputc(output_frame->visible_data(VideoFrame::kYPlane)[i], out_file);
+#define ALIGN(x, y) ((x + (y - 1)) & (~(y - 1)))
+      for (int j = 0;
+           j < ALIGN(output_frame->visible_rect().width(), 2) / 2 *
+                   ALIGN(output_frame->visible_rect().height(), 2) / 2;
+           j++)
+        fputc(output_frame->visible_data(VideoFrame::kUVPlane)[j * 2],
+              out_file);
+      for (int j = 0;
+           j < ALIGN(output_frame->visible_rect().width(), 2) / 2 *
+                   ALIGN(output_frame->visible_rect().height(), 2) / 2;
+           j++)
+        fputc(output_frame->visible_data(VideoFrame::kUVPlane)[j * 2 + 1],
+              out_file);
+      fclose(out_file);
+    }
+
     const auto timestamp = job_record->input_frame->timestamp();
     auto iter = buffer_tracers_.find(timestamp);
     if (iter != buffer_tracers_.end()) {
diff --git a/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.cc b/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.cc
index 1135c6e..ca3fd50 100644
--- a/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.cc
+++ b/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.cc
@@ -137,7 +137,7 @@
     return false;
   }
 
-  if (!CreateAvd())
+  if (!CreateDecoder())
     return false;
 
   if (input_queue_->SupportsRequests()) {
@@ -399,26 +399,26 @@
 
   pause_reason_ = PauseReason::kNone;
   while (true) {
-    switch (avd_->Decode()) {
+    switch (decoder_->Decode()) {
       case AcceleratedVideoDecoder::kConfigChange:
-        if (avd_->GetBitDepth() != 8u) {
+        if (decoder_->GetBitDepth() != 8u) {
           VLOGF(2) << "Unsupported bit depth: "
-                   << base::strict_cast<int>(avd_->GetBitDepth());
+                   << base::strict_cast<int>(decoder_->GetBitDepth());
           return false;
         }
 
-        if (profile_ != avd_->GetProfile()) {
+        if (profile_ != decoder_->GetProfile()) {
           DVLOGF(3) << "Profile is changed: " << profile_ << " -> "
-                    << avd_->GetProfile();
-          if (!IsSupportedProfile(avd_->GetProfile())) {
-            VLOGF(2) << "Unsupported profile: " << avd_->GetProfile();
+                    << decoder_->GetProfile();
+          if (!IsSupportedProfile(decoder_->GetProfile())) {
+            VLOGF(2) << "Unsupported profile: " << decoder_->GetProfile();
             return false;
           }
 
-          profile_ = avd_->GetProfile();
+          profile_ = decoder_->GetProfile();
         }
 
-        if (pic_size_ == avd_->GetPicSize()) {
+        if (pic_size_ == decoder_->GetPicSize()) {
           // There is no need to do anything in V4L2 API when only a profile is
           // changed.
           DVLOGF(3) << "Only profile is changed. No need to do anything.";
@@ -447,12 +447,12 @@
         decode_request_queue_.pop();
 
         if (current_decode_request_->buffer->end_of_stream()) {
-          if (!avd_->Flush()) {
+          if (!decoder_->Flush()) {
             VLOGF(1) << "Failed flushing the decoder.";
             return false;
           }
           // Put the decoder in an idle state, ready to resume.
-          avd_->Reset();
+          decoder_->Reset();
 
           client_->InitiateFlush();
           DCHECK(!flush_cb_);
@@ -464,8 +464,8 @@
           return true;
         }
 
-        avd_->SetStream(current_decode_request_->bitstream_id,
-                        *current_decode_request_->buffer);
+        decoder_->SetStream(current_decode_request_->bitstream_id,
+                            *current_decode_request_->buffer);
         break;
 
       case AcceleratedVideoDecoder::kRanOutOfSurfaces:
@@ -500,8 +500,8 @@
     if (!output_request_queue_.front().IsReady()) {
       DVLOGF(3) << "The first surface is not ready yet.";
       // It is possible that that V4L2 buffers for this output surface are not
-      // even queued yet. Make sure that avd_->Decode() is called to continue
-      // that work and prevent the decoding thread from starving.
+      // even queued yet. Make sure that decoder_->Decode() is called to
+      // continue that work and prevent the decoding thread from starving.
       resume_decode = true;
       break;
     }
@@ -568,9 +568,9 @@
   DCHECK(surfaces_at_device_.empty());
   DCHECK(output_request_queue_.empty());
 
-  size_t num_output_frames = avd_->GetRequiredNumOfPictures();
-  gfx::Rect visible_rect = avd_->GetVisibleRect();
-  gfx::Size pic_size = avd_->GetPicSize();
+  size_t num_output_frames = decoder_->GetRequiredNumOfPictures();
+  gfx::Rect visible_rect = decoder_->GetVisibleRect();
+  gfx::Size pic_size = decoder_->GetPicSize();
   // Set output format with the new resolution.
   DCHECK(!pic_size.IsEmpty());
   DVLOGF(3) << "Change resolution to " << pic_size.ToString();
@@ -611,7 +611,7 @@
     return;
   }
 
-  pic_size_ = avd_->GetPicSize();
+  pic_size_ = decoder_->GetPicSize();
   client_->CompleteFlush();
   task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&V4L2StatelessVideoDecoderBackend::DoDecodeWork,
@@ -632,13 +632,13 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DVLOGF(3);
 
-  if (avd_) {
+  if (decoder_) {
     // If we reset during resolution change, re-create AVD. Then the new AVD
     // will trigger resolution change again after reset.
-    if (pic_size_ != avd_->GetPicSize()) {
-      CreateAvd();
+    if (pic_size_ != decoder_->GetPicSize()) {
+      CreateDecoder();
     } else {
-      avd_->Reset();
+      decoder_->Reset();
     }
   }
 
@@ -683,11 +683,10 @@
     for (const auto& entry : profiles)
       supported_profiles_.push_back(entry.profile);
   }
-  return std::find(supported_profiles_.begin(), supported_profiles_.end(),
-                   profile) != supported_profiles_.end();
+  return base::Contains(supported_profiles_, profile);
 }
 
-bool V4L2StatelessVideoDecoderBackend::CreateAvd() {
+bool V4L2StatelessVideoDecoderBackend::CreateDecoder() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DVLOGF(3);
 
@@ -695,12 +694,12 @@
 
   if (profile_ >= H264PROFILE_MIN && profile_ <= H264PROFILE_MAX) {
     if (input_queue_->SupportsRequests()) {
-      avd_ = std::make_unique<H264Decoder>(
+      decoder_ = std::make_unique<H264Decoder>(
           std::make_unique<V4L2VideoDecoderDelegateH264>(this, device_.get()),
           profile_, color_space_);
     } else {
 #if BUILDFLAG(IS_CHROMEOS)
-      avd_ = std::make_unique<H264Decoder>(
+      decoder_ = std::make_unique<H264Decoder>(
           std::make_unique<V4L2VideoDecoderDelegateH264Legacy>(this,
                                                                device_.get()),
           profile_, color_space_);
@@ -711,12 +710,12 @@
     }
   } else if (profile_ >= VP8PROFILE_MIN && profile_ <= VP8PROFILE_MAX) {
     if (input_queue_->SupportsRequests()) {
-      avd_ = std::make_unique<VP8Decoder>(
+      decoder_ = std::make_unique<VP8Decoder>(
           std::make_unique<V4L2VideoDecoderDelegateVP8>(this, device_.get()),
           color_space_);
     } else {
 #if BUILDFLAG(IS_CHROMEOS)
-      avd_ = std::make_unique<VP8Decoder>(
+      decoder_ = std::make_unique<VP8Decoder>(
           std::make_unique<V4L2VideoDecoderDelegateVP8Legacy>(this,
                                                               device_.get()),
           color_space_);
@@ -735,12 +734,12 @@
       const bool supports_stable_api =
           device_->IsCtrlExposed(V4L2_CID_STATELESS_VP9_FRAME);
       CHECK(supports_stable_api);
-      avd_ = std::make_unique<VP9Decoder>(
+      decoder_ = std::make_unique<VP9Decoder>(
           std::make_unique<V4L2VideoDecoderDelegateVP9>(this, device_.get()),
           profile_, color_space_);
     } else {
 #if BUILDFLAG(IS_CHROMEOS)
-      avd_ = std::make_unique<VP9Decoder>(
+      decoder_ = std::make_unique<VP9Decoder>(
           std::make_unique<V4L2VideoDecoderDelegateVP9Legacy>(this,
                                                               device_.get()),
           profile_, color_space_);
diff --git a/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.h b/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.h
index 862cf6d..5ccd303 100644
--- a/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.h
+++ b/media/gpu/v4l2/v4l2_video_decoder_backend_stateless.h
@@ -137,7 +137,7 @@
   bool IsSupportedProfile(VideoCodecProfile profile);
 
   // Create codec-specific AcceleratedVideoDecoder and reset related variables.
-  bool CreateAvd();
+  bool CreateDecoder();
 
   // Video profile we are decoding.
   VideoCodecProfile profile_;
@@ -149,7 +149,7 @@
   gfx::Size pic_size_;
 
   // Video decoder used to parse stream headers by software.
-  std::unique_ptr<AcceleratedVideoDecoder> avd_;
+  std::unique_ptr<AcceleratedVideoDecoder> decoder_;
 
   // The decode request which is currently processed.
   absl::optional<DecodeRequest> current_decode_request_;
diff --git a/media/gpu/vaapi/BUILD.gn b/media/gpu/vaapi/BUILD.gn
index 338fc77..e77d07f 100644
--- a/media/gpu/vaapi/BUILD.gn
+++ b/media/gpu/vaapi/BUILD.gn
@@ -369,6 +369,12 @@
     "test/av1_decoder.cc",
     "test/av1_decoder.h",
     "test/decode.cc",
+    "test/h264_decoder.cc",
+    "test/h264_decoder.h",
+    "test/h264_dpb.cc",
+    "test/h264_dpb.h",
+    "test/h264_vaapi_wrapper.cc",
+    "test/h264_vaapi_wrapper.h",
     "test/macros.h",
     "test/scoped_va_config.cc",
     "test/scoped_va_config.h",
diff --git a/media/gpu/vaapi/test/av1_decoder.cc b/media/gpu/vaapi/test/av1_decoder.cc
index c7e2f7f..146d7c4 100644
--- a/media/gpu/vaapi/test/av1_decoder.cc
+++ b/media/gpu/vaapi/test/av1_decoder.cc
@@ -531,9 +531,7 @@
 Av1Decoder::Av1Decoder(std::unique_ptr<IvfParser> ivf_parser,
                        const VaapiDevice& va_device,
                        SharedVASurface::FetchPolicy fetch_policy)
-    : VideoDecoder::VideoDecoder(std::move(ivf_parser),
-                                 va_device,
-                                 fetch_policy),
+    : VideoDecoder::VideoDecoder(va_device, fetch_policy),
       buffer_pool_(std::make_unique<libgav1::BufferPool>(
           /*on_frame_buffer_size_changed=*/nullptr,
           /*get_frame_buffer=*/nullptr,
@@ -541,7 +539,8 @@
           /*callback_private_data=*/nullptr)),
       state_(std::make_unique<libgav1::DecoderState>()),
       ref_frames_(kAv1NumRefFrames),
-      display_surfaces_(kAv1NumRefFrames) {}
+      display_surfaces_(kAv1NumRefFrames),
+      ivf_parser_(std::move(ivf_parser)) {}
 
 Av1Decoder::~Av1Decoder() {
   // We destroy the state explicitly to ensure it's destroyed before the
diff --git a/media/gpu/vaapi/test/av1_decoder.h b/media/gpu/vaapi/test/av1_decoder.h
index 0c56f85..a9a8abe 100644
--- a/media/gpu/vaapi/test/av1_decoder.h
+++ b/media/gpu/vaapi/test/av1_decoder.h
@@ -79,6 +79,9 @@
   // If film grain is applied, the film grain surface is stored in
   // |display_surfaces_|. Otherwise, matches |ref_frames_|.
   std::vector<scoped_refptr<SharedVASurface>> display_surfaces_;
+
+  // Parser for the IVF stream to decode.
+  std::unique_ptr<IvfParser> ivf_parser_;
 };
 
 }  // namespace vaapi_test
diff --git a/media/gpu/vaapi/test/decode.cc b/media/gpu/vaapi/test/decode.cc
index 526405b..187a0f1 100644
--- a/media/gpu/vaapi/test/decode.cc
+++ b/media/gpu/vaapi/test/decode.cc
@@ -17,6 +17,7 @@
 #include "build/chromeos_buildflags.h"
 #include "media/filters/ivf_parser.h"
 #include "media/gpu/vaapi/test/av1_decoder.h"
+#include "media/gpu/vaapi/test/h264_decoder.h"
 #include "media/gpu/vaapi/test/shared_va_surface.h"
 #include "media/gpu/vaapi/test/vaapi_device.h"
 #include "media/gpu/vaapi/test/video_decoder.h"
@@ -27,6 +28,7 @@
 #include "ui/gfx/geometry/size.h"
 
 using media::vaapi_test::Av1Decoder;
+using media::vaapi_test::H264Decoder;
 using media::vaapi_test::SharedVASurface;
 using media::vaapi_test::VaapiDevice;
 using media::vaapi_test::VideoDecoder;
@@ -113,14 +115,21 @@
 }
 
 // Creates the appropriate decoder for |stream_data| which is expected to point
-// to IVF data of length |stream_len|. The decoder will use |va_device| to issue
-// VAAPI calls. Returns nullptr on failure.
+// to H264 Annex B data of length |stream_len|. The decoder will use
+// |va_device| to issue VAAPI calls. Returns nullptr on failure.
 std::unique_ptr<VideoDecoder> CreateDecoder(
     const VaapiDevice& va_device,
     SharedVASurface::FetchPolicy fetch_policy,
     const uint8_t* stream_data,
     size_t stream_len) {
-  // Set up video parser.
+  if (*reinterpret_cast<const uint32_t*>(stream_data) == fourcc(0, 0, 0, 1) ||
+      ((*reinterpret_cast<const uint32_t*>(stream_data)) & 0x00FFFFFF) ==
+          fourcc(0, 0, 1, 0)) {
+    return std::make_unique<H264Decoder>(stream_data, stream_len, va_device,
+                                         fetch_policy);
+  }
+
+  // Set up IVF parser.
   auto ivf_parser = std::make_unique<media::IvfParser>();
   media::IvfFileHeader file_header{};
   if (!ivf_parser->Initialize(stream_data, stream_len, &file_header)) {
diff --git a/media/gpu/vaapi/test/h264_decoder.cc b/media/gpu/vaapi/test/h264_decoder.cc
new file mode 100644
index 0000000..15131ae1
--- /dev/null
+++ b/media/gpu/vaapi/test/h264_decoder.cc
@@ -0,0 +1,1288 @@
+// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/gpu/vaapi/test/h264_decoder.h"
+
+#include "base/notreached.h"
+#include "media/base/subsample_entry.h"
+#include "media/gpu/macros.h"
+#include "media/gpu/vaapi/test/h264_dpb.h"
+#include "media/gpu/vaapi/test/video_decoder.h"
+#include "media/video/h264_parser.h"
+
+#include <va/va.h>
+
+namespace media::vaapi_test {
+
+namespace {
+
+struct PicNumDescCompare {
+  bool operator()(const scoped_refptr<H264Picture>& a,
+                  const scoped_refptr<H264Picture>& b) const {
+    return a->pic_num > b->pic_num;
+  }
+};
+
+struct LongTermPicNumAscCompare {
+  bool operator()(const scoped_refptr<H264Picture>& a,
+                  const scoped_refptr<H264Picture>& b) const {
+    return a->long_term_pic_num < b->long_term_pic_num;
+  }
+};
+
+struct POCAscCompare {
+  bool operator()(const scoped_refptr<H264Picture>& a,
+                  const scoped_refptr<H264Picture>& b) const {
+    return a->pic_order_cnt < b->pic_order_cnt;
+  }
+};
+
+struct POCDescCompare {
+  bool operator()(const scoped_refptr<H264Picture>& a,
+                  const scoped_refptr<H264Picture>& b) const {
+    return a->pic_order_cnt > b->pic_order_cnt;
+  }
+};
+
+bool FillH264PictureFromSliceHeader(const H264SPS* sps,
+                                    const H264SliceHeader& slice_hdr,
+                                    H264Picture* pic) {
+  DCHECK(pic);
+
+  pic->idr = slice_hdr.idr_pic_flag;
+  if (pic->idr)
+    pic->idr_pic_id = slice_hdr.idr_pic_id;
+
+  if (slice_hdr.field_pic_flag) {
+    pic->field = slice_hdr.bottom_field_flag ? H264Picture::FIELD_BOTTOM
+                                             : H264Picture::FIELD_TOP;
+  } else {
+    pic->field = H264Picture::FIELD_NONE;
+  }
+
+  if (pic->field != H264Picture::FIELD_NONE) {
+    VLOG(1) << "Interlaced video not supported.";
+    return false;
+  }
+
+  pic->nal_ref_idc = slice_hdr.nal_ref_idc;
+  pic->ref = slice_hdr.nal_ref_idc != 0;
+  // This assumes non-interlaced stream.
+  pic->frame_num = pic->pic_num = slice_hdr.frame_num;
+
+  if (!sps)
+    return false;
+
+  pic->pic_order_cnt_type = sps->pic_order_cnt_type;
+  switch (pic->pic_order_cnt_type) {
+    case 0:
+      pic->pic_order_cnt_lsb = slice_hdr.pic_order_cnt_lsb;
+      pic->delta_pic_order_cnt_bottom = slice_hdr.delta_pic_order_cnt_bottom;
+      break;
+
+    case 1:
+      pic->delta_pic_order_cnt0 = slice_hdr.delta_pic_order_cnt0;
+      pic->delta_pic_order_cnt1 = slice_hdr.delta_pic_order_cnt1;
+      break;
+
+    case 2:
+      break;
+
+    default:
+      NOTREACHED();
+      return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+H264Decoder::H264Decoder(const uint8_t* stream_data,
+                         size_t stream_len,
+                         const VaapiDevice& va_device,
+                         SharedVASurface::FetchPolicy fetch_policy)
+    : VideoDecoder::VideoDecoder(va_device, fetch_policy),
+      parser_(std::make_unique<H264Parser>()),
+      curr_sps_id_(-1),
+      curr_pps_id_(-1),
+      curr_slice_hdr_(nullptr),
+      curr_nalu_(nullptr),
+      curr_picture_(nullptr),
+      is_stream_over_(false),
+      va_wrapper_(va_device) {
+  parser_->SetStream(stream_data, stream_len);
+
+  LOG_ASSERT(GetStreamMetadata()) << "Stream contains no Sequence Parameters!";
+
+  ExtractSliceHeader();
+}
+
+H264Decoder::~H264Decoder() {
+  dpb_.Clear();
+  last_decoded_surface_.reset();
+}
+
+VideoDecoder::Result H264Decoder::DecodeNextFrame() {
+  while (!is_stream_over_ && output_queue.empty())
+    DecodeNextFrameInStream();
+
+  if (is_stream_over_ && output_queue.empty())
+    return VideoDecoder::kEOStream;
+
+  last_decoded_surface_ = output_queue.front()->surface;
+  VLOG(4) << "Outputting frame poc: " << output_queue.front()->pic_order_cnt;
+  last_decoded_frame_visible_ = !output_queue.front()->nonexisting;
+  output_queue.pop();
+  return VideoDecoder::kOk;
+}
+
+// H264 does not guarantee frames appear in the order they need to be shown in.
+// This method merely decodes the next frame in the stream, it's up to the
+// caller to actually figure out how to order them.
+void H264Decoder::DecodeNextFrameInStream() {
+  StartNewFrame();
+  ProcessSlice();
+
+  while (true) {
+    curr_nalu_ = std::make_unique<H264NALU>();
+    H264Parser::Result parser_result =
+        parser_->AdvanceToNextNALU(curr_nalu_.get());
+    if (parser_result == H264Parser::kEOStream) {
+      is_stream_over_ = true;
+      break;
+    }
+
+    if (curr_nalu_->nal_unit_type == H264NALU::kNonIDRSlice ||
+        curr_nalu_->nal_unit_type == H264NALU::kIDRSlice) {
+      ExtractSliceHeader();
+
+      if (IsNewFrame())
+        break;
+
+      ProcessSlice();
+    } else if (curr_nalu_->nal_unit_type == H264NALU::kSPS) {
+      UpdateSequenceParams();
+    } else if (curr_nalu_->nal_unit_type == H264NALU::kPPS) {
+      UpdatePictureParams();
+    } else {
+      VLOG(4) << "Skipping NALU type " << curr_nalu_->nal_unit_type;
+    }
+  }
+
+  DecodeFrame();
+
+  FinishPicture(curr_picture_);
+
+  if (is_stream_over_) {
+    FlushDPB();
+  }
+}
+
+void H264Decoder::ProcessSlice() {
+  if (!curr_slice_hdr_->field_pic_flag) {
+    max_pic_num_ = max_frame_num_;
+  } else {
+    max_pic_num_ = 2 * max_frame_num_;
+  }
+
+  H264Picture::Vector ref_pic_list0, ref_pic_list1;
+  LOG_ASSERT(ModifyReferencePicLists(curr_slice_hdr_.get(), &ref_pic_list0,
+                                     &ref_pic_list1))
+      << "Error modify reference pic lists!";
+
+  const H264PPS* pic_params = parser_->GetPPS(curr_pps_id_);
+  LOG_ASSERT(pic_params) << "No picture params for ID " << curr_pps_id_;
+
+  va_wrapper_.SubmitSlice(
+      pic_params, curr_slice_hdr_.get(), ref_pic_list0, ref_pic_list1,
+      curr_picture_.get(), curr_slice_hdr_->nalu_data,
+      curr_slice_hdr_->nalu_size, parser_->GetCurrentSubsamples());
+}
+
+void H264Decoder::UpdateSequenceParams() {
+  LOG_ASSERT(parser_->ParseSPS(&curr_sps_id_) == H264Parser::kOk)
+      << "Error parsing SPS NALU";
+
+  const H264SPS* sps = parser_->GetSPS(curr_sps_id_);
+  LOG_ASSERT(sps) << "SPS not present for ID " << curr_sps_id_;
+
+  const gfx::Rect new_visible_rect =
+      sps->GetVisibleRect().value_or(gfx::Rect());
+  if (visible_rect_ != new_visible_rect) {
+    VLOG(2) << "New visible rect: " << new_visible_rect.ToString();
+    visible_rect_ = new_visible_rect;
+  }
+
+  const gfx::Size new_pic_size = sps->GetCodedSize().value_or(gfx::Size());
+  LOG_ASSERT(!new_pic_size.IsEmpty()) << "Invalid picture size";
+
+  const int width_mb = base::checked_cast<int>(new_pic_size.width()) / 16;
+  const int height_mb = base::checked_cast<int>(new_pic_size.height()) / 16;
+
+  LOG_ASSERT(std::numeric_limits<int>::max() / width_mb > height_mb)
+      << "Picture size is too big: " << new_pic_size.ToString();
+
+  // Spec A.3.1 and A.3.2
+  // For Baseline, Constrained Baseline and Main profile, the indicated level is
+  // Level 1b if level_idc is equal to 11 and constraint_set3_flag is equal to 1
+  uint8_t level = base::checked_cast<uint8_t>(sps->level_idc);
+  if ((sps->profile_idc == H264SPS::kProfileIDCBaseline ||
+       sps->profile_idc == H264SPS::kProfileIDCConstrainedBaseline ||
+       sps->profile_idc == H264SPS::kProfileIDCMain) &&
+      level == 11 && sps->constraint_set3_flag) {
+    level = 9;  // Level 1b
+  }
+  int max_dpb_mbs = base::checked_cast<int>(H264LevelToMaxDpbMbs(level));
+  LOG_ASSERT(max_dpb_mbs) << "Invalid profile level " << level;
+
+  // MaxDpbFrames from level limits per spec.
+  size_t max_dpb_frames = std::min(max_dpb_mbs / (width_mb * height_mb),
+                                   static_cast<int>(H264DPB::kDPBMaxSize));
+  VLOG(1) << "MaxDpbFrames: " << max_dpb_frames
+          << ", max_num_ref_frames: " << sps->max_num_ref_frames
+          << ", max_dec_frame_buffering: " << sps->max_dec_frame_buffering;
+
+  // Set DPB size to at least the level limit, or what the stream requires.
+  size_t max_dpb_size =
+      std::max(static_cast<int>(max_dpb_frames),
+               std::max(sps->max_num_ref_frames, sps->max_dec_frame_buffering));
+  // Some non-conforming streams specify more frames are needed than the current
+  // level limit. Allow this, but only up to the maximum number of reference
+  // frames allowed per spec.
+  LOG_ASSERT(max_dpb_size <= max_dpb_frames)
+      << "Invalid stream, DPB size > MaxDpbFrames";
+  LOG_ASSERT(max_dpb_size != 0 && max_dpb_size <= H264DPB::kDPBMaxSize)
+      << "Invalid DPB size: " << max_dpb_size;
+
+  LOG_ASSERT(sps->chroma_format_idc == 1) << "Only YUV 4:2:0 is supported";
+
+  dpb_.set_max_num_pics(max_dpb_size);
+
+  UpdateMaxNumReorderFrames(sps);
+}
+
+void H264Decoder::UpdatePictureParams() {
+  LOG_ASSERT(parser_->ParsePPS(&curr_pps_id_) == H264Parser::kOk)
+      << "Error parsing PPS NALU";
+}
+
+void H264Decoder::DecodeFrame() {
+  va_wrapper_.SubmitDecode(curr_picture_);
+}
+
+void H264Decoder::FinishPicture(scoped_refptr<H264Picture> pic) {
+  // Finish processing the picture.
+  // Start by storing previous picture data for later use.
+  if (pic->ref) {
+    ReferencePictureMarking(pic);
+    prev_ref_has_memmgmnt5_ = pic->mem_mgmt_5;
+    prev_ref_top_field_order_cnt_ = pic->top_field_order_cnt;
+    prev_ref_pic_order_cnt_msb_ = pic->pic_order_cnt_msb;
+    prev_ref_pic_order_cnt_lsb_ = pic->pic_order_cnt_lsb;
+    prev_ref_field_ = pic->field;
+    prev_ref_frame_num_ = pic->frame_num;
+  }
+  prev_frame_num_ = pic->frame_num;
+  prev_has_memmgmnt5_ = pic->mem_mgmt_5;
+  prev_frame_num_offset_ = pic->frame_num_offset;
+
+  dpb_.DeleteUnused();
+
+  VLOG(4) << "Finishing picture frame_num: " << pic->frame_num
+          << ", poc: " << pic->pic_order_cnt
+          << ", entries in DPB: " << dpb_.size();
+  if (recovery_frame_cnt_) {
+    // This is the first picture after the recovery point SEI message. Computes
+    // the frame_num of the frame that should be output from (Spec D.2.8).
+    recovery_frame_num_ =
+        (*recovery_frame_cnt_ + pic->frame_num) % max_frame_num_;
+    VLOG(3) << "recovery_frame_num_" << *recovery_frame_num_;
+    recovery_frame_cnt_.reset();
+  }
+
+  // Get all pictures that haven't been outputted yet.
+  H264Picture::Vector not_outputted;
+  dpb_.GetNotOutputtedPicsAppending(&not_outputted);
+  // Include the one we've just decoded.
+  not_outputted.push_back(pic);
+
+  // Sort in output order.
+  std::sort(not_outputted.begin(), not_outputted.end(), POCAscCompare());
+
+  // Try to output as many pictures as we can. A picture can be output,
+  // if the number of decoded and not yet outputted pictures that would remain
+  // in DPB afterwards would at least be equal to max_num_reorder_frames.
+  // If the outputted picture is not a reference picture, it doesn't have
+  // to remain in the DPB and can be removed.
+  auto output_candidate = not_outputted.begin();
+  size_t num_remaining = not_outputted.size();
+  while (num_remaining > max_num_reorder_frames_ ||
+         // If the condition below is used, this is an invalid stream. We should
+         // not be forced to output beyond max_num_reorder_frames in order to
+         // make room in DPB to store the current picture (if we need to do so).
+         // However, if this happens, ignore max_num_reorder_frames and try
+         // to output more. This may cause out-of-order output, but is not
+         // fatal, and better than failing instead.
+         ((dpb_.IsFull() && (!pic->outputted || pic->ref)) && num_remaining)) {
+    VLOG_IF(1, num_remaining <= max_num_reorder_frames_)
+        << "Invalid stream: max_num_reorder_frames not preserved";
+
+    if (!recovery_frame_num_ ||
+        // If we are decoding ahead to reach a SEI recovery point, skip
+        // outputting all pictures before it, to avoid outputting corrupted
+        // frames.
+        (*output_candidate)->frame_num == *recovery_frame_num_) {
+      recovery_frame_num_ = absl::nullopt;
+      output_queue.push(*output_candidate);
+      (*output_candidate)->outputted = true;
+    }
+
+    if (!(*output_candidate)->ref) {
+      // Current picture hasn't been inserted into DPB yet, so don't remove it
+      // if we managed to output it immediately.
+      int outputted_poc = (*output_candidate)->pic_order_cnt;
+      if (outputted_poc != pic->pic_order_cnt) {
+        dpb_.DeleteByPOC(outputted_poc);
+      }
+    }
+
+    ++output_candidate;
+    --num_remaining;
+  }
+
+  // If we haven't managed to output the picture that we just decoded, or if
+  // it's a reference picture, we have to store it in DPB.
+  if (!pic->outputted || pic->ref) {
+    if (dpb_.IsFull()) {
+      // If we haven't managed to output anything to free up space in DPB
+      // to store this picture, it's an error in the stream.
+      VLOG(1) << "Could not free up space in DPB!";
+    }
+
+    dpb_.StorePic(std::move(pic));
+  }
+}
+
+bool H264Decoder::GetStreamMetadata() {
+  bool found_sps = false;
+  while (true) {
+    curr_nalu_ = std::make_unique<H264NALU>();
+    H264Parser::Result parser_result =
+        parser_->AdvanceToNextNALU(curr_nalu_.get());
+    if (parser_result == H264Parser::kEOStream)
+      return found_sps;
+
+    if (curr_nalu_->nal_unit_type == H264NALU::kNonIDRSlice ||
+        curr_nalu_->nal_unit_type == H264NALU::kIDRSlice) {
+      return found_sps;
+    } else if (curr_nalu_->nal_unit_type == H264NALU::kSPS) {
+      found_sps = true;
+      UpdateSequenceParams();
+    } else if (curr_nalu_->nal_unit_type == H264NALU::kPPS) {
+      UpdatePictureParams();
+    } else {
+      VLOG(4) << "Skipping NALU type " << curr_nalu_->nal_unit_type;
+    }
+  }
+}
+
+bool H264Decoder::IsNewFrame() {
+  if (curr_slice_hdr_->frame_num != curr_picture_->frame_num ||
+      curr_slice_hdr_->pic_parameter_set_id != curr_pps_id_ ||
+      curr_slice_hdr_->nal_ref_idc != curr_picture_->nal_ref_idc ||
+      curr_slice_hdr_->idr_pic_flag != curr_picture_->idr ||
+      (curr_slice_hdr_->idr_pic_flag &&
+       (curr_slice_hdr_->idr_pic_id != curr_picture_->idr_pic_id ||
+        curr_slice_hdr_->first_mb_in_slice == 0))) {
+    return true;
+  }
+
+  const H264SPS* sequence_params = parser_->GetSPS(curr_sps_id_);
+  if (!sequence_params)
+    return false;
+
+  if (sequence_params->pic_order_cnt_type ==
+      curr_picture_->pic_order_cnt_type) {
+    if (curr_picture_->pic_order_cnt_type == 0) {
+      if (curr_slice_hdr_->pic_order_cnt_lsb !=
+              curr_picture_->pic_order_cnt_lsb ||
+          curr_slice_hdr_->delta_pic_order_cnt_bottom !=
+              curr_picture_->delta_pic_order_cnt_bottom) {
+        return true;
+      }
+    } else if (curr_picture_->pic_order_cnt_type == 1) {
+      if (curr_slice_hdr_->delta_pic_order_cnt0 !=
+              curr_picture_->delta_pic_order_cnt0 ||
+          curr_slice_hdr_->delta_pic_order_cnt1 !=
+              curr_picture_->delta_pic_order_cnt1) {
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
+void H264Decoder::ExtractSliceHeader() {
+  curr_slice_hdr_ = std::make_unique<H264SliceHeader>();
+  H264Parser::Result result =
+      parser_->ParseSliceHeader(*curr_nalu_, curr_slice_hdr_.get());
+  LOG_ASSERT(result == H264Parser::kOk)
+      << "Error parsing slice header! Parser returned " << result;
+}
+
+void H264Decoder::StartNewFrame() {
+  if (curr_slice_hdr_->idr_pic_flag) {
+    if (!curr_slice_hdr_->no_output_of_prior_pics_flag)
+      FlushDPB();
+
+    dpb_.Clear();
+  }
+
+  curr_pps_id_ = curr_slice_hdr_->pic_parameter_set_id;
+  const H264PPS* picture_params = parser_->GetPPS(curr_pps_id_);
+  LOG_ASSERT(picture_params)
+      << "Error extracting picture parameters for ID " << curr_pps_id_;
+
+  curr_sps_id_ = picture_params->seq_parameter_set_id;
+  const H264SPS* sequence_params = parser_->GetSPS(curr_sps_id_);
+  LOG_ASSERT(sequence_params)
+      << "Error extracting sequence parameters for ID" << curr_sps_id_;
+
+  curr_picture_ = va_wrapper_.CreatePicture(sequence_params);
+
+  max_frame_num_ = 1 << (sequence_params->log2_max_frame_num_minus4 + 4);
+  int frame_num = curr_slice_hdr_->frame_num;
+
+  if (curr_slice_hdr_->idr_pic_flag)
+    prev_ref_frame_num_ = 0;
+
+  if (frame_num != prev_ref_frame_num_ &&
+      frame_num != (prev_ref_frame_num_ + 1) % max_frame_num_) {
+    LOG_ASSERT(HandleFrameNumGap(frame_num)) << "Error handling frame num gap";
+  }
+
+  LOG_ASSERT(InitCurrPicture(curr_slice_hdr_.get())) << "Error initializing"
+                                                     << " picture.";
+
+  UpdatePicNums(frame_num);
+  ConstructReferencePicListsP();
+  ConstructReferencePicListsB();
+
+  va_wrapper_.SubmitFrameMetadata(sequence_params, picture_params, dpb_,
+                                  ref_pic_list_p0_, ref_pic_list_b0_,
+                                  ref_pic_list_b1_, curr_picture_);
+}
+
+bool H264Decoder::InitCurrPicture(const H264SliceHeader* slice_hdr) {
+  if (!FillH264PictureFromSliceHeader(parser_->GetSPS(curr_sps_id_), *slice_hdr,
+                                      curr_picture_.get())) {
+    return false;
+  }
+
+  if (!CalculatePicOrderCounts(curr_picture_))
+    return false;
+
+  curr_picture_->long_term_reference_flag = slice_hdr->long_term_reference_flag;
+  curr_picture_->adaptive_ref_pic_marking_mode_flag =
+      slice_hdr->adaptive_ref_pic_marking_mode_flag;
+
+  // If the slice header indicates we will have to perform reference marking
+  // process after this picture is decoded, store required data for that
+  // purpose.
+  if (slice_hdr->adaptive_ref_pic_marking_mode_flag) {
+    static_assert(sizeof(curr_picture_->ref_pic_marking) ==
+                      sizeof(slice_hdr->ref_pic_marking),
+                  "Array sizes of ref pic marking do not match.");
+    memcpy(curr_picture_->ref_pic_marking, slice_hdr->ref_pic_marking,
+           sizeof(curr_picture_->ref_pic_marking));
+  }
+
+  curr_picture_->visible_rect = visible_rect_;
+
+  return true;
+}
+
+void H264Decoder::ConstructReferencePicListsP() {
+  // RefPicList0 (8.2.4.2.1) [[1] [2]], where:
+  // [1] shortterm ref pics sorted by descending pic_num,
+  // [2] longterm ref pics by ascending long_term_pic_num.
+  ref_pic_list_p0_.clear();
+
+  // First get the short ref pics...
+  dpb_.GetShortTermRefPicsAppending(&ref_pic_list_p0_);
+  size_t num_short_refs = ref_pic_list_p0_.size();
+
+  // and sort them to get [1].
+  std::sort(ref_pic_list_p0_.begin(), ref_pic_list_p0_.end(),
+            PicNumDescCompare());
+
+  // Now get long term pics and sort them by long_term_pic_num to get [2].
+  dpb_.GetLongTermRefPicsAppending(&ref_pic_list_p0_);
+  std::sort(ref_pic_list_p0_.begin() + num_short_refs, ref_pic_list_p0_.end(),
+            LongTermPicNumAscCompare());
+}
+
+void H264Decoder::ConstructReferencePicListsB() {
+  // RefPicList0 (8.2.4.2.3) [[1] [2] [3]], where:
+  // [1] shortterm ref pics with POC < curr_pic's POC sorted by descending POC,
+  // [2] shortterm ref pics with POC > curr_pic's POC by ascending POC,
+  // [3] longterm ref pics by ascending long_term_pic_num.
+  ref_pic_list_b0_.clear();
+  ref_pic_list_b1_.clear();
+  dpb_.GetShortTermRefPicsAppending(&ref_pic_list_b0_);
+  size_t num_short_refs = ref_pic_list_b0_.size();
+
+  // First sort ascending, this will put [1] in right place and finish [2].
+  std::sort(ref_pic_list_b0_.begin(), ref_pic_list_b0_.end(), POCAscCompare());
+
+  // Find first with POC > curr_pic's POC to get first element in [2]...
+  H264Picture::Vector::iterator iter;
+  iter = std::upper_bound(ref_pic_list_b0_.begin(), ref_pic_list_b0_.end(),
+                          curr_picture_.get(), POCAscCompare());
+
+  // and sort [1] descending, thus finishing sequence [1] [2].
+  std::sort(ref_pic_list_b0_.begin(), iter, POCDescCompare());
+
+  // Now add [3] and sort by ascending long_term_pic_num.
+  dpb_.GetLongTermRefPicsAppending(&ref_pic_list_b0_);
+  std::sort(ref_pic_list_b0_.begin() + num_short_refs, ref_pic_list_b0_.end(),
+            LongTermPicNumAscCompare());
+
+  // RefPicList1 (8.2.4.2.4) [[1] [2] [3]], where:
+  // [1] shortterm ref pics with POC > curr_pic's POC sorted by ascending POC,
+  // [2] shortterm ref pics with POC < curr_pic's POC by descending POC,
+  // [3] longterm ref pics by ascending long_term_pic_num.
+
+  dpb_.GetShortTermRefPicsAppending(&ref_pic_list_b1_);
+  num_short_refs = ref_pic_list_b1_.size();
+
+  // First sort by descending POC.
+  std::sort(ref_pic_list_b1_.begin(), ref_pic_list_b1_.end(), POCDescCompare());
+
+  // Find first with POC < curr_pic's POC to get first element in [2]...
+  iter = std::upper_bound(ref_pic_list_b1_.begin(), ref_pic_list_b1_.end(),
+                          curr_picture_.get(), POCDescCompare());
+
+  // and sort [1] ascending.
+  std::sort(ref_pic_list_b1_.begin(), iter, POCAscCompare());
+
+  // Now add [3] and sort by ascending long_term_pic_num
+  dpb_.GetLongTermRefPicsAppending(&ref_pic_list_b1_);
+  std::sort(ref_pic_list_b1_.begin() + num_short_refs, ref_pic_list_b1_.end(),
+            LongTermPicNumAscCompare());
+
+  // If lists identical, swap first two entries in RefPicList1 (spec 8.2.4.2.3)
+  if (ref_pic_list_b1_.size() > 1 &&
+      std::equal(ref_pic_list_b0_.begin(), ref_pic_list_b0_.end(),
+                 ref_pic_list_b1_.begin()))
+    std::swap(ref_pic_list_b1_[0], ref_pic_list_b1_[1]);
+}
+
+void H264Decoder::UpdatePicNums(int frame_num) {
+  for (auto& pic : dpb_) {
+    if (!pic->ref)
+      continue;
+
+    // 8.2.4.1. Assumes non-interlaced stream.
+    DCHECK_EQ(pic->field, H264Picture::FIELD_NONE);
+    if (pic->long_term) {
+      pic->long_term_pic_num = pic->long_term_frame_idx;
+    } else {
+      if (pic->frame_num > frame_num)
+        pic->frame_num_wrap = pic->frame_num - max_frame_num_;
+      else
+        pic->frame_num_wrap = pic->frame_num;
+
+      pic->pic_num = pic->frame_num_wrap;
+    }
+  }
+}
+
+bool H264Decoder::InitNonexistingPicture(scoped_refptr<H264Picture> pic,
+                                         int frame_num,
+                                         bool ref) {
+  pic->nonexisting = true;
+  pic->nal_ref_idc = 1;
+  pic->frame_num = pic->pic_num = frame_num;
+  pic->adaptive_ref_pic_marking_mode_flag = false;
+  pic->ref = ref;
+  pic->long_term_reference_flag = false;
+  pic->field = H264Picture::FIELD_NONE;
+
+  return CalculatePicOrderCounts(pic);
+}
+
+bool H264Decoder::HandleFrameNumGap(int frame_num) {
+  const H264SPS* sps = parser_->GetSPS(curr_sps_id_);
+  if (!sps)
+    return false;
+
+  if (!sps->gaps_in_frame_num_value_allowed_flag) {
+    VLOG(1) << "Invalid frame_num: " << frame_num;
+    // TODO(b:129119729, b:146914440): Youtube android app sometimes sends an
+    // invalid frame number after a seek. The sequence goes like:
+    // Seek, SPS, PPS, IDR-frame, non-IDR, ... non-IDR with invalid number.
+    // The only way to work around this reliably is to ignore this error.
+    // Video playback is not affected, no artefacts are visible.
+    // return false;
+  }
+
+  VLOG(2) << "Handling frame_num gap: " << prev_ref_frame_num_ << "->"
+          << frame_num;
+
+  // 7.4.3/7-23
+  int unused_short_term_frame_num = (prev_ref_frame_num_ + 1) % max_frame_num_;
+  while (unused_short_term_frame_num != frame_num) {
+    scoped_refptr<H264Picture> pic = va_wrapper_.CreatePicture(sps);
+    if (!InitNonexistingPicture(pic, unused_short_term_frame_num, true))
+      return false;
+
+    UpdatePicNums(unused_short_term_frame_num);
+
+    FinishPicture(pic);
+
+    unused_short_term_frame_num++;
+    unused_short_term_frame_num %= max_frame_num_;
+  }
+
+  return true;
+}
+
+bool H264Decoder::CalculatePicOrderCounts(scoped_refptr<H264Picture> pic) {
+  const H264SPS* sps = parser_->GetSPS(curr_sps_id_);
+  if (!sps)
+    return false;
+
+  switch (pic->pic_order_cnt_type) {
+    case 0: {
+      // See spec 8.2.1.1.
+      int prev_pic_order_cnt_msb, prev_pic_order_cnt_lsb;
+
+      if (pic->idr) {
+        prev_pic_order_cnt_msb = prev_pic_order_cnt_lsb = 0;
+      } else {
+        if (prev_ref_has_memmgmnt5_) {
+          if (prev_ref_field_ != H264Picture::FIELD_BOTTOM) {
+            prev_pic_order_cnt_msb = 0;
+            prev_pic_order_cnt_lsb = prev_ref_top_field_order_cnt_;
+          } else {
+            prev_pic_order_cnt_msb = 0;
+            prev_pic_order_cnt_lsb = 0;
+          }
+        } else {
+          prev_pic_order_cnt_msb = prev_ref_pic_order_cnt_msb_;
+          prev_pic_order_cnt_lsb = prev_ref_pic_order_cnt_lsb_;
+        }
+      }
+
+      int max_pic_order_cnt_lsb =
+          1 << (sps->log2_max_pic_order_cnt_lsb_minus4 + 4);
+      DCHECK_NE(max_pic_order_cnt_lsb, 0);
+      if ((pic->pic_order_cnt_lsb < prev_pic_order_cnt_lsb) &&
+          (prev_pic_order_cnt_lsb - pic->pic_order_cnt_lsb >=
+           max_pic_order_cnt_lsb / 2)) {
+        pic->pic_order_cnt_msb = prev_pic_order_cnt_msb + max_pic_order_cnt_lsb;
+      } else if ((pic->pic_order_cnt_lsb > prev_pic_order_cnt_lsb) &&
+                 (pic->pic_order_cnt_lsb - prev_pic_order_cnt_lsb >
+                  max_pic_order_cnt_lsb / 2)) {
+        pic->pic_order_cnt_msb = prev_pic_order_cnt_msb - max_pic_order_cnt_lsb;
+      } else {
+        pic->pic_order_cnt_msb = prev_pic_order_cnt_msb;
+      }
+
+      if (pic->field != H264Picture::FIELD_BOTTOM) {
+        pic->top_field_order_cnt =
+            pic->pic_order_cnt_msb + pic->pic_order_cnt_lsb;
+      }
+
+      if (pic->field != H264Picture::FIELD_TOP) {
+        if (pic->field == H264Picture::FIELD_NONE) {
+          pic->bottom_field_order_cnt =
+              pic->top_field_order_cnt + pic->delta_pic_order_cnt_bottom;
+        } else {
+          pic->bottom_field_order_cnt =
+              pic->pic_order_cnt_msb + pic->pic_order_cnt_lsb;
+        }
+      }
+      break;
+    }
+
+    case 1: {
+      // See spec 8.2.1.2.
+      if (prev_has_memmgmnt5_) {
+        prev_frame_num_offset_ = 0;
+      }
+
+      if (pic->idr) {
+        pic->frame_num_offset = 0;
+      } else if (prev_frame_num_ > pic->frame_num) {
+        pic->frame_num_offset = prev_frame_num_offset_ + max_frame_num_;
+      } else {
+        pic->frame_num_offset = prev_frame_num_offset_;
+      }
+
+      int abs_frame_num = 0;
+      if (sps->num_ref_frames_in_pic_order_cnt_cycle != 0) {
+        abs_frame_num = pic->frame_num_offset + pic->frame_num;
+      } else {
+        abs_frame_num = 0;
+      }
+
+      if (pic->nal_ref_idc == 0 && abs_frame_num > 0) {
+        --abs_frame_num;
+      }
+
+      int expected_pic_order_cnt = 0;
+      if (abs_frame_num > 0) {
+        if (sps->num_ref_frames_in_pic_order_cnt_cycle == 0) {
+          VLOG(1) << "Invalid num_ref_frames_in_pic_order_cnt_cycle "
+                  << "in stream";
+          return false;
+        }
+
+        int pic_order_cnt_cycle_cnt =
+            (abs_frame_num - 1) / sps->num_ref_frames_in_pic_order_cnt_cycle;
+        int frame_num_in_pic_order_cnt_cycle =
+            (abs_frame_num - 1) % sps->num_ref_frames_in_pic_order_cnt_cycle;
+
+        expected_pic_order_cnt = pic_order_cnt_cycle_cnt *
+                                 sps->expected_delta_per_pic_order_cnt_cycle;
+        // frame_num_in_pic_order_cnt_cycle is verified < 255 in parser
+        for (int i = 0; i <= frame_num_in_pic_order_cnt_cycle; ++i) {
+          expected_pic_order_cnt += sps->offset_for_ref_frame[i];
+        }
+      }
+
+      if (!pic->nal_ref_idc) {
+        expected_pic_order_cnt += sps->offset_for_non_ref_pic;
+      }
+
+      if (pic->field == H264Picture::FIELD_NONE) {
+        pic->top_field_order_cnt =
+            expected_pic_order_cnt + pic->delta_pic_order_cnt0;
+        pic->bottom_field_order_cnt = pic->top_field_order_cnt +
+                                      sps->offset_for_top_to_bottom_field +
+                                      pic->delta_pic_order_cnt1;
+      } else if (pic->field != H264Picture::FIELD_BOTTOM) {
+        pic->top_field_order_cnt =
+            expected_pic_order_cnt + pic->delta_pic_order_cnt0;
+      } else {
+        pic->bottom_field_order_cnt = expected_pic_order_cnt +
+                                      sps->offset_for_top_to_bottom_field +
+                                      pic->delta_pic_order_cnt0;
+      }
+      break;
+    }
+
+    case 2: {
+      // See spec 8.2.1.3.
+      if (prev_has_memmgmnt5_) {
+        prev_frame_num_offset_ = 0;
+      }
+
+      if (pic->idr) {
+        pic->frame_num_offset = 0;
+      } else if (prev_frame_num_ > pic->frame_num) {
+        pic->frame_num_offset = prev_frame_num_offset_ + max_frame_num_;
+      } else {
+        pic->frame_num_offset = prev_frame_num_offset_;
+      }
+
+      int temp_pic_order_cnt;
+      if (pic->idr) {
+        temp_pic_order_cnt = 0;
+      } else if (!pic->nal_ref_idc) {
+        temp_pic_order_cnt = 2 * (pic->frame_num_offset + pic->frame_num) - 1;
+      } else {
+        temp_pic_order_cnt = 2 * (pic->frame_num_offset + pic->frame_num);
+      }
+
+      if (pic->field == H264Picture::FIELD_NONE) {
+        pic->top_field_order_cnt = temp_pic_order_cnt;
+        pic->bottom_field_order_cnt = temp_pic_order_cnt;
+      } else if (pic->field == H264Picture::FIELD_BOTTOM) {
+        pic->bottom_field_order_cnt = temp_pic_order_cnt;
+      } else {
+        pic->top_field_order_cnt = temp_pic_order_cnt;
+      }
+      break;
+    }
+
+    default:
+      VLOG(1) << "Invalid pic_order_cnt_type: " << sps->pic_order_cnt_type;
+      return false;
+  }
+
+  switch (pic->field) {
+    case H264Picture::FIELD_NONE:
+      pic->pic_order_cnt =
+          std::min(pic->top_field_order_cnt, pic->bottom_field_order_cnt);
+      break;
+    case H264Picture::FIELD_TOP:
+      pic->pic_order_cnt = pic->top_field_order_cnt;
+      break;
+    case H264Picture::FIELD_BOTTOM:
+      pic->pic_order_cnt = pic->bottom_field_order_cnt;
+      break;
+  }
+
+  return true;
+}
+
+void H264Decoder::UpdateMaxNumReorderFrames(const H264SPS* sps) {
+  if (sps->vui_parameters_present_flag && sps->bitstream_restriction_flag) {
+    max_num_reorder_frames_ =
+        base::checked_cast<size_t>(sps->max_num_reorder_frames);
+  } else if (sps->constraint_set3_flag) {
+    // max_num_reorder_frames not present, infer from profile/constraints
+    // (see VUI semantics in spec).
+    switch (sps->profile_idc) {
+      case 44:
+      case 86:
+      case 100:
+      case 110:
+      case 122:
+      case 244:
+        max_num_reorder_frames_ = 0;
+        break;
+      default:
+        max_num_reorder_frames_ = dpb_.max_num_pics();
+        break;
+    }
+  } else {
+    max_num_reorder_frames_ = dpb_.max_num_pics();
+  }
+}
+
+bool H264Decoder::ModifyReferencePicLists(const H264SliceHeader* slice_hdr,
+                                          H264Picture::Vector* ref_pic_list0,
+                                          H264Picture::Vector* ref_pic_list1) {
+  ref_pic_list0->clear();
+  ref_pic_list1->clear();
+
+  // Fill reference picture lists for B and S/SP slices.
+  if (slice_hdr->IsPSlice() || slice_hdr->IsSPSlice()) {
+    *ref_pic_list0 = ref_pic_list_p0_;
+    return ModifyReferencePicList(slice_hdr, 0, ref_pic_list0);
+  } else if (slice_hdr->IsBSlice()) {
+    *ref_pic_list0 = ref_pic_list_b0_;
+    *ref_pic_list1 = ref_pic_list_b1_;
+    return ModifyReferencePicList(slice_hdr, 0, ref_pic_list0) &&
+           ModifyReferencePicList(slice_hdr, 1, ref_pic_list1);
+  }
+
+  return true;
+}
+
+bool H264Decoder::ModifyReferencePicList(const H264SliceHeader* slice_hdr,
+                                         int list,
+                                         H264Picture::Vector* ref_pic_listx) {
+  bool ref_pic_list_modification_flag_lX;
+  int num_ref_idx_lX_active_minus1;
+  const H264ModificationOfPicNum* list_mod;
+
+  // This can process either ref_pic_list0 or ref_pic_list1, depending on
+  // the list argument. Set up pointers to proper list to be processed here.
+  if (list == 0) {
+    ref_pic_list_modification_flag_lX =
+        slice_hdr->ref_pic_list_modification_flag_l0;
+    num_ref_idx_lX_active_minus1 = slice_hdr->num_ref_idx_l0_active_minus1;
+    list_mod = slice_hdr->ref_list_l0_modifications;
+  } else {
+    ref_pic_list_modification_flag_lX =
+        slice_hdr->ref_pic_list_modification_flag_l1;
+    num_ref_idx_lX_active_minus1 = slice_hdr->num_ref_idx_l1_active_minus1;
+    list_mod = slice_hdr->ref_list_l1_modifications;
+  }
+
+  // Resize the list to the size requested in the slice header.
+  // Note that per 8.2.4.2 it's possible for num_ref_idx_lX_active_minus1 to
+  // indicate there should be more ref pics on list than we constructed.
+  // Those superfluous ones should be treated as non-reference and will be
+  // initialized to nullptr, which must be handled by clients.
+  DCHECK_GE(num_ref_idx_lX_active_minus1, 0);
+  size_t original_size = ref_pic_listx->size();
+  ref_pic_listx->resize(num_ref_idx_lX_active_minus1 + 1);
+  for (int i = original_size; i < num_ref_idx_lX_active_minus1 + 1; i++) {
+    scoped_refptr<H264Picture> nonref_pic =
+        base::WrapRefCounted(new H264Picture(nullptr));
+    LOG_ASSERT(InitNonexistingPicture(nonref_pic, 0, false));
+    (*ref_pic_listx)[i] = nonref_pic;
+  }
+
+  if (!ref_pic_list_modification_flag_lX)
+    return true;
+
+  // Spec 8.2.4.3:
+  // Reorder pictures on the list in a way specified in the stream.
+  int pic_num_lx_pred = curr_picture_->pic_num;
+  int ref_idx_lx = 0;
+  int pic_num_lx_no_wrap;
+  int pic_num_lx;
+  bool done = false;
+  scoped_refptr<H264Picture> pic;
+  for (int i = 0; i < H264SliceHeader::kRefListModSize && !done; ++i) {
+    switch (list_mod->modification_of_pic_nums_idc) {
+      case 0:
+      case 1:
+        // Modify short reference picture position.
+        if (list_mod->modification_of_pic_nums_idc == 0) {
+          // Subtract given value from predicted PicNum.
+          pic_num_lx_no_wrap =
+              pic_num_lx_pred -
+              (static_cast<int>(list_mod->abs_diff_pic_num_minus1) + 1);
+          // Wrap around max_pic_num_ if it becomes < 0 as result
+          // of subtraction.
+          if (pic_num_lx_no_wrap < 0)
+            pic_num_lx_no_wrap += max_pic_num_;
+        } else {
+          // Add given value to predicted PicNum.
+          pic_num_lx_no_wrap =
+              pic_num_lx_pred +
+              (static_cast<int>(list_mod->abs_diff_pic_num_minus1) + 1);
+          // Wrap around max_pic_num_ if it becomes >= max_pic_num_ as result
+          // of the addition.
+          if (pic_num_lx_no_wrap >= max_pic_num_)
+            pic_num_lx_no_wrap -= max_pic_num_;
+        }
+
+        // For use in next iteration.
+        pic_num_lx_pred = pic_num_lx_no_wrap;
+
+        if (pic_num_lx_no_wrap > curr_picture_->pic_num)
+          pic_num_lx = pic_num_lx_no_wrap - max_pic_num_;
+        else
+          pic_num_lx = pic_num_lx_no_wrap;
+
+        DCHECK_LT(num_ref_idx_lX_active_minus1 + 1,
+                  H264SliceHeader::kRefListModSize);
+        pic = dpb_.GetShortRefPicByPicNum(pic_num_lx);
+        if (!pic) {
+          VLOG(1) << "Malformed stream, no pic num " << pic_num_lx;
+          return false;
+        }
+
+        if (ref_idx_lx > num_ref_idx_lX_active_minus1) {
+          VLOG(1) << "Bounds mismatch: expected " << ref_idx_lx
+                  << " <= " << num_ref_idx_lX_active_minus1;
+          return false;
+        }
+
+        ShiftRightAndInsert(ref_pic_listx, ref_idx_lx,
+                            num_ref_idx_lX_active_minus1, pic);
+        ref_idx_lx++;
+
+        for (int src = ref_idx_lx, dst = ref_idx_lx;
+             src <= num_ref_idx_lX_active_minus1 + 1; ++src) {
+          auto* src_pic = (*ref_pic_listx)[src].get();
+          int src_pic_num_lx = src_pic ? PicNumF(*src_pic) : -1;
+          if (src_pic_num_lx != pic_num_lx)
+            (*ref_pic_listx)[dst++] = (*ref_pic_listx)[src];
+        }
+        break;
+
+      case 2:
+        // Modify long term reference picture position.
+        DCHECK_LT(num_ref_idx_lX_active_minus1 + 1,
+                  H264SliceHeader::kRefListModSize);
+        pic = dpb_.GetLongRefPicByLongTermPicNum(list_mod->long_term_pic_num);
+        if (!pic) {
+          VLOG(1) << "Malformed stream, no pic num "
+                  << list_mod->long_term_pic_num;
+          return false;
+        }
+        ShiftRightAndInsert(ref_pic_listx, ref_idx_lx,
+                            num_ref_idx_lX_active_minus1, pic);
+        ref_idx_lx++;
+
+        for (int src = ref_idx_lx, dst = ref_idx_lx;
+             src <= num_ref_idx_lX_active_minus1 + 1; ++src) {
+          if ((*ref_pic_listx)[src] &&
+              LongTermPicNumF(*(*ref_pic_listx)[src]) !=
+                  static_cast<int>(list_mod->long_term_pic_num))
+            (*ref_pic_listx)[dst++] = (*ref_pic_listx)[src];
+        }
+        break;
+
+      case 3:
+        // End of modification list.
+        done = true;
+        break;
+
+      default:
+        // May be recoverable.
+        VLOG(1) << "Invalid modification_of_pic_nums_idc="
+                << list_mod->modification_of_pic_nums_idc << " in position "
+                << i;
+        break;
+    }
+
+    ++list_mod;
+  }
+
+  // Per NOTE 2 in 8.2.4.3.2, the ref_pic_listx size in the above loop is
+  // temporarily made one element longer than the required final list.
+  // Resize the list back to its required size.
+  ref_pic_listx->resize(num_ref_idx_lX_active_minus1 + 1);
+
+  return true;
+}
+
+// This method ensures that DPB does not overflow, either by removing
+// reference pictures as specified in the stream, or using a sliding window
+// procedure to remove the oldest one.
+// It also performs marking and unmarking pictures as reference.
+// See spac 8.2.5.1.
+bool H264Decoder::ReferencePictureMarking(scoped_refptr<H264Picture> pic) {
+  // If the current picture is an IDR, all reference pictures are unmarked.
+  if (pic->idr) {
+    dpb_.MarkAllUnusedForRef();
+
+    if (pic->long_term_reference_flag) {
+      pic->long_term = true;
+      pic->long_term_frame_idx = 0;
+      max_long_term_frame_idx_ = 0;
+    } else {
+      pic->long_term = false;
+      max_long_term_frame_idx_ = -1;
+    }
+
+    return true;
+  }
+
+  // Not an IDR. If the stream contains instructions on how to discard pictures
+  // from DPB and how to mark/unmark existing reference pictures, do so.
+  // Otherwise, fall back to default sliding window process.
+  if (pic->adaptive_ref_pic_marking_mode_flag) {
+    DCHECK(!pic->nonexisting);
+    return HandleMemoryManagementOps(pic);
+  } else {
+    return SlidingWindowPictureMarking();
+  }
+}
+
+bool H264Decoder::HandleMemoryManagementOps(scoped_refptr<H264Picture> pic) {
+  // 8.2.5.4
+  for (size_t i = 0; i < std::size(pic->ref_pic_marking); ++i) {
+    // Code below does not support interlaced stream (per-field pictures).
+    H264DecRefPicMarking* ref_pic_marking = &pic->ref_pic_marking[i];
+    scoped_refptr<H264Picture> to_mark;
+    int pic_num_x;
+
+    switch (ref_pic_marking->memory_mgmnt_control_operation) {
+      case 0:
+        // Normal end of operations' specification.
+        return true;
+
+      case 1:
+        // Mark a short term reference picture as unused so it can be removed
+        // if outputted.
+        pic_num_x =
+            pic->pic_num - (ref_pic_marking->difference_of_pic_nums_minus1 + 1);
+        to_mark = dpb_.GetShortRefPicByPicNum(pic_num_x);
+        if (to_mark) {
+          to_mark->ref = false;
+        } else {
+          VLOG(1) << "Invalid short ref pic num to unmark";
+          return false;
+        }
+        break;
+
+      case 2:
+        // Mark a long term reference picture as unused so it can be removed
+        // if outputted.
+        to_mark = dpb_.GetLongRefPicByLongTermPicNum(
+            ref_pic_marking->long_term_pic_num);
+        if (to_mark) {
+          to_mark->ref = false;
+        } else {
+          VLOG(1) << "Invalid long term ref pic num to unmark";
+          return false;
+        }
+        break;
+
+      case 3:
+        // Mark a short term reference picture as long term reference.
+        pic_num_x =
+            pic->pic_num - (ref_pic_marking->difference_of_pic_nums_minus1 + 1);
+        to_mark = dpb_.GetShortRefPicByPicNum(pic_num_x);
+        if (to_mark) {
+          DCHECK(to_mark->ref && !to_mark->long_term);
+          to_mark->long_term = true;
+          to_mark->long_term_frame_idx = ref_pic_marking->long_term_frame_idx;
+        } else {
+          VLOG(1) << "Invalid short term ref pic num to mark as long ref";
+          return false;
+        }
+        break;
+
+      case 4: {
+        // Unmark all reference pictures with long_term_frame_idx over new max.
+        max_long_term_frame_idx_ =
+            ref_pic_marking->max_long_term_frame_idx_plus1 - 1;
+        H264Picture::Vector long_terms;
+        dpb_.GetLongTermRefPicsAppending(&long_terms);
+        for (auto long_term_pic : long_terms) {
+          DCHECK(long_term_pic->ref && long_term_pic->long_term);
+          // Ok to cast, max_long_term_frame_idx is much smaller than 16bit.
+          if (long_term_pic->long_term_frame_idx >
+              static_cast<int>(max_long_term_frame_idx_))
+            long_term_pic->ref = false;
+        }
+        break;
+      }
+
+      case 5:
+        // Unmark all reference pictures.
+        dpb_.MarkAllUnusedForRef();
+        max_long_term_frame_idx_ = -1;
+        pic->mem_mgmt_5 = true;
+        break;
+
+      case 6: {
+        // Replace long term reference pictures with current picture.
+        // First unmark if any existing with this long_term_frame_idx...
+        H264Picture::Vector long_terms;
+        dpb_.GetLongTermRefPicsAppending(&long_terms);
+        for (auto long_term_pic : long_terms) {
+          DCHECK(long_term_pic->ref && long_term_pic->long_term);
+          // Ok to cast, long_term_frame_idx is much smaller than 16bit.
+          if (long_term_pic->long_term_frame_idx ==
+              static_cast<int>(ref_pic_marking->long_term_frame_idx))
+            long_term_pic->ref = false;
+        }
+
+        // and mark the current one instead.
+        pic->ref = true;
+        pic->long_term = true;
+        pic->long_term_frame_idx = ref_pic_marking->long_term_frame_idx;
+        break;
+      }
+
+      default:
+        // Would indicate a bug in parser.
+        NOTREACHED();
+    }
+  }
+
+  return true;
+}
+
+bool H264Decoder::SlidingWindowPictureMarking() {
+  const H264SPS* sps = parser_->GetSPS(curr_sps_id_);
+  if (!sps)
+    return false;
+
+  // 8.2.5.3. Ensure the DPB doesn't overflow by discarding the oldest picture.
+  int num_ref_pics = dpb_.CountRefPics();
+  DCHECK_LE(num_ref_pics, std::max<int>(sps->max_num_ref_frames, 1));
+  if (num_ref_pics == std::max<int>(sps->max_num_ref_frames, 1)) {
+    // Max number of reference pics reached, need to remove one of the short
+    // term ones. Find smallest frame_num_wrap short reference picture and mark
+    // it as unused.
+    scoped_refptr<H264Picture> to_unmark =
+        dpb_.GetLowestFrameNumWrapShortRefPic();
+    if (!to_unmark) {
+      VLOG(1) << "Couldn't find a short ref picture to unmark";
+      return false;
+    }
+
+    to_unmark->ref = false;
+  }
+
+  return true;
+}
+
+// See 8.2.4
+int H264Decoder::PicNumF(const H264Picture& pic) const {
+  if (!pic.long_term)
+    return pic.pic_num;
+  else
+    return max_pic_num_;
+}
+
+// See 8.2.4
+int H264Decoder::LongTermPicNumF(const H264Picture& pic) const {
+  if (pic.ref && pic.long_term)
+    return pic.long_term_pic_num;
+  else
+    return 2 * (max_long_term_frame_idx_ + 1);
+}
+
+// Shift elements on the |v| starting from |from| to |to|, inclusive,
+// one position to the right and insert pic at |from|.
+void H264Decoder::ShiftRightAndInsert(H264Picture::Vector* v,
+                                      int from,
+                                      int to,
+                                      scoped_refptr<H264Picture> pic) {
+  // Security checks, do not disable in Debug mode.
+  CHECK(from <= to);
+  CHECK(to <= std::numeric_limits<int>::max() - 2);
+  // Additional checks. Debug mode ok.
+  DCHECK(v);
+  DCHECK(pic);
+  DCHECK((to + 1 == static_cast<int>(v->size())) ||
+         (to + 2 == static_cast<int>(v->size())));
+
+  v->resize(to + 2);
+
+  for (int i = to + 1; i > from; --i)
+    (*v)[i] = (*v)[i - 1];
+
+  (*v)[from] = std::move(pic);
+}
+
+uint32_t H264Decoder::H264LevelToMaxDpbMbs(uint8_t level) {
+  switch (level) {
+    case H264SPS::kLevelIDC1p0:
+    case H264SPS::kLevelIDC1B:
+      return 396;
+    case H264SPS::kLevelIDC1p1:
+      return 900;
+    case H264SPS::kLevelIDC1p2:
+    case H264SPS::kLevelIDC1p3:
+    case H264SPS::kLevelIDC2p0:
+      return 2376;
+    case H264SPS::kLevelIDC2p1:
+      return 4752;
+    case H264SPS::kLevelIDC2p2:
+    case H264SPS::kLevelIDC3p0:
+      return 8100;
+    case H264SPS::kLevelIDC3p1:
+      return 18000;
+    case H264SPS::kLevelIDC3p2:
+      return 20480;
+    case H264SPS::kLevelIDC4p0:
+    case H264SPS::kLevelIDC4p1:
+      return 32768;
+    case H264SPS::kLevelIDC4p2:
+      return 34816;
+    case H264SPS::kLevelIDC5p0:
+      return 110400;
+    case H264SPS::kLevelIDC5p1:
+    case H264SPS::kLevelIDC5p2:
+      return 184320;
+    case H264SPS::kLevelIDC6p1:
+    case H264SPS::kLevelIDC6p2:
+      return 696320;
+    default:
+      return 0;
+  }
+}
+
+void H264Decoder::FlushDPB() {
+  H264Picture::Vector not_outputted_vec;
+  dpb_.GetNotOutputtedPicsAppending(&not_outputted_vec);
+  std::sort(not_outputted_vec.begin(), not_outputted_vec.end(),
+            POCDescCompare());
+  while (!not_outputted_vec.empty()) {
+    output_queue.push(not_outputted_vec.back());
+    not_outputted_vec.back()->outputted = true;
+    not_outputted_vec.pop_back();
+  }
+  dpb_.Clear();
+}
+
+}  // namespace media::vaapi_test
diff --git a/media/gpu/vaapi/test/h264_decoder.h b/media/gpu/vaapi/test/h264_decoder.h
new file mode 100644
index 0000000..76ff0bd
--- /dev/null
+++ b/media/gpu/vaapi/test/h264_decoder.h
@@ -0,0 +1,190 @@
+// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_GPU_VAAPI_TEST_H264_DECODER_H_
+#define MEDIA_GPU_VAAPI_TEST_H264_DECODER_H_
+
+#include "media/gpu/vaapi/test/h264_dpb.h"
+#include "media/gpu/vaapi/test/h264_vaapi_wrapper.h"
+#include "media/gpu/vaapi/test/video_decoder.h"
+#include "media/video/h264_parser.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+
+#include <queue>
+
+namespace media {
+namespace vaapi_test {
+
+class H264Decoder : public VideoDecoder {
+ public:
+  H264Decoder(const uint8_t* stream_data,
+              size_t stream_len,
+              const VaapiDevice& va_device,
+              SharedVASurface::FetchPolicy fetch_policy);
+  H264Decoder(const H264Decoder&) = delete;
+  H264Decoder& operator=(const H264Decoder&) = delete;
+  ~H264Decoder() override;
+
+  // VideoDecoder implementation.
+  VideoDecoder::Result DecodeNextFrame() override;
+
+ private:
+  // Decode the next frame in the bitstream.
+  void DecodeNextFrameInStream();
+
+  // Process a slice.
+  void ProcessSlice();
+
+  // Process an SPS NALU.
+  void UpdateSequenceParams();
+
+  // Process a PPS NALU.
+  void UpdatePictureParams();
+
+  // Issue the final commands to VAAPI to decode the slices we have been
+  // inputing.
+  void DecodeFrame();
+
+  // Finalize the picture and handle reference picture buffer memory management.
+  void FinishPicture(scoped_refptr<H264Picture> pic);
+
+  // Process SPS and PPS NALUs until we hit a slice header.
+  bool GetStreamMetadata();
+
+  // Detects if the last slice header refers to a new frame.
+  bool IsNewFrame();
+
+  // Populate some local variables based on a slice NALU.
+  void ExtractSliceHeader();
+
+  // Create a new picture and hand some metadata to VAAPI.
+  void StartNewFrame();
+
+  // Initialize a picture.
+  bool InitCurrPicture(const H264SliceHeader* slice_hdr);
+
+  // Some helper functions for preparing the reference picture buffers. H.264
+  // has separate P and B frame reference picture buffers.
+  void ConstructReferencePicListsP();
+  void ConstructReferencePicListsB();
+
+  // Re-order the reference frame buf based on the current frame number.
+  void UpdatePicNums(int frame_num);
+
+  // Some helper functions for handling gaps in the frame data.
+  bool InitNonexistingPicture(scoped_refptr<H264Picture> pic,
+                              int frame_num,
+                              bool ref);
+  bool HandleFrameNumGap(int frame_num);
+
+  // Calculate Pic Order Counts. This is approximately what we would intuitively
+  // think of as the frame number in display order, except it wraps around.
+  bool CalculatePicOrderCounts(scoped_refptr<H264Picture> pic);
+  void UpdateMaxNumReorderFrames(const H264SPS* sps);
+
+  // Populate the reference lists that we send to VAAPI using data from the
+  // DPB.
+  bool ModifyReferencePicLists(const H264SliceHeader* slice_hdr,
+                               H264Picture::Vector* ref_pic_list0,
+                               H264Picture::Vector* ref_pic_list1);
+  bool ModifyReferencePicList(const H264SliceHeader* slice_hdr,
+                              int list,
+                              H264Picture::Vector* ref_pic_listx);
+
+  // Helps manage the DPB and mark what we need to keep for reference and what
+  // we can mark as unused. Note that a frame might still be kept around even if
+  // it isn't being used as a reference because we might need to output it.
+  // Normal H.264 decoder implementations have finer grained cache control, but
+  // since we can only output one picture DecodeNextFrame() call, we have to
+  // keep a pretty big cache around until the output catches up to the decode.
+  bool ReferencePictureMarking(scoped_refptr<H264Picture> pic);
+  bool HandleMemoryManagementOps(scoped_refptr<H264Picture> pic);
+  bool SlidingWindowPictureMarking();
+  int PicNumF(const H264Picture& pic) const;
+  int LongTermPicNumF(const H264Picture& pic) const;
+  static void ShiftRightAndInsert(H264Picture::Vector* v,
+                                  int from,
+                                  int to,
+                                  scoped_refptr<H264Picture> pic);
+
+  // Used for computing how large the DPB should be for each level.
+  static uint32_t H264LevelToMaxDpbMbs(uint8_t level);
+
+  // Output all remaining images in the DPB.
+  void FlushDPB();
+
+  // H.264 NALU parser
+  std::unique_ptr<H264Parser> parser_;
+
+  // The last sequence param id.
+  int curr_sps_id_;
+
+  // The last picture param id.
+  int curr_pps_id_;
+
+  // The last slice header parsed.
+  std::unique_ptr<H264SliceHeader> curr_slice_hdr_;
+
+  // The last NALU that was parsed.
+  std::unique_ptr<H264NALU> curr_nalu_;
+
+  // Picture we are currently decoding. Not necessarily the one we will output
+  // since H.264 sends pictures out of order.
+  scoped_refptr<H264Picture> curr_picture_;
+
+  // Set once we hit EOS.
+  bool is_stream_over_;
+
+  // Reference picture lists. P = forward prediction, B = bidirectional
+  // prediction.
+  H264Picture::Vector ref_pic_list_p0_;
+  H264Picture::Vector ref_pic_list_b0_;
+  H264Picture::Vector ref_pic_list_b1_;
+
+  // Some primitive related to frame number to help with the ordering of the
+  // reference pictures. Note that "frame_num" in this instance does not
+  // intuitively refer to the index of the frame when it gets output, but just
+  // the index of the frame in the bitstream. Pic order count is more closely
+  // related to output order, but it can also wrap.
+  int max_frame_num_;
+  int max_pic_num_;
+  int max_long_term_frame_idx_;
+  size_t max_num_reorder_frames_;
+  int prev_frame_num_;
+  int prev_ref_frame_num_;
+  int prev_frame_num_offset_;
+  bool prev_has_memmgmnt5_;
+
+  // These are absl::nullopt unless get recovery point SEI message after Reset.
+  // A frame_num of the frame at output order that is correct in content.
+  absl::optional<int> recovery_frame_num_;
+  // A value in the recovery point SEI message to compute |recovery_frame_num_|
+  // later.
+  absl::optional<int> recovery_frame_cnt_;
+
+  // Buffer object to keep track of our reference images.
+  H264DPB dpb_;
+
+  // Some handy abstractions for issuing VAAPI calls.
+  H264VaapiWrapper va_wrapper_;
+
+  // Values related to previously decoded reference picture.
+  bool prev_ref_has_memmgmnt5_;
+  int prev_ref_top_field_order_cnt_;
+  int prev_ref_pic_order_cnt_msb_;
+  int prev_ref_pic_order_cnt_lsb_;
+  H264Picture::Field prev_ref_field_;
+
+  // The actual size of the output picture.
+  gfx::Rect visible_rect_;
+
+  // Pictures to flush in descending POC order.
+  std::queue<scoped_refptr<H264Picture>> output_queue;
+};
+
+}  // namespace vaapi_test
+}  // namespace media
+
+#endif  // MEDIA_GPU_VAAPI_TEST_H264_DECODER_H_
diff --git a/media/gpu/vaapi/test/h264_dpb.cc b/media/gpu/vaapi/test/h264_dpb.cc
new file mode 100644
index 0000000..05988116
--- /dev/null
+++ b/media/gpu/vaapi/test/h264_dpb.cc
@@ -0,0 +1,164 @@
+// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/gpu/vaapi/test/h264_dpb.h"
+
+#include "media/gpu/macros.h"
+#include "media/gpu/vaapi/test/macros.h"
+
+namespace media::vaapi_test {
+
+H264Picture::H264Picture(scoped_refptr<SharedVASurface> target_surface)
+    : pic_order_cnt_type(0),
+      top_field_order_cnt(0),
+      bottom_field_order_cnt(0),
+      pic_order_cnt(0),
+      pic_order_cnt_msb(0),
+      pic_order_cnt_lsb(0),
+      delta_pic_order_cnt_bottom(0),
+      delta_pic_order_cnt0(0),
+      delta_pic_order_cnt1(0),
+      pic_num(0),
+      long_term_pic_num(0),
+      frame_num(0),
+      frame_num_offset(0),
+      frame_num_wrap(0),
+      long_term_frame_idx(0),
+      type(H264SliceHeader::kPSlice),
+      nal_ref_idc(0),
+      idr(false),
+      idr_pic_id(0),
+      ref(false),
+      ref_pic_list_modification_flag_l0(0),
+      abs_diff_pic_num_minus1(0),
+      long_term(false),
+      outputted(false),
+      mem_mgmt_5(false),
+      nonexisting(false),
+      field(FIELD_NONE),
+      long_term_reference_flag(false),
+      adaptive_ref_pic_marking_mode_flag(false),
+      dpb_position(0),
+      surface(target_surface) {}
+
+H264Picture::~H264Picture() = default;
+
+H264DPB::H264DPB() : max_num_pics_(0) {}
+
+H264DPB::~H264DPB() = default;
+
+void H264DPB::set_max_num_pics(size_t max_num_pics) {
+  DCHECK_LE(max_num_pics, static_cast<size_t>(kDPBMaxSize));
+  max_num_pics_ = max_num_pics;
+  if (pics_.size() > max_num_pics_)
+    pics_.resize(max_num_pics_);
+}
+
+void H264DPB::Clear() {
+  pics_.clear();
+}
+
+void H264DPB::UpdatePicPositions() {
+  size_t i = 0;
+  for (auto& pic : pics_) {
+    pic->dpb_position = i;
+    ++i;
+  }
+}
+
+void H264DPB::DeleteByPOC(int poc) {
+  for (auto it = pics_.begin(); it != pics_.end(); ++it) {
+    if ((*it)->pic_order_cnt == poc) {
+      pics_.erase(it);
+      UpdatePicPositions();
+      return;
+    }
+  }
+  LOG_ASSERT(false) << "Missing POC: " << poc;
+}
+
+void H264DPB::DeleteUnused() {
+  for (auto it = pics_.begin(); it != pics_.end();) {
+    if ((*it)->outputted && !(*it)->ref)
+      it = pics_.erase(it);
+    else
+      ++it;
+  }
+  UpdatePicPositions();
+}
+
+void H264DPB::StorePic(scoped_refptr<H264Picture> pic) {
+  VLOG(3) << "Adding PicNum: " << pic->pic_num
+          << " ref: " << static_cast<int>(pic->ref)
+          << " longterm: " << static_cast<int>(pic->long_term) << " to DPB";
+  pic->dpb_position = pics_.size();
+  pics_.push_back(std::move(pic));
+}
+
+int H264DPB::CountRefPics() {
+  int ret = 0;
+  for (auto pic : pics_) {
+    if (pic->ref)
+      ++ret;
+  }
+  return ret;
+}
+
+void H264DPB::MarkAllUnusedForRef() {
+  for (auto pic : pics_)
+    pic->ref = false;
+}
+
+scoped_refptr<H264Picture> H264DPB::GetShortRefPicByPicNum(int pic_num) {
+  for (const auto& pic : pics_) {
+    if (pic->ref && !pic->long_term && pic->pic_num == pic_num)
+      return pic;
+  }
+
+  VLOG(1) << "Missing short ref pic num: " << pic_num;
+  return nullptr;
+}
+
+scoped_refptr<H264Picture> H264DPB::GetLongRefPicByLongTermPicNum(int pic_num) {
+  for (const auto& pic : pics_) {
+    if (pic->ref && pic->long_term && pic->long_term_pic_num == pic_num)
+      return pic;
+  }
+
+  VLOG(1) << "Missing long term pic num: " << pic_num;
+  return nullptr;
+}
+
+scoped_refptr<H264Picture> H264DPB::GetLowestFrameNumWrapShortRefPic() {
+  scoped_refptr<H264Picture> ret;
+  for (const auto& pic : pics_) {
+    if (pic->ref && !pic->long_term &&
+        (!ret || pic->frame_num_wrap < ret->frame_num_wrap))
+      ret = pic;
+  }
+  return ret;
+}
+
+void H264DPB::GetNotOutputtedPicsAppending(H264Picture::Vector* out) {
+  for (const auto& pic : pics_) {
+    if (!pic->outputted)
+      out->push_back(pic);
+  }
+}
+
+void H264DPB::GetShortTermRefPicsAppending(H264Picture::Vector* out) {
+  for (const auto& pic : pics_) {
+    if (pic->ref && !pic->long_term)
+      out->push_back(pic);
+  }
+}
+
+void H264DPB::GetLongTermRefPicsAppending(H264Picture::Vector* out) {
+  for (const auto& pic : pics_) {
+    if (pic->ref && pic->long_term)
+      out->push_back(pic);
+  }
+}
+
+}  // namespace media::vaapi_test
diff --git a/media/gpu/vaapi/test/h264_dpb.h b/media/gpu/vaapi/test/h264_dpb.h
new file mode 100644
index 0000000..312ba2d
--- /dev/null
+++ b/media/gpu/vaapi/test/h264_dpb.h
@@ -0,0 +1,183 @@
+// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_GPU_VAAPI_TEST_H264_DPB_H_
+#define MEDIA_GPU_VAAPI_TEST_H264_DPB_H_
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "media/gpu/vaapi/test/shared_va_surface.h"
+#include "media/video/h264_parser.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace media::vaapi_test {
+
+// Abstract reference of a decoded picture. Helpful for keeping track of not
+// only the decoded surface, but metadata necessary for memory management. Note
+// that this object owns both the surface of the decoded buffer and the
+// compressed slice data buffers memory.
+class H264Picture : public base::RefCountedThreadSafe<H264Picture> {
+ public:
+  using Vector = std::vector<scoped_refptr<H264Picture>>;
+
+  enum Field {
+    FIELD_NONE,
+    FIELD_TOP,
+    FIELD_BOTTOM,
+  };
+
+  // Values calculated per H.264 specification or taken from slice header.
+  // See spec for more details on each (some names have been converted from
+  // CamelCase in spec to Chromium-style names).
+  int pic_order_cnt_type = 0;
+  int top_field_order_cnt = 0;
+  int bottom_field_order_cnt = 0;
+  int pic_order_cnt = 0;  // note that this can wrap around
+  int pic_order_cnt_msb = 0;
+  int pic_order_cnt_lsb = 0;
+  int delta_pic_order_cnt_bottom = 0;
+  int delta_pic_order_cnt0 = 0;
+  int delta_pic_order_cnt1 = 0;
+
+  int pic_num = 0;
+  int long_term_pic_num = 0;
+  int frame_num = 0;  // from slice header, not picture order count
+  int frame_num_offset = 0;
+  int frame_num_wrap = 0;
+  int long_term_frame_idx = 0;
+
+  // Keep the underlying data for the VAAPI buffers around as long as the
+  // surface is around.
+  std::vector<std::unique_ptr<uint8_t[]>> slice_data_buffers;
+
+  H264SliceHeader::Type type;
+  int nal_ref_idc = 0;
+  bool idr = false;    // IDR picture?
+  int idr_pic_id = 0;  // Valid only if idr == true.
+  bool ref = false;    // reference picture?
+  int ref_pic_list_modification_flag_l0 = 0;
+  int abs_diff_pic_num_minus1 = 0;
+  bool long_term = false;  // long term reference picture?
+  bool outputted = false;
+  // Does memory management op 5 needs to be executed after this
+  // picture has finished decoding?
+  bool mem_mgmt_5 = false;
+
+  // Created by the decoding process for gaps in frame_num.
+  // Not for decode or output.
+  bool nonexisting = false;
+
+  Field field;
+
+  // Values from slice_hdr to be used during reference marking and
+  // memory management after finishing this picture.
+  bool long_term_reference_flag = false;
+  bool adaptive_ref_pic_marking_mode_flag = false;
+  H264DecRefPicMarking ref_pic_marking[H264SliceHeader::kRefListSize];
+
+  // Position in DPB (i.e. index in DPB).
+  int dpb_position = 0;
+
+  // Visible rectangle. Note that this may not match the size of the coded
+  // picture, since H.264 pads them to be 16 pixel aligned in all dimensions.
+  // This is the source of truth for the output image size however.
+  gfx::Rect visible_rect;
+
+  scoped_refptr<SharedVASurface> surface;
+
+  explicit H264Picture(scoped_refptr<SharedVASurface> target_surface);
+
+ protected:
+  friend class base::RefCountedThreadSafe<H264Picture>;
+
+  virtual ~H264Picture();
+};
+
+// DPB - Decoded Picture Buffer.
+// Stores decoded pictures that will be used for future display
+// and/or reference.
+class H264DPB {
+ public:
+  H264DPB();
+  ~H264DPB();
+
+  H264DPB(const H264DPB&) = delete;
+  H264DPB& operator=(const H264DPB&) = delete;
+
+  void set_max_num_pics(size_t max_num_pics);
+  size_t max_num_pics() const { return max_num_pics_; }
+
+  // Remove unused (not reference and already outputted) pictures from DPB
+  // and free it.
+  void DeleteUnused();
+
+  // Remove a picture by its pic_order_cnt and free it.
+  void DeleteByPOC(int poc);
+
+  // Clear DPB.
+  void Clear();
+
+  // Store picture in DPB. DPB takes ownership of its resources.
+  void StorePic(scoped_refptr<H264Picture> pic);
+
+  // Return the number of reference pictures in DPB.
+  int CountRefPics();
+
+  // Mark all pictures in DPB as unused for reference.
+  void MarkAllUnusedForRef();
+
+  // Return a short-term reference picture by its pic_num.
+  scoped_refptr<H264Picture> GetShortRefPicByPicNum(int pic_num);
+
+  // Return a long-term reference picture by its long_term_pic_num.
+  scoped_refptr<H264Picture> GetLongRefPicByLongTermPicNum(int pic_num);
+
+  // Return the short reference picture with lowest frame_num. Used for sliding
+  // window memory management.
+  scoped_refptr<H264Picture> GetLowestFrameNumWrapShortRefPic();
+
+  // Append all pictures that have not been outputted yet to the passed |out|
+  // vector, sorted by lowest pic_order_cnt (in output order).
+  void GetNotOutputtedPicsAppending(H264Picture::Vector* out);
+
+  // Append all short term reference pictures to the passed |out| vector.
+  void GetShortTermRefPicsAppending(H264Picture::Vector* out);
+
+  // Append all long term reference pictures to the passed |out| vector.
+  void GetLongTermRefPicsAppending(H264Picture::Vector* out);
+
+  // Iterators for direct access to DPB contents.
+  // Will be invalidated after any of Remove* calls.
+  H264Picture::Vector::iterator begin() { return pics_.begin(); }
+  H264Picture::Vector::iterator end() { return pics_.end(); }
+  H264Picture::Vector::const_iterator begin() const { return pics_.begin(); }
+  H264Picture::Vector::const_iterator end() const { return pics_.end(); }
+  H264Picture::Vector::const_reverse_iterator rbegin() const {
+    return pics_.rbegin();
+  }
+  H264Picture::Vector::const_reverse_iterator rend() const {
+    return pics_.rend();
+  }
+
+  size_t size() const { return pics_.size(); }
+  bool IsFull() const { return pics_.size() >= max_num_pics_; }
+
+  // Per H264 spec, increase to 32 if interlaced video is supported.
+  enum {
+    kDPBMaxSize = 16,
+  };
+
+ private:
+  void UpdatePicPositions();
+
+  H264Picture::Vector pics_;
+  size_t max_num_pics_ = 0;
+};
+
+}  // namespace media::vaapi_test
+
+#endif  // MEDIA_GPU_VAAPI_TEST_H264_DPB_H_
diff --git a/media/gpu/vaapi/test/h264_vaapi_wrapper.cc b/media/gpu/vaapi/test/h264_vaapi_wrapper.cc
new file mode 100644
index 0000000..c878795
--- /dev/null
+++ b/media/gpu/vaapi/test/h264_vaapi_wrapper.cc
@@ -0,0 +1,397 @@
+// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/gpu/vaapi/test/h264_vaapi_wrapper.h"
+
+#include "base/cxx17_backports.h"
+#include "base/trace_event/trace_event.h"
+#include "media/gpu/macros.h"
+#include "media/gpu/vaapi/test/h264_dpb.h"
+#include "media/gpu/vaapi/test/macros.h"
+#include "media/gpu/vaapi/test/scoped_va_config.h"
+#include "media/gpu/vaapi/test/scoped_va_context.h"
+#include "media/gpu/vaapi/test/shared_va_surface.h"
+#include "media/gpu/vaapi/test/vaapi_device.h"
+#include "media/video/h264_parser.h"
+
+#include <va/va.h>
+#include <memory>
+
+namespace media::vaapi_test {
+
+namespace {
+
+// from ITU-T REC H.264 spec
+// section 8.5.6
+// "Inverse scanning process for 4x4 transform coefficients and scaling lists"
+static constexpr int kZigzagScan4x4[16] = {0, 1,  4,  8,  5, 2,  3,  6,
+                                           9, 12, 13, 10, 7, 11, 14, 15};
+
+// section 8.5.7
+// "Inverse scanning process for 8x8 transform coefficients and scaling lists"
+static constexpr uint8_t kZigzagScan8x8[64] = {
+    0,  1,  8,  16, 9,  2,  3,  10, 17, 24, 32, 25, 18, 11, 4,  5,
+    12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6,  7,  14, 21, 28,
+    35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
+    58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63};
+
+VAProfile GetProfile(const H264SPS* sps) {
+  switch (sps->profile_idc) {
+    case H264SPS::kProfileIDCBaseline:
+      return VAProfileH264ConstrainedBaseline;
+    case H264SPS::kProfileIDCMain:
+      return VAProfileH264Main;
+    case H264SPS::kProfileIDCHigh:
+      return VAProfileH264High;
+    case H264SPS::kProfileIDSMultiviewHigh:
+      return VAProfileH264MultiviewHigh;
+    case H264SPS::kProfileIDStereoHigh:
+      return VAProfileH264StereoHigh;
+    default:
+      LOG_ASSERT(false) << "Invalid IDC profile " << sps->profile_idc;
+      return VAProfileNone;
+  }
+}
+
+unsigned int GetFormatForProfile(const VAProfile& profile) {
+  // VAAPI doesn't support H.264 10 bit color.
+  return VA_RT_FORMAT_YUV420;
+}
+
+void InitVAPicture(VAPictureH264* va_pic) {
+  memset(va_pic, 0, sizeof(*va_pic));
+  va_pic->picture_id = VA_INVALID_ID;
+  va_pic->flags = VA_PICTURE_H264_INVALID;
+}
+
+void FillVAPicture(VAPictureH264* va_pic, scoped_refptr<H264Picture> pic) {
+  VASurfaceID va_surface_id = VA_INVALID_SURFACE;
+
+  if (!pic->nonexisting)
+    va_surface_id = pic->surface->id();
+
+  va_pic->picture_id = va_surface_id;
+  va_pic->frame_idx = pic->frame_num;
+  va_pic->flags = 0;
+
+  switch (pic->field) {
+    case H264Picture::FIELD_NONE:
+      break;
+    case H264Picture::FIELD_TOP:
+      va_pic->flags |= VA_PICTURE_H264_TOP_FIELD;
+      break;
+    case H264Picture::FIELD_BOTTOM:
+      va_pic->flags |= VA_PICTURE_H264_BOTTOM_FIELD;
+      break;
+  }
+
+  if (pic->ref) {
+    va_pic->flags |= pic->long_term ? VA_PICTURE_H264_LONG_TERM_REFERENCE
+                                    : VA_PICTURE_H264_SHORT_TERM_REFERENCE;
+  }
+
+  va_pic->TopFieldOrderCnt = pic->top_field_order_cnt;
+  va_pic->BottomFieldOrderCnt = pic->bottom_field_order_cnt;
+}
+
+int FillVARefFramesFromDPB(const H264DPB& dpb,
+                           VAPictureH264* va_pics,
+                           int num_pics) {
+  H264Picture::Vector::const_reverse_iterator rit;
+  int i;
+
+  // Return reference frames in reverse order of insertion.
+  // Libva does not document this, but other implementations (e.g. mplayer)
+  // do it this way as well.
+  for (rit = dpb.rbegin(), i = 0; rit != dpb.rend() && i < num_pics; ++rit) {
+    if ((*rit)->ref)
+      FillVAPicture(&va_pics[i++], *rit);
+  }
+
+  return i;
+}
+
+}  // namespace
+
+H264VaapiWrapper::H264VaapiWrapper(const VaapiDevice& va_device)
+    : va_device_(va_device), va_config_(nullptr), va_context_(nullptr) {}
+
+H264VaapiWrapper::~H264VaapiWrapper() = default;
+
+scoped_refptr<H264Picture> H264VaapiWrapper::CreatePicture(const H264SPS* sps) {
+  const VAProfile profile = GetProfile(sps);
+  const gfx::Size size = sps->GetVisibleRect().value().size();
+
+  if (!va_config_) {
+    va_config_ = std::make_unique<ScopedVAConfig>(va_device_, profile,
+                                                  GetFormatForProfile(profile));
+  }
+  if (!va_context_) {
+    va_context_ =
+        std::make_unique<ScopedVAContext>(va_device_, *va_config_, size);
+  }
+
+  VASurfaceAttrib attribute{};
+  attribute.type = VASurfaceAttribUsageHint;
+  attribute.flags = VA_SURFACE_ATTRIB_SETTABLE;
+  attribute.value.type = VAGenericValueTypeInteger;
+  attribute.value.value.i = VA_SURFACE_ATTRIB_USAGE_HINT_DECODER;
+
+  scoped_refptr<SharedVASurface> surface = SharedVASurface::Create(
+      va_device_, va_config_->va_rt_format(), size, attribute);
+
+  return base::WrapRefCounted(new H264Picture(surface));
+}
+
+void H264VaapiWrapper::SubmitFrameMetadata(
+    const H264SPS* sps,
+    const H264PPS* pps,
+    const H264DPB& dpb,
+    const H264Picture::Vector& ref_pic_listp0,
+    const H264Picture::Vector& ref_pic_listb0,
+    const H264Picture::Vector& ref_pic_listb1,
+    scoped_refptr<H264Picture> pic) {
+  VAPictureParameterBufferH264 pic_param;
+  memset(&pic_param, 0, sizeof(pic_param));
+
+#define FROM_SPS_TO_PP(a) pic_param.a = sps->a
+#define FROM_SPS_TO_PP2(a, b) pic_param.b = sps->a
+  FROM_SPS_TO_PP2(pic_width_in_mbs_minus1, picture_width_in_mbs_minus1);
+  // This assumes non-interlaced video
+  FROM_SPS_TO_PP2(pic_height_in_map_units_minus1, picture_height_in_mbs_minus1);
+  FROM_SPS_TO_PP(bit_depth_luma_minus8);
+  FROM_SPS_TO_PP(bit_depth_chroma_minus8);
+#undef FROM_SPS_TO_PP
+#undef FROM_SPS_TO_PP2
+
+#define FROM_SPS_TO_PP_SF(a) pic_param.seq_fields.bits.a = sps->a
+#define FROM_SPS_TO_PP_SF2(a, b) pic_param.seq_fields.bits.b = sps->a
+  FROM_SPS_TO_PP_SF(chroma_format_idc);
+  FROM_SPS_TO_PP_SF2(separate_colour_plane_flag,
+                     residual_colour_transform_flag);
+  FROM_SPS_TO_PP_SF(gaps_in_frame_num_value_allowed_flag);
+  FROM_SPS_TO_PP_SF(frame_mbs_only_flag);
+  FROM_SPS_TO_PP_SF(mb_adaptive_frame_field_flag);
+  FROM_SPS_TO_PP_SF(direct_8x8_inference_flag);
+  pic_param.seq_fields.bits.MinLumaBiPredSize8x8 = (sps->level_idc >= 31);
+  FROM_SPS_TO_PP_SF(log2_max_frame_num_minus4);
+  FROM_SPS_TO_PP_SF(pic_order_cnt_type);
+  FROM_SPS_TO_PP_SF(log2_max_pic_order_cnt_lsb_minus4);
+  FROM_SPS_TO_PP_SF(delta_pic_order_always_zero_flag);
+#undef FROM_SPS_TO_PP_SF
+#undef FROM_SPS_TO_PP_SF2
+
+#define FROM_PPS_TO_PP(a) pic_param.a = pps->a
+  FROM_PPS_TO_PP(pic_init_qp_minus26);
+  FROM_PPS_TO_PP(pic_init_qs_minus26);
+  FROM_PPS_TO_PP(chroma_qp_index_offset);
+  FROM_PPS_TO_PP(second_chroma_qp_index_offset);
+#undef FROM_PPS_TO_PP
+
+#define FROM_PPS_TO_PP_PF(a) pic_param.pic_fields.bits.a = pps->a
+#define FROM_PPS_TO_PP_PF2(a, b) pic_param.pic_fields.bits.b = pps->a
+  FROM_PPS_TO_PP_PF(entropy_coding_mode_flag);
+  FROM_PPS_TO_PP_PF(weighted_pred_flag);
+  FROM_PPS_TO_PP_PF(weighted_bipred_idc);
+  FROM_PPS_TO_PP_PF(transform_8x8_mode_flag);
+
+  pic_param.pic_fields.bits.field_pic_flag = 0;
+  FROM_PPS_TO_PP_PF(constrained_intra_pred_flag);
+  FROM_PPS_TO_PP_PF2(bottom_field_pic_order_in_frame_present_flag,
+                     pic_order_present_flag);
+  FROM_PPS_TO_PP_PF(deblocking_filter_control_present_flag);
+  FROM_PPS_TO_PP_PF(redundant_pic_cnt_present_flag);
+  pic_param.pic_fields.bits.reference_pic_flag = pic->ref;
+#undef FROM_PPS_TO_PP_PF
+#undef FROM_PPS_TO_PP_PF2
+
+  pic_param.frame_num = pic->frame_num;
+
+  InitVAPicture(&pic_param.CurrPic);
+  FillVAPicture(&pic_param.CurrPic, std::move(pic));
+
+  // Init reference pictures' array.
+  for (int i = 0; i < 16; ++i)
+    InitVAPicture(&pic_param.ReferenceFrames[i]);
+
+  // And fill it with picture info from DPB.
+  FillVARefFramesFromDPB(dpb, pic_param.ReferenceFrames,
+                         std::size(pic_param.ReferenceFrames));
+
+  pic_param.num_ref_frames = sps->max_num_ref_frames;
+
+  VAIQMatrixBufferH264 iq_matrix_buf;
+  memset(&iq_matrix_buf, 0, sizeof(iq_matrix_buf));
+
+  if (pps->pic_scaling_matrix_present_flag) {
+    for (int i = 0; i < 6; ++i) {
+      for (int j = 0; j < 16; ++j)
+        iq_matrix_buf.ScalingList4x4[i][kZigzagScan4x4[j]] =
+            pps->scaling_list4x4[i][j];
+    }
+
+    for (int i = 0; i < 2; ++i) {
+      for (int j = 0; j < 64; ++j)
+        iq_matrix_buf.ScalingList8x8[i][kZigzagScan8x8[j]] =
+            pps->scaling_list8x8[i][j];
+    }
+  } else {
+    for (int i = 0; i < 6; ++i) {
+      for (int j = 0; j < 16; ++j)
+        iq_matrix_buf.ScalingList4x4[i][kZigzagScan4x4[j]] =
+            sps->scaling_list4x4[i][j];
+    }
+
+    for (int i = 0; i < 2; ++i) {
+      for (int j = 0; j < 64; ++j)
+        iq_matrix_buf.ScalingList8x8[i][kZigzagScan8x8[j]] =
+            sps->scaling_list8x8[i][j];
+    }
+  }
+
+  VABufferID buffer_id;
+  VAStatus va_res = vaCreateBuffer(
+      va_device_.display(), va_context_->id(), VAPictureParameterBufferType,
+      sizeof(pic_param), 1, &pic_param, &buffer_id);
+  VA_LOG_ASSERT(va_res, "vaCreateBuffer");
+  buffers_.push_back(buffer_id);
+  va_res = vaCreateBuffer(va_device_.display(), va_context_->id(),
+                          VAIQMatrixBufferType, sizeof(iq_matrix_buf), 1,
+                          &iq_matrix_buf, &buffer_id);
+  VA_LOG_ASSERT(va_res, "vaCreateBuffer");
+  buffers_.push_back(buffer_id);
+}
+
+void H264VaapiWrapper::SubmitSlice(
+    const H264PPS* pps,
+    const H264SliceHeader* slice_hdr,
+    const H264Picture::Vector& ref_pic_list0,
+    const H264Picture::Vector& ref_pic_list1,
+    scoped_refptr<H264Picture> pic,
+    const uint8_t* data,
+    size_t size,
+    const std::vector<SubsampleEntry>& subsamples) {
+  VASliceParameterBufferH264 slice_param;
+  memset(&slice_param, 0, sizeof(slice_param));
+
+  slice_param.slice_data_size = slice_hdr->nalu_size;
+  slice_param.slice_data_offset = 0;
+  slice_param.slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
+  slice_param.slice_data_bit_offset = slice_hdr->header_bit_size;
+
+#define SHDRToSP(a) slice_param.a = slice_hdr->a
+  SHDRToSP(first_mb_in_slice);
+  slice_param.slice_type = slice_hdr->slice_type % 5;
+  SHDRToSP(direct_spatial_mv_pred_flag);
+
+  SHDRToSP(num_ref_idx_l0_active_minus1);
+  SHDRToSP(num_ref_idx_l1_active_minus1);
+  SHDRToSP(cabac_init_idc);
+  SHDRToSP(slice_qp_delta);
+  SHDRToSP(disable_deblocking_filter_idc);
+  SHDRToSP(slice_alpha_c0_offset_div2);
+  SHDRToSP(slice_beta_offset_div2);
+
+  if (((slice_hdr->IsPSlice() || slice_hdr->IsSPSlice()) &&
+       pps->weighted_pred_flag) ||
+      (slice_hdr->IsBSlice() && pps->weighted_bipred_idc == 1)) {
+    SHDRToSP(luma_log2_weight_denom);
+    SHDRToSP(chroma_log2_weight_denom);
+
+    SHDRToSP(luma_weight_l0_flag);
+    SHDRToSP(luma_weight_l1_flag);
+
+    SHDRToSP(chroma_weight_l0_flag);
+    SHDRToSP(chroma_weight_l1_flag);
+
+    for (int i = 0; i <= slice_param.num_ref_idx_l0_active_minus1; ++i) {
+      slice_param.luma_weight_l0[i] =
+          slice_hdr->pred_weight_table_l0.luma_weight[i];
+      slice_param.luma_offset_l0[i] =
+          slice_hdr->pred_weight_table_l0.luma_offset[i];
+
+      for (int j = 0; j < 2; ++j) {
+        slice_param.chroma_weight_l0[i][j] =
+            slice_hdr->pred_weight_table_l0.chroma_weight[i][j];
+        slice_param.chroma_offset_l0[i][j] =
+            slice_hdr->pred_weight_table_l0.chroma_offset[i][j];
+      }
+    }
+
+    if (slice_hdr->IsBSlice()) {
+      for (int i = 0; i <= slice_param.num_ref_idx_l1_active_minus1; ++i) {
+        slice_param.luma_weight_l1[i] =
+            slice_hdr->pred_weight_table_l1.luma_weight[i];
+        slice_param.luma_offset_l1[i] =
+            slice_hdr->pred_weight_table_l1.luma_offset[i];
+
+        for (int j = 0; j < 2; ++j) {
+          slice_param.chroma_weight_l1[i][j] =
+              slice_hdr->pred_weight_table_l1.chroma_weight[i][j];
+          slice_param.chroma_offset_l1[i][j] =
+              slice_hdr->pred_weight_table_l1.chroma_offset[i][j];
+        }
+      }
+    }
+  }
+
+  static_assert(
+      std::size(slice_param.RefPicList0) == std::size(slice_param.RefPicList1),
+      "Invalid RefPicList sizes");
+
+  for (size_t i = 0; i < std::size(slice_param.RefPicList0); ++i) {
+    InitVAPicture(&slice_param.RefPicList0[i]);
+    InitVAPicture(&slice_param.RefPicList1[i]);
+  }
+
+  for (size_t i = 0;
+       i < ref_pic_list0.size() && i < std::size(slice_param.RefPicList0);
+       ++i) {
+    if (ref_pic_list0[i])
+      FillVAPicture(&slice_param.RefPicList0[i], ref_pic_list0[i]);
+  }
+  for (size_t i = 0;
+       i < ref_pic_list1.size() && i < std::size(slice_param.RefPicList1);
+       ++i) {
+    if (ref_pic_list1[i])
+      FillVAPicture(&slice_param.RefPicList1[i], ref_pic_list1[i]);
+  }
+
+  pic->slice_data_buffers.emplace_back(std::make_unique<uint8_t[]>(size));
+  memcpy(pic->slice_data_buffers.back().get(), data, size);
+
+  VABufferID buffer_id;
+  VAStatus va_res = vaCreateBuffer(
+      va_device_.display(), va_context_->id(), VASliceParameterBufferType,
+      sizeof(slice_param), 1, &slice_param, &buffer_id);
+  VA_LOG_ASSERT(va_res, "vaCreateBuffer");
+  buffers_.push_back(buffer_id);
+  va_res = vaCreateBuffer(va_device_.display(), va_context_->id(),
+                          VASliceDataBufferType, size, 1,
+                          pic->slice_data_buffers.back().get(), &buffer_id);
+  VA_LOG_ASSERT(va_res, "vaCreateBuffer");
+  buffers_.push_back(buffer_id);
+}
+
+void H264VaapiWrapper::SubmitDecode(scoped_refptr<H264Picture> pic) {
+  CHECK(gfx::Rect(pic->surface->size()).Contains(pic->visible_rect));
+
+  VAStatus va_res = vaBeginPicture(va_device_.display(), va_context_->id(),
+                                   pic->surface->id());
+  VA_LOG_ASSERT(va_res, "vaBeginPicture");
+
+  va_res = vaRenderPicture(va_device_.display(), va_context_->id(),
+                           buffers_.data(), buffers_.size());
+  VA_LOG_ASSERT(va_res, "vaRenderPicture");
+
+  va_res = vaEndPicture(va_device_.display(), va_context_->id());
+  VA_LOG_ASSERT(va_res, "vaEndPicture");
+
+  for (auto id : buffers_) {
+    vaDestroyBuffer(va_device_.display(), id);
+  }
+  buffers_.clear();
+}
+
+}  // namespace media::vaapi_test
diff --git a/media/gpu/vaapi/test/h264_vaapi_wrapper.h b/media/gpu/vaapi/test/h264_vaapi_wrapper.h
new file mode 100644
index 0000000..f1db94b
--- /dev/null
+++ b/media/gpu/vaapi/test/h264_vaapi_wrapper.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_GPU_VAAPI_TEST_H264_VAAPI_WRAPPER_H_
+#define MEDIA_GPU_VAAPI_TEST_H264_VAAPI_WRAPPER_H_
+
+#include "base/sequence_checker.h"
+#include "media/gpu/vaapi/test/h264_dpb.h"
+#include "media/gpu/vaapi/test/scoped_va_config.h"
+#include "media/gpu/vaapi/test/scoped_va_context.h"
+#include "media/gpu/vaapi/test/vaapi_device.h"
+#include "media/video/h264_parser.h"
+
+namespace media::vaapi_test {
+
+class H264VaapiWrapper {
+ public:
+  explicit H264VaapiWrapper(const VaapiDevice& va_device);
+  ~H264VaapiWrapper();
+
+  // Generates a picture object with a SharedVASurface.
+  scoped_refptr<H264Picture> CreatePicture(const H264SPS* sps);
+
+  // Start a new frame.
+  void SubmitFrameMetadata(const H264SPS* sps,
+                           const H264PPS* pps,
+                           const H264DPB& dpb,
+                           const H264Picture::Vector& ref_pic_listp0,
+                           const H264Picture::Vector& ref_pic_listb0,
+                           const H264Picture::Vector& ref_pic_listb1,
+                           scoped_refptr<H264Picture> pic);
+
+  // Add a slice to the current frame.
+  void SubmitSlice(const H264PPS* pps,
+                   const H264SliceHeader* slice_header,
+                   const H264Picture::Vector& ref_pic_list0,
+                   const H264Picture::Vector& ref_pic_list1,
+                   scoped_refptr<H264Picture> pic,
+                   const uint8_t* data,
+                   size_t size,
+                   const std::vector<SubsampleEntry>& subsamples);
+
+  // Perform the actual decoding process.
+  void SubmitDecode(scoped_refptr<H264Picture> pic);
+
+ private:
+  const VaapiDevice& va_device_;
+
+  std::unique_ptr<ScopedVAConfig> va_config_;
+  std::unique_ptr<ScopedVAContext> va_context_;
+
+  std::vector<VABufferID> buffers_;
+};
+
+}  // namespace media::vaapi_test
+
+#endif  // MEDIA_GPU_VAAPI_TEST_H264_VAAPI_WRAPPER_H_
diff --git a/media/gpu/vaapi/test/video_decoder.cc b/media/gpu/vaapi/test/video_decoder.cc
index 47d3045..c779534 100644
--- a/media/gpu/vaapi/test/video_decoder.cc
+++ b/media/gpu/vaapi/test/video_decoder.cc
@@ -9,12 +9,9 @@
 namespace media {
 namespace vaapi_test {
 
-VideoDecoder::VideoDecoder(std::unique_ptr<IvfParser> ivf_parser,
-                           const VaapiDevice& va_device,
+VideoDecoder::VideoDecoder(const VaapiDevice& va_device,
                            SharedVASurface::FetchPolicy fetch_policy)
-    : ivf_parser_(std::move(ivf_parser)),
-      va_device_(va_device),
-      fetch_policy_(fetch_policy) {}
+    : va_device_(va_device), fetch_policy_(fetch_policy) {}
 
 VideoDecoder::~VideoDecoder() {
   // The implementation should have destroyed everything in the right order,
diff --git a/media/gpu/vaapi/test/video_decoder.h b/media/gpu/vaapi/test/video_decoder.h
index 6f80b318..3f2872a 100644
--- a/media/gpu/vaapi/test/video_decoder.h
+++ b/media/gpu/vaapi/test/video_decoder.h
@@ -8,9 +8,6 @@
 #include "media/gpu/vaapi/test/shared_va_surface.h"
 
 namespace media {
-
-class IvfParser;
-
 namespace vaapi_test {
 
 class VaapiDevice;
@@ -25,8 +22,7 @@
     kEOStream,
   };
 
-  VideoDecoder(std::unique_ptr<IvfParser> ivf_parser,
-               const VaapiDevice& va_device,
+  VideoDecoder(const VaapiDevice& va_device,
                SharedVASurface::FetchPolicy fetch_policy);
   // Implementations of VideoDecoder are expected to handle the destruction of
   // |last_decoded_surface_| and in particular ensure it is done in the right
@@ -58,9 +54,6 @@
   bool LastDecodedFrameVisible() const { return last_decoded_frame_visible_; }
 
  protected:
-  // Parser for the IVF stream to decode.
-  const std::unique_ptr<IvfParser> ivf_parser_;
-
   // VA handles.
   const VaapiDevice& va_device_;
   scoped_refptr<SharedVASurface> last_decoded_surface_;
diff --git a/media/gpu/vaapi/test/vp8_decoder.cc b/media/gpu/vaapi/test/vp8_decoder.cc
index 57fcd3f..fa1220b 100644
--- a/media/gpu/vaapi/test/vp8_decoder.cc
+++ b/media/gpu/vaapi/test/vp8_decoder.cc
@@ -31,13 +31,14 @@
 Vp8Decoder::Vp8Decoder(std::unique_ptr<IvfParser> ivf_parser,
                        const VaapiDevice& va_device,
                        SharedVASurface::FetchPolicy fetch_policy)
-    : VideoDecoder(std::move(ivf_parser), va_device, fetch_policy),
+    : VideoDecoder(va_device, fetch_policy),
       va_config_(
           std::make_unique<ScopedVAConfig>(va_device_,
                                            VAProfile::VAProfileVP8Version0_3,
                                            VA_RT_FORMAT_YUV420)),
       vp8_parser_(std::make_unique<Vp8Parser>()),
-      ref_frames_(kNumVp8ReferenceBuffers) {
+      ref_frames_(kNumVp8ReferenceBuffers),
+      ivf_parser_(std::move(ivf_parser)) {
   std::fill(ref_frames_.begin(), ref_frames_.end(), nullptr);
 }
 
diff --git a/media/gpu/vaapi/test/vp8_decoder.h b/media/gpu/vaapi/test/vp8_decoder.h
index 821a1f9..a830d9f 100644
--- a/media/gpu/vaapi/test/vp8_decoder.h
+++ b/media/gpu/vaapi/test/vp8_decoder.h
@@ -6,6 +6,7 @@
 #define MEDIA_GPU_VAAPI_TEST_VP8_DECODER_H_
 
 #include "base/memory/scoped_refptr.h"
+#include "media/filters/ivf_parser.h"
 #include "media/gpu/vaapi/test/scoped_va_config.h"
 #include "media/gpu/vaapi/test/scoped_va_context.h"
 #include "media/gpu/vaapi/test/vaapi_device.h"
@@ -55,6 +56,9 @@
   // VP8-specific data.
   const std::unique_ptr<Vp8Parser> vp8_parser_;
   std::vector<scoped_refptr<SharedVASurface>> ref_frames_;
+
+  // Parser for the IVF stream to decode.
+  const std::unique_ptr<IvfParser> ivf_parser_;
 };
 
 }  // namespace vaapi_test
diff --git a/media/gpu/vaapi/test/vp9_decoder.cc b/media/gpu/vaapi/test/vp9_decoder.cc
index f15395c..f3f9af6 100644
--- a/media/gpu/vaapi/test/vp9_decoder.cc
+++ b/media/gpu/vaapi/test/vp9_decoder.cc
@@ -53,12 +53,11 @@
 Vp9Decoder::Vp9Decoder(std::unique_ptr<IvfParser> ivf_parser,
                        const VaapiDevice& va_device,
                        SharedVASurface::FetchPolicy fetch_policy)
-    : VideoDecoder::VideoDecoder(std::move(ivf_parser),
-                                 va_device,
-                                 fetch_policy),
+    : VideoDecoder::VideoDecoder(va_device, fetch_policy),
       vp9_parser_(
           std::make_unique<Vp9Parser>(/*parsing_compressed_header=*/false)),
-      ref_frames_(kVp9NumRefFrames) {}
+      ref_frames_(kVp9NumRefFrames),
+      ivf_parser_(std::move(ivf_parser)) {}
 
 Vp9Decoder::~Vp9Decoder() {
   // We destroy the VA handles explicitly to ensure the correct order.
diff --git a/media/gpu/vaapi/test/vp9_decoder.h b/media/gpu/vaapi/test/vp9_decoder.h
index 449ef1e..ff20b540 100644
--- a/media/gpu/vaapi/test/vp9_decoder.h
+++ b/media/gpu/vaapi/test/vp9_decoder.h
@@ -50,6 +50,9 @@
   // VP9-specific data.
   const std::unique_ptr<Vp9Parser> vp9_parser_;
   std::vector<scoped_refptr<SharedVASurface>> ref_frames_;
+
+  // Parser for the IVF stream to decode.
+  const std::unique_ptr<IvfParser> ivf_parser_;
 };
 
 }  // namespace vaapi_test
diff --git a/media/mojo/mojom/video_decoder.mojom b/media/mojo/mojom/video_decoder.mojom
index 46867aa..c19e33e 100644
--- a/media/mojo/mojom/video_decoder.mojom
+++ b/media/mojo/mojom/video_decoder.mojom
@@ -45,12 +45,16 @@
 interface VideoFrameHandleReleaser {
   // Signals that the VideoFrame identified by |release_token| should be
   // released. |release_sync_token| indicates the last use of the VideoFrame
-  // (in a GPU command buffer) by the client.
-  //
-  // TODO(sandersd): Do we need release notification for non-texture
-  // VideoFrames? If so, |release_sync_token| should be optional.
+  // (in a GPU command buffer) by the client. If the VideoDecoder outputs frames
+  // that have a callback for releasing mailboxes (i.e.,
+  // VideoFrame::HasReleaseMailboxCB() returns true), the |release_sync_token|
+  // is required but may be empty, and in that case, implementations should let
+  // the about-to-be-released VideoFrame retain whatever SyncToken it has. For
+  // other frames, it's assumed that the frame can be released immediately upon
+  // calling ReleaseVideoFrame() and |release_sync_token| does not need to be
+  // supplied (and should be ignored by implementations if supplied).
   ReleaseVideoFrame(mojo_base.mojom.UnguessableToken release_token,
-                    gpu.mojom.SyncToken release_sync_token);
+                    gpu.mojom.SyncToken? release_sync_token);
 };
 
 // A Mojo equivalent of media::VideoDecoder. In practice, this is used for
diff --git a/media/mojo/services/mojo_video_decoder_service.cc b/media/mojo/services/mojo_video_decoder_service.cc
index 78b47d49..0cc5024 100644
--- a/media/mojo/services/mojo_video_decoder_service.cc
+++ b/media/mojo/services/mojo_video_decoder_service.cc
@@ -79,16 +79,30 @@
   }
 
   // mojom::MojoVideoFrameHandleReleaser implementation
-  void ReleaseVideoFrame(const base::UnguessableToken& release_token,
-                         const gpu::SyncToken& release_sync_token) final {
+  void ReleaseVideoFrame(
+      const base::UnguessableToken& release_token,
+      const absl::optional<gpu::SyncToken>& release_sync_token) final {
     DVLOG(3) << __func__ << "(" << release_token.ToString() << ")";
     auto it = video_frames_.find(release_token);
     if (it == video_frames_.end()) {
       mojo::ReportBadMessage("Unknown |release_token|.");
       return;
     }
-    SimpleSyncTokenClient client(release_sync_token);
-    it->second->UpdateReleaseSyncToken(&client);
+    if (it->second->HasReleaseMailboxCB()) {
+      if (!release_sync_token) {
+        mojo::ReportBadMessage(
+            "A SyncToken is required to release frames that have a callback "
+            "for releasing mailboxes.");
+        return;
+      }
+      // An empty *|release_sync_token| can be taken as a signal that the
+      // about-to-be-released VideoFrame was never used by the client.
+      // Therefore, we should let that frame retain whatever SyncToken it has.
+      if (release_sync_token->HasData()) {
+        SimpleSyncTokenClient client(*release_sync_token);
+        it->second->UpdateReleaseSyncToken(&client);
+      }
+    }
     video_frames_.erase(it);
   }
 
diff --git a/media/mojo/services/stable_video_decoder_service.cc b/media/mojo/services/stable_video_decoder_service.cc
index 728c16d..ce70dcf1 100644
--- a/media/mojo/services/stable_video_decoder_service.cc
+++ b/media/mojo/services/stable_video_decoder_service.cc
@@ -128,7 +128,13 @@
 void StableVideoDecoderService::ReleaseVideoFrame(
     const base::UnguessableToken& release_token) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
+  DCHECK(video_frame_handle_releaser_remote_.is_bound());
+  // Note: we don't pass a gpu::SyncToken because it's assumed that the client
+  // (the GPU process) has already waited on the SyncToken that comes from the
+  // ultimate client (the renderer process) before calling ReleaseVideoFrame()
+  // on the out-of-process video decoder.
+  video_frame_handle_releaser_remote_->ReleaseVideoFrame(
+      release_token, /*release_sync_token=*/absl::nullopt);
 }
 
 void StableVideoDecoderService::OnVideoFrameDecoded(
diff --git a/media/mojo/services/stable_video_decoder_service_unittest.cc b/media/mojo/services/stable_video_decoder_service_unittest.cc
index fba3d4a..3b8fb996 100644
--- a/media/mojo/services/stable_video_decoder_service_unittest.cc
+++ b/media/mojo/services/stable_video_decoder_service_unittest.cc
@@ -56,7 +56,7 @@
   // mojom::VideoFrameHandleReleaser implementation.
   MOCK_METHOD2(ReleaseVideoFrame,
                void(const base::UnguessableToken& release_token,
-                    const gpu::SyncToken& release_sync_token));
+                    const absl::optional<gpu::SyncToken>& release_sync_token));
 
  private:
   mojo::Receiver<mojom::VideoFrameHandleReleaser>
@@ -660,6 +660,37 @@
   stable_video_decoder_remote.FlushForTesting();
 }
 
+// Tests that a call to
+// stable::mojom::VideoFrameHandleReleaser::ReleaseVideoFrame() gets routed
+// correctly to the underlying mojom::VideoFrameHandleReleaser.
+TEST_F(StableVideoDecoderServiceTest, VideoFramesCanBeReleased) {
+  auto mock_video_decoder = std::make_unique<StrictMock<MockVideoDecoder>>();
+  auto* mock_video_decoder_raw = mock_video_decoder.get();
+  auto stable_video_decoder_remote =
+      CreateStableVideoDecoder(std::move(mock_video_decoder));
+  ASSERT_TRUE(stable_video_decoder_remote.is_bound());
+  ASSERT_TRUE(stable_video_decoder_remote.is_connected());
+  auto auxiliary_endpoints = ConstructStableVideoDecoder(
+      stable_video_decoder_remote, *mock_video_decoder_raw,
+      /*expect_construct_call=*/true);
+  ASSERT_TRUE(auxiliary_endpoints);
+  ASSERT_TRUE(auxiliary_endpoints->stable_video_frame_handle_releaser_remote);
+  ASSERT_TRUE(auxiliary_endpoints->mock_video_frame_handle_releaser);
+
+  const base::UnguessableToken release_token_to_send =
+      base::UnguessableToken::Create();
+  const absl::optional<gpu::SyncToken> expected_release_sync_token =
+      absl::nullopt;
+
+  EXPECT_CALL(
+      *auxiliary_endpoints->mock_video_frame_handle_releaser,
+      ReleaseVideoFrame(release_token_to_send, expected_release_sync_token));
+  auxiliary_endpoints->stable_video_frame_handle_releaser_remote
+      ->ReleaseVideoFrame(release_token_to_send);
+  auxiliary_endpoints->stable_video_frame_handle_releaser_remote
+      .FlushForTesting();
+}
+
 TEST_F(StableVideoDecoderServiceTest,
        StableVideoDecoderClientReceivesOnVideoFrameDecodedEvent) {
   auto mock_video_decoder = std::make_unique<StrictMock<MockVideoDecoder>>();
diff --git a/media/mojo/test/mojo_video_decoder_integration_test.cc b/media/mojo/test/mojo_video_decoder_integration_test.cc
index ee84b45..201f2ac 100644
--- a/media/mojo/test/mojo_video_decoder_integration_test.cc
+++ b/media/mojo/test/mojo_video_decoder_integration_test.cc
@@ -25,6 +25,7 @@
 #include "media/base/decrypt_config.h"
 #include "media/base/media_log.h"
 #include "media/base/mock_media_log.h"
+#include "media/base/simple_sync_token_client.h"
 #include "media/base/test_helpers.h"
 #include "media/base/video_decoder.h"
 #include "media/base/video_frame.h"
@@ -465,6 +466,12 @@
   Mock::VerifyAndClearExpectations(&output_cb_);
 
   EXPECT_CALL(release_cb, Run(_));
+  gpu::SyncToken release_sync_token(gpu::CommandBufferNamespace::GPU_IO,
+                                    gpu::CommandBufferId(),
+                                    /*release_count=*/1u);
+  release_sync_token.SetVerifyFlush();
+  SimpleSyncTokenClient client(release_sync_token);
+  frame->UpdateReleaseSyncToken(&client);
   frame = nullptr;
   RunUntilIdle();
 }
diff --git a/net/socket/socket_test_util.h b/net/socket/socket_test_util.h
index cc693df..27ed864 100644
--- a/net/socket/socket_test_util.h
+++ b/net/socket/socket_test_util.h
@@ -1360,7 +1360,8 @@
   MockUDPClientSocket* GetLastProducedUDPSocket() const { return udp_socket_; }
 
  private:
-  raw_ptr<MockTaggingStreamSocket> tcp_socket_ = nullptr;
+  // TODO(crbug.com/1298696): Breaks net_unittests.
+  raw_ptr<MockTaggingStreamSocket, DegradeToNoOpWhenMTE> tcp_socket_ = nullptr;
   raw_ptr<MockUDPClientSocket> udp_socket_ = nullptr;
 };
 
diff --git a/remoting/host/pairing_registry_delegate_linux.cc b/remoting/host/pairing_registry_delegate_linux.cc
index 4bbf1a6..d71ab873 100644
--- a/remoting/host/pairing_registry_delegate_linux.cc
+++ b/remoting/host/pairing_registry_delegate_linux.cc
@@ -37,8 +37,8 @@
 
 PairingRegistryDelegateLinux::~PairingRegistryDelegateLinux() = default;
 
-std::unique_ptr<base::ListValue> PairingRegistryDelegateLinux::LoadAll() {
-  std::unique_ptr<base::ListValue> pairings(new base::ListValue());
+base::Value::List PairingRegistryDelegateLinux::LoadAll() {
+  base::Value::List pairings;
 
   // Enumerate all pairing files in the pairing registry.
   base::FilePath registry_path = GetRegistryPath();
@@ -59,8 +59,7 @@
       continue;
     }
 
-    pairings->GetList().Append(
-        base::Value::FromUniquePtrValue(std::move(pairing_json)));
+    pairings.Append(base::Value::FromUniquePtrValue(std::move(pairing_json)));
   }
 
   return pairings;
@@ -104,8 +103,7 @@
     return PairingRegistry::Pairing();
   }
 
-  return PairingRegistry::Pairing::CreateFromValue(
-      base::Value::AsDictionaryValue(*pairing));
+  return PairingRegistry::Pairing::CreateFromValue(pairing->GetDict());
 }
 
 bool PairingRegistryDelegateLinux::Save(
@@ -119,7 +117,7 @@
 
   std::string pairing_json;
   JSONStringValueSerializer serializer(&pairing_json);
-  if (!serializer.Serialize(*pairing.ToValue())) {
+  if (!serializer.Serialize(pairing.ToValue())) {
     LOG(ERROR) << "Failed to serialize pairing data for "
                << pairing.client_id();
     return false;
diff --git a/remoting/host/pairing_registry_delegate_linux.h b/remoting/host/pairing_registry_delegate_linux.h
index c8cb4491..4318dd0d 100644
--- a/remoting/host/pairing_registry_delegate_linux.h
+++ b/remoting/host/pairing_registry_delegate_linux.h
@@ -10,10 +10,6 @@
 #include "base/files/file_path.h"
 #include "base/gtest_prod_util.h"
 
-namespace base {
-class ListValue;
-}  // namespace base
-
 namespace remoting {
 
 class PairingRegistryDelegateLinux
@@ -28,7 +24,7 @@
   ~PairingRegistryDelegateLinux() override;
 
   // PairingRegistry::Delegate interface
-  std::unique_ptr<base::ListValue> LoadAll() override;
+  base::Value::List LoadAll() override;
   bool DeleteAll() override;
   protocol::PairingRegistry::Pairing Load(
       const std::string& client_id) override;
diff --git a/remoting/host/pairing_registry_delegate_linux_unittest.cc b/remoting/host/pairing_registry_delegate_linux_unittest.cc
index 7da8d07..50c3b6e 100644
--- a/remoting/host/pairing_registry_delegate_linux_unittest.cc
+++ b/remoting/host/pairing_registry_delegate_linux_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "remoting/host/pairing_registry_delegate_linux.h"
 
+#include <utility>
+
 #include "base/files/file_util.h"
 #include "base/values.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -35,7 +37,7 @@
   delegate->SetRegistryPathForTesting(temp_registry_);
 
   // Check that registry is initially empty.
-  EXPECT_TRUE(delegate->LoadAll()->GetListDeprecated().empty());
+  EXPECT_TRUE(delegate->LoadAll().empty());
 
   // Add a couple of pairings.
   PairingRegistry::Pairing pairing1(base::Time::Now(), "xxx", "xxx", "xxx");
@@ -44,7 +46,7 @@
   EXPECT_TRUE(delegate->Save(pairing2));
 
   // Verify that there are two pairings in the store now.
-  EXPECT_EQ(delegate->LoadAll()->GetListDeprecated().size(), 2u);
+  EXPECT_EQ(delegate->LoadAll().size(), 2u);
 
   // Verify that they can be retrieved.
   EXPECT_EQ(delegate->Load(pairing1.client_id()), pairing1);
@@ -58,15 +60,16 @@
   EXPECT_EQ(delegate->Load(pairing2.client_id()), pairing2);
 
   // Verify that the only value that left is |pairing2|.
-  EXPECT_EQ(delegate->LoadAll()->GetListDeprecated().size(), 1u);
-  std::unique_ptr<base::ListValue> pairings = delegate->LoadAll();
-  base::DictionaryValue* json;
-  EXPECT_TRUE(pairings->GetDictionary(0, &json));
-  EXPECT_EQ(PairingRegistry::Pairing::CreateFromValue(*json), pairing2);
+  EXPECT_EQ(delegate->LoadAll().size(), 1u);
+  base::Value::List pairings = delegate->LoadAll();
+  ASSERT_TRUE(pairings[0].is_dict());
+  EXPECT_EQ(PairingRegistry::Pairing::CreateFromValue(
+                std::move(pairings[0].GetDict())),
+            pairing2);
 
   // Delete the rest and verify.
   EXPECT_TRUE(delegate->DeleteAll());
-  EXPECT_TRUE(delegate->LoadAll()->GetListDeprecated().empty());
+  EXPECT_TRUE(delegate->LoadAll().empty());
 }
 
 // Verifies that the delegate is stateless by using two different instances.
diff --git a/remoting/host/pairing_registry_delegate_win.cc b/remoting/host/pairing_registry_delegate_win.cc
index 4258a65d..faf34b3 100644
--- a/remoting/host/pairing_registry_delegate_win.cc
+++ b/remoting/host/pairing_registry_delegate_win.cc
@@ -11,6 +11,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "base/win/registry.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #include <windows.h>
 
@@ -40,15 +41,15 @@
 
 // Reads value |value_name| from |key| as a JSON string and returns it as
 // |base::Value|.
-std::unique_ptr<base::DictionaryValue> ReadValue(const base::win::RegKey& key,
-                                                 const wchar_t* value_name) {
+absl::optional<base::Value::Dict> ReadValue(const base::win::RegKey& key,
+                                            const wchar_t* value_name) {
   // presubmit: allow wstring
   std::wstring value_json;
   LONG result = key.ReadValue(value_name, &value_json);
   if (result != ERROR_SUCCESS) {
     SetLastError(result);
     PLOG(ERROR) << "Cannot read value '" << value_name << "'";
-    return nullptr;
+    return absl::nullopt;
   }
 
   // Parse the value.
@@ -61,25 +62,25 @@
   if (!value) {
     LOG(ERROR) << "Failed to parse '" << value_name << "': " << error_message
                << " (" << error_code << ").";
-    return nullptr;
+    return absl::nullopt;
   }
 
   if (!value->is_dict()) {
     LOG(ERROR) << "Failed to parse '" << value_name << "': not a dictionary.";
-    return nullptr;
+    return absl::nullopt;
   }
 
-  return base::DictionaryValue::From(std::move(value));
+  return std::move(value->GetDict());
 }
 
 // Serializes |value| into a JSON string and writes it as value |value_name|
 // under |key|.
 bool WriteValue(base::win::RegKey& key,
                 const wchar_t* value_name,
-                std::unique_ptr<base::DictionaryValue> value) {
+                const base::Value::Dict& value) {
   std::string value_json_utf8;
   JSONStringValueSerializer serializer(&value_json_utf8);
-  if (!serializer.Serialize(*value)) {
+  if (!serializer.Serialize(value)) {
     LOG(ERROR) << "Failed to serialize '" << value_name << "'";
     return false;
   }
@@ -123,8 +124,8 @@
   return true;
 }
 
-std::unique_ptr<base::ListValue> PairingRegistryDelegateWin::LoadAll() {
-  std::unique_ptr<base::ListValue> pairings(new base::ListValue());
+base::Value::List PairingRegistryDelegateWin::LoadAll() {
+  base::Value::List pairings;
 
   // Enumerate and parse all values under the unprivileged key.
   DWORD count = unprivileged_.GetValueCount();
@@ -140,8 +141,7 @@
 
     PairingRegistry::Pairing pairing = Load(base::WideToUTF8(value_name));
     if (pairing.is_valid()) {
-      pairings->GetList().Append(
-          base::Value::FromUniquePtrValue(pairing.ToValue()));
+      pairings.Append(pairing.ToValue());
     }
   }
 
@@ -190,20 +190,20 @@
   std::wstring value_name = base::UTF8ToWide(client_id);
 
   // Read unprivileged fields first.
-  std::unique_ptr<base::DictionaryValue> pairing =
+  absl::optional<base::Value::Dict> pairing =
       ReadValue(unprivileged_, value_name.c_str());
   if (!pairing)
     return PairingRegistry::Pairing();
 
   // Read the shared secret.
   if (privileged_.Valid()) {
-    std::unique_ptr<base::DictionaryValue> secret =
+    absl::optional<base::Value::Dict> secret =
         ReadValue(privileged_, value_name.c_str());
     if (!secret)
       return PairingRegistry::Pairing();
 
     // Merge the two dictionaries.
-    pairing->MergeDictionary(secret.get());
+    pairing->Merge(std::move(*secret));
   }
 
   return PairingRegistry::Pairing::CreateFromValue(*pairing);
@@ -217,16 +217,14 @@
   }
 
   // Convert pairing to JSON.
-  std::unique_ptr<base::DictionaryValue> pairing_json = pairing.ToValue();
+  base::Value::Dict pairing_json = pairing.ToValue();
 
   // Extract the shared secret to a separate dictionary.
   absl::optional<base::Value> secret_key =
-      pairing_json->ExtractKey(PairingRegistry::kSharedSecretKey);
+      pairing_json.Extract(PairingRegistry::kSharedSecretKey);
   CHECK(secret_key.has_value());
-  std::unique_ptr<base::DictionaryValue> secret_json(
-      new base::DictionaryValue());
-  secret_json->SetKey(PairingRegistry::kSharedSecretKey,
-                      std::move(*secret_key));
+  base::Value::Dict secret_json;
+  secret_json.Set(PairingRegistry::kSharedSecretKey, std::move(*secret_key));
 
   // presubmit: allow wstring
   std::wstring value_name = base::UTF8ToWide(pairing.client_id());
diff --git a/remoting/host/pairing_registry_delegate_win.h b/remoting/host/pairing_registry_delegate_win.h
index c6371b54..17f0d54 100644
--- a/remoting/host/pairing_registry_delegate_win.h
+++ b/remoting/host/pairing_registry_delegate_win.h
@@ -5,17 +5,12 @@
 #ifndef REMOTING_HOST_PAIRING_REGISTRY_DELEGATE_WIN_H_
 #define REMOTING_HOST_PAIRING_REGISTRY_DELEGATE_WIN_H_
 
-#include <memory>
 #include <string>
 
 #include "base/compiler_specific.h"
 #include "base/win/registry.h"
 #include "remoting/protocol/pairing_registry.h"
 
-namespace base {
-class ListValue;
-}  // namespace base
-
 namespace remoting {
 
 #if defined(OFFICIAL_BUILD)
@@ -56,7 +51,7 @@
   bool SetRootKeys(HKEY privileged, HKEY unprivileged);
 
   // PairingRegistry::Delegate interface
-  std::unique_ptr<base::ListValue> LoadAll() override;
+  base::Value::List LoadAll() override;
   bool DeleteAll() override;
   protocol::PairingRegistry::Pairing Load(
       const std::string& client_id) override;
diff --git a/remoting/host/pairing_registry_delegate_win_unittest.cc b/remoting/host/pairing_registry_delegate_win_unittest.cc
index f9abaa9..1e7f681 100644
--- a/remoting/host/pairing_registry_delegate_win_unittest.cc
+++ b/remoting/host/pairing_registry_delegate_win_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include <memory>
+#include <utility>
 
 #include "remoting/host/pairing_registry_delegate_win.h"
 
@@ -53,7 +54,7 @@
   delegate->SetRootKeys(privileged_.Handle(), unprivileged_.Handle());
 
   // Check that registry is initially empty.
-  EXPECT_TRUE(delegate->LoadAll()->GetListDeprecated().empty());
+  EXPECT_TRUE(delegate->LoadAll().empty());
 
   // Add a couple of pairings.
   PairingRegistry::Pairing pairing1(base::Time::Now(), "xxx", "xxx", "xxx");
@@ -62,7 +63,7 @@
   EXPECT_TRUE(delegate->Save(pairing2));
 
   // Verify that there are two pairings in the store now.
-  EXPECT_EQ(delegate->LoadAll()->GetListDeprecated().size(), 2u);
+  EXPECT_EQ(delegate->LoadAll().size(), 2u);
 
   // Verify that they can be retrieved.
   EXPECT_EQ(delegate->Load(pairing1.client_id()), pairing1);
@@ -76,15 +77,16 @@
   EXPECT_EQ(delegate->Load(pairing2.client_id()), pairing2);
 
   // Verify that the only remaining value is |pairing2|.
-  EXPECT_EQ(delegate->LoadAll()->GetListDeprecated().size(), 1u);
-  std::unique_ptr<base::ListValue> pairings = delegate->LoadAll();
-  base::DictionaryValue* json;
-  EXPECT_TRUE(pairings->GetDictionary(0, &json));
-  EXPECT_EQ(PairingRegistry::Pairing::CreateFromValue(*json), pairing2);
+  EXPECT_EQ(delegate->LoadAll().size(), 1u);
+  base::Value::List pairings = delegate->LoadAll();
+  ASSERT_TRUE(pairings[0].is_dict());
+  EXPECT_EQ(PairingRegistry::Pairing::CreateFromValue(
+                std::move(pairings[0].GetDict())),
+            pairing2);
 
   // Delete the rest and verify.
   EXPECT_TRUE(delegate->DeleteAll());
-  EXPECT_TRUE(delegate->LoadAll()->GetListDeprecated().empty());
+  EXPECT_TRUE(delegate->LoadAll().empty());
 }
 
 // Verifies that the delegate is stateless by using two different instances.
diff --git a/remoting/host/setup/daemon_controller.cc b/remoting/host/setup/daemon_controller.cc
index fca5091a..04a7f14 100644
--- a/remoting/host/setup/daemon_controller.cc
+++ b/remoting/host/setup/daemon_controller.cc
@@ -58,10 +58,9 @@
   return delegate_->CheckPermission(it2me, std::move(callback));
 }
 
-void DaemonController::SetConfigAndStart(
-    std::unique_ptr<base::DictionaryValue> config,
-    bool consent,
-    CompletionCallback done) {
+void DaemonController::SetConfigAndStart(base::Value::Dict config,
+                                         bool consent,
+                                         CompletionCallback done) {
   DCHECK(caller_task_runner_->BelongsToCurrentThread());
 
   CompletionCallback wrapped_done =
@@ -73,9 +72,8 @@
   ServiceOrQueueRequest(std::move(request));
 }
 
-void DaemonController::UpdateConfig(
-    std::unique_ptr<base::DictionaryValue> config,
-    CompletionCallback done) {
+void DaemonController::UpdateConfig(base::Value::Dict config,
+                                    CompletionCallback done) {
   DCHECK(caller_task_runner_->BelongsToCurrentThread());
 
   CompletionCallback wrapped_done =
@@ -121,23 +119,21 @@
 void DaemonController::DoGetConfig(GetConfigCallback done) {
   DCHECK(delegate_task_runner_->BelongsToCurrentThread());
 
-  std::unique_ptr<base::DictionaryValue> config = delegate_->GetConfig();
+  absl::optional<base::Value::Dict> config = delegate_->GetConfig();
   caller_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(std::move(done), std::move(config)));
 }
 
-void DaemonController::DoSetConfigAndStart(
-    std::unique_ptr<base::DictionaryValue> config,
-    bool consent,
-    CompletionCallback done) {
+void DaemonController::DoSetConfigAndStart(base::Value::Dict config,
+                                           bool consent,
+                                           CompletionCallback done) {
   DCHECK(delegate_task_runner_->BelongsToCurrentThread());
 
   delegate_->SetConfigAndStart(std::move(config), consent, std::move(done));
 }
 
-void DaemonController::DoUpdateConfig(
-    std::unique_ptr<base::DictionaryValue> config,
-    CompletionCallback done) {
+void DaemonController::DoUpdateConfig(base::Value::Dict config,
+                                      CompletionCallback done) {
   DCHECK(delegate_task_runner_->BelongsToCurrentThread());
 
   delegate_->UpdateConfig(std::move(config), std::move(done));
@@ -176,7 +172,7 @@
 
 void DaemonController::InvokeConfigCallbackAndScheduleNext(
     GetConfigCallback done,
-    std::unique_ptr<base::DictionaryValue> config) {
+    absl::optional<base::Value::Dict> config) {
   DCHECK(caller_task_runner_->BelongsToCurrentThread());
 
   std::move(done).Run(std::move(config));
diff --git a/remoting/host/setup/daemon_controller.h b/remoting/host/setup/daemon_controller.h
index 534abe1..3f60e3f4 100644
--- a/remoting/host/setup/daemon_controller.h
+++ b/remoting/host/setup/daemon_controller.h
@@ -11,9 +11,10 @@
 #include "base/callback.h"
 #include "base/containers/queue.h"
 #include "base/memory/ref_counted.h"
+#include "base/values.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace base {
-class DictionaryValue;
 class SingleThreadTaskRunner;
 }  // namespace base
 
@@ -65,8 +66,7 @@
   // is returned containing host_id and xmpp_login, with security-sensitive
   // fields filtered out. An empty dictionary is returned if the host is not
   // configured, and nullptr if the configuration is corrupt or cannot be read.
-  typedef base::OnceCallback<void(
-      std::unique_ptr<base::DictionaryValue> config)>
+  typedef base::OnceCallback<void(absl::optional<base::Value::Dict> config)>
       GetConfigCallback;
 
   // Callback used for asynchronous operations, e.g. when
@@ -110,7 +110,7 @@
 
     // Queries current host configuration. Any values that might be security
     // sensitive have been filtered out.
-    virtual std::unique_ptr<base::DictionaryValue> GetConfig() = 0;
+    virtual absl::optional<base::Value::Dict> GetConfig() = 0;
 
     // Checks to verify that the required OS permissions have been granted to
     // the host process, querying the user if necessary. Notifies the callback
@@ -121,17 +121,16 @@
     // Starts the daemon process. This may require that the daemon be
     // downloaded and installed. |done| is invoked on the calling thread when
     // the operation is completed.
-    virtual void SetConfigAndStart(
-        std::unique_ptr<base::DictionaryValue> config,
-        bool consent,
-        CompletionCallback done) = 0;
+    virtual void SetConfigAndStart(base::Value::Dict config,
+                                   bool consent,
+                                   CompletionCallback done) = 0;
 
     // Updates current host configuration with the values specified in
     // |config|. Any value in the existing configuration that isn't specified in
     // |config| is preserved. |config| must not contain host_id or xmpp_login
     // values, because implementations of this method cannot change them. |done|
     // is invoked on the calling thread when the operation is completed.
-    virtual void UpdateConfig(std::unique_ptr<base::DictionaryValue> config,
+    virtual void UpdateConfig(base::Value::Dict config,
                               CompletionCallback done) = 0;
 
     // Stops the daemon process. |done| is invoked on the calling thread when
@@ -176,7 +175,7 @@
   // these two steps are merged for simplicity. Consider splitting it
   // into SetConfig() and Start() once we have basic host setup flow
   // working.
-  void SetConfigAndStart(std::unique_ptr<base::DictionaryValue> config,
+  void SetConfigAndStart(base::Value::Dict config,
                          bool consent,
                          CompletionCallback done);
 
@@ -185,8 +184,7 @@
   // Any value in the existing configuration that isn't specified in |config|
   // is preserved. |config| must not contain host_id or xmpp_login values,
   // because implementations of this method cannot change them.
-  void UpdateConfig(std::unique_ptr<base::DictionaryValue> config,
-                    CompletionCallback done);
+  void UpdateConfig(base::Value::Dict config, CompletionCallback done);
 
   // Stop the daemon process. It is permitted to call Stop while the daemon
   // process is being installed, in which case the installation should be
@@ -205,11 +203,10 @@
 
   // Blocking helper methods used to call the delegate.
   void DoGetConfig(GetConfigCallback done);
-  void DoSetConfigAndStart(std::unique_ptr<base::DictionaryValue> config,
+  void DoSetConfigAndStart(base::Value::Dict config,
                            bool consent,
                            CompletionCallback done);
-  void DoUpdateConfig(std::unique_ptr<base::DictionaryValue> config,
-                      CompletionCallback done);
+  void DoUpdateConfig(base::Value::Dict config, CompletionCallback done);
   void DoStop(CompletionCallback done);
   void DoGetUsageStatsConsent(GetUsageStatsConsentCallback done);
 
@@ -219,7 +216,7 @@
                                                AsyncResult result);
   void InvokeConfigCallbackAndScheduleNext(
       GetConfigCallback done,
-      std::unique_ptr<base::DictionaryValue> config);
+      absl::optional<base::Value::Dict> config);
   void InvokeConsentCallbackAndScheduleNext(GetUsageStatsConsentCallback done,
                                             const UsageStatsConsent& consent);
 
diff --git a/remoting/host/setup/daemon_controller_delegate_linux.cc b/remoting/host/setup/daemon_controller_delegate_linux.cc
index f65ad56..120c5be4 100644
--- a/remoting/host/setup/daemon_controller_delegate_linux.cc
+++ b/remoting/host/setup/daemon_controller_delegate_linux.cc
@@ -5,6 +5,7 @@
 #include "remoting/host/setup/daemon_controller_delegate_linux.h"
 
 #include <unistd.h>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/command_line.h"
@@ -145,22 +146,21 @@
   }
 }
 
-std::unique_ptr<base::DictionaryValue>
-DaemonControllerDelegateLinux::GetConfig() {
+absl::optional<base::Value::Dict> DaemonControllerDelegateLinux::GetConfig() {
   absl::optional<base::Value> host_config(
       HostConfigFromJsonFile(GetConfigPath()));
   if (!host_config.has_value())
-    return nullptr;
+    return absl::nullopt;
 
-  std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue);
+  base::Value::Dict result;
   std::string* value = host_config->FindStringKey(kHostIdConfigPath);
   if (value) {
-    result->SetString(kHostIdConfigPath, *value);
+    result.Set(kHostIdConfigPath, *value);
   }
 
   value = host_config->FindStringKey(kXmppLoginConfigPath);
   if (value) {
-    result->SetString(kXmppLoginConfigPath, *value);
+    result.Set(kXmppLoginConfigPath, *value);
   }
 
   return result;
@@ -173,7 +173,7 @@
 }
 
 void DaemonControllerDelegateLinux::SetConfigAndStart(
-    std::unique_ptr<base::DictionaryValue> config,
+    base::Value::Dict config,
     bool consent,
     DaemonController::CompletionCallback done) {
   // Ensure the configuration directory exists.
@@ -189,7 +189,7 @@
   }
 
   // Write config.
-  if (!HostConfigToJsonFile(*config, GetConfigPath())) {
+  if (!HostConfigToJsonFile(base::Value(std::move(config)), GetConfigPath())) {
     LOG(ERROR) << "Failed to update config file.";
     std::move(done).Run(DaemonController::RESULT_FAILED);
     return;
@@ -215,19 +215,20 @@
 }
 
 void DaemonControllerDelegateLinux::UpdateConfig(
-    std::unique_ptr<base::DictionaryValue> config,
+    base::Value::Dict config,
     DaemonController::CompletionCallback done) {
   absl::optional<base::Value> new_config(
       HostConfigFromJsonFile(GetConfigPath()));
-  if (!new_config.has_value()) {
+  if (!new_config.has_value() || !new_config->is_dict()) {
     LOG(ERROR) << "Failed to read existing config file.";
     std::move(done).Run(DaemonController::RESULT_FAILED);
     return;
   }
 
-  new_config->MergeDictionary(config.get());
+  new_config->GetDict().Merge(std::move(config));
 
-  if (!HostConfigToJsonFile(new_config.value(), GetConfigPath())) {
+  if (!HostConfigToJsonFile(base::Value(std::move(*new_config)),
+                            GetConfigPath())) {
     LOG(ERROR) << "Failed to update config file.";
     std::move(done).Run(DaemonController::RESULT_FAILED);
     return;
diff --git a/remoting/host/setup/daemon_controller_delegate_linux.h b/remoting/host/setup/daemon_controller_delegate_linux.h
index bf305b7..87e7122 100644
--- a/remoting/host/setup/daemon_controller_delegate_linux.h
+++ b/remoting/host/setup/daemon_controller_delegate_linux.h
@@ -23,12 +23,12 @@
 
   // DaemonController::Delegate interface.
   DaemonController::State GetState() override;
-  std::unique_ptr<base::DictionaryValue> GetConfig() override;
+  absl::optional<base::Value::Dict> GetConfig() override;
   void CheckPermission(bool it2me, DaemonController::BoolCallback) override;
-  void SetConfigAndStart(std::unique_ptr<base::DictionaryValue> config,
+  void SetConfigAndStart(base::Value::Dict config,
                          bool consent,
                          DaemonController::CompletionCallback done) override;
-  void UpdateConfig(std::unique_ptr<base::DictionaryValue> config,
+  void UpdateConfig(base::Value::Dict config,
                     DaemonController::CompletionCallback done) override;
   void Stop(DaemonController::CompletionCallback done) override;
   DaemonController::UsageStatsConsent GetUsageStatsConsent() override;
diff --git a/remoting/host/setup/daemon_controller_delegate_mac.h b/remoting/host/setup/daemon_controller_delegate_mac.h
index 8b33121..f506567 100644
--- a/remoting/host/setup/daemon_controller_delegate_mac.h
+++ b/remoting/host/setup/daemon_controller_delegate_mac.h
@@ -30,12 +30,12 @@
 
   // DaemonController::Delegate interface.
   DaemonController::State GetState() override;
-  std::unique_ptr<base::DictionaryValue> GetConfig() override;
+  absl::optional<base::Value::Dict> GetConfig() override;
   void CheckPermission(bool it2me, DaemonController::BoolCallback) override;
-  void SetConfigAndStart(std::unique_ptr<base::DictionaryValue> config,
+  void SetConfigAndStart(base::Value::Dict config,
                          bool consent,
                          DaemonController::CompletionCallback done) override;
-  void UpdateConfig(std::unique_ptr<base::DictionaryValue> config,
+  void UpdateConfig(base::Value::Dict config,
                     DaemonController::CompletionCallback done) override;
   void Stop(DaemonController::CompletionCallback done) override;
   DaemonController::UsageStatsConsent GetUsageStatsConsent() override;
diff --git a/remoting/host/setup/daemon_controller_delegate_mac.mm b/remoting/host/setup/daemon_controller_delegate_mac.mm
index 5731ce465..52c0258c 100644
--- a/remoting/host/setup/daemon_controller_delegate_mac.mm
+++ b/remoting/host/setup/daemon_controller_delegate_mac.mm
@@ -6,6 +6,7 @@
 
 #include <launch.h>
 #include <sys/types.h>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/command_line.h"
@@ -163,14 +164,14 @@
   return false;
 }
 
-void ElevateAndSetConfig(const base::DictionaryValue& config,
+void ElevateAndSetConfig(base::Value::Dict config,
                          DaemonController::CompletionCallback done) {
   // Find out if the host service is running.
   pid_t job_pid = base::mac::PIDForJob(remoting::kServiceName);
   bool service_running = (job_pid > 0);
 
   const char* command = service_running ? "--save-config" : "--enable";
-  std::string input_data = HostConfigToJson(config);
+  std::string input_data = HostConfigToJson(base::Value(std::move(config)));
   if (!RunHelperAsRoot(command, input_data)) {
     LOG(ERROR) << "Failed to run the helper tool.";
     std::move(done).Run(DaemonController::RESULT_FAILED);
@@ -232,22 +233,21 @@
   }
 }
 
-std::unique_ptr<base::DictionaryValue>
-DaemonControllerDelegateMac::GetConfig() {
+absl::optional<base::Value::Dict> DaemonControllerDelegateMac::GetConfig() {
   base::FilePath config_path(kHostConfigFilePath);
   absl::optional<base::Value> host_config(HostConfigFromJsonFile(config_path));
   if (!host_config.has_value())
-    return nullptr;
+    return absl::nullopt;
 
-  std::unique_ptr<base::DictionaryValue> config(new base::DictionaryValue);
+  base::Value::Dict config;
   std::string* value = host_config->FindStringKey(kHostIdConfigPath);
   if (value) {
-    config->SetString(kHostIdConfigPath, *value);
+    config.Set(kHostIdConfigPath, *value);
   }
 
   value = host_config->FindStringKey(kXmppLoginConfigPath);
   if (value) {
-    config->SetString(kXmppLoginConfigPath, *value);
+    config.Set(kXmppLoginConfigPath, *value);
   }
 
   return config;
@@ -265,27 +265,28 @@
 }
 
 void DaemonControllerDelegateMac::SetConfigAndStart(
-    std::unique_ptr<base::DictionaryValue> config,
+    base::Value::Dict config,
     bool consent,
     DaemonController::CompletionCallback done) {
-  config->SetBoolean(kUsageStatsConsentConfigPath, consent);
-  ElevateAndSetConfig(*config, std::move(done));
+  config.Set(kUsageStatsConsentConfigPath, consent);
+  ElevateAndSetConfig(std::move(config), std::move(done));
 }
 
 void DaemonControllerDelegateMac::UpdateConfig(
-    std::unique_ptr<base::DictionaryValue> config,
+    base::Value::Dict config,
     DaemonController::CompletionCallback done) {
   base::FilePath config_file_path(kHostConfigFilePath);
   absl::optional<base::Value> host_config(
       HostConfigFromJsonFile(config_file_path));
-  if (!host_config.has_value()) {
+  if (!host_config.has_value() || !host_config->is_dict()) {
     std::move(done).Run(DaemonController::RESULT_FAILED);
     return;
   }
 
-  host_config->MergeDictionary(config.get());
-  ElevateAndSetConfig(base::Value::AsDictionaryValue(host_config.value()),
-                      std::move(done));
+  base::Value::Dict& host_config_dict = host_config->GetDict();
+
+  host_config_dict.Merge(std::move(config));
+  ElevateAndSetConfig(std::move(host_config_dict), std::move(done));
 }
 
 void DaemonControllerDelegateMac::Stop(
diff --git a/remoting/host/setup/daemon_controller_delegate_win.cc b/remoting/host/setup/daemon_controller_delegate_win.cc
index 772b1c3..2b6c955 100644
--- a/remoting/host/setup/daemon_controller_delegate_win.cc
+++ b/remoting/host/setup/daemon_controller_delegate_win.cc
@@ -7,6 +7,7 @@
 #include <stddef.h>
 
 #include <tuple>
+#include <utility>
 
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -66,8 +67,7 @@
 
 // Reads and parses the configuration file up to |kMaxConfigFileSize| in
 // size.
-bool ReadConfig(const base::FilePath& filename,
-                std::unique_ptr<base::DictionaryValue>* config_out) {
+bool ReadConfig(const base::FilePath& filename, base::Value::Dict& config_out) {
   std::string file_content;
   if (!base::ReadFileToStringWithMaxSize(filename, &file_content,
                                          kMaxConfigFileSize)) {
@@ -76,17 +76,15 @@
   }
 
   // Parse the JSON configuration, expecting it to contain a dictionary.
-  std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(
-      file_content, base::JSON_ALLOW_TRAILING_COMMAS);
+  absl::optional<base::Value> value =
+      base::JSONReader::Read(file_content, base::JSON_ALLOW_TRAILING_COMMAS);
 
-  base::DictionaryValue* dictionary;
-  if (!value || !value->GetAsDictionary(&dictionary)) {
+  if (!value || !value->is_dict()) {
     LOG(ERROR) << "Failed to parse '" << filename.value() << "'.";
     return false;
   }
 
-  std::ignore = value.release();
-  config_out->reset(dictionary);
+  config_out = std::move(value->GetDict());
   return true;
 }
 
@@ -160,33 +158,31 @@
   }
 
   // Extract the configuration data that the user will verify.
-  std::unique_ptr<base::Value> config_value =
-      base::JSONReader::ReadDeprecated(content);
-  if (!config_value.get()) {
+  absl::optional<base::Value> config_value = base::JSONReader::Read(content);
+  if (!config_value || !config_value->is_dict()) {
     return false;
   }
-  base::DictionaryValue* config_dict = nullptr;
-  if (!config_value->GetAsDictionary(&config_dict)) {
+
+  base::Value::Dict& config_dict = config_value->GetDict();
+
+  std::string* email;
+  if (!(email = config_dict.FindString(kHostOwnerEmailConfigPath)) &&
+      !(email = config_dict.FindString(kHostOwnerConfigPath)) &&
+      !(email = config_dict.FindString(kXmppLoginConfigPath))) {
     return false;
   }
-  std::string email;
-  if (!config_dict->GetString(kHostOwnerEmailConfigPath, &email) &&
-      !config_dict->GetString(kHostOwnerConfigPath, &email) &&
-      !config_dict->GetString(kXmppLoginConfigPath, &email)) {
-    return false;
-  }
-  std::string host_id, host_secret_hash;
-  if (!config_dict->GetString(kHostIdConfigPath, &host_id) ||
-      !config_dict->GetString(kHostSecretHashConfigPath, &host_secret_hash)) {
+  std::string* host_id = config_dict.FindString(kHostIdConfigPath);
+  std::string* host_secret_hash =
+      config_dict.FindString(kHostSecretHashConfigPath);
+  if (!host_id || !host_secret_hash) {
     return false;
   }
 
   // Extract the unprivileged fields from the configuration.
-  base::DictionaryValue unprivileged_config_dict;
+  base::Value::Dict unprivileged_config_dict;
   for (const char* key : kUnprivilegedConfigKeys) {
-    std::u16string value;
-    if (config_dict->GetString(key, &value)) {
-      unprivileged_config_dict.SetString(key, value);
+    if (std::string* value = config_dict.FindString(key)) {
+      unprivileged_config_dict.Set(key, std::move(*value));
     }
   }
   std::string unprivileged_config_str;
@@ -367,24 +363,23 @@
   return ConvertToDaemonState(status.dwCurrentState);
 }
 
-std::unique_ptr<base::DictionaryValue>
-DaemonControllerDelegateWin::GetConfig() {
+absl::optional<base::Value::Dict> DaemonControllerDelegateWin::GetConfig() {
   base::FilePath config_dir = remoting::GetConfigDir();
 
   // Read the unprivileged part of host configuration.
-  std::unique_ptr<base::DictionaryValue> config;
-  if (!ReadConfig(config_dir.Append(kUnprivilegedConfigFileName), &config))
-    return nullptr;
+  base::Value::Dict config;
+  if (!ReadConfig(config_dir.Append(kUnprivilegedConfigFileName), config))
+    return absl::nullopt;
 
   return config;
 }
 
 void DaemonControllerDelegateWin::UpdateConfig(
-    std::unique_ptr<base::DictionaryValue> config,
+    base::Value::Dict config,
     DaemonController::CompletionCallback done) {
   // Check for bad keys.
   for (size_t i = 0; i < std::size(kReadonlyKeys); ++i) {
-    if (config->FindKey(kReadonlyKeys[i])) {
+    if (config.Find(kReadonlyKeys[i])) {
       LOG(ERROR) << "Cannot update config: '" << kReadonlyKeys[i]
                  << "' is read only.";
       InvokeCompletionCallback(std::move(done), false);
@@ -393,18 +388,18 @@
   }
   // Get the old config.
   base::FilePath config_dir = remoting::GetConfigDir();
-  std::unique_ptr<base::DictionaryValue> config_old;
-  if (!ReadConfig(config_dir.Append(kConfigFileName), &config_old)) {
+  base::Value::Dict config_old;
+  if (!ReadConfig(config_dir.Append(kConfigFileName), config_old)) {
     InvokeCompletionCallback(std::move(done), false);
     return;
   }
 
   // Merge items from the given config into the old config.
-  config_old->MergeDictionary(config.release());
+  config_old.Merge(std::move(config));
 
   // Write the updated config.
   std::string config_updated_str;
-  base::JSONWriter::Write(*config_old, &config_updated_str);
+  base::JSONWriter::Write(config_old, &config_updated_str);
   bool result = WriteConfig(config_updated_str);
 
   InvokeCompletionCallback(std::move(done), result);
@@ -444,7 +439,7 @@
 }
 
 void DaemonControllerDelegateWin::SetConfigAndStart(
-    std::unique_ptr<base::DictionaryValue> config,
+    base::Value::Dict config,
     bool consent,
     DaemonController::CompletionCallback done) {
   // Record the user's consent.
@@ -455,7 +450,7 @@
 
   // Set the configuration.
   std::string config_str;
-  base::JSONWriter::Write(*config, &config_str);
+  base::JSONWriter::Write(config, &config_str);
 
   // Determine the config directory path and create it if necessary.
   base::FilePath config_dir = remoting::GetConfigDir();
diff --git a/remoting/host/setup/daemon_controller_delegate_win.h b/remoting/host/setup/daemon_controller_delegate_win.h
index 1bd85c2..551a1f5 100644
--- a/remoting/host/setup/daemon_controller_delegate_win.h
+++ b/remoting/host/setup/daemon_controller_delegate_win.h
@@ -21,12 +21,12 @@
 
   // DaemonController::Delegate interface.
   DaemonController::State GetState() override;
-  std::unique_ptr<base::DictionaryValue> GetConfig() override;
+  absl::optional<base::Value::Dict> GetConfig() override;
   void CheckPermission(bool it2me, DaemonController::BoolCallback) override;
-  void SetConfigAndStart(std::unique_ptr<base::DictionaryValue> config,
+  void SetConfigAndStart(base::Value::Dict config,
                          bool consent,
                          DaemonController::CompletionCallback done) override;
-  void UpdateConfig(std::unique_ptr<base::DictionaryValue> config,
+  void UpdateConfig(base::Value::Dict config,
                     DaemonController::CompletionCallback done) override;
   void Stop(DaemonController::CompletionCallback done) override;
   DaemonController::UsageStatsConsent GetUsageStatsConsent() override;
diff --git a/remoting/host/setup/host_starter.cc b/remoting/host/setup/host_starter.cc
index aba0ba6..d00d157 100644
--- a/remoting/host/setup/host_starter.cc
+++ b/remoting/host/setup/host_starter.cc
@@ -186,14 +186,14 @@
 void HostStarter::StartHostProcess() {
   // Start the host.
   std::string host_secret_hash = remoting::MakeHostPinHash(host_id_, host_pin_);
-  std::unique_ptr<base::DictionaryValue> config(new base::DictionaryValue());
-  config->SetString("host_owner", host_owner_);
-  config->SetString("xmpp_login", xmpp_login_);
-  config->SetString("oauth_refresh_token", host_refresh_token_);
-  config->SetString("host_id", host_id_);
-  config->SetString("host_name", host_name_);
-  config->SetString("private_key", key_pair_->ToString());
-  config->SetString("host_secret_hash", host_secret_hash);
+  base::Value::Dict config;
+  config.Set("host_owner", host_owner_);
+  config.Set("xmpp_login", xmpp_login_);
+  config.Set("oauth_refresh_token", host_refresh_token_);
+  config.Set("host_id", host_id_);
+  config.Set("host_name", host_name_);
+  config.Set("private_key", key_pair_->ToString());
+  config.Set("host_secret_hash", host_secret_hash);
   daemon_controller_->SetConfigAndStart(
       std::move(config), consent_to_data_collection_,
       base::BindOnce(&HostStarter::OnHostStarted, base::Unretained(this)));
diff --git a/remoting/host/setup/host_stopper.cc b/remoting/host/setup/host_stopper.cc
index 44a8d98..731dc4b 100644
--- a/remoting/host/setup/host_stopper.cc
+++ b/remoting/host/setup/host_stopper.cc
@@ -27,10 +27,9 @@
       base::BindOnce(&HostStopper::OnConfigLoaded, weak_ptr_));
 }
 
-void HostStopper::OnConfigLoaded(
-    std::unique_ptr<base::DictionaryValue> config) {
+void HostStopper::OnConfigLoaded(absl::optional<base::Value::Dict> config) {
   const std::string* hostId = nullptr;
-  if (!config || !(hostId = config->FindStringPath("host_id"))) {
+  if (!config || !(hostId = config->FindString("host_id"))) {
     std::move(on_done_).Run();
     return;
   }
diff --git a/remoting/host/setup/host_stopper.h b/remoting/host/setup/host_stopper.h
index a5b8bd5..dac5f7be 100644
--- a/remoting/host/setup/host_stopper.h
+++ b/remoting/host/setup/host_stopper.h
@@ -7,8 +7,10 @@
 
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
+#include "base/values.h"
 #include "remoting/host/setup/daemon_controller.h"
 #include "remoting/host/setup/service_client.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace remoting {
 
@@ -25,7 +27,7 @@
   void StopLocalHost(std::string access_token, base::OnceClosure on_done);
 
  private:
-  void OnConfigLoaded(std::unique_ptr<base::DictionaryValue> config);
+  void OnConfigLoaded(absl::optional<base::Value::Dict> config);
   void StopHost();
   void OnStopped(DaemonController::AsyncResult);
 
diff --git a/remoting/host/setup/me2me_native_messaging_host.cc b/remoting/host/setup/me2me_native_messaging_host.cc
index 01b3878..8f1a493 100644
--- a/remoting/host/setup/me2me_native_messaging_host.cc
+++ b/remoting/host/setup/me2me_native_messaging_host.cc
@@ -30,6 +30,7 @@
 #include "remoting/host/native_messaging/log_message_handler.h"
 #include "remoting/host/pin_hash.h"
 #include "remoting/protocol/pairing_registry.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if BUILDFLAG(IS_WIN)
 #include "remoting/host/win/elevated_native_messaging_host.h"
@@ -55,16 +56,14 @@
 #endif  // BUILDFLAG(IS_APPLE)
 };
 
-// Helper to extract the "config" part of a message as a DictionaryValue.
+// Helper to extract the "config" part of a message as a base::Value::Dict.
 // Returns nullptr on failure, and logs an error message.
-std::unique_ptr<base::DictionaryValue> ConfigDictionaryFromMessage(
-    std::unique_ptr<base::DictionaryValue> message) {
-  std::unique_ptr<base::DictionaryValue> result;
-  const base::DictionaryValue* config_dict;
-  if (message->GetDictionary("config", &config_dict)) {
-    result = config_dict->CreateDeepCopy();
+absl::optional<base::Value::Dict> ConfigDictionaryFromMessage(
+    base::Value::Dict message) {
+  if (base::Value::Dict* config_dict = message.FindDict("config")) {
+    return std::move(*config_dict);
   }
-  return result;
+  return absl::nullopt;
 }
 
 }  // namespace
@@ -96,69 +95,66 @@
 void Me2MeNativeMessagingHost::OnMessage(const std::string& message) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
-  auto response = std::make_unique<base::DictionaryValue>();
-  std::unique_ptr<base::Value> message_value =
-      base::JSONReader::ReadDeprecated(message);
-  if (!message_value->is_dict()) {
+  base::Value::Dict response;
+  absl::optional<base::Value> message_value = base::JSONReader::Read(message);
+  if (!message_value || !message_value->is_dict()) {
     OnError("Received a message that's not a dictionary.");
     return;
   }
 
-  std::unique_ptr<base::DictionaryValue> message_dict(
-      static_cast<base::DictionaryValue*>(message_value.release()));
+  base::Value::Dict& message_dict = message_value->GetDict();
 
   // If the client supplies an ID, it will expect it in the response. This
   // might be a string or a number, so cope with both.
-  const base::Value* id;
-  if (message_dict->Get("id", &id))
-    response->SetKey("id", id->Clone());
+  if (const base::Value* id = message_dict.Find("id"))
+    response.Set("id", id->Clone());
 
-  std::string type;
-  if (!message_dict->GetString("type", &type)) {
+  const std::string* type = message_dict.FindString("type");
+  if (!type) {
     OnError("'type' not found");
     return;
   }
 
-  response->SetStringKey("type", type + "Response");
+  response.Set("type", *type + "Response");
 
-  if (type == "hello") {
+  if (*type == "hello") {
     ProcessHello(std::move(message_dict), std::move(response));
-  } else if (type == "clearPairedClients") {
+  } else if (*type == "clearPairedClients") {
     ProcessClearPairedClients(std::move(message_dict), std::move(response));
-  } else if (type == "deletePairedClient") {
+  } else if (*type == "deletePairedClient") {
     ProcessDeletePairedClient(std::move(message_dict), std::move(response));
-  } else if (type == "getHostName") {
+  } else if (*type == "getHostName") {
     ProcessGetHostName(std::move(message_dict), std::move(response));
-  } else if (type == "getPinHash") {
+  } else if (*type == "getPinHash") {
     ProcessGetPinHash(std::move(message_dict), std::move(response));
-  } else if (type == "generateKeyPair") {
+  } else if (*type == "generateKeyPair") {
     ProcessGenerateKeyPair(std::move(message_dict), std::move(response));
-  } else if (type == "updateDaemonConfig") {
+  } else if (*type == "updateDaemonConfig") {
     ProcessUpdateDaemonConfig(std::move(message_dict), std::move(response));
-  } else if (type == "getDaemonConfig") {
+  } else if (*type == "getDaemonConfig") {
     ProcessGetDaemonConfig(std::move(message_dict), std::move(response));
-  } else if (type == "getPairedClients") {
+  } else if (*type == "getPairedClients") {
     ProcessGetPairedClients(std::move(message_dict), std::move(response));
-  } else if (type == "getUsageStatsConsent") {
+  } else if (*type == "getUsageStatsConsent") {
     ProcessGetUsageStatsConsent(std::move(message_dict), std::move(response));
-  } else if (type == "startDaemon") {
+  } else if (*type == "startDaemon") {
     ProcessStartDaemon(std::move(message_dict), std::move(response));
-  } else if (type == "stopDaemon") {
+  } else if (*type == "stopDaemon") {
     ProcessStopDaemon(std::move(message_dict), std::move(response));
-  } else if (type == "getDaemonState") {
+  } else if (*type == "getDaemonState") {
     ProcessGetDaemonState(std::move(message_dict), std::move(response));
-  } else if (type == "getHostClientId") {
+  } else if (*type == "getHostClientId") {
     ProcessGetHostClientId(std::move(message_dict), std::move(response));
-  } else if (type == "getCredentialsFromAuthCode") {
+  } else if (*type == "getCredentialsFromAuthCode") {
     ProcessGetCredentialsFromAuthCode(
         std::move(message_dict), std::move(response), true);
-  } else if (type == "getRefreshTokenFromAuthCode") {
+  } else if (*type == "getRefreshTokenFromAuthCode") {
     ProcessGetCredentialsFromAuthCode(
         std::move(message_dict), std::move(response), false);
-  } else if (type == "it2mePermissionCheck") {
+  } else if (*type == "it2mePermissionCheck") {
     ProcessIt2mePermissionCheck(std::move(message_dict), std::move(response));
   } else {
-    OnError("Unsupported request type: " + type);
+    OnError("Unsupported request type: " + *type);
   }
 }
 
@@ -175,23 +171,22 @@
   return host_context_->ui_task_runner();
 }
 
-void Me2MeNativeMessagingHost::ProcessHello(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+void Me2MeNativeMessagingHost::ProcessHello(base::Value::Dict message,
+                                            base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
-  response->SetStringKey("version", STRINGIZE(VERSION));
-  auto supported_features_list = std::make_unique<base::ListValue>();
+  response.Set("version", STRINGIZE(VERSION));
+  base::Value::List supported_features_list;
   for (const char* feature : kSupportedFeatures) {
-    supported_features_list->Append(feature);
+    supported_features_list.Append(feature);
   }
-  response->Set("supportedFeatures", std::move(supported_features_list));
-  SendMessageToClient(std::move(response));
+  response.Set("supportedFeatures", std::move(supported_features_list));
+  SendMessageToClient(base::Value(std::move(response)));
 }
 
 void Me2MeNativeMessagingHost::ProcessClearPairedClients(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+    base::Value::Dict message,
+    base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   if (needs_elevation_) {
@@ -211,8 +206,8 @@
 }
 
 void Me2MeNativeMessagingHost::ProcessDeletePairedClient(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+    base::Value::Dict message,
+    base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   if (needs_elevation_) {
@@ -222,9 +217,9 @@
     return;
   }
 
-  std::string client_id;
-  if (!message->GetString(protocol::PairingRegistry::kClientIdKey,
-                          &client_id)) {
+  std::string* client_id =
+      message.FindString(protocol::PairingRegistry::kClientIdKey);
+  if (!client_id) {
     OnError("'" + std::string(protocol::PairingRegistry::kClientIdKey) +
             "' string not found.");
     return;
@@ -232,59 +227,58 @@
 
   if (pairing_registry_.get()) {
     pairing_registry_->DeletePairing(
-        client_id, base::BindOnce(&Me2MeNativeMessagingHost::SendBooleanResult,
-                                  weak_ptr_, std::move(response)));
+        std::move(*client_id),
+        base::BindOnce(&Me2MeNativeMessagingHost::SendBooleanResult, weak_ptr_,
+                       std::move(response)));
   } else {
     SendBooleanResult(std::move(response), false);
   }
 }
 
-void Me2MeNativeMessagingHost::ProcessGetHostName(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+void Me2MeNativeMessagingHost::ProcessGetHostName(base::Value::Dict message,
+                                                  base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
-  response->SetStringKey("hostname", net::GetHostName());
-  SendMessageToClient(std::move(response));
+  response.Set("hostname", net::GetHostName());
+  SendMessageToClient(base::Value(std::move(response)));
 }
 
-void Me2MeNativeMessagingHost::ProcessGetPinHash(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+void Me2MeNativeMessagingHost::ProcessGetPinHash(base::Value::Dict message,
+                                                 base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
-  std::string host_id;
-  if (!message->GetString("hostId", &host_id)) {
+  std::string* host_id = message.FindString("hostId");
+  if (!host_id) {
     std::string message_json;
-    base::JSONWriter::Write(*message, &message_json);
+    base::JSONWriter::Write(message, &message_json);
     OnError("'hostId' not found: " + message_json);
     return;
   }
-  std::string pin;
-  if (!message->GetString("pin", &pin)) {
+  std::string* pin = message.FindString("pin");
+  if (!pin) {
     std::string message_json;
-    base::JSONWriter::Write(*message, &message_json);
+    base::JSONWriter::Write(message, &message_json);
     OnError("'pin' not found: " + message_json);
     return;
   }
-  response->SetStringKey("hash", MakeHostPinHash(host_id, pin));
-  SendMessageToClient(std::move(response));
+  response.Set("hash", MakeHostPinHash(std::move(*host_id), std::move(*pin)));
+  SendMessageToClient(base::Value(std::move(response)));
 }
 
 void Me2MeNativeMessagingHost::ProcessGenerateKeyPair(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+    base::Value::Dict message,
+    base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   scoped_refptr<RsaKeyPair> key_pair = RsaKeyPair::Generate();
-  response->SetStringKey("privateKey", key_pair->ToString());
-  response->SetStringKey("publicKey", key_pair->GetPublicKey());
-  SendMessageToClient(std::move(response));
+  response.Set("privateKey", key_pair->ToString());
+  response.Set("publicKey", key_pair->GetPublicKey());
+  SendMessageToClient(base::Value(std::move(response)));
 }
 
 void Me2MeNativeMessagingHost::ProcessUpdateDaemonConfig(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+    base::Value::Dict message,
+    base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   if (needs_elevation_) {
@@ -302,7 +296,7 @@
     }
   }
 
-  std::unique_ptr<base::DictionaryValue> config_dict =
+  absl::optional<base::Value::Dict> config_dict =
       ConfigDictionaryFromMessage(std::move(message));
   if (!config_dict) {
     OnError("'config' dictionary not found");
@@ -310,14 +304,14 @@
   }
 
   daemon_controller_->UpdateConfig(
-      std::move(config_dict),
+      std::move(*config_dict),
       base::BindOnce(&Me2MeNativeMessagingHost::SendAsyncResult, weak_ptr_,
                      std::move(response)));
 }
 
 void Me2MeNativeMessagingHost::ProcessGetDaemonConfig(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+    base::Value::Dict message,
+    base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   daemon_controller_->GetConfig(
@@ -326,8 +320,8 @@
 }
 
 void Me2MeNativeMessagingHost::ProcessGetPairedClients(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+    base::Value::Dict message,
+    base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   if (pairing_registry_.get()) {
@@ -335,15 +329,14 @@
         base::BindOnce(&Me2MeNativeMessagingHost::SendPairedClientsResponse,
                        weak_ptr_, std::move(response)));
   } else {
-    std::unique_ptr<base::ListValue> no_paired_clients(new base::ListValue);
     SendPairedClientsResponse(std::move(response),
-                              std::move(no_paired_clients));
+                              /*pairings=*/base::Value::List());
   }
 }
 
 void Me2MeNativeMessagingHost::ProcessGetUsageStatsConsent(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+    base::Value::Dict message,
+    base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   daemon_controller_->GetUsageStatsConsent(
@@ -351,9 +344,8 @@
                      weak_ptr_, std::move(response)));
 }
 
-void Me2MeNativeMessagingHost::ProcessStartDaemon(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+void Me2MeNativeMessagingHost::ProcessStartDaemon(base::Value::Dict message,
+                                                  base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   if (needs_elevation_) {
@@ -371,13 +363,13 @@
     }
   }
 
-  absl::optional<bool> consent = message->FindBoolKey("consent");
+  absl::optional<bool> consent = message.FindBool("consent");
   if (!consent) {
     OnError("'consent' not found.");
     return;
   }
 
-  std::unique_ptr<base::DictionaryValue> config_dict =
+  absl::optional<base::Value::Dict> config_dict =
       ConfigDictionaryFromMessage(std::move(message));
   if (!config_dict) {
     OnError("'config' dictionary not found");
@@ -385,14 +377,13 @@
   }
 
   daemon_controller_->SetConfigAndStart(
-      std::move(config_dict), *consent,
+      std::move(*config_dict), *consent,
       base::BindOnce(&Me2MeNativeMessagingHost::SendAsyncResult, weak_ptr_,
                      std::move(response)));
 }
 
-void Me2MeNativeMessagingHost::ProcessStopDaemon(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+void Me2MeNativeMessagingHost::ProcessStopDaemon(base::Value::Dict message,
+                                                 base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   if (needs_elevation_) {
@@ -416,52 +407,52 @@
 }
 
 void Me2MeNativeMessagingHost::ProcessGetDaemonState(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+    base::Value::Dict message,
+    base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   DaemonController::State state = daemon_controller_->GetState();
   switch (state) {
     case DaemonController::STATE_NOT_IMPLEMENTED:
-      response->SetStringKey("state", "NOT_IMPLEMENTED");
+      response.Set("state", "NOT_IMPLEMENTED");
       break;
     case DaemonController::STATE_STOPPED:
-      response->SetStringKey("state", "STOPPED");
+      response.Set("state", "STOPPED");
       break;
     case DaemonController::STATE_STARTING:
-      response->SetStringKey("state", "STARTING");
+      response.Set("state", "STARTING");
       break;
     case DaemonController::STATE_STARTED:
-      response->SetStringKey("state", "STARTED");
+      response.Set("state", "STARTED");
       break;
     case DaemonController::STATE_STOPPING:
-      response->SetStringKey("state", "STOPPING");
+      response.Set("state", "STOPPING");
       break;
     case DaemonController::STATE_UNKNOWN:
-      response->SetStringKey("state", "UNKNOWN");
+      response.Set("state", "UNKNOWN");
       break;
   }
-  SendMessageToClient(std::move(response));
+  SendMessageToClient(base::Value(std::move(response)));
 }
 
 void Me2MeNativeMessagingHost::ProcessGetHostClientId(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+    base::Value::Dict message,
+    base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
-  response->SetStringKey("clientId", google_apis::GetOAuth2ClientID(
-                                         google_apis::CLIENT_REMOTING_HOST));
-  SendMessageToClient(std::move(response));
+  response.Set("clientId", google_apis::GetOAuth2ClientID(
+                               google_apis::CLIENT_REMOTING_HOST));
+  SendMessageToClient(base::Value(std::move(response)));
 }
 
 void Me2MeNativeMessagingHost::ProcessGetCredentialsFromAuthCode(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response,
+    base::Value::Dict message,
+    base::Value::Dict response,
     bool need_user_email) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
-  std::string auth_code;
-  if (!message->GetString("authorizationCode", &auth_code)) {
+  std::string* auth_code = message.FindString("authorizationCode");
+  if (!auth_code) {
     OnError("'authorizationCode' string not found.");
     return;
   }
@@ -473,14 +464,14 @@
   };
 
   oauth_client_->GetCredentialsFromAuthCode(
-      oauth_client_info, auth_code, need_user_email,
+      oauth_client_info, std::move(*auth_code), need_user_email,
       base::BindOnce(&Me2MeNativeMessagingHost::SendCredentialsResponse,
                      weak_ptr_, std::move(response)));
 }
 
 void Me2MeNativeMessagingHost::ProcessIt2mePermissionCheck(
-    std::unique_ptr<base::DictionaryValue> message,
-    std::unique_ptr<base::DictionaryValue> response) {
+    base::Value::Dict message,
+    base::Value::Dict response) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   daemon_controller_->CheckPermission(
@@ -490,84 +481,82 @@
 }
 
 void Me2MeNativeMessagingHost::SendConfigResponse(
-    std::unique_ptr<base::DictionaryValue> response,
-    std::unique_ptr<base::DictionaryValue> config) {
+    base::Value::Dict response,
+    absl::optional<base::Value::Dict> config) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   if (config) {
-    response->Set("config", std::move(config));
+    response.Set("config", std::move(*config));
   } else {
-    response->Set("config", std::make_unique<base::Value>());
+    response.Set("config", base::Value());
   }
-  SendMessageToClient(std::move(response));
+  SendMessageToClient(base::Value(std::move(response)));
 }
 
 void Me2MeNativeMessagingHost::SendPairedClientsResponse(
-    std::unique_ptr<base::DictionaryValue> response,
-    std::unique_ptr<base::ListValue> pairings) {
+    base::Value::Dict response,
+    base::Value::List pairings) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
-  response->Set("pairedClients", std::move(pairings));
-  SendMessageToClient(std::move(response));
+  response.Set("pairedClients", std::move(pairings));
+  SendMessageToClient(base::Value(std::move(response)));
 }
 
 void Me2MeNativeMessagingHost::SendUsageStatsConsentResponse(
-    std::unique_ptr<base::DictionaryValue> response,
+    base::Value::Dict response,
     const DaemonController::UsageStatsConsent& consent) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
-  response->SetBoolKey("supported", consent.supported);
-  response->SetBoolKey("allowed", consent.allowed);
-  response->SetBoolKey("setByPolicy", consent.set_by_policy);
-  SendMessageToClient(std::move(response));
+  response.Set("supported", consent.supported);
+  response.Set("allowed", consent.allowed);
+  response.Set("setByPolicy", consent.set_by_policy);
+  SendMessageToClient(base::Value(std::move(response)));
 }
 
 void Me2MeNativeMessagingHost::SendAsyncResult(
-    std::unique_ptr<base::DictionaryValue> response,
+    base::Value::Dict response,
     DaemonController::AsyncResult result) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   switch (result) {
     case DaemonController::RESULT_OK:
-      response->SetStringKey("result", "OK");
+      response.Set("result", "OK");
       break;
     case DaemonController::RESULT_FAILED:
-      response->SetStringKey("result", "FAILED");
+      response.Set("result", "FAILED");
       break;
     case DaemonController::RESULT_CANCELLED:
-      response->SetStringKey("result", "CANCELLED");
+      response.Set("result", "CANCELLED");
       break;
   }
-  SendMessageToClient(std::move(response));
+  SendMessageToClient(base::Value(std::move(response)));
 }
 
-void Me2MeNativeMessagingHost::SendBooleanResult(
-    std::unique_ptr<base::DictionaryValue> response,
-    bool result) {
+void Me2MeNativeMessagingHost::SendBooleanResult(base::Value::Dict response,
+                                                 bool result) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
-  response->SetBoolKey("result", result);
-  SendMessageToClient(std::move(response));
+  response.Set("result", result);
+  SendMessageToClient(base::Value(std::move(response)));
 }
 
 void Me2MeNativeMessagingHost::SendCredentialsResponse(
-    std::unique_ptr<base::DictionaryValue> response,
+    base::Value::Dict response,
     const std::string& user_email,
     const std::string& refresh_token) {
   DCHECK(task_runner()->BelongsToCurrentThread());
 
   if (!user_email.empty()) {
-    response->SetStringKey("userEmail", user_email);
+    response.Set("userEmail", user_email);
   }
-  response->SetStringKey("refreshToken", refresh_token);
-  SendMessageToClient(std::move(response));
+  response.Set("refreshToken", refresh_token);
+  SendMessageToClient(base::Value(std::move(response)));
 }
 
-void Me2MeNativeMessagingHost::SendMessageToClient(
-    std::unique_ptr<base::Value> message) const {
+void Me2MeNativeMessagingHost::SendMessageToClient(base::Value message) const {
   DCHECK(task_runner()->BelongsToCurrentThread());
   std::string message_json;
-  base::JSONWriter::Write(*message, &message_json);
+  base::JSONWriter::Write(message, &message_json);
   client_->PostMessageFromNativeHost(message_json);
 }
 
@@ -585,8 +574,7 @@
 #if BUILDFLAG(IS_WIN)
 
 Me2MeNativeMessagingHost::DelegationResult
-Me2MeNativeMessagingHost::DelegateToElevatedHost(
-    std::unique_ptr<base::DictionaryValue> message) {
+Me2MeNativeMessagingHost::DelegateToElevatedHost(base::Value::Dict message) {
   DCHECK(task_runner()->BelongsToCurrentThread());
   DCHECK(needs_elevation_);
 
@@ -600,7 +588,8 @@
 
   ProcessLaunchResult result = elevated_host_->EnsureElevatedHostCreated();
   if (result == PROCESS_LAUNCH_RESULT_SUCCESS) {
-    elevated_host_->SendMessage(std::move(message));
+    elevated_host_->SendMessage(
+        base::Value::ToUniquePtrValue(base::Value(std::move(message))));
   }
 
   switch (result) {
@@ -616,8 +605,7 @@
 #else  // BUILDFLAG(IS_WIN)
 
 Me2MeNativeMessagingHost::DelegationResult
-Me2MeNativeMessagingHost::DelegateToElevatedHost(
-    std::unique_ptr<base::DictionaryValue> message) {
+Me2MeNativeMessagingHost::DelegateToElevatedHost(base::Value::Dict message) {
   NOTREACHED();
   return DELEGATION_FAILED;
 }
diff --git a/remoting/host/setup/me2me_native_messaging_host.h b/remoting/host/setup/me2me_native_messaging_host.h
index 73fb940..b442a51 100644
--- a/remoting/host/setup/me2me_native_messaging_host.h
+++ b/remoting/host/setup/me2me_native_messaging_host.h
@@ -13,16 +13,14 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_checker.h"
+#include "base/values.h"
 #include "build/build_config.h"
 #include "extensions/browser/api/messaging/native_message_host.h"
 #include "remoting/base/oauth_client.h"
 #include "remoting/host/setup/daemon_controller.h"
 
 namespace base {
-class DictionaryValue;
-class ListValue;
 class SingleThreadTaskRunner;
-class Value;
 }  // namespace base
 
 namespace remoting {
@@ -66,72 +64,60 @@
   // These "Process.." methods handle specific request types. The |response|
   // dictionary is pre-filled by ProcessMessage() with the parts of the
   // response already known ("id" and "type" fields).
-  void ProcessHello(std::unique_ptr<base::DictionaryValue> message,
-                    std::unique_ptr<base::DictionaryValue> response);
-  void ProcessClearPairedClients(
-      std::unique_ptr<base::DictionaryValue> message,
-      std::unique_ptr<base::DictionaryValue> response);
-  void ProcessDeletePairedClient(
-      std::unique_ptr<base::DictionaryValue> message,
-      std::unique_ptr<base::DictionaryValue> response);
-  void ProcessGetHostName(std::unique_ptr<base::DictionaryValue> message,
-                          std::unique_ptr<base::DictionaryValue> response);
-  void ProcessGetPinHash(std::unique_ptr<base::DictionaryValue> message,
-                         std::unique_ptr<base::DictionaryValue> response);
-  void ProcessGenerateKeyPair(std::unique_ptr<base::DictionaryValue> message,
-                              std::unique_ptr<base::DictionaryValue> response);
-  void ProcessUpdateDaemonConfig(
-      std::unique_ptr<base::DictionaryValue> message,
-      std::unique_ptr<base::DictionaryValue> response);
-  void ProcessGetDaemonConfig(std::unique_ptr<base::DictionaryValue> message,
-                              std::unique_ptr<base::DictionaryValue> response);
-  void ProcessGetPairedClients(std::unique_ptr<base::DictionaryValue> message,
-                               std::unique_ptr<base::DictionaryValue> response);
-  void ProcessGetUsageStatsConsent(
-      std::unique_ptr<base::DictionaryValue> message,
-      std::unique_ptr<base::DictionaryValue> response);
-  void ProcessStartDaemon(std::unique_ptr<base::DictionaryValue> message,
-                          std::unique_ptr<base::DictionaryValue> response);
-  void ProcessStopDaemon(std::unique_ptr<base::DictionaryValue> message,
-                         std::unique_ptr<base::DictionaryValue> response);
-  void ProcessGetDaemonState(std::unique_ptr<base::DictionaryValue> message,
-                             std::unique_ptr<base::DictionaryValue> response);
-  void ProcessGetHostClientId(std::unique_ptr<base::DictionaryValue> message,
-                              std::unique_ptr<base::DictionaryValue> response);
-  void ProcessGetCredentialsFromAuthCode(
-      std::unique_ptr<base::DictionaryValue> message,
-      std::unique_ptr<base::DictionaryValue> response,
-      bool need_user_email);
-  void ProcessIt2mePermissionCheck(
-      std::unique_ptr<base::DictionaryValue> message,
-      std::unique_ptr<base::DictionaryValue> response);
+  void ProcessHello(base::Value::Dict message, base::Value::Dict response);
+  void ProcessClearPairedClients(base::Value::Dict message,
+                                 base::Value::Dict response);
+  void ProcessDeletePairedClient(base::Value::Dict message,
+                                 base::Value::Dict response);
+  void ProcessGetHostName(base::Value::Dict message,
+                          base::Value::Dict response);
+  void ProcessGetPinHash(base::Value::Dict message, base::Value::Dict response);
+  void ProcessGenerateKeyPair(base::Value::Dict message,
+                              base::Value::Dict response);
+  void ProcessUpdateDaemonConfig(base::Value::Dict message,
+                                 base::Value::Dict response);
+  void ProcessGetDaemonConfig(base::Value::Dict message,
+                              base::Value::Dict response);
+  void ProcessGetPairedClients(base::Value::Dict message,
+                               base::Value::Dict response);
+  void ProcessGetUsageStatsConsent(base::Value::Dict message,
+                                   base::Value::Dict response);
+  void ProcessStartDaemon(base::Value::Dict message,
+                          base::Value::Dict response);
+  void ProcessStopDaemon(base::Value::Dict message, base::Value::Dict response);
+  void ProcessGetDaemonState(base::Value::Dict message,
+                             base::Value::Dict response);
+  void ProcessGetHostClientId(base::Value::Dict message,
+                              base::Value::Dict response);
+  void ProcessGetCredentialsFromAuthCode(base::Value::Dict message,
+                                         base::Value::Dict response,
+                                         bool need_user_email);
+  void ProcessIt2mePermissionCheck(base::Value::Dict message,
+                                   base::Value::Dict response);
 
   // These Send... methods get called on the DaemonController's internal thread,
   // or on the calling thread if called by the PairingRegistry.
   // These methods fill in the |response| dictionary from the other parameters,
   // and pass it to SendResponse().
-  void SendConfigResponse(std::unique_ptr<base::DictionaryValue> response,
-                          std::unique_ptr<base::DictionaryValue> config);
-  void SendPairedClientsResponse(
-      std::unique_ptr<base::DictionaryValue> response,
-      std::unique_ptr<base::ListValue> pairings);
+  void SendConfigResponse(base::Value::Dict response,
+                          absl::optional<base::Value::Dict> config);
+  void SendPairedClientsResponse(base::Value::Dict response,
+                                 base::Value::List pairings);
   void SendUsageStatsConsentResponse(
-      std::unique_ptr<base::DictionaryValue> response,
+      base::Value::Dict response,
       const DaemonController::UsageStatsConsent& consent);
-  void SendAsyncResult(std::unique_ptr<base::DictionaryValue> response,
+  void SendAsyncResult(base::Value::Dict response,
                        DaemonController::AsyncResult result);
-  void SendBooleanResult(std::unique_ptr<base::DictionaryValue> response,
-                         bool result);
-  void SendCredentialsResponse(std::unique_ptr<base::DictionaryValue> response,
+  void SendBooleanResult(base::Value::Dict response, bool result);
+  void SendCredentialsResponse(base::Value::Dict response,
                                const std::string& user_email,
                                const std::string& refresh_token);
-  void SendMessageToClient(std::unique_ptr<base::Value> message) const;
+  void SendMessageToClient(base::Value message) const;
 
   void OnError(const std::string& error_message);
 
   // Returns whether the request was successfully sent to the elevated host.
-  DelegationResult DelegateToElevatedHost(
-      std::unique_ptr<base::DictionaryValue> message);
+  DelegationResult DelegateToElevatedHost(base::Value::Dict message);
 
   bool needs_elevation_;
 
diff --git a/remoting/host/setup/me2me_native_messaging_host_unittest.cc b/remoting/host/setup/me2me_native_messaging_host_unittest.cc
index 2bce5ef..617327a4 100644
--- a/remoting/host/setup/me2me_native_messaging_host_unittest.cc
+++ b/remoting/host/setup/me2me_native_messaging_host_unittest.cc
@@ -35,6 +35,7 @@
 #include "remoting/protocol/pairing_registry.h"
 #include "remoting/protocol/protocol_mock_objects.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace {
 
@@ -43,122 +44,114 @@
 using remoting::protocol::SynchronousPairingRegistry;
 using ::testing::Optional;
 
-void VerifyHelloResponse(std::unique_ptr<base::DictionaryValue> response) {
-  ASSERT_TRUE(response);
-  std::string value;
-  EXPECT_TRUE(response->GetString("type", &value));
-  EXPECT_EQ("helloResponse", value);
-  EXPECT_TRUE(response->GetString("version", &value));
+void VerifyHelloResponse(const base::Value::Dict& response) {
+  const std::string* value = response.FindString("type");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("helloResponse", *value);
 
-  // The check below will compile but fail if VERSION isn't defined (STRINGIZE
-  // silently converts undefined values).
-  #ifndef VERSION
-  #error VERSION must be defined
-  #endif
-  EXPECT_EQ(STRINGIZE(VERSION), value);
+  value = response.FindString("version");
+  ASSERT_TRUE(value);
+
+// The check below will compile but fail if VERSION isn't defined (STRINGIZE
+// silently converts undefined values).
+#ifndef VERSION
+#error VERSION must be defined
+#endif
+  EXPECT_EQ(STRINGIZE(VERSION), *value);
 }
 
-void VerifyGetHostNameResponse(
-    std::unique_ptr<base::DictionaryValue> response) {
-  ASSERT_TRUE(response);
-  std::string value;
-  EXPECT_TRUE(response->GetString("type", &value));
-  EXPECT_EQ("getHostNameResponse", value);
-  EXPECT_TRUE(response->GetString("hostname", &value));
-  EXPECT_EQ(net::GetHostName(), value);
+void VerifyGetHostNameResponse(const base::Value::Dict& response) {
+  const std::string* value = response.FindString("type");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("getHostNameResponse", *value);
+  value = response.FindString("hostname");
+  ASSERT_TRUE(value);
+  EXPECT_EQ(net::GetHostName(), *value);
 }
 
-void VerifyGetPinHashResponse(std::unique_ptr<base::DictionaryValue> response) {
-  ASSERT_TRUE(response);
-  std::string value;
-  EXPECT_TRUE(response->GetString("type", &value));
-  EXPECT_EQ("getPinHashResponse", value);
-  EXPECT_TRUE(response->GetString("hash", &value));
-  EXPECT_EQ(remoting::MakeHostPinHash("my_host", "1234"), value);
+void VerifyGetPinHashResponse(const base::Value::Dict& response) {
+  const std::string* value = response.FindString("type");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("getPinHashResponse", *value);
+  value = response.FindString("hash");
+  ASSERT_TRUE(value);
+  EXPECT_EQ(remoting::MakeHostPinHash("my_host", "1234"), *value);
 }
 
-void VerifyGenerateKeyPairResponse(
-    std::unique_ptr<base::DictionaryValue> response) {
-  ASSERT_TRUE(response);
-  std::string value;
-  EXPECT_TRUE(response->GetString("type", &value));
-  EXPECT_EQ("generateKeyPairResponse", value);
-  EXPECT_TRUE(response->GetString("privateKey", &value));
-  EXPECT_TRUE(response->GetString("publicKey", &value));
+void VerifyGenerateKeyPairResponse(const base::Value::Dict& response) {
+  const std::string* value = response.FindString("type");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("generateKeyPairResponse", *value);
+  EXPECT_TRUE(response.FindString("privateKey"));
+  EXPECT_TRUE(response.FindString("publicKey"));
 }
 
-void VerifyGetDaemonConfigResponse(
-    std::unique_ptr<base::DictionaryValue> response) {
-  ASSERT_TRUE(response);
-  std::string value;
-  EXPECT_TRUE(response->GetString("type", &value));
-  EXPECT_EQ("getDaemonConfigResponse", value);
-  const base::DictionaryValue* config = nullptr;
-  EXPECT_TRUE(response->GetDictionary("config", &config));
-  EXPECT_EQ(base::DictionaryValue(), *config);
+void VerifyGetDaemonConfigResponse(const base::Value::Dict& response) {
+  const std::string* value = response.FindString("type");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("getDaemonConfigResponse", *value);
+  const base::Value::Dict* config = response.FindDict("config");
+  ASSERT_TRUE(config);
+  EXPECT_EQ(base::Value::Dict(), *config);
 }
 
-void VerifyGetUsageStatsConsentResponse(
-    std::unique_ptr<base::DictionaryValue> response) {
-  ASSERT_TRUE(response);
-  std::string value;
-  EXPECT_TRUE(response->GetString("type", &value));
-  EXPECT_EQ("getUsageStatsConsentResponse", value);
+void VerifyGetUsageStatsConsentResponse(const base::Value::Dict& response) {
+  const std::string* value = response.FindString("type");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("getUsageStatsConsentResponse", *value);
 
-  EXPECT_THAT(response->FindBoolKey("supported"), Optional(true));
-  EXPECT_THAT(response->FindBoolKey("allowed"), Optional(true));
-  EXPECT_THAT(response->FindBoolKey("setByPolicy"), Optional(true));
+  EXPECT_THAT(response.FindBool("supported"), Optional(true));
+  EXPECT_THAT(response.FindBool("allowed"), Optional(true));
+  EXPECT_THAT(response.FindBool("setByPolicy"), Optional(true));
 }
 
-void VerifyStopDaemonResponse(std::unique_ptr<base::DictionaryValue> response) {
-  ASSERT_TRUE(response);
-  std::string value;
-  EXPECT_TRUE(response->GetString("type", &value));
-  EXPECT_EQ("stopDaemonResponse", value);
-  EXPECT_TRUE(response->GetString("result", &value));
-  EXPECT_EQ("OK", value);
+void VerifyStopDaemonResponse(const base::Value::Dict& response) {
+  const std::string* value = response.FindString("type");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("stopDaemonResponse", *value);
+  value = response.FindString("result");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("OK", *value);
 }
 
-void VerifyGetDaemonStateResponse(
-    std::unique_ptr<base::DictionaryValue> response) {
-  ASSERT_TRUE(response);
-  std::string value;
-  EXPECT_TRUE(response->GetString("type", &value));
-  EXPECT_EQ("getDaemonStateResponse", value);
-  EXPECT_TRUE(response->GetString("state", &value));
-  EXPECT_EQ("STARTED", value);
+void VerifyGetDaemonStateResponse(const base::Value::Dict& response) {
+  const std::string* value = response.FindString("type");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("getDaemonStateResponse", *value);
+  value = response.FindString("state");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("STARTED", *value);
 }
 
-void VerifyUpdateDaemonConfigResponse(
-    std::unique_ptr<base::DictionaryValue> response) {
-  ASSERT_TRUE(response);
-  std::string value;
-  EXPECT_TRUE(response->GetString("type", &value));
-  EXPECT_EQ("updateDaemonConfigResponse", value);
-  EXPECT_TRUE(response->GetString("result", &value));
-  EXPECT_EQ("OK", value);
+void VerifyUpdateDaemonConfigResponse(const base::Value::Dict& response) {
+  const std::string* value = response.FindString("type");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("updateDaemonConfigResponse", *value);
+  value = response.FindString("result");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("OK", *value);
 }
 
-void VerifyStartDaemonResponse(
-    std::unique_ptr<base::DictionaryValue> response) {
-  ASSERT_TRUE(response);
-  std::string value;
-  EXPECT_TRUE(response->GetString("type", &value));
-  EXPECT_EQ("startDaemonResponse", value);
-  EXPECT_TRUE(response->GetString("result", &value));
-  EXPECT_EQ("OK", value);
+void VerifyStartDaemonResponse(const base::Value::Dict& response) {
+  const std::string* value = response.FindString("type");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("startDaemonResponse", *value);
+  value = response.FindString("result");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("OK", *value);
 }
 
 void VerifyGetCredentialsFromAuthCodeResponse(
-    std::unique_ptr<base::DictionaryValue> response) {
-  ASSERT_TRUE(response);
-  std::string value;
-  EXPECT_TRUE(response->GetString("type", &value));
-  EXPECT_EQ("getCredentialsFromAuthCodeResponse", value);
-  EXPECT_TRUE(response->GetString("userEmail", &value));
-  EXPECT_EQ("fake_user_email", value);
-  EXPECT_TRUE(response->GetString("refreshToken", &value));
-  EXPECT_EQ("fake_refresh_token", value);
+    const base::Value::Dict& response) {
+  const std::string* value = response.FindString("type");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("getCredentialsFromAuthCodeResponse", *value);
+  value = response.FindString("userEmail");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("fake_user_email", *value);
+  value = response.FindString("refreshToken");
+  ASSERT_TRUE(value);
+  EXPECT_EQ("fake_refresh_token", *value);
 }
 
 }  // namespace
@@ -177,13 +170,13 @@
 
   // DaemonController::Delegate interface.
   DaemonController::State GetState() override;
-  std::unique_ptr<base::DictionaryValue> GetConfig() override;
+  absl::optional<base::Value::Dict> GetConfig() override;
   void CheckPermission(bool it2me,
                        DaemonController::BoolCallback callback) override;
-  void SetConfigAndStart(std::unique_ptr<base::DictionaryValue> config,
+  void SetConfigAndStart(base::Value::Dict config,
                          bool consent,
                          DaemonController::CompletionCallback done) override;
-  void UpdateConfig(std::unique_ptr<base::DictionaryValue> config,
+  void UpdateConfig(base::Value::Dict config,
                     DaemonController::CompletionCallback done) override;
   void Stop(DaemonController::CompletionCallback done) override;
   DaemonController::UsageStatsConsent GetUsageStatsConsent() override;
@@ -197,9 +190,8 @@
   return DaemonController::STATE_STARTED;
 }
 
-std::unique_ptr<base::DictionaryValue>
-MockDaemonControllerDelegate::GetConfig() {
-  return std::make_unique<base::DictionaryValue>();
+absl::optional<base::Value::Dict> MockDaemonControllerDelegate::GetConfig() {
+  return base::Value::Dict();
 }
 
 void MockDaemonControllerDelegate::CheckPermission(
@@ -209,11 +201,11 @@
 }
 
 void MockDaemonControllerDelegate::SetConfigAndStart(
-    std::unique_ptr<base::DictionaryValue> config,
+    base::Value::Dict config,
     bool consent,
     DaemonController::CompletionCallback done) {
   // Verify parameters passed in.
-  if (consent && config && config->FindKey("start")) {
+  if (consent && config.Find("start")) {
     std::move(done).Run(DaemonController::RESULT_OK);
   } else {
     std::move(done).Run(DaemonController::RESULT_FAILED);
@@ -221,9 +213,9 @@
 }
 
 void MockDaemonControllerDelegate::UpdateConfig(
-    std::unique_ptr<base::DictionaryValue> config,
+    base::Value::Dict config,
     DaemonController::CompletionCallback done) {
-  if (config && config->FindKey("update")) {
+  if (config.Find("update")) {
     std::move(done).Run(DaemonController::RESULT_OK);
   } else {
     std::move(done).Run(DaemonController::RESULT_FAILED);
@@ -257,9 +249,9 @@
   void SetUp() override;
   void TearDown() override;
 
-  std::unique_ptr<base::DictionaryValue> ReadMessageFromOutputPipe();
+  absl::optional<base::Value::Dict> ReadMessageFromOutputPipe();
 
-  void WriteMessageToInputPipe(const base::Value& message);
+  void WriteMessageToInputPipe(const base::ValueView& message);
 
   // The Host process should shut down when it receives a malformed request.
   // This is tested by sending a known-good request, followed by |message|,
@@ -407,7 +399,7 @@
   test_run_loop_->Run();
 
   // Verify there are no more message in the output pipe.
-  std::unique_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
+  absl::optional<base::Value::Dict> response = ReadMessageFromOutputPipe();
   EXPECT_FALSE(response);
 
   // The It2MeMe2MeNativeMessagingHost dtor closes the handles that are passed
@@ -415,42 +407,39 @@
   output_read_file_.Close();
 }
 
-std::unique_ptr<base::DictionaryValue>
+absl::optional<base::Value::Dict>
 Me2MeNativeMessagingHostTest::ReadMessageFromOutputPipe() {
   while (true) {
     uint32_t length;
     int read_result = output_read_file_.ReadAtCurrentPos(
         reinterpret_cast<char*>(&length), sizeof(length));
     if (read_result != sizeof(length)) {
-      return nullptr;
+      return absl::nullopt;
     }
 
     std::string message_json(length, '\0');
     read_result =
         output_read_file_.ReadAtCurrentPos(std::data(message_json), length);
     if (read_result != static_cast<int>(length)) {
-      return nullptr;
+      return absl::nullopt;
     }
 
-    std::unique_ptr<base::Value> message =
-        base::JSONReader::ReadDeprecated(message_json);
+    absl::optional<base::Value> message = base::JSONReader::Read(message_json);
     if (!message || !message->is_dict()) {
-      return nullptr;
+      return absl::nullopt;
     }
 
-    std::unique_ptr<base::DictionaryValue> result = base::WrapUnique(
-        static_cast<base::DictionaryValue*>(message.release()));
-    std::string type;
+    base::Value::Dict& result = message->GetDict();
+    const std::string* type = result.FindString("type");
     // If this is a debug message log, ignore it, otherwise return it.
-    if (!result->GetString("type", &type) ||
-        type != LogMessageHandler::kDebugMessageTypeName) {
-      return result;
+    if (!type || *type != LogMessageHandler::kDebugMessageTypeName) {
+      return std::move(result);
     }
   }
 }
 
 void Me2MeNativeMessagingHostTest::WriteMessageToInputPipe(
-    const base::Value& message) {
+    const base::ValueView& message) {
   std::string message_json;
   base::JSONWriter::Write(message, &message_json);
 
@@ -461,8 +450,8 @@
 }
 
 void Me2MeNativeMessagingHostTest::TestBadRequest(const base::Value& message) {
-  base::DictionaryValue good_message;
-  good_message.SetStringKey("type", "hello");
+  base::Value::Dict good_message;
+  good_message.Set("type", "hello");
 
   // This test currently relies on synchronous processing of hello messages and
   // message parameters verification.
@@ -471,8 +460,9 @@
   WriteMessageToInputPipe(good_message);
 
   // Read from output pipe, and verify responses.
-  std::unique_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
-  VerifyHelloResponse(std::move(response));
+  absl::optional<base::Value::Dict> response = ReadMessageFromOutputPipe();
+  ASSERT_TRUE(response);
+  VerifyHelloResponse(std::move(*response));
 
   response = ReadMessageFromOutputPipe();
   EXPECT_FALSE(response);
@@ -482,64 +472,64 @@
 // Test all valid request-types.
 TEST_F(Me2MeNativeMessagingHostTest, All) {
   int next_id = 0;
-  base::DictionaryValue message;
-  message.SetIntKey("id", next_id++);
-  message.SetStringKey("type", "hello");
+  base::Value::Dict message;
+  message.Set("id", next_id++);
+  message.Set("type", "hello");
   WriteMessageToInputPipe(message);
 
-  message.SetIntKey("id", next_id++);
-  message.SetStringKey("type", "getHostName");
+  message.Set("id", next_id++);
+  message.Set("type", "getHostName");
   WriteMessageToInputPipe(message);
 
-  message.SetIntKey("id", next_id++);
-  message.SetStringKey("type", "getPinHash");
-  message.SetStringKey("hostId", "my_host");
-  message.SetStringKey("pin", "1234");
+  message.Set("id", next_id++);
+  message.Set("type", "getPinHash");
+  message.Set("hostId", "my_host");
+  message.Set("pin", "1234");
   WriteMessageToInputPipe(message);
 
-  message.DictClear();
-  message.SetIntKey("id", next_id++);
-  message.SetStringKey("type", "generateKeyPair");
+  message.clear();
+  message.Set("id", next_id++);
+  message.Set("type", "generateKeyPair");
   WriteMessageToInputPipe(message);
 
-  message.SetIntKey("id", next_id++);
-  message.SetStringKey("type", "getDaemonConfig");
+  message.Set("id", next_id++);
+  message.Set("type", "getDaemonConfig");
   WriteMessageToInputPipe(message);
 
-  message.SetIntKey("id", next_id++);
-  message.SetStringKey("type", "getUsageStatsConsent");
+  message.Set("id", next_id++);
+  message.Set("type", "getUsageStatsConsent");
   WriteMessageToInputPipe(message);
 
-  message.SetIntKey("id", next_id++);
-  message.SetStringKey("type", "stopDaemon");
+  message.Set("id", next_id++);
+  message.Set("type", "stopDaemon");
   WriteMessageToInputPipe(message);
 
-  message.SetIntKey("id", next_id++);
-  message.SetStringKey("type", "getDaemonState");
+  message.Set("id", next_id++);
+  message.Set("type", "getDaemonState");
   WriteMessageToInputPipe(message);
 
   // Following messages require a "config" dictionary.
-  base::DictionaryValue config;
-  config.SetBoolKey("update", true);
-  message.Set("config", config.CreateDeepCopy());
-  message.SetIntKey("id", next_id++);
-  message.SetStringKey("type", "updateDaemonConfig");
+  base::Value::Dict config;
+  config.Set("update", true);
+  message.Set("config", config.Clone());
+  message.Set("id", next_id++);
+  message.Set("type", "updateDaemonConfig");
   WriteMessageToInputPipe(message);
 
-  config.DictClear();
-  config.SetBoolKey("start", true);
-  message.Set("config", config.CreateDeepCopy());
-  message.SetBoolKey("consent", true);
-  message.SetIntKey("id", next_id++);
-  message.SetStringKey("type", "startDaemon");
+  config.clear();
+  config.Set("start", true);
+  message.Set("config", config.Clone());
+  message.Set("consent", true);
+  message.Set("id", next_id++);
+  message.Set("type", "startDaemon");
   WriteMessageToInputPipe(message);
 
-  message.SetIntKey("id", next_id++);
-  message.SetStringKey("type", "getCredentialsFromAuthCode");
-  message.SetStringKey("authorizationCode", "fake_auth_code");
+  message.Set("id", next_id++);
+  message.Set("type", "getCredentialsFromAuthCode");
+  message.Set("authorizationCode", "fake_auth_code");
   WriteMessageToInputPipe(message);
 
-  void (*verify_routines[])(std::unique_ptr<base::DictionaryValue>) = {
+  void (*verify_routines[])(const base::Value::Dict&) = {
       &VerifyHelloResponse,
       &VerifyGetHostNameResponse,
       &VerifyGetPinHashResponse,
@@ -556,107 +546,106 @@
 
   // Read all responses from output pipe, and verify them.
   for (int i = 0; i < next_id; ++i) {
-    std::unique_ptr<base::DictionaryValue> response =
-        ReadMessageFromOutputPipe();
+    absl::optional<base::Value::Dict> response = ReadMessageFromOutputPipe();
+    ASSERT_TRUE(response);
 
     // Make sure that id is available and is in the range.
-    int id;
-    ASSERT_TRUE(response->GetInteger("id", &id));
-    ASSERT_TRUE(0 <= id && id < next_id);
+    absl::optional<int> id = response->FindInt("id");
+    ASSERT_TRUE(id);
+    ASSERT_TRUE(0 <= *id && *id < next_id);
 
     // Call the verification routine corresponding to the message id.
-    ASSERT_TRUE(verify_routines[id]);
-    verify_routines[id](std::move(response));
+    ASSERT_TRUE(verify_routines[*id]);
+    verify_routines[*id](std::move(*response));
 
     // Clear the pointer so that the routine cannot be called the second time.
-    verify_routines[id] = nullptr;
+    verify_routines[*id] = nullptr;
   }
 }
 
 // Verify that response ID matches request ID.
 TEST_F(Me2MeNativeMessagingHostTest, Id) {
-  base::DictionaryValue message;
-  message.SetStringKey("type", "hello");
+  base::Value::Dict message;
+  message.Set("type", "hello");
   WriteMessageToInputPipe(message);
-  message.SetStringKey("id", "42");
+  message.Set("id", "42");
   WriteMessageToInputPipe(message);
 
-  std::unique_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
+  absl::optional<base::Value::Dict> response = ReadMessageFromOutputPipe();
   EXPECT_TRUE(response);
-  std::string value;
-  EXPECT_FALSE(response->GetString("id", &value));
+  std::string* value = response->FindString("id");
+  EXPECT_FALSE(value);
 
   response = ReadMessageFromOutputPipe();
   EXPECT_TRUE(response);
-  EXPECT_TRUE(response->GetString("id", &value));
-  EXPECT_EQ("42", value);
+  value = response->FindString("id");
+  EXPECT_TRUE(value);
+  EXPECT_EQ("42", *value);
 }
 
 // Verify non-Dictionary requests are rejected.
 TEST_F(Me2MeNativeMessagingHostTest, WrongFormat) {
-  base::ListValue message;
-  TestBadRequest(message);
+  TestBadRequest(base::Value(base::Value::Type::LIST));
 }
 
 // Verify requests with no type are rejected.
 TEST_F(Me2MeNativeMessagingHostTest, MissingType) {
-  base::DictionaryValue message;
-  TestBadRequest(message);
+  TestBadRequest(base::Value(base::Value::Type::DICT));
 }
 
 // Verify rejection if type is unrecognized.
 TEST_F(Me2MeNativeMessagingHostTest, InvalidType) {
-  base::DictionaryValue message;
-  message.SetStringKey("type", "xxx");
-  TestBadRequest(message);
+  base::Value::Dict message;
+  message.Set("type", "xxx");
+  TestBadRequest(base::Value(std::move(message)));
 }
 
 // Verify rejection if getPinHash request has no hostId.
 TEST_F(Me2MeNativeMessagingHostTest, GetPinHashNoHostId) {
-  base::DictionaryValue message;
-  message.SetStringKey("type", "getPinHash");
-  message.SetStringKey("pin", "1234");
-  TestBadRequest(message);
+  base::Value::Dict message;
+  message.Set("type", "getPinHash");
+  message.Set("pin", "1234");
+  TestBadRequest(base::Value(std::move(message)));
 }
 
 // Verify rejection if getPinHash request has no pin.
 TEST_F(Me2MeNativeMessagingHostTest, GetPinHashNoPin) {
-  base::DictionaryValue message;
-  message.SetStringKey("type", "getPinHash");
-  message.SetStringKey("hostId", "my_host");
-  TestBadRequest(message);
+  base::Value::Dict message;
+  message.Set("type", "getPinHash");
+  message.Set("hostId", "my_host");
+  TestBadRequest(base::Value(std::move(message)));
 }
 
 // Verify rejection if updateDaemonConfig request has invalid config.
 TEST_F(Me2MeNativeMessagingHostTest, UpdateDaemonConfigInvalidConfig) {
-  base::DictionaryValue message;
-  message.SetStringKey("type", "updateDaemonConfig");
-  message.SetStringKey("config", "xxx");
-  TestBadRequest(message);
+  base::Value::Dict message;
+  message.Set("type", "updateDaemonConfig");
+  message.Set("config", "xxx");
+  TestBadRequest(base::Value(std::move(message)));
 }
 
 // Verify rejection if startDaemon request has invalid config.
 TEST_F(Me2MeNativeMessagingHostTest, StartDaemonInvalidConfig) {
-  base::DictionaryValue message;
-  message.SetStringKey("type", "startDaemon");
-  message.SetStringKey("config", "xxx");
-  message.SetBoolKey("consent", true);
-  TestBadRequest(message);
+  base::Value::Dict message;
+  message.Set("type", "startDaemon");
+  message.Set("config", "xxx");
+  message.Set("consent", true);
+  TestBadRequest(base::Value(std::move(message)));
 }
 
 // Verify rejection if startDaemon request has no "consent" parameter.
 TEST_F(Me2MeNativeMessagingHostTest, StartDaemonNoConsent) {
-  base::DictionaryValue message;
-  message.SetStringKey("type", "startDaemon");
-  message.Set("config", base::DictionaryValue().CreateDeepCopy());
-  TestBadRequest(message);
+  base::Value::Dict message;
+  message.Set("type", "startDaemon");
+  message.Set("config", base::Value::Dict());
+  TestBadRequest(base::Value(std::move(message)));
 }
 
 // Verify rejection if getCredentialsFromAuthCode has no auth code.
 TEST_F(Me2MeNativeMessagingHostTest, GetCredentialsFromAuthCodeNoAuthCode) {
-  base::DictionaryValue message;
-  message.SetStringKey("type", "getCredentialsFromAuthCode");
-  TestBadRequest(message);
+  base::Value::Dict message;
+  message.Set("type", "getCredentialsFromAuthCode");
+  TestBadRequest(base::Value(std::move(message)));
 }
 
 }  // namespace remoting
diff --git a/remoting/protocol/pairing_registry.cc b/remoting/protocol/pairing_registry.cc
index 93d977f..c64ef763 100644
--- a/remoting/protocol/pairing_registry.cc
+++ b/remoting/protocol/pairing_registry.cc
@@ -60,31 +60,30 @@
 }
 
 PairingRegistry::Pairing PairingRegistry::Pairing::CreateFromValue(
-    const base::DictionaryValue& pairing) {
-  std::string client_name, client_id;
+    const base::Value::Dict& pairing) {
   absl::optional<double> created_time_value =
-      pairing.FindDoubleKey(kCreatedTimeKey);
-  if (created_time_value && pairing.GetString(kClientNameKey, &client_name) &&
-      pairing.GetString(kClientIdKey, &client_id)) {
+      pairing.FindDouble(kCreatedTimeKey);
+  const std::string* client_name = pairing.FindString(kClientNameKey);
+  const std::string* client_id = pairing.FindString(kClientIdKey);
+  if (created_time_value && client_name && client_id) {
     // The shared secret is optional.
-    std::string shared_secret;
-    pairing.GetString(kSharedSecretKey, &shared_secret);
+    const std::string* shared_secret = pairing.FindString(kSharedSecretKey);
     base::Time created_time = base::Time::FromJsTime(*created_time_value);
-    return Pairing(created_time, client_name, client_id, shared_secret);
+    return Pairing(created_time, *client_name, *client_id,
+                   shared_secret ? *shared_secret : "");
   }
 
   LOG(ERROR) << "Failed to load pairing information: unexpected format.";
   return Pairing();
 }
 
-std::unique_ptr<base::DictionaryValue> PairingRegistry::Pairing::ToValue()
-    const {
-  std::unique_ptr<base::DictionaryValue> pairing(new base::DictionaryValue());
-  pairing->SetDouble(kCreatedTimeKey, created_time().ToJsTime());
-  pairing->SetString(kClientNameKey, client_name());
-  pairing->SetString(kClientIdKey, client_id());
+base::Value::Dict PairingRegistry::Pairing::ToValue() const {
+  base::Value::Dict pairing;
+  pairing.Set(kCreatedTimeKey, static_cast<double>(created_time().ToJsTime()));
+  pairing.Set(kClientNameKey, client_name());
+  pairing.Set(kClientIdKey, client_id());
   if (!shared_secret().empty())
-    pairing->SetString(kSharedSecretKey, shared_secret());
+    pairing.Set(kSharedSecretKey, shared_secret());
   return pairing;
 }
 
@@ -183,7 +182,7 @@
 void PairingRegistry::DoLoadAll(GetAllPairingsCallback callback) {
   DCHECK(delegate_task_runner_->BelongsToCurrentThread());
 
-  std::unique_ptr<base::ListValue> pairings = delegate_->LoadAll();
+  base::Value::List pairings = delegate_->LoadAll();
   PostTask(caller_task_runner_, FROM_HERE,
            base::BindOnce(std::move(callback), std::move(pairings)));
 }
@@ -242,27 +241,25 @@
 
 void PairingRegistry::InvokeGetAllPairingsCallbackAndScheduleNext(
     GetAllPairingsCallback callback,
-    std::unique_ptr<base::ListValue> pairings) {
+    base::Value::List pairings) {
   std::move(callback).Run(std::move(pairings));
   pending_requests_.pop();
   ServiceNextRequest();
 }
 
-void PairingRegistry::SanitizePairings(
-    GetAllPairingsCallback callback,
-    std::unique_ptr<base::ListValue> pairings) {
+void PairingRegistry::SanitizePairings(GetAllPairingsCallback callback,
+                                       base::Value::List pairings) {
   DCHECK(caller_task_runner_->BelongsToCurrentThread());
 
-  std::unique_ptr<base::ListValue> sanitized_pairings(new base::ListValue());
-  for (size_t i = 0; i < pairings->GetListDeprecated().size(); ++i) {
-    base::DictionaryValue* pairing_json;
-    if (!pairings->GetDictionary(i, &pairing_json)) {
+  base::Value::List sanitized_pairings;
+  for (const base::Value& pairing_json : pairings) {
+    if (!pairing_json.is_dict()) {
       LOG(WARNING) << "A pairing entry is not a dictionary.";
       continue;
     }
 
     // Parse the pairing data.
-    Pairing pairing = Pairing::CreateFromValue(*pairing_json);
+    Pairing pairing = Pairing::CreateFromValue(pairing_json.GetDict());
     if (!pairing.is_valid()) {
       LOG(WARNING) << "Could not parse a pairing entry.";
       continue;
@@ -274,8 +271,7 @@
         pairing.client_name(),
         pairing.client_id(),
         "");
-    sanitized_pairings->GetList().Append(
-        base::Value::FromUniquePtrValue(sanitized_pairing.ToValue()));
+    sanitized_pairings.Append(sanitized_pairing.ToValue());
   }
 
   std::move(callback).Run(std::move(sanitized_pairings));
diff --git a/remoting/protocol/pairing_registry.h b/remoting/protocol/pairing_registry.h
index e104250..21991d3c 100644
--- a/remoting/protocol/pairing_registry.h
+++ b/remoting/protocol/pairing_registry.h
@@ -15,10 +15,9 @@
 #include "base/gtest_prod_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/time/time.h"
+#include "base/values.h"
 
 namespace base {
-class DictionaryValue;
-class ListValue;
 class Location;
 class SingleThreadTaskRunner;
 }  // namespace base
@@ -47,9 +46,9 @@
     ~Pairing();
 
     static Pairing Create(const std::string& client_name);
-    static Pairing CreateFromValue(const base::DictionaryValue& pairing);
+    static Pairing CreateFromValue(const base::Value::Dict& pairing);
 
-    std::unique_ptr<base::DictionaryValue> ToValue() const;
+    base::Value::Dict ToValue() const;
 
     bool operator==(const Pairing& other) const;
 
@@ -72,7 +71,7 @@
 
   // Delegate callbacks.
   typedef base::OnceCallback<void(bool success)> DoneCallback;
-  typedef base::OnceCallback<void(std::unique_ptr<base::ListValue> pairings)>
+  typedef base::OnceCallback<void(base::Value::List pairings)>
       GetAllPairingsCallback;
   typedef base::OnceCallback<void(Pairing pairing)> GetPairingCallback;
 
@@ -87,7 +86,7 @@
     virtual ~Delegate() {}
 
     // Retrieves all JSON-encoded pairings from persistent storage.
-    virtual std::unique_ptr<base::ListValue> LoadAll() = 0;
+    virtual base::Value::List LoadAll() = 0;
 
     // Deletes all pairings in persistent storage.
     virtual bool DeleteAll() = 0;
@@ -121,7 +120,7 @@
   // with an invalid Pairing.
   void GetPairing(const std::string& client_id, GetPairingCallback callback);
 
-  // Gets all pairings with the shared secrets removed as a base::ListValue.
+  // Gets all pairings with the shared secrets removed as a base::Value::List.
   void GetAllPairings(GetAllPairingsCallback callback);
 
   // Delete a pairing, identified by its client ID. |callback| is called with
@@ -163,11 +162,11 @@
                                                Pairing pairing);
   void InvokeGetAllPairingsCallbackAndScheduleNext(
       GetAllPairingsCallback callback,
-      std::unique_ptr<base::ListValue> pairings);
+      base::Value::List pairings);
 
   // Sanitize |pairings| by parsing each entry and removing the secret from it.
   void SanitizePairings(GetAllPairingsCallback callback,
-                        std::unique_ptr<base::ListValue> pairings);
+                        base::Value::List pairings);
 
   // Queue management methods.
   void ServiceOrQueueRequest(base::OnceClosure request);
diff --git a/remoting/protocol/pairing_registry_unittest.cc b/remoting/protocol/pairing_registry_unittest.cc
index 2a03793..4c11d65 100644
--- a/remoting/protocol/pairing_registry_unittest.cc
+++ b/remoting/protocol/pairing_registry_unittest.cc
@@ -7,7 +7,6 @@
 #include <stdlib.h>
 
 #include <algorithm>
-#include <memory>
 #include <utility>
 
 #include "base/bind.h"
@@ -37,27 +36,22 @@
   virtual ~MockPairingRegistryCallbacks() = default;
 
   MOCK_METHOD1(DoneCallback, void(bool));
-  MOCK_METHOD1(GetAllPairingsCallbackPtr, void(base::ListValue*));
+  MOCK_METHOD1(GetAllPairingsCallback, void(base::Value::List));
   MOCK_METHOD1(GetPairingCallback, void(PairingRegistry::Pairing));
-
-  void GetAllPairingsCallback(std::unique_ptr<base::ListValue> pairings) {
-    GetAllPairingsCallbackPtr(pairings.get());
-  }
 };
 
 // Verify that a pairing Dictionary has correct entries, but doesn't include
 // any shared secret.
 void VerifyPairing(PairingRegistry::Pairing expected,
-                   const base::DictionaryValue& actual) {
-  const std::string* value =
-      actual.FindStringKey(PairingRegistry::kClientNameKey);
+                   const base::Value::Dict& actual) {
+  const std::string* value = actual.FindString(PairingRegistry::kClientNameKey);
   ASSERT_TRUE(value);
   EXPECT_EQ(expected.client_name(), *value);
-  value = actual.FindStringKey(PairingRegistry::kClientIdKey);
+  value = actual.FindString(PairingRegistry::kClientIdKey);
   ASSERT_TRUE(value);
   EXPECT_EQ(expected.client_id(), *value);
 
-  EXPECT_FALSE(actual.FindKey(PairingRegistry::kSharedSecretKey));
+  EXPECT_FALSE(actual.Find(PairingRegistry::kSharedSecretKey));
 }
 
 }  // namespace
@@ -69,7 +63,7 @@
  public:
   void SetUp() override { callback_count_ = 0; }
 
-  void set_pairings(std::unique_ptr<base::ListValue> pairings) {
+  void set_pairings(base::Value::List pairings) {
     pairings_ = std::move(pairings);
   }
 
@@ -89,7 +83,7 @@
   base::RunLoop run_loop_;
 
   int callback_count_;
-  std::unique_ptr<base::ListValue> pairings_;
+  base::Value::List pairings_;
 };
 
 TEST_F(PairingRegistryTest, CreateAndGetPairings) {
@@ -123,21 +117,19 @@
   registry->GetAllPairings(base::BindOnce(&PairingRegistryTest::set_pairings,
                                           base::Unretained(this)));
 
-  ASSERT_EQ(2u, pairings_->GetListDeprecated().size());
-  const base::Value& actual_pairing_1_value = pairings_->GetListDeprecated()[0];
+  ASSERT_EQ(2u, pairings_.size());
+  const base::Value& actual_pairing_1_value = pairings_[0];
   ASSERT_TRUE(actual_pairing_1_value.is_dict());
-  const base::Value& actual_pairing_2_value = pairings_->GetListDeprecated()[1];
+  const base::Value& actual_pairing_2_value = pairings_[1];
   ASSERT_TRUE(actual_pairing_2_value.is_dict());
-  const base::DictionaryValue* actual_pairing_1 =
-      &base::Value::AsDictionaryValue(actual_pairing_1_value);
-  const base::DictionaryValue* actual_pairing_2 =
-      &base::Value::AsDictionaryValue(actual_pairing_2_value);
+  const base::Value::Dict* actual_pairing_1 = &actual_pairing_1_value.GetDict();
+  const base::Value::Dict* actual_pairing_2 = &actual_pairing_2_value.GetDict();
 
   // Ordering is not guaranteed, so swap if necessary.
-  std::string actual_client_id;
-  ASSERT_TRUE(actual_pairing_1->GetString(PairingRegistry::kClientIdKey,
-                                          &actual_client_id));
-  if (actual_client_id != pairing_1.client_id()) {
+  const std::string* actual_client_id =
+      actual_pairing_1->FindString(PairingRegistry::kClientIdKey);
+  ASSERT_TRUE(actual_client_id);
+  if (*actual_client_id != pairing_1.client_id()) {
     std::swap(actual_pairing_1, actual_pairing_2);
   }
 
@@ -160,15 +152,14 @@
   registry->GetAllPairings(base::BindOnce(&PairingRegistryTest::set_pairings,
                                           base::Unretained(this)));
 
-  ASSERT_EQ(1u, pairings_->GetListDeprecated().size());
-  const base::Value& actual_pairing_2_value = pairings_->GetListDeprecated()[0];
+  ASSERT_EQ(1u, pairings_.size());
+  const base::Value& actual_pairing_2_value = pairings_[0];
   ASSERT_TRUE(actual_pairing_2_value.is_dict());
-  const base::DictionaryValue& actual_pairing_2 =
-      base::Value::AsDictionaryValue(actual_pairing_2_value);
-  std::string actual_client_id;
-  ASSERT_TRUE(actual_pairing_2.GetString(PairingRegistry::kClientIdKey,
-                                         &actual_client_id));
-  EXPECT_EQ(pairing_2.client_id(), actual_client_id);
+  const std::string* actual_client_id =
+      actual_pairing_2_value.GetDict().FindString(
+          PairingRegistry::kClientIdKey);
+  ASSERT_TRUE(actual_client_id);
+  EXPECT_EQ(pairing_2.client_id(), *actual_client_id);
 }
 
 TEST_F(PairingRegistryTest, ClearAllPairings) {
@@ -184,7 +175,7 @@
   registry->GetAllPairings(base::BindOnce(&PairingRegistryTest::set_pairings,
                                           base::Unretained(this)));
 
-  EXPECT_TRUE(pairings_->GetListDeprecated().empty());
+  EXPECT_TRUE(pairings_.empty());
 }
 
 ACTION_P(QuitMessageLoop, callback) {
@@ -196,7 +187,7 @@
 }
 
 MATCHER(NoPairings, "") {
-  return arg->GetListDeprecated().empty();
+  return arg.empty();
 }
 
 TEST_F(PairingRegistryTest, SerializedRequests) {
@@ -214,8 +205,7 @@
       .InSequence(s);
   EXPECT_CALL(callbacks, DoneCallback(true))
       .InSequence(s);
-  EXPECT_CALL(callbacks, GetAllPairingsCallbackPtr(NoPairings()))
-      .InSequence(s);
+  EXPECT_CALL(callbacks, GetAllPairingsCallback(NoPairings())).InSequence(s);
   EXPECT_CALL(callbacks, GetPairingCallback(EqualsClientName("client3")))
       .InSequence(s)
       .WillOnce(QuitMessageLoop(run_loop_.QuitClosure()));
diff --git a/remoting/protocol/protocol_mock_objects.cc b/remoting/protocol/protocol_mock_objects.cc
index 00b6426..049295e 100644
--- a/remoting/protocol/protocol_mock_objects.cc
+++ b/remoting/protocol/protocol_mock_objects.cc
@@ -51,12 +51,11 @@
 MockPairingRegistryDelegate::MockPairingRegistryDelegate() = default;
 MockPairingRegistryDelegate::~MockPairingRegistryDelegate() = default;
 
-std::unique_ptr<base::ListValue> MockPairingRegistryDelegate::LoadAll() {
-  std::unique_ptr<base::ListValue> result(new base::ListValue());
+base::Value::List MockPairingRegistryDelegate::LoadAll() {
+  base::Value::List result;
   for (Pairings::const_iterator i = pairings_.begin(); i != pairings_.end();
        ++i) {
-    result->GetList().Append(
-        base::Value::FromUniquePtrValue(i->second.ToValue()));
+    result.Append(i->second.ToValue());
   }
   return result;
 }
diff --git a/remoting/protocol/protocol_mock_objects.h b/remoting/protocol/protocol_mock_objects.h
index 8a3b9a9e..388a305 100644
--- a/remoting/protocol/protocol_mock_objects.h
+++ b/remoting/protocol/protocol_mock_objects.h
@@ -256,7 +256,7 @@
   ~MockPairingRegistryDelegate() override;
 
   // PairingRegistry::Delegate implementation.
-  std::unique_ptr<base::ListValue> LoadAll() override;
+  base::Value::List LoadAll() override;
   bool DeleteAll() override;
   protocol::PairingRegistry::Pairing Load(
       const std::string& client_id) override;
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 2fe471c..9fbb457b9 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -959,7 +959,12 @@
     base::Time start_time,
     base::Time end_time,
     base::OnceClosure completion_callback) {
+#if BUILDFLAG(IS_CT_SUPPORTED)
+  auto barrier = base::BarrierClosure(3, std::move(completion_callback));
+  sct_auditing_handler()->ClearPendingReports(barrier);
+#else
   auto barrier = base::BarrierClosure(2, std::move(completion_callback));
+#endif  // BUIDLFLAG(IS_CT_SUPPORTED)
 
   url_request_context_->transport_security_state()->DeleteAllDynamicDataBetween(
       start_time, end_time, barrier);
diff --git a/services/network/sct_auditing/sct_auditing_handler.cc b/services/network/sct_auditing/sct_auditing_handler.cc
index 5fa61232..f559d8b 100644
--- a/services/network/sct_auditing/sct_auditing_handler.cc
+++ b/services/network/sct_auditing/sct_auditing_handler.cc
@@ -209,9 +209,12 @@
   // Parse the serialized reports.
   absl::optional<base::Value> value = base::JSONReader::Read(serialized);
   if (!value || !value->is_list()) {
+    base::UmaHistogramCounts100(
+        "Security.SCTAuditing.NumPersistedReportsLoaded", 0);
     return;
   }
 
+  size_t num_reporters_deserialized = 0u;
   for (base::Value& sct_entry : value->GetList()) {
     base::Value::Dict* entry_dict = sct_entry.GetIfDict();
     if (!sct_entry.is_dict()) {
@@ -272,8 +275,10 @@
 
     AddReporter(cache_key, std::move(audit_report), std::move(sct_metadata),
                 std::move(backoff_entry));
+    ++num_reporters_deserialized;
   }
-  // TODO(crbug.com/1144205): Add metrics for number of reporters deserialized.
+  base::UmaHistogramCounts100("Security.SCTAuditing.NumPersistedReportsLoaded",
+                              num_reporters_deserialized);
 }
 
 void SCTAuditingHandler::OnStartupFinished() {
@@ -331,11 +336,14 @@
   DeserializeData(serialized);
 }
 
-// TODO(crbug.com/1144205): This method should take a completion callback (for
-// callers like NetworkContext::ClearNetworkingHistoryBetween() that want to be
-// able to wait for the write completing), and pass it through to the `writer_`,
-// like TransportSecurityState does.
-void SCTAuditingHandler::ClearPendingReports() {
+void SCTAuditingHandler::OnWriteFinished(base::OnceClosure callback,
+                                         bool /*unused*/) {
+  DCHECK(background_runner_->RunsTasksInCurrentSequence());
+  foreground_runner_->PostTask(FROM_HERE, std::move(callback));
+}
+
+void SCTAuditingHandler::ClearPendingReports(base::OnceClosure callback) {
+  DCHECK(foreground_runner_->RunsTasksInCurrentSequence());
   // Delete any outstanding Reporters. This will delete any extant URLLoader
   // instances owned by the Reporters, which will cancel any outstanding
   // requests/connections. Pending (delayed) retry tasks will fast-fail when
@@ -343,7 +351,15 @@
   // task.
   pending_reporters_.Clear();
   if (writer_) {
-    writer_->ScheduleWrite(this);
+    writer_->RegisterOnNextWriteCallbacks(
+        base::OnceClosure(),
+        base::BindOnce(&SCTAuditingHandler::OnWriteFinished,
+                       weak_factory_.GetWeakPtr(), std::move(callback)));
+    auto data = std::make_unique<std::string>();
+    SerializeData(data.get());
+    writer_->WriteNow(std::move(data));
+  } else {
+    std::move(callback).Run();
   }
 }
 
@@ -359,7 +375,7 @@
                            &SCTAuditingHandler::ReportHWMMetrics);
   } else {
     histogram_timer_.Stop();
-    ClearPendingReports();
+    ClearPendingReports(base::DoNothing());
   }
 }
 
diff --git a/services/network/sct_auditing/sct_auditing_handler.h b/services/network/sct_auditing/sct_auditing_handler.h
index 16c426e..51a5779 100644
--- a/services/network/sct_auditing/sct_auditing_handler.h
+++ b/services/network/sct_auditing/sct_auditing_handler.h
@@ -107,7 +107,7 @@
   void OnReportsLoadedFromDisk(const std::string& serialized);
 
   // Clears the set of pending reporters for this SCTAuditingHandler.
-  void ClearPendingReports();
+  void ClearPendingReports(base::OnceClosure callback);
 
   base::LRUCache<net::HashValue, std::unique_ptr<SCTAuditingReporter>>*
   GetPendingReportersForTesting() {
@@ -136,7 +136,15 @@
  private:
   void OnReporterStateUpdated();
   void OnReporterFinished(net::HashValue reporter_key);
+
+  // Wrapper to pass the callback back to the foreground runner, as the
+  // underlying ImportantFileWriter runs its callback on the background runner.
+  // ImportantFileWriter requires a `OnceCallback<void(bool success)>` for the
+  // write completion callback, but the boolean is currently unused here.
+  void OnWriteFinished(base::OnceClosure callback, bool /*unused*/);
+
   void ReportHWMMetrics();
+
   mojom::URLLoaderFactory* GetURLLoaderFactory();
 
   // The NetworkContext which owns this SCTAuditingHandler.
diff --git a/services/network/sct_auditing/sct_auditing_handler_unittest.cc b/services/network/sct_auditing/sct_auditing_handler_unittest.cc
index fd184a2..6a40cd03 100644
--- a/services/network/sct_auditing/sct_auditing_handler_unittest.cc
+++ b/services/network/sct_auditing/sct_auditing_handler_unittest.cc
@@ -127,6 +127,9 @@
         {features::kSCTAuditingRetryReports,
          features::kSCTAuditingPersistReports},
         {});
+
+    // Clear out any pending tasks before starting tests.
+    task_environment_.RunUntilIdle();
   }
 
   // Get the contents of `persistence_path_`. Pumps the message loop before
@@ -171,6 +174,10 @@
   }
 
  protected:
+  // Must be first because ScopedFeatureList must be initialized before other
+  // threads are started.
+  base::test::ScopedFeatureList feature_list_{
+      /*enable_feature=*/features::kSCTAuditingPersistReports};
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::MainThreadType::IO,
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
@@ -369,8 +376,12 @@
     histograms.ExpectUniqueSample(
         "Security.SCTAuditing.OptOut.PopularSCTSkipped", false,
         mode == mojom::SCTAuditingMode::kHashdance ? 1 : 0);
-    handler_->ClearPendingReports();
+
+    // Reset by clearing all pending reports and cache entries.
+    base::RunLoop run_loop;
+    handler_->ClearPendingReports(run_loop.QuitClosure());
     network_service_->sct_auditing_cache()->ClearCache();
+    run_loop.Run();
   }
 }
 
@@ -415,8 +426,11 @@
         "Security.SCTAuditing.OptOut.PopularSCTSkipped", true,
         mode == mojom::SCTAuditingMode::kHashdance ? 1 : 0);
 
-    handler_->ClearPendingReports();
+    // Reset by clearing all pending reports and cache entries.
+    base::RunLoop run_loop;
+    handler_->ClearPendingReports(run_loop.QuitClosure());
     network_service_->sct_auditing_cache()->ClearCache();
+    run_loop.Run();
   }
 }
 
@@ -731,6 +745,8 @@
 // Test that deserializing bad data shouldn't result in any reporters being
 // created.
 TEST_F(SCTAuditingHandlerTest, DeserializeBadData) {
+  base::HistogramTester histograms;
+
   mojo::PendingRemote<network::mojom::URLLoaderFactory> factory_remote;
   url_loader_factory_.Clone(factory_remote.InitWithNewPipeAndPassReceiver());
 
@@ -766,11 +782,19 @@
 
   // Check that no file got written to the persistence path.
   EXPECT_EQ(GetTestFileContents(), std::string());
+
+  // Check that these deserializations resulted in logging the 0-bucket of the
+  // NumPersistedReportsLoaded histogram. The count should be equal to the
+  // number of calls to DeserializeData() above.
+  histograms.ExpectUniqueSample(
+      "Security.SCTAuditing.NumPersistedReportsLoaded", 0, 5);
 }
 
 // Test that a handler loads valid persisted data from disk and creates pending
 // reporters for each entry.
 TEST_F(SCTAuditingHandlerTest, HandlerWithExistingPersistedData) {
+  base::HistogramTester histograms;
+
   // Set up previously persisted data on disk:
   // - Default-initialized net::HashValue(net::HASH_VALUE_SHA256)
   // - Empty SCTClientReport for origin "example.test:443".
@@ -818,6 +842,10 @@
   ASSERT_FALSE(file_writer->HasPendingWrite());
   EXPECT_FALSE(FileContentsHasString(
       "sha256/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqo="));
+
+  // Check that the NumPersistedReportsLoaded histogram was correctly logged.
+  histograms.ExpectUniqueSample(
+      "Security.SCTAuditing.NumPersistedReportsLoaded", 1, 1);
 }
 
 // Test that scheduling a retry causes the failure count to increment in
diff --git a/storage/browser/quota/quota_manager_proxy.h b/storage/browser/quota/quota_manager_proxy.h
index add2907..0169af8 100644
--- a/storage/browser/quota/quota_manager_proxy.h
+++ b/storage/browser/quota/quota_manager_proxy.h
@@ -101,7 +101,7 @@
   // quota_manager_impl_task_runner. Additionally, the asychonrous version of
   // this method `GetOrCreateBucket` is preferred; only use this synchronous
   // version where asynchronous bucket retrieval is not possible.
-  QuotaErrorOr<BucketInfo> GetOrCreateBucketSync(
+  virtual QuotaErrorOr<BucketInfo> GetOrCreateBucketSync(
       const BucketInitParams& params);
 
   // Same as GetOrCreateBucket but takes in StorageType. This should only be
diff --git a/storage/browser/test/mock_quota_manager.cc b/storage/browser/test/mock_quota_manager.cc
index b77bc1e..daad968d 100644
--- a/storage/browser/test/mock_quota_manager.cc
+++ b/storage/browser/test/mock_quota_manager.cc
@@ -12,6 +12,7 @@
 #include "base/callback_helpers.h"
 #include "base/location.h"
 #include "base/memory/ref_counted.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/services/storage/public/cpp/buckets/bucket_locator.h"
@@ -76,6 +77,23 @@
   std::move(callback).Run(std::move(bucket));
 }
 
+QuotaErrorOr<BucketInfo> MockQuotaManager::GetOrCreateBucketSync(
+    const BucketInitParams& params) {
+  QuotaErrorOr<BucketInfo> bucket;
+  base::WaitableEvent waiter(base::WaitableEvent::ResetPolicy::AUTOMATIC,
+                             base::WaitableEvent::InitialState::NOT_SIGNALED);
+  UpdateOrCreateBucket(params, base::BindOnce(
+                                   [](base::WaitableEvent* waiter,
+                                      QuotaErrorOr<BucketInfo>* sync_bucket,
+                                      QuotaErrorOr<BucketInfo> result_bucket) {
+                                     *sync_bucket = std::move(result_bucket);
+                                     waiter->Signal();
+                                   },
+                                   &waiter, &bucket));
+  waiter.Wait();
+  return bucket;
+}
+
 void MockQuotaManager::CreateBucketForTesting(
     const blink::StorageKey& storage_key,
     const std::string& bucket_name,
diff --git a/storage/browser/test/mock_quota_manager.h b/storage/browser/test/mock_quota_manager.h
index 906b8d2..dd20b117 100644
--- a/storage/browser/test/mock_quota_manager.h
+++ b/storage/browser/test/mock_quota_manager.h
@@ -52,6 +52,14 @@
 
   // Overrides QuotaManager's implementation that maintains an internal
   // container of created buckets and avoids going to the DB.
+  // NOTE: the asychonrous version of this method `GetOrCreateBucket` is
+  // preferred; only use this synchronous version where asynchronous bucket
+  // retrieval is not possible.
+  QuotaErrorOr<BucketInfo> GetOrCreateBucketSync(
+      const BucketInitParams& params);
+
+  // Overrides QuotaManager's implementation that maintains an internal
+  // container of created buckets and avoids going to the DB.
   void GetOrCreateBucketDeprecated(
       const BucketInitParams& bucket_params,
       blink::mojom::StorageType type,
diff --git a/storage/browser/test/mock_quota_manager_proxy.cc b/storage/browser/test/mock_quota_manager_proxy.cc
index a6b9f2a9..b8bab02 100644
--- a/storage/browser/test/mock_quota_manager_proxy.cc
+++ b/storage/browser/test/mock_quota_manager_proxy.cc
@@ -42,6 +42,13 @@
     mock_quota_manager_->UpdateOrCreateBucket(params, std::move(callback));
 }
 
+QuotaErrorOr<BucketInfo> MockQuotaManagerProxy::GetOrCreateBucketSync(
+    const BucketInitParams& params) {
+  return (mock_quota_manager_)
+             ? mock_quota_manager_->GetOrCreateBucketSync(params)
+             : QuotaError::kUnknownError;
+}
+
 void MockQuotaManagerProxy::CreateBucketForTesting(
     const blink::StorageKey& storage_key,
     const std::string& bucket_name,
diff --git a/storage/browser/test/mock_quota_manager_proxy.h b/storage/browser/test/mock_quota_manager_proxy.h
index f5882f0..bb365e5b 100644
--- a/storage/browser/test/mock_quota_manager_proxy.h
+++ b/storage/browser/test/mock_quota_manager_proxy.h
@@ -48,6 +48,9 @@
       scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
       base::OnceCallback<void(QuotaErrorOr<BucketInfo>)> callback) override;
 
+  QuotaErrorOr<BucketInfo> GetOrCreateBucketSync(
+      const BucketInitParams& params) override;
+
   void GetBucket(const blink::StorageKey& storage_key,
                  const std::string& bucket_name,
                  blink::mojom::StorageType type,
diff --git a/storage/browser/test/mock_quota_manager_unittest.cc b/storage/browser/test/mock_quota_manager_unittest.cc
index ffe83b61..dc5de5bd 100644
--- a/storage/browser/test/mock_quota_manager_unittest.cc
+++ b/storage/browser/test/mock_quota_manager_unittest.cc
@@ -213,6 +213,50 @@
   }
 }
 
+TEST_F(MockQuotaManagerTest, GetOrCreateBucketSync) {
+  const StorageKey kStorageKey1 =
+      StorageKey::CreateFromStringForTesting("http://host1:1/");
+  const StorageKey kStorageKey2 =
+      StorageKey::CreateFromStringForTesting("http://host2:1/");
+  const char kBucketName[] = "bucket_name";
+
+  EXPECT_EQ(manager()->BucketDataCount(kClientFile), 0);
+  EXPECT_EQ(manager()->BucketDataCount(kClientDB), 0);
+
+  BucketInitParams params(kStorageKey1, kBucketName);
+  QuotaErrorOr<BucketInfo> bucket1 = manager()->GetOrCreateBucketSync(params);
+  EXPECT_TRUE(bucket1.ok());
+  EXPECT_EQ(bucket1->storage_key, kStorageKey1);
+  EXPECT_EQ(bucket1->name, kBucketName);
+  EXPECT_EQ(bucket1->type, kTemporary);
+  EXPECT_EQ(manager()->BucketDataCount(kClientFile), 1);
+  EXPECT_TRUE(manager()->BucketHasData(bucket1.value(), kClientFile));
+
+  params = BucketInitParams(kStorageKey2, kBucketName);
+  QuotaErrorOr<BucketInfo> bucket2 = manager()->GetOrCreateBucketSync(params);
+  EXPECT_TRUE(bucket2.ok());
+  EXPECT_EQ(bucket2->storage_key, kStorageKey2);
+  EXPECT_EQ(bucket2->name, kBucketName);
+  EXPECT_EQ(bucket2->type, kTemporary);
+  EXPECT_EQ(manager()->BucketDataCount(kClientFile), 2);
+  EXPECT_TRUE(manager()->BucketHasData(bucket2.value(), kClientFile));
+
+  params = BucketInitParams(kStorageKey1, kBucketName);
+  QuotaErrorOr<BucketInfo> dupe_bucket =
+      manager()->GetOrCreateBucketSync(params);
+  EXPECT_TRUE(dupe_bucket.ok());
+  EXPECT_EQ(dupe_bucket.value(), bucket1.value());
+  EXPECT_EQ(manager()->BucketDataCount(kClientFile), 2);
+
+  // GetOrCreateBucket actually creates buckets associated with all quota client
+  // types, so check them all.
+  for (auto client_type : AllQuotaClientTypes()) {
+    EXPECT_EQ(manager()->BucketDataCount(client_type), 2);
+    EXPECT_TRUE(manager()->BucketHasData(bucket1.value(), client_type));
+    EXPECT_TRUE(manager()->BucketHasData(bucket2.value(), client_type));
+  }
+}
+
 TEST_F(MockQuotaManagerTest, CreateBucketForTesting) {
   const StorageKey kStorageKey1 =
       StorageKey::CreateFromStringForTesting("http://host1:1/");
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index c5f609b..53be54d 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -3996,7 +3996,8 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android24.textpb"
+          "--avd-config=../../tools/android/avd/proto/generic_android24.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_n.content_shell_test_apk.filter"
         ],
         "isolate_profile_data": true,
         "merge": {
@@ -8364,7 +8365,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.102"
+              "revision": "version:103.0.5060.114"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8449,7 +8450,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M104",
-              "revision": "version:104.0.5112.31"
+              "revision": "version:104.0.5112.32"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8874,7 +8875,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.102"
+              "revision": "version:103.0.5060.114"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8959,7 +8960,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M104",
-              "revision": "version:104.0.5112.31"
+              "revision": "version:104.0.5112.32"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index eda9e2b..fd9018a 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -46542,7 +46542,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.102"
+              "revision": "version:103.0.5060.114"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46627,7 +46627,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M104",
-              "revision": "version:104.0.5112.31"
+              "revision": "version:104.0.5112.32"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47052,7 +47052,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.102"
+              "revision": "version:103.0.5060.114"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47137,7 +47137,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M104",
-              "revision": "version:104.0.5112.31"
+              "revision": "version:104.0.5112.32"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47566,7 +47566,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.102"
+              "revision": "version:103.0.5060.114"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47651,7 +47651,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M104",
-              "revision": "version:104.0.5112.31"
+              "revision": "version:104.0.5112.32"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48076,7 +48076,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.102"
+              "revision": "version:103.0.5060.114"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48161,7 +48161,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M104",
-              "revision": "version:104.0.5112.31"
+              "revision": "version:104.0.5112.32"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48658,7 +48658,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.102"
+              "revision": "version:103.0.5060.114"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48743,7 +48743,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M104",
-              "revision": "version:104.0.5112.31"
+              "revision": "version:104.0.5112.32"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49168,7 +49168,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.102"
+              "revision": "version:103.0.5060.114"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49253,7 +49253,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M104",
-              "revision": "version:104.0.5112.31"
+              "revision": "version:104.0.5112.32"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49750,7 +49750,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.102"
+              "revision": "version:103.0.5060.114"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49835,7 +49835,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M104",
-              "revision": "version:104.0.5112.31"
+              "revision": "version:104.0.5112.32"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -50260,7 +50260,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M103",
-              "revision": "version:103.0.5060.102"
+              "revision": "version:103.0.5060.114"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -50345,7 +50345,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M104",
-              "revision": "version:104.0.5112.31"
+              "revision": "version:104.0.5112.32"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index b8a46f2..13666669 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5688,21 +5688,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5153.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -5715,7 +5715,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "isolate_profile_data": true,
@@ -5853,21 +5853,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5153.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -5879,7 +5879,7 @@
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "args": [
@@ -5999,21 +5999,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5153.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -6025,7 +6025,7 @@
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "isolate_profile_data": true,
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index a1578427..07c48c6 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -92974,21 +92974,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5153.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -92996,7 +92996,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "isolate_profile_data": true,
@@ -93109,28 +93109,28 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5153.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "args": [
@@ -93230,28 +93230,28 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5153.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "isolate_profile_data": true,
@@ -94589,20 +94589,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5153.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -94616,7 +94616,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "merge": {
@@ -94754,20 +94754,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5153.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -94780,7 +94780,7 @@
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "args": [
@@ -94900,20 +94900,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5153.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -94926,7 +94926,7 @@
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "merge": {
@@ -96422,20 +96422,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5153.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -96449,7 +96449,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "merge": {
@@ -96587,20 +96587,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5153.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -96613,7 +96613,7 @@
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "args": [
@@ -96733,20 +96733,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5153.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -96759,7 +96759,7 @@
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "merge": {
@@ -97494,20 +97494,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5153.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -97520,7 +97520,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       }
     ]
   },
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index a750855..573ff12 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -20874,21 +20874,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5153.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -20901,7 +20901,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "isolate_profile_data": true,
@@ -21039,21 +21039,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5153.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -21065,7 +21065,7 @@
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "args": [
@@ -21185,21 +21185,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5153.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 105.0.5155.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v105.0.5153.0",
-              "revision": "version:105.0.5153.0"
+              "location": "lacros_version_skew_tests_v105.0.5155.0",
+              "revision": "version:105.0.5155.0"
             }
           ],
           "dimension_sets": [
@@ -21211,7 +21211,7 @@
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 105.0.5153.0"
+        "variant_id": "Lacros version skew testing ash 105.0.5155.0"
       },
       {
         "isolate_profile_data": true,
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index 79e0478..fc7ecc8 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -157,6 +157,7 @@
     "//testing/buildbot/filters/android.emulator_11.content_shell_test_apk.filter",
     "//testing/buildbot/filters/android.emulator_12.content_shell_test_apk.filter",
     "//testing/buildbot/filters/android.emulator_m.content_shell_test_apk.filter",
+    "//testing/buildbot/filters/android.emulator_n.content_shell_test_apk.filter",
   ]
 }
 
diff --git a/testing/buildbot/filters/android.emulator_n.content_shell_test_apk.filter b/testing/buildbot/filters/android.emulator_n.content_shell_test_apk.filter
new file mode 100644
index 0000000..64b43a3
--- /dev/null
+++ b/testing/buildbot/filters/android.emulator_n.content_shell_test_apk.filter
@@ -0,0 +1,3 @@
+# crbug.com/1189804
+-org.chromium.content.browser.input.ImeInputModeTest.testShowAndHideInputMode
+-org.chromium.content.browser.input.ImeInputModeTest.testShowAndHideInputModeWithPhysicalKeyboard
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 4b0b6b02e5..03620d1 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1778,6 +1778,11 @@
           'quickrun_shards': 8,
         },
       },
+      'android-nougat-x86-rel': {
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_n.content_shell_test_apk.filter',
+        ],
+      },
       'android-pie-x86-rel': {
         'args': [
           '--gtest_filter=-ContentViewScrollingTest.testFling',
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index ddc267c..3ab5700 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -22,15 +22,15 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5153.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v105.0.5155.0/test_ash_chrome',
     ],
-    'identifier': 'Lacros version skew testing ash 105.0.5153.0',
+    'identifier': 'Lacros version skew testing ash 105.0.5155.0',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v105.0.5153.0',
-          'revision': 'version:105.0.5153.0',
+          'location': 'lacros_version_skew_tests_v105.0.5155.0',
+          'revision': 'version:105.0.5155.0',
         },
       ],
     },
@@ -546,7 +546,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M104',
-          'revision': 'version:104.0.5112.31'
+          'revision': 'version:104.0.5112.32'
         }
       ]
     }
@@ -570,7 +570,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M103',
-          'revision': 'version:103.0.5060.102'
+          'revision': 'version:103.0.5060.114'
         }
       ]
     }
@@ -690,7 +690,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M104',
-          'revision': 'version:104.0.5112.31'
+          'revision': 'version:104.0.5112.32'
         }
       ]
     }
@@ -714,7 +714,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M103',
-          'revision': 'version:103.0.5060.102'
+          'revision': 'version:103.0.5060.114'
         }
       ]
     }
@@ -834,7 +834,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M104',
-          'revision': 'version:104.0.5112.31'
+          'revision': 'version:104.0.5112.32'
         }
       ]
     }
@@ -858,7 +858,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M103',
-          'revision': 'version:103.0.5060.102'
+          'revision': 'version:103.0.5060.114'
         }
       ]
     }
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index ea5068c..7d17ca75 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -8112,6 +8112,25 @@
             ]
         }
     ],
+    "SuggestionAnswerColorReverse": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "omnibox_answer_color_reversal_countries": "zh-CN,zh-TW,ja-JP,ko-KR",
+                        "omnibox_answer_color_reversal_finance_only": "true"
+                    },
+                    "enable_features": [
+                        "SuggestionAnswerColorReverse"
+                    ]
+                }
+            ]
+        }
+    ],
     "SuppressToolbarCaptures": [
         {
             "platforms": [
diff --git a/third_party/blink/common/storage_key/storage_key.cc b/third_party/blink/common/storage_key/storage_key.cc
index de1ac50..fa171909 100644
--- a/third_party/blink/common/storage_key/storage_key.cc
+++ b/third_party/blink/common/storage_key/storage_key.cc
@@ -343,14 +343,18 @@
 }
 
 const net::SiteForCookies StorageKey::ToNetSiteForCookies() const {
-  if (!nonce_ &&
-      ancestor_chain_bit_ == blink::mojom::AncestorChainBit::kSameSite &&
-      net::registry_controlled_domains::SameDomainOrHost(
-          origin_, url::Origin::Create(top_level_site_.GetURL()),
-          net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
-    return net::SiteForCookies::FromUrl(top_level_site_.GetURL());
+  if (nonce_ ||
+      ancestor_chain_bit_ == blink::mojom::AncestorChainBit::kCrossSite) {
+    // If any of the ancestor frames are cross-site to `origin_` then the
+    // SiteForCookies should be null. The existence of `nonce_` means the same
+    // thing.
+    return net::SiteForCookies();
   }
-  return net::SiteForCookies();
+
+  // The `ancestor_chain_bit_` being kSameSite should already indicate that the
+  // `top_level_site_` and `origin_` are same-site.
+  DCHECK(top_level_site_ == net::SchemefulSite(origin_));
+  return net::SiteForCookies(top_level_site_);
 }
 
 // static
diff --git a/third_party/blink/common/storage_key/storage_key_unittest.cc b/third_party/blink/common/storage_key/storage_key_unittest.cc
index 56f05fa6..544ce8a 100644
--- a/third_party/blink/common/storage_key/storage_key_unittest.cc
+++ b/third_party/blink/common/storage_key/storage_key_unittest.cc
@@ -622,8 +622,8 @@
     const bool has_nonce = false;
   } test_cases[] = {
       {kOrigin, kOrigin, net::SchemefulSite(kOrigin)},
-      {kOrigin, kInsecureOrigin, net::SchemefulSite(kInsecureOrigin)},
-      {kInsecureOrigin, kOrigin, net::SchemefulSite(kOrigin)},
+      {kOrigin, kInsecureOrigin, net::SchemefulSite(), true},
+      {kInsecureOrigin, kOrigin, net::SchemefulSite(), true},
       {kOrigin, kSubdomainOrigin, net::SchemefulSite(kOrigin)},
       {kSubdomainOrigin, kOrigin, net::SchemefulSite(kOrigin)},
       {kOrigin, kDifferentSite, net::SchemefulSite(), true},
@@ -637,9 +637,19 @@
                      .ToNetSiteForCookies()
                      .site();
     } else {
-      got_site = StorageKey(test_case.origin, test_case.top_level_origin)
-                     .ToNetSiteForCookies()
-                     .site();
+      net::SchemefulSite top_level_site =
+          net::SchemefulSite(test_case.top_level_origin);
+
+      blink::mojom::AncestorChainBit ancestor_chain_bit =
+          top_level_site == net::SchemefulSite(test_case.origin)
+              ? blink::mojom::AncestorChainBit::kSameSite
+              : blink::mojom::AncestorChainBit::kCrossSite;
+
+      got_site =
+          StorageKey::CreateWithOptionalNonce(test_case.origin, top_level_site,
+                                              nullptr, ancestor_chain_bit)
+              .ToNetSiteForCookies()
+              .site();
     }
     if (test_case.expected_opaque) {
       EXPECT_TRUE(got_site.opaque());
diff --git a/third_party/blink/public/common/storage_key/storage_key.h b/third_party/blink/public/common/storage_key/storage_key.h
index 159591e0..80718de 100644
--- a/third_party/blink/public/common/storage_key/storage_key.h
+++ b/third_party/blink/public/common/storage_key/storage_key.h
@@ -144,10 +144,16 @@
 
   // Return the "site for cookies" for the StorageKey's frame (or worker).
   //
-  // Right now this "site for cookies" is not entirely accurate. For example
-  // consider if A.com embeds B.com which embeds A.com in a child frame. The
-  // site for cookies according to this method will be A.com, but according to
-  // the spec it should be an opaque origin.
+  // While the SiteForCookie object returned matches the current default
+  // behavior it's important to note that it may not exactly match a
+  // SiteForCookies created for the same frame context and could cause
+  // behavioral difference for users using the
+  // LegacySameSiteCookieBehaviorEnabledForDomainList enterprise policy. The
+  // impact is expected to be minimal however.
+  //
+  // (The difference is due to StorageKey not tracking the same state as
+  // SiteForCookies, see see net::SiteForCookies::schemefully_same_ for more
+  // info.)
   const net::SiteForCookies ToNetSiteForCookies() const;
 
   // Returns true if the registration key string is partitioned by top-level
diff --git a/third_party/blink/renderer/core/events/event_type_names.json5 b/third_party/blink/renderer/core/events/event_type_names.json5
index 3db0e93..d569901 100644
--- a/third_party/blink/renderer/core/events/event_type_names.json5
+++ b/third_party/blink/renderer/core/events/event_type_names.json5
@@ -107,6 +107,7 @@
     "dispose",
     "downloading",
     "dataavailable",
+    "dequeue",
     "drag",
     "dragend",
     "dragenter",
diff --git a/third_party/blink/renderer/modules/event_target_modules_names.json5 b/third_party/blink/renderer/modules/event_target_modules_names.json5
index 8e034ef..a70b6e7 100644
--- a/third_party/blink/renderer/modules/event_target_modules_names.json5
+++ b/third_party/blink/renderer/modules/event_target_modules_names.json5
@@ -85,5 +85,7 @@
     "USB",
     "WakeLockSentinel",
     "WindowControlsOverlay",
+    "AudioEncoder",
+    "VideoEncoder",
   ],
 }
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_encoder.cc b/third_party/blink/renderer/modules/webcodecs/audio_encoder.cc
index 3f61d6c..c73ce4f 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_encoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/audio_encoder.cc
@@ -359,6 +359,7 @@
   base::TimeTicks timestamp = base::TimeTicks() + data->timestamp();
 
   --requested_encodes_;
+  ScheduleDequeueEvent();
   media_encoder_->Encode(std::move(audio_bus), timestamp,
                          ConvertToBaseOnceCallback(CrossThreadBindOnce(
                              done_callback, WrapCrossThreadWeakPersistent(this),
@@ -457,4 +458,8 @@
                         .ToLocalChecked());
 }
 
+const AtomicString& AudioEncoder::InterfaceName() const {
+  return event_target_names::kAudioEncoder;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_encoder.h b/third_party/blink/renderer/modules/webcodecs/audio_encoder.h
index ed3ff6c..d00e27fe 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_encoder.h
+++ b/third_party/blink/renderer/modules/webcodecs/audio_encoder.h
@@ -65,6 +65,9 @@
     return Base::encode(data, nullptr, exception_state);
   }
 
+  // EventTarget interface
+  const AtomicString& InterfaceName() const override;
+
   static ScriptPromise isConfigSupported(ScriptState*,
                                          const AudioEncoderConfig*,
                                          ExceptionState&);
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_encoder.idl b/third_party/blink/renderer/modules/webcodecs/audio_encoder.idl
index 4f7ffb86..6290914 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_encoder.idl
+++ b/third_party/blink/renderer/modules/webcodecs/audio_encoder.idl
@@ -9,7 +9,7 @@
     SecureContext,
     RuntimeEnabled=WebCodecs,
     ActiveScriptWrappable
-] interface AudioEncoder {
+] interface AudioEncoder : EventTarget {
     [CallWith=ScriptState, RaisesException, MeasureAs=WebCodecsAudioEncoder]
     constructor(AudioEncoderInit init);
 
@@ -17,6 +17,11 @@
     // that have been sent to the underlying codec.
     readonly attribute unsigned long encodeQueueSize;
 
+    // Fires to signal a decrease in encodeQueueSize. Will fire at most once
+    // for a given turn of the event loop.
+    [RuntimeEnabled=WebCodecsDequeueEvent]
+    attribute EventHandler ondequeue;
+
     // Enqueues a control message to configure the audio encoder for encoding
     // audio data as described by config.
     [RaisesException]
diff --git a/third_party/blink/renderer/modules/webcodecs/encoder_base.cc b/third_party/blink/renderer/modules/webcodecs/encoder_base.cc
index bdfd6a1b..4d1b31e 100644
--- a/third_party/blink/renderer/modules/webcodecs/encoder_base.cc
+++ b/third_party/blink/renderer/modules/webcodecs/encoder_base.cc
@@ -25,6 +25,8 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_encode_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_init.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/core/dom/events/event.h"
+#include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/modules/webcodecs/audio_encoder.h"
 #include "third_party/blink/renderer/modules/webcodecs/codec_state_helper.h"
 #include "third_party/blink/renderer/modules/webcodecs/encoded_video_chunk.h"
@@ -33,6 +35,7 @@
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
@@ -217,7 +220,10 @@
     if (pending_req->input)
       pending_req->input.Release()->close();
   }
-  requested_encodes_ = 0;
+  if (requested_encodes_ > 0) {
+    requested_encodes_ = 0;
+    ScheduleDequeueEvent();
+  }
   blocking_request_in_progress_ = false;
 
   // Schedule deletion of |media_encoder_| for later.
@@ -371,12 +377,47 @@
 }
 
 template <typename Traits>
+void EncoderBase<Traits>::DispatchDequeueEvent(Event* event) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  probe::AsyncTask async_task(GetExecutionContext(),
+                              event->async_task_context());
+  dequeue_event_pending_ = false;
+  DispatchEvent(*event);
+}
+
+template <typename Traits>
+void EncoderBase<Traits>::ScheduleDequeueEvent() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!RuntimeEnabledFeatures::WebCodecsDequeueEventEnabled())
+    return;
+
+  if (dequeue_event_pending_)
+    return;
+  dequeue_event_pending_ = true;
+
+  Event* event = Event::Create(event_type_names::kDequeue);
+  event->SetTarget(this);
+  event->async_task_context()->Schedule(GetExecutionContext(), event->type());
+
+  callback_runner_->PostTask(
+      FROM_HERE, WTF::Bind(&EncoderBase<Traits>::DispatchDequeueEvent,
+                           WrapWeakPersistent(this), WrapPersistent(event)));
+}
+
+template <typename Traits>
+ExecutionContext* EncoderBase<Traits>::GetExecutionContext() const {
+  return ExecutionContextLifecycleObserver::GetExecutionContext();
+}
+
+template <typename Traits>
 void EncoderBase<Traits>::Trace(Visitor* visitor) const {
   visitor->Trace(active_config_);
   visitor->Trace(script_state_);
   visitor->Trace(output_callback_);
   visitor->Trace(error_callback_);
   visitor->Trace(requests_);
+  EventTargetWithInlineData::Trace(visitor);
   ScriptWrappable::Trace(visitor);
   ExecutionContextLifecycleObserver::Trace(visitor);
   ReclaimableCodec::Trace(visitor);
diff --git a/third_party/blink/renderer/modules/webcodecs/encoder_base.h b/third_party/blink/renderer/modules/webcodecs/encoder_base.h
index 4503aee6..54e17bca 100644
--- a/third_party/blink/renderer/modules/webcodecs/encoder_base.h
+++ b/third_party/blink/renderer/modules/webcodecs/encoder_base.h
@@ -13,6 +13,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_codec_state.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_webcodecs_error_callback.h"
+#include "third_party/blink/renderer/modules/event_target_modules.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/modules/webcodecs/codec_logger.h"
 #include "third_party/blink/renderer/modules/webcodecs/codec_trace_names.h"
@@ -33,7 +34,7 @@
 
 template <typename Traits>
 class MODULES_EXPORT EncoderBase
-    : public ScriptWrappable,
+    : public EventTargetWithInlineData,
       public ActiveScriptWrappable<EncoderBase<Traits>>,
       public ReclaimableCodec {
  public:
@@ -54,6 +55,8 @@
   // *_encoder.idl implementation.
   uint32_t encodeQueueSize() { return requested_encodes_; }
 
+  DEFINE_ATTRIBUTE_EVENT_LISTENER(dequeue, kDequeue);
+
   void configure(const ConfigType*, ExceptionState&);
 
   void encode(InputType* input,
@@ -68,6 +71,9 @@
 
   String state() { return state_; }
 
+  // EventTarget override.
+  ExecutionContext* GetExecutionContext() const override;
+
   // ExecutionContextLifecycleObserver override.
   void ContextDestroyed() override;
 
@@ -136,6 +142,10 @@
 
   void TraceQueueSizes() const;
 
+  void ScheduleDequeueEvent();
+  void DispatchDequeueEvent(Event* event);
+  bool dequeue_event_pending_ = false;
+
   std::unique_ptr<CodecLogger<media::EncoderStatus>> logger_;
 
   std::unique_ptr<MediaEncoderType> media_encoder_;
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
index 5332ae4..f654175 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
@@ -55,6 +55,7 @@
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/core/streams/readable_stream.h"
 #include "third_party/blink/renderer/core/streams/writable_stream.h"
+#include "third_party/blink/renderer/modules/event_modules.h"
 #include "third_party/blink/renderer/modules/webcodecs/allow_shared_buffer_source_util.h"
 #include "third_party/blink/renderer/modules/webcodecs/codec_state_helper.h"
 #include "third_party/blink/renderer/modules/webcodecs/encoded_video_chunk.h"
@@ -623,6 +624,10 @@
          original_config.hw_pref == new_config.hw_pref;
 }
 
+const AtomicString& VideoEncoder::InterfaceName() const {
+  return event_target_names::kVideoEncoder;
+}
+
 bool VideoEncoder::HasPendingActivity() const {
   return (active_encodes_ > 0) || Base::HasPendingActivity();
 }
@@ -701,6 +706,7 @@
 
             DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
             --self->requested_encodes_;
+            self->ScheduleDequeueEvent();
             self->blocking_request_in_progress_ = false;
             self->media_encoder_->Encode(std::move(frame), keyframe,
                                          std::move(done_callback));
@@ -792,6 +798,7 @@
   }
 
   --requested_encodes_;
+  ScheduleDequeueEvent();
   media_encoder_->Encode(frame, keyframe,
                          ConvertToBaseOnceCallback(CrossThreadBindOnce(
                              done_callback, WrapCrossThreadWeakPersistent(this),
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.h b/third_party/blink/renderer/modules/webcodecs/video_encoder.h
index cb3d786..cf2945ee 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.h
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.h
@@ -73,6 +73,10 @@
   static ScriptPromise isConfigSupported(ScriptState*,
                                          const VideoEncoderConfig*,
                                          ExceptionState&);
+
+  // EventTarget interface
+  const AtomicString& InterfaceName() const override;
+
   // ScriptWrappable override.
   bool HasPendingActivity() const override;
 
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.idl b/third_party/blink/renderer/modules/webcodecs/video_encoder.idl
index 12d6424..30b78b45 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.idl
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.idl
@@ -9,7 +9,7 @@
     SecureContext,
     RuntimeEnabled=WebCodecs,
     ActiveScriptWrappable
-] interface VideoEncoder {
+] interface VideoEncoder : EventTarget {
     [CallWith=ScriptState, RaisesException, MeasureAs=WebCodecsVideoEncoder]
     constructor(VideoEncoderInit init);
 
@@ -17,6 +17,11 @@
     // that have been sent to the underlying codec.
     readonly attribute unsigned long encodeQueueSize;
 
+    // Fires to signal a decrease in encodeQueueSize. Will fire at most once
+    // for a given turn of the event loop.
+    [RuntimeEnabled=WebCodecsDequeueEvent]
+    attribute EventHandler ondequeue;
+
     // Enqueues a control message to configure the video encoder for encoding
     // frames as described by config.
     [RaisesException]
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_primitive_state.idl b/third_party/blink/renderer/modules/webgpu/gpu_primitive_state.idl
index 0ea11b4..ff132c1 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_primitive_state.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_primitive_state.idl
@@ -9,7 +9,7 @@
   GPUIndexFormat stripIndexFormat;
   GPUFrontFace frontFace = "ccw";
   GPUCullMode cullMode = "none";
-  boolean? unclippedDepth;
+  boolean unclippedDepth = false;
 };
 
 enum GPUIndexFormat {
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.cc b/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.cc
index 5888744..3283e581 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_pipeline.cc
@@ -120,12 +120,10 @@
   dawn_state->dawn_desc.frontFace = AsDawnEnum(webgpu_desc->frontFace());
   dawn_state->dawn_desc.cullMode = AsDawnEnum(webgpu_desc->cullMode());
 
-  if (webgpu_desc->hasUnclippedDepth()) {
+  if (webgpu_desc->unclippedDepth()) {
     auto* depth_clip_control = &dawn_state->depth_clip_control;
     depth_clip_control->chain.sType = WGPUSType_PrimitiveDepthClipControl;
-    depth_clip_control->unclippedDepth =
-        webgpu_desc->unclippedDepth().has_value() &&
-        webgpu_desc->unclippedDepth().value();
+    depth_clip_control->unclippedDepth = webgpu_desc->unclippedDepth();
     dawn_state->dawn_desc.nextInChain =
         reinterpret_cast<WGPUChainedStruct*>(depth_clip_control);
   }
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index e587d03..fccbfd9 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -2440,6 +2440,16 @@
   dict = "//testing/libfuzzer/fuzzers/dicts/mathml_operator_dictionary.dict"
 }
 
+# Fuzzer for blink::OpenTypeMathSupportTest.
+fuzzer_test("open_type_math_support_fuzzer") {
+  sources = [ "fonts/opentype/open_type_math_support_fuzzer.cc" ]
+  deps = [
+    ":blink_fuzzer_test_support",
+    ":platform",
+  ]
+  seed_corpus = "//third_party/blink/web_tests/external/wpt/fonts/math"
+}
+
 # Fuzzer for blink::MHTMLParser.
 fuzzer_test("mhtml_parser_fuzzer") {
   sources = [ "mhtml/mhtml_fuzzer.cc" ]
diff --git a/third_party/blink/renderer/platform/fonts/opentype/open_type_math_support_fuzzer.cc b/third_party/blink/renderer/platform/fonts/opentype/open_type_math_support_fuzzer.cc
new file mode 100644
index 0000000..d8de1db
--- /dev/null
+++ b/third_party/blink/renderer/platform/fonts/opentype/open_type_math_support_fuzzer.cc
@@ -0,0 +1,73 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include "third_party/blink/renderer/platform/fonts/font.h"
+#include "third_party/blink/renderer/platform/fonts/opentype/open_type_math_test_fonts.h"
+#include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
+#include "third_party/blink/renderer/platform/testing/font_test_base.h"
+#include "third_party/blink/renderer/platform/testing/font_test_helpers.h"
+
+namespace blink {
+
+int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  static BlinkFuzzerTestSupport test_support = BlinkFuzzerTestSupport();
+
+  FontCachePurgePreventer font_cache_purge_preventer;
+  FontDescription::VariantLigatures ligatures;
+  Font math =
+      test::CreateTestFont("MathTestFont", data, size, 1000, &ligatures);
+
+  // HasMathData should be used by other API functions below for early return.
+  // Explicitly call it here for exhaustivity, since it is fast anyway.
+  OpenTypeMathSupport::HasMathData(
+      math.PrimaryFont()->PlatformData().GetHarfBuzzFace());
+
+  // There is only a small amount of math constants and each call is fast, so
+  // all of these values are queried.
+  for (int constant = OpenTypeMathSupport::kScriptPercentScaleDown;
+       constant <= OpenTypeMathSupport::kRadicalDegreeBottomRaisePercent;
+       constant++) {
+    OpenTypeMathSupport::MathConstant(
+        math.PrimaryFont()->PlatformData().GetHarfBuzzFace(),
+        static_cast<OpenTypeMathSupport::MathConstants>(constant));
+  }
+
+  // TODO(crbug.com/1340884): This is only testing API for three characters.
+  // TODO(crbug.com/1340884): Use FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION to
+  // limit the size of the vector returned by GetGlyphVariantRecords and
+  // GetGlyphPartRecords?
+  for (auto character : {kNAryWhiteVerticalBarCodePoint, kLeftBraceCodePoint,
+                         kOverBraceCodePoint}) {
+    if (auto glyph = math.PrimaryFont()->GlyphForCharacter(character)) {
+      for (auto stretch_direction :
+           {OpenTypeMathStretchData::StretchAxis::Horizontal,
+            OpenTypeMathStretchData::StretchAxis::Vertical}) {
+        Vector<OpenTypeMathStretchData::GlyphVariantRecord> variants =
+            OpenTypeMathSupport::GetGlyphVariantRecords(
+                math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), glyph,
+                stretch_direction);
+        for (auto variant : variants) {
+          OpenTypeMathSupport::MathItalicCorrection(
+              math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), variant);
+        }
+        float italic_correction = 0;
+        OpenTypeMathSupport::GetGlyphPartRecords(
+            math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), glyph,
+            stretch_direction, &italic_correction);
+      }
+    }
+  }
+
+  return 0;
+}
+
+}  // namespace blink
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  return blink::LLVMFuzzerTestOneInput(data, size);
+}
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 098795b..38e9c2e 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2670,6 +2670,10 @@
       origin_trial_feature_name: "WebCodecs"
     },
     {
+      name: "WebCodecsDequeueEvent",
+      status: "experimental",
+    },
+    {
       name: "WebGLColorManagement",
       status: "stable",
     },
diff --git a/third_party/blink/renderer/platform/testing/font_test_helpers.cc b/third_party/blink/renderer/platform/testing/font_test_helpers.cc
index 46c4f45..49d6857 100644
--- a/third_party/blink/renderer/platform/testing/font_test_helpers.cc
+++ b/third_party/blink/renderer/platform/testing/font_test_helpers.cc
@@ -26,6 +26,17 @@
         FontCustomPlatformData::Create(font_buffer.get(), ots_parse_message));
   }
 
+  static TestFontSelector* Create(const uint8_t* data, size_t size) {
+    scoped_refptr<SharedBuffer> font_buffer = SharedBuffer::Create(data, size);
+    String ots_parse_message;
+    scoped_refptr<FontCustomPlatformData> font_custom_platform_data =
+        FontCustomPlatformData::Create(font_buffer.get(), ots_parse_message);
+    if (!font_custom_platform_data)
+      return nullptr;
+    return MakeGarbageCollected<TestFontSelector>(
+        std::move(font_custom_platform_data));
+  }
+
   TestFontSelector(scoped_refptr<FontCustomPlatformData> custom_platform_data)
       : custom_platform_data_(std::move(custom_platform_data)) {
     DCHECK(custom_platform_data_);
@@ -104,6 +115,24 @@
 }  // namespace
 
 Font CreateTestFont(const AtomicString& family_name,
+                    const uint8_t* data,
+                    size_t data_size,
+                    float size,
+                    const FontDescription::VariantLigatures* ligatures) {
+  FontFamily family;
+  family.SetFamily(family_name, FontFamily::Type::kFamilyName);
+
+  FontDescription font_description;
+  font_description.SetFamily(family);
+  font_description.SetSpecifiedSize(size);
+  font_description.SetComputedSize(size);
+  if (ligatures)
+    font_description.SetVariantLigatures(*ligatures);
+
+  return Font(font_description, TestFontSelector::Create(data, data_size));
+}
+
+Font CreateTestFont(const AtomicString& family_name,
                     const String& font_path,
                     float size,
                     const FontDescription::VariantLigatures* ligatures) {
diff --git a/third_party/blink/renderer/platform/testing/font_test_helpers.h b/third_party/blink/renderer/platform/testing/font_test_helpers.h
index 6b2f3b3..d6bafb7e 100644
--- a/third_party/blink/renderer/platform/testing/font_test_helpers.h
+++ b/third_party/blink/renderer/platform/testing/font_test_helpers.h
@@ -22,6 +22,13 @@
                     float size,
                     const FontDescription::VariantLigatures* = nullptr);
 
+// Reads a font from raw font data, for use in fuzzing test only.
+Font CreateTestFont(const AtomicString& family_name,
+                    const uint8_t* data,
+                    size_t data_size,
+                    float size,
+                    const FontDescription::VariantLigatures* = nullptr);
+
 #if BUILDFLAG(IS_WIN)
 class TestFontPrewarmer : public WebFontPrewarmer {
  public:
diff --git a/third_party/blink/web_tests/PRESUBMIT.py b/third_party/blink/web_tests/PRESUBMIT.py
index 0de56440..ccd5a36b 100644
--- a/third_party/blink/web_tests/PRESUBMIT.py
+++ b/third_party/blink/web_tests/PRESUBMIT.py
@@ -13,6 +13,8 @@
 import os
 import sys
 import tempfile
+import re
+from html.parser import HTMLParser
 
 USE_PYTHON3 = True
 
@@ -299,6 +301,53 @@
     return CheckNotWebViewExposedInterfaces(input_api, output_api)
 
 
+class _DoctypeParser(HTMLParser):
+    """Parses HTML to check if there exists a DOCTYPE declaration before all other tags.
+    """
+
+    def __init__(self):
+        super().__init__()
+        self.encountered_tag = False
+        self.doctype = ""
+
+    def handle_starttag(self, *_):
+        self.encountered_tag = True
+
+    def handle_startendtag(self, *_):
+        self.encountered_tag = True
+
+    def handle_decl(self, decl):
+        if not self.encountered_tag:
+            self.doctype = decl
+            self.encountered_tag = True
+
+
+def _CheckForDoctypeHTML(input_api, output_api):
+    """Checks that all changed HTML files start with the correct <!DOCTYPE html> tag.
+    """
+    results = []
+    doctype_pattern = re.compile("DOCTYPE\s*html\s*$", re.IGNORECASE)
+
+    for f in input_api.AffectedFiles(include_deletes=False):
+        path = f.LocalPath()
+        fname = os.path.basename(path)
+
+        if not fname.endswith(".html") or "quirk" in fname:
+            continue
+
+        parser = _DoctypeParser()
+        parser.feed(input_api.ReadFile(f))
+
+        if not doctype_pattern.match(parser.doctype):
+            results.append(
+                output_api.PresubmitError(
+                    "HTML file \"%s\" does not start with <!DOCTYPE html>. "
+                    "If you really intend to test in quirks mode, add \"quirk\" "
+                    "to the name of your test." % path))
+
+    return results
+
+
 def CheckChangeOnUpload(input_api, output_api):
     results = []
     results.extend(_CheckTestharnessResults(input_api, output_api))
@@ -310,6 +359,7 @@
     results.extend(_CheckForUnlistedTestFolder(input_api, output_api))
     results.extend(_CheckForExtraVirtualBaselines(input_api, output_api))
     results.extend(_CheckWebViewExpectations(input_api, output_api))
+    results.extend(_CheckForDoctypeHTML(input_api, output_api))
     return results
 
 
@@ -321,4 +371,5 @@
     results.extend(_CheckForUnlistedTestFolder(input_api, output_api))
     results.extend(_CheckForExtraVirtualBaselines(input_api, output_api))
     results.extend(_CheckWebViewExpectations(input_api, output_api))
+    results.extend(_CheckForDoctypeHTML(input_api, output_api))
     return results
diff --git a/third_party/blink/web_tests/PRESUBMIT_test.py b/third_party/blink/web_tests/PRESUBMIT_test.py
new file mode 100755
index 0000000..8a8a56a
--- /dev/null
+++ b/third_party/blink/web_tests/PRESUBMIT_test.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+import sys
+import os
+import PRESUBMIT
+
+sys.path.append(
+    os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', '..'))
+from PRESUBMIT_test_mocks import MockInputApi, MockOutputApi, MockAffectedFile
+
+
+class PresubmitTest(unittest.TestCase):
+    def testCheckForDoctypeHTML(self):
+        """This verifies that we correctly identify missing DOCTYPE html tags.
+        """
+
+        file1 = MockAffectedFile("some/dir/file1.html", [
+            "<!DOCTYPE html>", "<html>", "<body>", "<p>Test</p>", "</body>",
+            "</html>"
+        ])
+        file2 = MockAffectedFile(
+            "some/dir2/file2.html",
+            ["<html>", "<body>", "<p>Test</p>", "</body>", "</html>"])
+        file3 = MockAffectedFile("file3.html", [
+            "<!--Some comment-->", "<!docTYPE    htML>", "<html>", "<body>",
+            "<p>Test</p>", "</body>", "</html>"
+        ])
+        file4 = MockAffectedFile("dir/file4.html",
+                                 ["<script></script>", "<!DOCTYPE html>"])
+        file5 = MockAffectedFile("file5.html", [])
+        file6 = MockAffectedFile(
+            "file6.not_html",
+            ["<html>", "<body>", "<p>Test</p>", "</body>", "</html>"])
+        file7 = MockAffectedFile("file7.html", [
+            "<!DOCTYPE html   >", "<html>", "<body>", "<p>Test</p>", "</body>",
+            "</html>"
+        ])
+        file8 = MockAffectedFile("file8.html", [
+            "<!DOCTYPE html FOOBAR>", "<html>", "<body>", "<p>Test</p>",
+            "</body>", "</html>"
+        ])
+        file9 = MockAffectedFile(
+            "some/dir/quirk-file9.html",
+            ["<html>", "<body>", "<p>Test</p>", "</body>", "</html>"])
+
+        mock_input_api = MockInputApi()
+        mock_input_api.files = [
+            file1, file2, file3, file4, file5, file6, file7, file8, file9
+        ]
+        errors = PRESUBMIT._CheckForDoctypeHTML(mock_input_api,
+                                                MockOutputApi())
+
+        self.assertEqual(4, len(errors))
+        for i, file in enumerate([file2, file4, file5, file8]):
+            self.assertIn("\"%s\"" % file.LocalPath(), errors[i].message)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index fd6f3db..7c40e34 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -251,6 +251,7 @@
 crbug.com/1302831 virtual/prerender/external/wpt/speculation-rules/prerender/service-workers.https.html [ Failure Skip Timeout ]
 crbug.com/1302831 virtual/prerender/http/tests/inspector-protocol/prerender/prerender-ua-override.js [ Skip ]
 crbug.com/1302831 virtual/prerender/wpt_internal/prerender/restriction-web-auth.https.html [ Skip ]
+crbug.com/1302831 [ Mac12-arm64 ] virtual/prerender/wpt_internal/prerender/activate-from-iframe.html [ Failure Pass ]
 crbug.com/1302831 [ Mac10.14 ] external/wpt/html/browsers/browsing-the-web/back-forward-cache/eligibility/broadcast-channel.html [ Pass Timeout ]
 crbug.com/1302831 [ Win10.20h2 ] external/wpt/html/browsers/browsing-the-web/back-forward-cache/eligibility/broadcast-channel.html [ Pass Timeout ]
 
@@ -6206,6 +6207,7 @@
 crbug.com/1249176 [ Mac11-arm64 ] virtual/no-alloc-direct-call/external/wpt/html/canvas/element/manual/drawing-images-to-the-canvas/image-orientation/drawImage-from-blob.tentative.html [ Failure ]
 crbug.com/1249176 [ Mac12-arm64 ] virtual/no-alloc-direct-call/external/wpt/html/canvas/element/manual/drawing-images-to-the-canvas/image-orientation/drawImage-from-blob.tentative.html [ Failure ]
 crbug.com/1249176 [ Mac11-arm64 ] virtual/threaded/http/tests/devtools/tracing/timeline-paint/paint-profiler-update.js [ Failure ]
+crbug.com/1249176 [ Mac12-arm64 ] virtual/threaded/http/tests/devtools/tracing/timeline-paint/paint-profiler-update.js [ Failure Pass ]
 crbug.com/1249176 [ Mac11-arm64 ] virtual/threaded/http/tests/devtools/tracing/timeline-time/timeline-usertiming.js [ Failure ]
 crbug.com/1249176 [ Mac11-arm64 ] webaudio/codec-tests/webm/webm-decode.html [ Failure ]
 crbug.com/1249176 [ Mac11-arm64 ] webaudio/AudioBufferSource/audiobuffersource-detune-modulation.html [ Failure ]
@@ -6254,7 +6256,11 @@
 
 # Flakes that surfaced on the mac12 and mac12-arm64 waterfall
 crbug.com/1249176 [ Mac12 ] external/wpt/html/browsers/browsing-the-web/back-forward-cache/service-worker-controlled-after-restore.https.html [ Failure Pass ]
+crbug.com/1249176 [ Mac12 ] fast/dom/Element/scrollTop-scrollLeft-body.html [ Failure Pass ]
+crbug.com/1249176 [ Mac12 ] virtual/basic-card-enabled/payments/payment-request-interface.html [ Failure Pass ]
 crbug.com/1249176 [ Mac12-arm64 ] external/wpt/html/rendering/replaced-elements/embedded-content/cross-domain-iframe-in-multicol.sub.html [ Failure Pass ]
+crbug.com/1249176 [ Mac12-arm64 ] fast/js/instanceof-test.html [ Failure Pass ]
+crbug.com/1249176 [ Mac12-arm64 ] http/tests/devtools/elements/styles-2/nested-pseudo-elements.js [ Failure Pass ]
 crbug.com/1249176 [ Mac12-arm64 ] media/controls/overflow-menu-focus.html [ Failure Pass ]
 
 # Following tests timeout on mac11-arm64
@@ -6289,6 +6295,7 @@
 crbug.com/1249176 [ Mac12-arm64 ] http/tests/devtools/elements/accessibility/edit-aria-attributes.js [ Failure Pass ]
 crbug.com/1249176 [ Mac11-arm64 ] inspector-protocol/overlay/overlay-persistent-overlays-with-emulation.js [ Failure Pass ]
 crbug.com/1249176 [ Mac12-arm64 ] inspector-protocol/overlay/overlay-persistent-overlays-with-emulation.js [ Failure Pass ]
+crbug.com/1249176 [ Mac12-arm64 ] virtual/scroll-unification/plugins/missing-plugin.html [ Failure Pass ]
 crbug.com/1249176 [ Mac11-arm64 ] virtual/scroll-unification/plugins/multiple-plugins.html [ Failure Pass ]
 crbug.com/1249176 [ Mac12-arm64 ] virtual/scroll-unification/plugins/multiple-plugins.html [ Failure Pass ]
 crbug.com/1249176 [ Mac11-arm64 ] fast/layers/clip-rects-transformed-2.html [ Failure Pass ]
@@ -7121,7 +7128,7 @@
 crbug.com/1339293 [ Linux ] http/tests/devtools/network/network-initiator.js [ Failure Pass ]
 crbug.com/1339293 [ Linux ] media/controls/rotated-video-has-right-panel-width.html [ Failure Pass ]
 crbug.com/1339293 [ Linux ] media/media-controls-overflow-hidden.html [ Failure Pass ]
-crbug.com/1339293 [ Linux ] virtual/threaded-preload-scanner/external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/029.html [ Failure Pass ]
+crbug.com/1339293 [ Linux ] external/wpt/html/semantics/scripting-1/the-script-element/execution-timing/029.html [ Failure Pass ]
 crbug.com/1339293 [ Linux ] virtual/threaded/external/wpt/requestidlecallback/deadline-max-rAF-dynamic.html [ Failure Pass ]
 crbug.com/webrtc/14228 external/wpt/webrtc/protocol/h264-profile-levels.https.html [ Failure Pass ]
 
diff --git a/third_party/blink/web_tests/external/Version b/third_party/blink/web_tests/external/Version
index ffbafd5c..56d2701 100644
--- a/third_party/blink/web_tests/external/Version
+++ b/third_party/blink/web_tests/external/Version
@@ -1 +1 @@
-Version: 38decee65c87189195d3b29da81fd1e0c4d9fb72
+Version: c1b24fce6e625c1b79124a58f27bf9adce02d5d7
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index e024f22..da1adc6 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -298906,6 +298906,10 @@
      "2340a89c93ce4515026a5462cb9382dc2501bfdb",
      []
     ],
+    "encodeInto.any.js.headers": [
+     "4fff9d9fba4c81f953826ffea010a75be626b95d",
+     []
+    ],
     "eof-shift_jis-ref.html": [
      "b90f8032a31db260902c2c04b671b30ee9c62c8e",
      []
@@ -299623,6 +299627,10 @@
      []
     ],
     "streams": {
+     "decode-utf8.any.js.headers": [
+      "4fff9d9fba4c81f953826ffea010a75be626b95d",
+      []
+     ],
      "resources": {
       "readable-stream-from-array.js": [
        "5c12ba8c8bba60ef52a689abae17d1f1e0bfc458",
@@ -299633,7 +299641,15 @@
        []
       ]
      }
-    }
+    },
+    "textdecoder-copy.any.js.headers": [
+     "4fff9d9fba4c81f953826ffea010a75be626b95d",
+     []
+    ],
+    "textdecoder-streaming.any.js.headers": [
+     "4fff9d9fba4c81f953826ffea010a75be626b95d",
+     []
+    ]
    },
    "encoding-detection": {
     "__dir__.headers": [
@@ -321393,6 +321409,14 @@
       "e9a5af6c81295ae4577cd2d8a4c01cfebe1a15ba",
       []
      ],
+     "mediasource-worker-handle-transfer-to-main.js": [
+      "e83ab75c6a9f670aea8655ff61b563158d08c673",
+      []
+     ],
+     "mediasource-worker-handle-transfer.js": [
+      "4c62aeec7cd63bf8015861ec69fb2d98fc16fb2a",
+      []
+     ],
      "mediasource-worker-handle.js": [
       "577b1facbc9fcd04f6bcc3ce711504912ae5ba69",
       []
@@ -490577,6 +490601,13 @@
        {}
       ]
      ],
+     "mediasource-worker-handle-transfer.html": [
+      "cab5073299745764bc74aeadab31b288dbd73065",
+      [
+       null,
+       {}
+      ]
+     ],
      "mediasource-worker-handle.html": [
       "b084fb6d5bb9cb5b66ddc1536c2bf0719348e1ff",
       [
@@ -547570,7 +547601,7 @@
      ]
     ],
     "audio-encoder.https.any.js": [
-     "0f0c01f9dd1b4ef90471a0490a224c976e3ba6d0",
+     "d1ae55435a68f7fa744954eaf9a5776f04c6e4dd",
      [
       "webcodecs/audio-encoder.https.any.html",
       {
@@ -549578,7 +549609,7 @@
      ]
     ],
     "video-encoder.https.any.js": [
-     "4999276f3af40996d520195b677c8d0d520fd990",
+     "791b910ee40f4213ce868fbe277af3b46ca06b3c",
      [
       "webcodecs/video-encoder.https.any.html",
       {
diff --git a/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.js.headers b/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.js.headers
new file mode 100644
index 0000000..4fff9d9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.js.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/encoding/streams/decode-utf8.any.js.headers b/third_party/blink/web_tests/external/wpt/encoding/streams/decode-utf8.any.js.headers
new file mode 100644
index 0000000..4fff9d9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/encoding/streams/decode-utf8.any.js.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/encoding/textdecoder-copy.any.js.headers b/third_party/blink/web_tests/external/wpt/encoding/textdecoder-copy.any.js.headers
new file mode 100644
index 0000000..4fff9d9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/encoding/textdecoder-copy.any.js.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/encoding/textdecoder-streaming.any.js.headers b/third_party/blink/web_tests/external/wpt/encoding/textdecoder-streaming.any.js.headers
new file mode 100644
index 0000000..4fff9d9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/encoding/textdecoder-streaming.any.js.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-handle-transfer-to-main.js b/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-handle-transfer-to-main.js
new file mode 100644
index 0000000..e83ab75c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-handle-transfer-to-main.js
@@ -0,0 +1,10 @@
+importScripts('mediasource-message-util.js');
+
+// Note, we do not use testharness.js utilities within the worker context
+// because it also communicates using postMessage to the main HTML document's
+// harness, and would confuse the test case message parsing there.
+
+// Just obtain a MediaSourceHandle and transfer it to creator of our context.
+let handle = new MediaSource().getHandle();
+postMessage(
+    {subject: messageSubject.HANDLE, info: handle}, {transfer: [handle]});
diff --git a/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-handle-transfer.html b/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-handle-transfer.html
new file mode 100644
index 0000000..9e8f318
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-handle-transfer.html
@@ -0,0 +1,231 @@
+<!DOCTYPE html>
+<html>
+<title>Test MediaSourceHandle transfer characteristics</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="mediasource-message-util.js"></script>
+<body>
+<script>
+
+function assert_mseiw_supported() {
+  // Fail fast if MSE-in-Workers is not supported.
+  assert_true(
+      MediaSource.hasOwnProperty('canConstructInDedicatedWorker'),
+      'MediaSource hasOwnProperty \'canConstructInDedicatedWorker\'');
+  assert_true(
+      MediaSource.canConstructInDedicatedWorker,
+      'MediaSource.canConstructInDedicatedWorker');
+  assert_true(
+      window.hasOwnProperty('MediaSourceHandle'),
+      'window must have MediaSourceHandle visibility');
+}
+
+function get_handle_from_new_worker(t) {
+  return new Promise((r) => {
+    let worker = new Worker('mediasource-worker-handle-transfer-to-main.js');
+    worker.addEventListener(
+        'message', t.step_func(e => {
+          let subject = e.data.subject;
+          assert_true(
+              subject != undefined, 'message must have a subject field');
+          switch (subject) {
+            case messageSubject.ERROR:
+              assert_unreached('Worker error: ' + e.data.info);
+              break;
+            case messageSubject.HANDLE:
+              const handle = e.data.info;
+              assert_not_equals(
+                  handle, null, 'must have a non-null MediaSourceHandle');
+              r({worker, handle});
+              break;
+            default:
+              assert_unreached('Unexpected message subject: ' + subject);
+          }
+        }),
+        {once: true});
+  });
+}
+
+promise_test(async t => {
+  assert_mseiw_supported();
+  let {worker, handle} = await get_handle_from_new_worker(t);
+  assert_true(
+      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
+  assert_throws_dom('DataCloneError', function() {
+    worker.postMessage(handle);
+  }, 'serializing handle without transfer');
+}, 'MediaSourceHandle serialization without transfer must fail, tested in window context');
+
+promise_test(async t => {
+  assert_mseiw_supported();
+  let {worker, handle} = await get_handle_from_new_worker(t);
+  assert_true(
+      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
+  assert_throws_dom('DataCloneError', function() {
+    worker.postMessage(handle, [handle, handle]);
+  }, 'transferring same handle more than once in same postMessage');
+}, 'Same MediaSourceHandle transferred multiple times in single postMessage must fail, tested in window context');
+
+promise_test(async t => {
+  assert_mseiw_supported();
+  let {worker, handle} = await get_handle_from_new_worker(t);
+  assert_true(
+      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
+
+  // Transferring handle to worker without including it in the message is still
+  // a valid transfer, though the recipient will not be able to obtain the
+  // handle itself. Regardless, the handle in this sender's context will be
+  // detached.
+  worker.postMessage(null, [handle]);
+
+  assert_throws_dom('DataCloneError', function() {
+    worker.postMessage(null, [handle]);
+  }, 'transferring handle that was already detached should fail');
+
+  assert_throws_dom('DataCloneError', function() {
+    worker.postMessage(handle, [handle]);
+  }, 'transferring handle that was already detached should fail, even if this time it\'s included in the message');
+}, 'Attempt to transfer detached MediaSourceHandle must fail, tested in window context');
+
+promise_test(async t => {
+  assert_mseiw_supported();
+  let {worker, handle} = await get_handle_from_new_worker(t);
+  assert_true(
+      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
+
+  let video = document.createElement('video');
+  document.body.appendChild(video);
+  video.srcObject = handle;
+
+  assert_throws_dom('DataCloneError', function() {
+    worker.postMessage(handle, [handle]);
+  }, 'transferring handle that is currently srcObject fails');
+  assert_equals(video.srcObject, handle);
+
+  // Clear |handle| from being the srcObject value.
+  video.srcObject = null;
+
+  assert_throws_dom('DataCloneError', function() {
+    worker.postMessage(handle, [handle]);
+  }, 'transferring handle that was briefly srcObject before srcObject was reset to null should also fail');
+  assert_equals(video.srcObject, null);
+}, 'MediaSourceHandle cannot be transferred, immediately after set as srcObject, even if srcObject immediately reset to null');
+
+promise_test(async t => {
+  assert_mseiw_supported();
+  let {worker, handle} = await get_handle_from_new_worker(t);
+  assert_true(
+      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
+
+  let video = document.createElement('video');
+  document.body.appendChild(video);
+  video.srcObject = handle;
+  assert_not_equals(video.networkState, HTMLMediaElement.NETWORK_LOADING);
+  // Initial step of resource selection algorithm sets networkState to
+  // NETWORK_NO_SOURCE. networkState only becomes NETWORK_LOADING after stable
+  // state awaited and resource selection algorithm continues with, in this
+  // case, an assigned media provider object (which is the MediaSource
+  // underlying the handle).
+  assert_equals(video.networkState, HTMLMediaElement.NETWORK_NO_SOURCE);
+
+  // Wait until 'loadstart' media element event is dispatched.
+  await new Promise((r) => {
+    video.addEventListener(
+        'loadstart', t.step_func(e => {
+          r();
+        }),
+        {once: true});
+  });
+  assert_equals(video.networkState, HTMLMediaElement.NETWORK_LOADING);
+
+  assert_throws_dom('DataCloneError', function() {
+    worker.postMessage(handle, [handle]);
+  }, 'transferring handle that is currently srcObject, after loadstart, fails');
+  assert_equals(video.srcObject, handle);
+
+  // Clear |handle| from being the srcObject value.
+  video.srcObject = null;
+
+  assert_throws_dom('DataCloneError', function() {
+    worker.postMessage(handle, [handle]);
+  }, 'transferring handle that was srcObject until \'loadstart\' when srcObject was reset to null should also fail');
+  assert_equals(video.srcObject, null);
+}, 'MediaSourceHandle cannot be transferred, if it was srcObject when asynchronous load starts (loadstart), even if srcObject is then immediately reset to null');
+
+promise_test(async t => {
+  assert_mseiw_supported();
+  let {worker, handle} = await get_handle_from_new_worker(t);
+  assert_true(
+      handle instanceof MediaSourceHandle, 'must be a MediaSourceHandle');
+
+  let video = document.createElement('video');
+  document.body.appendChild(video);
+
+  // Transfer the handle away so that our instance of it is detached.
+  worker.postMessage(null, [handle]);
+
+  // Now assign handle to srcObject to attempt load. 'loadstart' event should
+  // occur, but then media element error should occur due to failure to attach
+  // to the underlying MediaSource of a detached MediaSourceHandle.
+
+  video.srcObject = handle;
+  assert_equals(
+      video.networkState, HTMLMediaElement.NETWORK_NO_SOURCE,
+      'before async load start, networkState should be NETWORK_NO_SOURCE');
+
+  // Before 'loadstart' dispatch, we don't expect the media element error.
+  video.onerror = t.unreached_func(
+      'Error is unexpected before \'loadstart\' event dispatch');
+
+  // Wait until 'loadstart' media element event is dispatched.
+  await new Promise((r) => {
+    video.addEventListener(
+        'loadstart', t.step_func(e => {
+          r();
+        }),
+        {once: true});
+  });
+
+  // Now wait until 'error' media element event is dispatched.
+  video.onerror = null;
+  await new Promise((r) => {
+    video.addEventListener(
+        'error', t.step_func(e => {
+          r();
+        }),
+        {once: true});
+  });
+
+  // Confirm expected error and states resulting from the "dedicated media
+  // source failure steps":
+  // https://html.spec.whatwg.org/multipage/media.html#dedicated-media-source-failure-steps
+  let e = video.error;
+  assert_true(e instanceof MediaError);
+  assert_equals(e.code, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
+  assert_equals(
+      video.readyState, HTMLMediaElement.HAVE_NOTHING,
+      'load failure should occur long before parsing any appended metadata.');
+  assert_equals(video.networkState, HTMLMediaElement.NETWORK_NO_SOURCE);
+
+  // Even if the handle is detached and attempt to load it failed, the handle is
+  // still detached, and as well, has also been used as srcObject now. Re-verify
+  // that such a handle instance must fail transfer attempt.
+  assert_throws_dom('DataCloneError', function() {
+    worker.postMessage(handle, [handle]);
+  }, 'transferring detached handle that is currently srcObject, after loadstart and load failure, fails');
+  assert_equals(video.srcObject, handle);
+
+  // Clear |handle| from being the srcObject value.
+  video.srcObject = null;
+
+  assert_throws_dom('DataCloneError', function() {
+    worker.postMessage(handle, [handle]);
+  }, 'transferring detached handle that was srcObject until \'loadstart\' and load failure when srcObject was reset to null should also fail');
+  assert_equals(video.srcObject, null);
+}, 'A detached (already transferred away) MediaSourceHandle cannot successfully load when assigned to srcObject');
+
+fetch_tests_from_worker(new Worker('mediasource-worker-handle-transfer.js'));
+
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-handle-transfer.js b/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-handle-transfer.js
new file mode 100644
index 0000000..4c62aee
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/media-source/dedicated-worker/mediasource-worker-handle-transfer.js
@@ -0,0 +1,19 @@
+importScripts('/resources/testharness.js');
+
+test(t => {
+  let handle = new MediaSource().getHandle();
+  assert_true(handle instanceof MediaSourceHandle);
+  assert_throws_dom('DataCloneError', function() {
+    postMessage(handle);
+  }, 'serializing handle without transfer');
+}, 'MediaSourceHandle serialization without transfer must fail, tested in worker');
+
+test(t => {
+  let handle = new MediaSource().getHandle();
+  assert_true(handle instanceof MediaSourceHandle);
+  assert_throws_dom('DataCloneError', function() {
+    postMessage(handle, [handle, handle]);
+  }, 'transferring same handle more than once in same postMessage');
+}, 'Same MediaSourceHandle transferred multiple times in single postMessage must fail, tested in worker');
+
+done();
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/audio-encoder.https.any.js b/third_party/blink/web_tests/external/wpt/webcodecs/audio-encoder.https.any.js
index 0f0c01f9..d1ae554 100644
--- a/third_party/blink/web_tests/external/wpt/webcodecs/audio-encoder.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webcodecs/audio-encoder.https.any.js
@@ -404,3 +404,71 @@
   assert_not_equals(decoder_config.description, null);
   encoder.close();
 }, "Emit decoder config and extra data.");
+
+promise_test(async t => {
+  let sample_rate = 48000;
+  let total_duration_s = 1;
+  let data_count = 100;
+  let init = getDefaultCodecInit(t);
+  init.output = (chunk, metadata) => {}
+
+  let encoder = new AudioEncoder(init);
+
+  // No encodes yet.
+  assert_equals(encoder.encodeQueueSize, 0);
+
+  let config = {
+    codec: 'opus',
+    sampleRate: sample_rate,
+    numberOfChannels: 2,
+    bitrate: 256000 //256kbit
+  };
+  encoder.configure(config);
+
+  // Still no encodes.
+  assert_equals(encoder.encodeQueueSize, 0);
+
+  let datas = [];
+  let timestamp_us = 0;
+  let data_duration_s = total_duration_s / data_count;
+  let data_length = data_duration_s * config.sampleRate;
+  for (let i = 0; i < data_count; i++) {
+    let data = make_audio_data(timestamp_us, config.numberOfChannels,
+      config.sampleRate, data_length);
+    datas.push(data);
+    timestamp_us += data_duration_s * 1_000_000;
+  }
+
+  let lastDequeueSize = Infinity;
+  encoder.ondequeue = () => {
+    assert_greater_than(lastDequeueSize, 0, "Dequeue event after queue empty");
+    assert_greater_than(lastDequeueSize, encoder.encodeQueueSize,
+                        "Dequeue event without decreased queue size");
+    lastDequeueSize = encoder.encodeQueueSize;
+  };
+
+  for (let data of datas)
+    encoder.encode(data);
+
+  assert_greater_than_equal(encoder.encodeQueueSize, 0);
+  assert_less_than_equal(encoder.encodeQueueSize, data_count);
+
+  await encoder.flush();
+  // We can guarantee that all encodes are processed after a flush.
+  assert_equals(encoder.encodeQueueSize, 0);
+  // Last dequeue event should fire when the queue is empty.
+  assert_equals(lastDequeueSize, 0);
+
+  // Reset this to Infinity to track the decline of queue size for this next
+  // batch of encodes.
+  lastDequeueSize = Infinity;
+
+  for (let data of datas) {
+    encoder.encode(data);
+    data.close();
+  }
+
+  assert_greater_than_equal(encoder.encodeQueueSize, 0);
+  encoder.reset();
+  assert_equals(encoder.encodeQueueSize, 0);
+}, 'encodeQueueSize test');
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/video-encoder.https.any.js b/third_party/blink/web_tests/external/wpt/webcodecs/video-encoder.https.any.js
index 4999276..791b910e 100644
--- a/third_party/blink/web_tests/external/wpt/webcodecs/video-encoder.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webcodecs/video-encoder.https.any.js
@@ -113,22 +113,36 @@
     frames.push(frame);
   }
 
+  let lastDequeueSize = Infinity;
+  encoder.ondequeue = () => {
+    assert_greater_than(lastDequeueSize, 0, "Dequeue event after queue empty");
+    assert_greater_than(lastDequeueSize, encoder.encodeQueueSize,
+                        "Dequeue event without decreased queue size");
+    lastDequeueSize = encoder.encodeQueueSize;
+  };
+
   for (let frame of frames)
     encoder.encode(frame);
 
-  assert_greater_than(encoder.encodeQueueSize, 0);
+  assert_greater_than_equal(encoder.encodeQueueSize, 0);
   assert_less_than_equal(encoder.encodeQueueSize, frames_count);
 
   await encoder.flush();
   // We can guarantee that all encodes are processed after a flush.
   assert_equals(encoder.encodeQueueSize, 0);
+  // Last dequeue event should fire when the queue is empty.
+  assert_equals(lastDequeueSize, 0);
+
+  // Reset this to Infinity to track the decline of queue size for this next
+  // batch of encodes.
+  lastDequeueSize = Infinity;
 
   for (let frame of frames) {
     encoder.encode(frame);
     frame.close();
   }
 
-  assert_greater_than(encoder.encodeQueueSize, 0);
+  assert_greater_than_equal(encoder.encodeQueueSize, 0);
   encoder.reset();
   assert_equals(encoder.encodeQueueSize, 0);
 }, 'encodeQueueSize test');
diff --git a/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 0f54e0c4..20ef893 100644
--- a/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -43,7 +43,7 @@
 [Worker]     method decode
 [Worker]     method flush
 [Worker]     method reset
-[Worker] interface AudioEncoder
+[Worker] interface AudioEncoder : EventTarget
 [Worker]     static method isConfigSupported
 [Worker]     attribute @@toStringTag
 [Worker]     getter encodeQueueSize
@@ -1609,7 +1609,7 @@
 [Worker]     method decode
 [Worker]     method flush
 [Worker]     method reset
-[Worker] interface VideoEncoder
+[Worker] interface VideoEncoder : EventTarget
 [Worker]     static method isConfigSupported
 [Worker]     attribute @@toStringTag
 [Worker]     getter encodeQueueSize
diff --git a/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-expected.txt
index 30f3443..3b386e9c 100644
--- a/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/platform/generic/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -184,7 +184,7 @@
     attribute @@toStringTag
     getter maxChannelCount
     method constructor
-interface AudioEncoder
+interface AudioEncoder : EventTarget
     static method isConfigSupported
     attribute @@toStringTag
     getter encodeQueueSize
@@ -8179,7 +8179,7 @@
     method decode
     method flush
     method reset
-interface VideoEncoder
+interface VideoEncoder : EventTarget
     static method isConfigSupported
     attribute @@toStringTag
     getter encodeQueueSize
diff --git a/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-dedicated-worker-expected.txt
index f8bd0dd5..8761ef6 100644
--- a/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -43,10 +43,11 @@
 [Worker]     method decode
 [Worker]     method flush
 [Worker]     method reset
-[Worker] interface AudioEncoder
+[Worker] interface AudioEncoder : EventTarget
 [Worker]     static method isConfigSupported
 [Worker]     attribute @@toStringTag
 [Worker]     getter encodeQueueSize
+[Worker]     getter ondequeue
 [Worker]     getter state
 [Worker]     method close
 [Worker]     method configure
@@ -54,6 +55,7 @@
 [Worker]     method encode
 [Worker]     method flush
 [Worker]     method reset
+[Worker]     setter ondequeue
 [Worker] interface BackgroundFetchManager
 [Worker]     attribute @@toStringTag
 [Worker]     method constructor
@@ -1824,10 +1826,11 @@
 [Worker]     method decode
 [Worker]     method flush
 [Worker]     method reset
-[Worker] interface VideoEncoder
+[Worker] interface VideoEncoder : EventTarget
 [Worker]     static method isConfigSupported
 [Worker]     attribute @@toStringTag
 [Worker]     getter encodeQueueSize
+[Worker]     getter ondequeue
 [Worker]     getter state
 [Worker]     method close
 [Worker]     method configure
@@ -1835,6 +1838,7 @@
 [Worker]     method encode
 [Worker]     method flush
 [Worker]     method reset
+[Worker]     setter ondequeue
 [Worker] interface VideoFrame
 [Worker]     attribute @@toStringTag
 [Worker]     getter codedHeight
diff --git a/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt
index 2a6f73b..ed30dad 100644
--- a/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/platform/generic/webexposed/global-interface-listing-expected.txt
@@ -310,10 +310,11 @@
     attribute @@toStringTag
     getter maxChannelCount
     method constructor
-interface AudioEncoder
+interface AudioEncoder : EventTarget
     static method isConfigSupported
     attribute @@toStringTag
     getter encodeQueueSize
+    getter ondequeue
     getter state
     method close
     method configure
@@ -321,6 +322,7 @@
     method encode
     method flush
     method reset
+    setter ondequeue
 interface AudioListener
     attribute @@toStringTag
     getter forwardX
@@ -9283,10 +9285,11 @@
     method decode
     method flush
     method reset
-interface VideoEncoder
+interface VideoEncoder : EventTarget
     static method isConfigSupported
     attribute @@toStringTag
     getter encodeQueueSize
+    getter ondequeue
     getter state
     method close
     method configure
@@ -9294,6 +9297,7 @@
     method encode
     method flush
     method reset
+    setter ondequeue
 interface VideoFrame
     attribute @@toStringTag
     getter codedHeight
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium
index cd435e8..427ed8e7 100644
--- a/third_party/crashpad/README.chromium
+++ b/third_party/crashpad/README.chromium
@@ -46,4 +46,6 @@
  - CloseMultipleNowOrOnExec has been updated to invoke the new
    base::subtle::ResetFDOwnership() API
  - FALLTHROUGH macro has been replaced with C++17 attribute [[fallthrough]]
- - Qualified calls to bit_cast<>() with base::.
\ No newline at end of file
+ - Qualified calls to bit_cast<>() with base::.
+ - MultiprocessExec.MultiprocessExec is disabled entirely on the Mac.
+   https://crbug.com/1341377
diff --git a/third_party/crashpad/crashpad/test/multiprocess_exec_test.cc b/third_party/crashpad/crashpad/test/multiprocess_exec_test.cc
index 2cb9db2a..098a7fbf 100644
--- a/third_party/crashpad/crashpad/test/multiprocess_exec_test.cc
+++ b/third_party/crashpad/crashpad/test/multiprocess_exec_test.cc
@@ -49,6 +49,8 @@
   }
 };
 
+// This fails under macOS 12; https://crbug.com/1341377
+//
 // TODO(tasak): enable this test after making address randomization not to
 // keep /dev/urandom open.
 // PartitionAllocator opens /dev/urandom because of address randomization.
@@ -56,7 +58,8 @@
 // //base/allocator/partition_allocator/random.cc) So when making
 // PartitionAllocator default, multiprocess_exec_test_child will crash because
 // of LOG(FATAL) << "close". https://crbug.com/1153544
-#if defined(OS_POSIX) && BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
+#if defined(OS_POSIX) && BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) || \
+    defined(OS_MAC)
 #define MAYBE_MultiprocessExec DISABLED_MultiprocessExec
 #else
 #define MAYBE_MultiprocessExec MultiprocessExec
diff --git a/third_party/dom_distiller_js/protoc_plugins/json_values_converter.py b/third_party/dom_distiller_js/protoc_plugins/json_values_converter.py
index 5a6fead..a65f08f 100755
--- a/third_party/dom_distiller_js/protoc_plugins/json_values_converter.py
+++ b/third_party/dom_distiller_js/protoc_plugins/json_values_converter.py
@@ -117,10 +117,10 @@
     if field.IsClassType():
       self.Output(
           'const auto& repeated_field = message.{field_name}();\n'
-          'base::Value::ListStorage field_list;\n'
+          'base::Value::List field_list;\n'
           'field_list.reserve(repeated_field.size());\n'
           'for (const auto& element : repeated_field) {{\n'
-          '  field_list.push_back(\n'
+          '  field_list.Append(\n'
           '      {inner_class_converter}::WriteToValue(element));\n'
           '}}\n'
           'dict.SetKey("{field_number}",\n'
@@ -132,8 +132,11 @@
     else:
       self.Output(
           'const auto& repeated_field = message.{field_name}();\n'
-          'base::Value::ListStorage field_list(\n'
-          '    repeated_field.begin(), repeated_field.end());\n'
+          'base::Value::List field_list;\n'
+          'field_list.reserve(repeated_field.size());\n'
+          'for (const auto& element : repeated_field) {{\n'
+          '  field_list.Append(element);\n'
+          '}}\n'
           'dict.SetKey("{field_number}",\n'
           '            base::Value(std::move(field_list)));\n',
           field_number=field.JavascriptIndex(),
diff --git a/third_party/dom_distiller_js/test_sample_json_converter.h.golden b/third_party/dom_distiller_js/test_sample_json_converter.h.golden
index 8b7bfa2..3dbb9fe 100644
--- a/third_party/dom_distiller_js/test_sample_json_converter.h.golden
+++ b/third_party/dom_distiller_js/test_sample_json_converter.h.golden
@@ -136,8 +136,11 @@
               base::Value dict(base::Value::Type::DICTIONARY);
               {
                 const auto& repeated_field = message.dummy();
-                base::Value::ListStorage field_list(
-                    repeated_field.begin(), repeated_field.end());
+                base::Value::List field_list;
+                field_list.reserve(repeated_field.size());
+                for (const auto& element : repeated_field) {
+                  field_list.Append(element);
+                }
                 dict.SetKey("1",
                             base::Value(std::move(field_list)));
               }
@@ -222,45 +225,60 @@
             base::Value dict(base::Value::Type::DICTIONARY);
             {
               const auto& repeated_field = message.float_value();
-              base::Value::ListStorage field_list(
-                  repeated_field.begin(), repeated_field.end());
+              base::Value::List field_list;
+              field_list.reserve(repeated_field.size());
+              for (const auto& element : repeated_field) {
+                field_list.Append(element);
+              }
               dict.SetKey("1",
                           base::Value(std::move(field_list)));
             }
             {
               const auto& repeated_field = message.double_value();
-              base::Value::ListStorage field_list(
-                  repeated_field.begin(), repeated_field.end());
+              base::Value::List field_list;
+              field_list.reserve(repeated_field.size());
+              for (const auto& element : repeated_field) {
+                field_list.Append(element);
+              }
               dict.SetKey("2",
                           base::Value(std::move(field_list)));
             }
             {
               const auto& repeated_field = message.int32_value();
-              base::Value::ListStorage field_list(
-                  repeated_field.begin(), repeated_field.end());
+              base::Value::List field_list;
+              field_list.reserve(repeated_field.size());
+              for (const auto& element : repeated_field) {
+                field_list.Append(element);
+              }
               dict.SetKey("3",
                           base::Value(std::move(field_list)));
             }
             {
               const auto& repeated_field = message.bool_value();
-              base::Value::ListStorage field_list(
-                  repeated_field.begin(), repeated_field.end());
+              base::Value::List field_list;
+              field_list.reserve(repeated_field.size());
+              for (const auto& element : repeated_field) {
+                field_list.Append(element);
+              }
               dict.SetKey("4",
                           base::Value(std::move(field_list)));
             }
             {
               const auto& repeated_field = message.string_value();
-              base::Value::ListStorage field_list(
-                  repeated_field.begin(), repeated_field.end());
+              base::Value::List field_list;
+              field_list.reserve(repeated_field.size());
+              for (const auto& element : repeated_field) {
+                field_list.Append(element);
+              }
               dict.SetKey("5",
                           base::Value(std::move(field_list)));
             }
             {
               const auto& repeated_field = message.message_value();
-              base::Value::ListStorage field_list;
+              base::Value::List field_list;
               field_list.reserve(repeated_field.size());
               for (const auto& element : repeated_field) {
-                field_list.push_back(
+                field_list.Append(
                     dom_distiller::test_sample::proto::json::Repeated::Message::WriteToValue(element));
               }
               dict.SetKey("6",
diff --git a/third_party/liblouis/README.chromium b/third_party/liblouis/README.chromium
index 4fdd3bb..d8f95c61 100644
--- a/third_party/liblouis/README.chromium
+++ b/third_party/liblouis/README.chromium
@@ -1,7 +1,7 @@
 Name: Braille Translation Library
 Short Name: liblouis
 URL: https://github.com/liblouis/liblouis
-Version: 3.21.0
+Version: 3.22.0
 CPEPrefix: cpe:/a:liblouis:liblouis:3.12.0
 License: LGPL3 and GPL3
 License Android Compatible: No
diff --git a/third_party/liblouis/wasm/liblouis_wasm.data b/third_party/liblouis/wasm/liblouis_wasm.data
index 244d65a..ce5d27c 100644
--- a/third_party/liblouis/wasm/liblouis_wasm.data
+++ b/third_party/liblouis/wasm/liblouis_wasm.data
@@ -2113,6 +2113,7 @@
 ETAGS = @ETAGS@
 EXEEXT = @EXEEXT@
 FGREP = @FGREP@
+FILECMD = @FILECMD@
 GETOPT_CDEFS_H = @GETOPT_CDEFS_H@
 GETOPT_H = @GETOPT_H@
 GL_CFLAG_ALLOW_WARNINGS = @GL_CFLAG_ALLOW_WARNINGS@
@@ -3342,6 +3343,7 @@
 	de-g2-core.cti \
 	de-g2-core-patterns.dic \
 	de-g2.ctb \
+	de-g2-detailed.ctb \
 	devanagari.cti \
 	digits6DotsPlusDot6.uti \
 	digits6Dots.uti \
@@ -3545,7 +3547,6 @@
 	no-no-generic.ctb \
 	no-no-generic.dis \
 	no-no-latinLetterDef6Dots_diacritics.uti \
-	no.tbl \
 	np-in-g1.utb \
 	nso-za-g1.utb \
 	nso-za-g2.ctb \
@@ -3584,12 +3585,9 @@
 	si-in-g1.utb \
 	sin.cti \
 	sin.utb \
-	sk-chardefs.cti \
 	sk-g1.ctb \
 	sk-sk-g1.utb \
 	sk-sk.utb \
-	sk.tbl \
-	sk-translation.cti \
 	sl-si-comp8.ctb \
 	sl-si-g1.utb \
 	sl.tbl \
@@ -87095,10 +87093,12 @@
 #Because in hungarian Braille usual the dot combinations equals with single consonants letters and double consonants letters (for example 146 with cs, 146-146 with ccs, and 146-146 with cscs), need doing last correction when happening a back translation
 #cscs, ccs word part related backtranslate corrections
 nofor correct "aganccsap" "agancscsap"	For example agancscsapról word
+nofor correct "aganccsatt" "agancscsatt"	For example agancscsattogásának word
 nofor correct "apoccsont" "apocscsont"	For example álkapocscsont, szárkapocscsont, szárkapocscsonttal word part corrections
-nofor correct "kapocscsal" "kapoccsal"
+nofor correct "Csúccsap" "Csúcscsap"	For example Csúcscsapat word
 nofor correct "csúccsap" "csúcscsap"	For example csúcscsapat word
-nofor correct "erkölccső" "erkölcscső"	For example erkölcscsősz, erkölcscsősszel words backtranslation correction
+nofor correct "kapocscsal" "kapoccsal"
+nofor correct "rkölccső" "rkölcscső"	For example erkölcscsősz, erkölcscsősszel words backtranslation correction
 nofor correct "furdanccsi" "furdancscsi"	With furdancscsiga backtranslation correction
 nofor correct "habarccsö" "habarcscsö"	For example habarcscsövekkel, habarcscsövet words backtranslation word correction
 nofor correct "habarccső" "habarcscső"	Similar correction, for example habarcscsővel, habarcscső word
@@ -87111,41 +87111,145 @@
 nofor correct "anccsap" "ancscsap"	For example mancsapással word backtranslation
 nofor correct "kinccsemp" "kincscsemp"	With műkincscsempész word part backtranslation
 nofor correct "felejccsok" "felejcscsok"	For example nefelejcscsokorral word part
-nofor correct "akáccsapat" "akácscsapat"	For example szakácscsapat word
+nofor correct "akáccsap" "akácscsap"	For example szakácscsapat word
 nofor correct "endviccsi" "endvicscsi"	For example szendvicscsinálás word
 nofor correct "anáccsap" "anácscsap"
-nofor correct "tanáccsop" "tanácscsop"	For example tanácscsoport word correction
-nofor correct "kartácscsal" "kartáccsal"	For example fémkartáccsal, kartáccsal words backtranslation
-nofor correct "narancscsal" "naranccsal"
-
+nofor correct "anáccsop" "anácscsop"	For example tanácscsoport word correction
+nofor correct "artácscsal" "artáccsal"	For example fémkartáccsal, kartáccsal words backtranslation
+nofor correct "arancscsal" "aranccsal"
+nofor correct "ARANCSCSAL" "ARANCCSAL"
+nofor correct "ronccsalá" "roncscsalá"	For example abroncscsalád, gumiabroncscsalád words
+nofor correct "ronccser" "roncscser"	For example abroncscsere, gumiabroncscsere words
+nofor correct "unccsod" "uncscsod"	For example puncscsoda word
+nofor correct "akanccsó" "akancscsó"	For example bakancscsók word
+nofor correct "abronccsapd" "abroncscsapd"	For example gumiabroncscsapda word
+nofor correct "Abronccsapd" "Abroncscsapd"	For example Abroncscsapda word
+nofor correct "ullanccsecs" "ullancscsecs"	For example kullancscsecsemő word
+nofor correct "endviccsoma" "endvicscsoma"	For example szendvicscsomagoló word
+nofor correct "gánccsapa" "gáncscsapa"	For example cselgáncscsapat word
+nofor correct "kviddiccsapa" "kviddicscsapa"	For example kviddicscsapat word
+nofor correct "úccsat" "úcscsat"	For example csúcscsatája word
+nofor correct "úccsont" "úcscsont"	For example csúcscsonttömeg word
+nofor correct "aláccso" "alácscso"	For example kalácscsoda word
+nofor correct "örccsi" "örcscsi"	For example görcscsillapító word
+nofor correct "accsőr" "acscsőr"	For example Kacscsőrű word
+nofor correct "ümölccsom" "ümölcscsom"	For example gyümölcscsomagolás word
+nofor correct "elgánccsar" "elgáncscsar"	For example cselgáncscsarnok word
+nofor correct "akáccsom" "akácscsom"	For example takácscsomót word
+nofor correct "inccsalá" "incscsalá"	For example műkincscsalás word
+nofor correct "ulaccser" "ulacscser"	For example kulacscsere word
+nofor correct "apuccsőr" "apucscsőr"	For example papucscsőrű word
 
 #ggy, gygy backtranslation rule corrections
+nofor correct "szalaggyak" *
+nofor correct "aggyakorla" "agygyakorla"	For example agygyakorlat word
+nofor correct "jeggyűj" "jegygyűj"	For example bankjegygyűjtő word
+nofor correct "Jeggyűj" "Jegygyűj"	For example Jegygyűjtő word
+nofor correct "jeggyil" "jegygyil"	For example bankjegygyilkos word
 nofor correct "jeggyűr" "jegygyűr"	For example jegygyűrű, jegygyűrűvel words backtranslation correction
+nofor correct "Jeggyűr" "Jegygyűr"	For example Jegygyűrű word
 nofor correct "árggyű" "árgygyű"	For example tárgygyűjtemény, kegytárgygyűjtemény words backtranslation corrections
-nofor correct "csapággyá" "csapágygyá"	For example csapágygyár, csapágygyártás, csapágygyárában word
+nofor correct "apággyá" "apágygyá"	For example csapágygyár, csapágygyártás, csapágygyárában word
+nofor correct "apággyűr" "apágygyűr"	For example csapágygyűrűinek word
+nofor correct "eggyerme" "egygyerme"	For example egygyermekes word
+nofor correct "Eggyerme" "Egygyerme"	For example Egygyermekes word
+nofor correct "leggyere" *	For example leggyerekesebb word not need apply the next exception
+nofor correct "eggyere" "egygyere"	For example egygyerekes word need apply a special backtranslation correction 
+nofor correct "Leggyere" *
+nofor correct "Eggyere" "Egygyere"	For example egygyerekes word
+nofor correct "éggyerek" "égygyerek"	For example négygyerekes word
+nofor correct "ogággyul" "ogágygyul"	For example fogágygyulladás word
+nofor correct "Néggyerm" "Négygyerm"	For example Négygyermekes word
+nofor correct "néggyerm" "négygyerm"	For example négygyermekes word
 nofor correct "naggyű" "nagygyű"	For example nagygyűlés, nagygyűlésen words backtranslation correction
+nofor correct "Naggyű" "Nagygyű"	For example Nagygyűlés, Nagygyűlésen words backtranslation correction
 nofor correct "tőggyull" "tőgygyull"	For example tőgygyulladás word backtranslation correction
+nofor correct "Tőggyull" "Tőgygyull"	For example Tőgygyulladás word
+nofor correct "iriggyull" "irigygyull"	For example pajzsmirigygyulladás, pajzsmirigygyulladást words
+nofor correct "gyönggyárt" "gyöngygyárt"	For example üveggyöngygyártás, gyöngygyártás words
+nofor correct "Gyöngygyárt" "Gyöngygyárt"	For example Gyöngygyártás word
+nofor correct "ggyökerű" "gygyökerű"	For example egygyökerű, Egygyökerű, egygyökerűség, Egygyökerűség words
+nofor correct "GGYÖKERŰ" "GYGYÖKERŰ"	For example the full uppercase version
+nofor correct "iriggyógy" "irigygyógy"	For example pajzsmirigygyógyszerek word
+nofor correct "ankjeggyár" "ankjegygyár"	For example bankjegygyártó word
+nofor correct "aggyimó" "agygyimó"	For example Nagygyimót, nagygyimóti words
+nofor correct "rüggyapot" "rügygyapot"	For example rügygyapot word
+nofor correct "Rüggyapot" "Rügygyapot"
+nofor correct "zveggyár" "zvegygyár"	For example özvegygyártónak word
+nofor correct "ággyártók" "ágygyártók"
+nofor correct "Ággyártók" "Ágygyártók"
+nofor correct "aggyalu" "agygyalu"	For example agygyalu word
+nofor correct "Aggyalu" "Agygyalu"	For example Agygyalu word
+nofor correct "aggyanú" "agygyanú"	For example fagygyanús word
+nofor correct "leggyüm" *
+nofor correct "Leggyüm" *
+nofor correct "éreggyüm" *
+nofor correct "eggyüm" "egygyüm"	For example egygyümölcsös word
+nofor correct "eteggy" *	For example beteggyógyász, beteggyanús, cukorbeteggyermek-táborok words
+nofor correct "herceggy" *	For example herceggyermek word
+nofor correct "árggyar" "árgygyar"	For example műtárgygyarapodás word
+nofor correct "azeinjeggyár" "azeinjegygyár"	For example kazeinjegygyár word
+nofor correct "ággyull" "ágygyull"	For example körömágygyulladás word
+nofor correct "ggyőzelemn" "gygyőzelemn"	For example egygyőzelem word
+nofor correct "vággyil" "vágygyil"	For example vágygyilkos word
+nofor correct "Vággyil" "Vágygyil"	For example Vágygyilkos word
+nofor correct "VÁGGYIL" "VÁGYGYIL"	For example VÁGYGYILKOS word
+nofor correct "árggyor" "árgygyor"	For example tárgygyorsítás word
 
 #nyny, nyny letters related backtranslate corrections
-nofor correct "arannyel" "aranynyel"	For example aranynyelű word
-nofor correct "asszonnyer" "asszonynyer"	For example asszonynyereg word back translation
-nofor correct "báránnyelv" "báránynyelv"	For example báránynyelv, báránynyelvből, báránynyelvvel words
+nofor correct "rannyak" "ranynyak"	For example aranynyakláncának word
+nofor correct "rannyel" "ranynyel"	For example aranynyelű word
+nofor correct "rannyer" "ranynyer"	For example aranynyerges word
+nofor correct "rannyom" "ranynyom"	For example aranynyomással word
+nofor correct "sszonnyer" "sszonynyer"	For example asszonynyereg, Asszonynyereg words back translation
+nofor correct "áránnyak" "áránynyak"	For example báránynyak word
+nofor correct "áránnyelv" "áránynyelv"	For example báránynyelv, báránynyelvből, báránynyelvvel words
 nofor correct "báránnyí" "báránynyí"	For example báránynyíróként word back translation
-nofor correct "boszorkánnyom" "boszorkánynyom"
+nofor correct "oszorkánnyom" "oszorkánynyom"
 nofor correct "ákánnyél" "ákánynyél"	For example csákánnyél, csákánynyélből, csákánynyél words
 nofor correct "énnyal" "énynyal"	For example edénynyalábokban, edénynyalábokból, edénynyalábról, fénynyalábbal, fénynyalábból, fénynyalábokban, fénynyalábokkal word
-nofor correct "énnyomás" "énynyomás"	For example fénynyomással, fénynyomatban, fénynyomatból fénynyomatról, fénynyomattól words
-nofor correct "ampánnyit" "ampánynyit"	For example kampánynyitó word backtransllation correction
-nofor correct "fénnyom" "fénynyom"	For example fénynyomatban word backtranslation correction
-nofor correct "versennyit" "versenynyit"	For example hangversenynyitány backtranslation correction
-nofor correct "horgonnyak" "horgonynyak"	For example horgonynyak backtranslation correction
+nofor correct "énnyomá" "énynyomá"	For example fénynyomással, fényomás words
+nofor correct "énnyoma" "énynyoma"	For example fénynyomatban, fénynyomatból, fénynyomatról, fénynyomattól words
+nofor correct "énnyomt" "énynyomt"	For example fénynyomtatás word
+nofor correct "kmánnyom" "kmánynyom"	For example okmánynyomtatásban word
+nofor correct "ampánnyil" "ampánynyil"	For example kampánynyilatkozat word
+nofor correct "ampánnyit" "ampánynyit"	For example kampánynyitó word backtranslation correction
+nofor correct "eménnyak" "eménynyak"	For example keménynyakú word backtranslation correction
+nofor correct "ötvénnyil" "ötvénynyil"	For example kötvénynyilvántartás, kötvénynyilvántartó words
+nofor correct "ersennyit" "ersenynyit"	For example hangversenynyitány backtranslation correction
+nofor correct "ersennyom" "ersenynyom"	For example versenynyomásra word
+nofor correct "ersennyüzsg" "ersenynyüzsg"	For example versenynyüzsgéshez word
+nofor correct "orgonnyak" "orgonynyak"	For example horgonynyak backtranslation correction
+nofor correct "dénnyit" "dénynyit"	For example idénynyitó word
+nofor correct "rénnyit" "rénynyit"	For example szekrénynyitó word
+nofor correct "énnyújt" "énynyújt"	For example élménynyújtás, teljesítménynyújtó word
 nofor correct "ormánnyak" "ormánynyak"	For example kormánynyakhoz, kormánynyakból words backtranslation correction
+nofor correct "ormánnyil" "ormánynyil"	For example kormánynyilatkozat word backtranslation correction
 nofor correct "öpennyú" "öpenynyú"	For example köpenynyúlvánnyal word backtranslation correction
 nofor correct "eánnyel" "eánynyel"	For example leánynyelv, leánynyelvekben words backtranslation corrections
 nofor correct "ősténnyú" "ősténynyú"	For example nősténynyúllal word backtranslation correction
 nofor correct "árnnyil" "árnynyil"	For example szárnynyilazás word backtranslation correction
 nofor correct "oronnyí" "oronynyí"	For example toronynyílás, toronynyílásban word backtranslation correction
 nofor correct "éleménnyil" "éleménynyil"	For example véleménynyilvánítás, véleménynyilvánításban words backtranslation corrections
+nofor correct "ÉLEMÉNNYIL" "ÉLEMÉNYNYIL"	For example VÉLEMÉNYNYILVÁNÍTÁS, VÉLEMÉNYNYILVÁNÍTÁSBAN words backtranslation corrections
+nofor correct "övénnyír" "övénynyír"	For example sövénynyíró word
+nofor correct "eljesítménnyilat" "eljesítménynyilat"	For example teljesítménynyilatkozat, Teljesítménynyilatkozat words
+nofor correct "átonnyelv" "átonynyelv"	For example zátonynyelv word
+nofor correct "lménnyaral" "lménynyaral"	For example élménynyaralás word
+nofor correct "árvánnyúl" "árványnyúl"	For example márványnyúl word
+nofor correct "örnnyúl" "örnynyúl"	For example szörnynyúl word
+nofor correct "sonnyak" "sonynyak"	For example bársonynyakékes word
+nofor correct "ménnyit" "ménynyit"	For example intézménynyitás word
+nofor correct "övénnyú" "övénynyú"	For example növénynyúzók word
+nofor correct "árvánnyo" "árványnyo"	For example járványnyomás word
+nofor correct "ÁRVÁNNYO" "ÁRVÁNYNYO"	For example JÁRVÁNYNYOMÁS word
+nofor correct "éménnyíl" "éménynyíl"	For example kéménynyílásai word
+nofor correct "hiánnyel" "hiánynyel"	For example hiánynyelv vord
+nofor correct "Hiánnyel" "Hiánynyel"	For example Hiánynyelv word
+nofor correct "HIÁNNYEL" "HIÁNYNYEL"	For example HIÁNYNYELV word
+nofor correct "övénnyes" "övénynyes"	For example növénynyesedék, sövénynyesedék words
+nofor correct "akonnyalog" "akonynyalog"	For example takonynyalogató word
+nofor correct "regénnyusz" "regénynyusz"	For example képregénynyuszi word
 
 #lyly letters related backtranslate corrections
 nofor correct "amelylyel" *
@@ -87153,144 +87257,497 @@
 #szsz backtranstrated words related exceptions
 nofor correct "bortusszab" "bortuszszab"	For example abortuszszabadság, abortuszszabály words backtranslation
 nofor correct "bortusszabadsággal" "bortuszszabadsággal"
+nofor correct "bortusszer" "bortuszszer"
 nofor correct "bortusszá" "bortuszszá"
 nofor correct "bortusszi" "bortuszszi"
+nofor correct "bortusszolgá" "bortuszszolgá"	For example abortuszszolgáltatók word
 nofor correct "knásszáz" "knászszáz"
 nofor correct "ananásszal" *
+nofor correct "Ananásszal" *
+nofor correct "ANANÁSSZAL" *
 nofor correct "ananássz" "ananászsz"	For example ananásszörp, ananászszelet, ananászszeletekből words
+nofor correct "Ananássz" "Ananászsz"	For example Ananászszár, Ananászszelet words
+nofor correct "ANANÁSSZ" "ANANÁSZSZ"	For example ANANÁSZSZár word
 nofor correct "aszszony" "asszony"
 nofor correct "árrésszab" *	Árrésszabály part not need applying the special szsz backtranslation correction
+nofor correct "érésszab" *
+nofor correct "Érésszab" *
 nofor correct "résszabá" "részszabá"
+nofor correct "öréssza" *	For example törésszakaszon word not need apply the special backtranslation szsz correction
+nofor correct "veréssza" *	For example átverésszagú word not need apply the special backtranslation correction
 nofor correct "réssza" "részsza"	For example résszavaiból word
 nofor correct "résszá" "részszá"	For example alkatrésszám, alkatrésszámainak, alkatrésszámaival, részszámla words
 nofor correct "résszerel" "részszerel"	For example alkatrésszerelő word
+nofor correct "fűrésszerű" "fűrésszerű"
 nofor correct "résszel" *	For example fűrésszel word not need applying next részsze word part related rule
 nofor correct "résszer" *	For example árverésszerű, kitörésszerű, lőrésszerű words not need applying the simple szsz backtranslation correction
 nofor correct "réssze" "részsze"	But for example résszempont, tulajdonrészszerzés words need applying a special replacement with backward translation
 nofor correct "résszo" "részszo"	For example részszolgáltatás, alkatrészszolgáltatás words
 nofor correct "résszó" "részszó"
+nofor correct "Résszó" "Részszó"
+nofor correct "övidítésszó" *	For example rövidítésszótárban word
+nofor correct "ésszó" "észszó"	For example mészszóró word
 nofor correct "atlasszala" "atlaszszala"
 nofor correct "atlasszo" "atlaszszo"
-nofor correct "bajusszál" "bajuszszál"
-nofor correct "busszá" "buszszá"
+nofor correct "ajusszál" "ajuszszál"
+nofor correct "ajussző" "ajuszsző"	For example bajuszszőrtől word backtranslation correction
+nofor correct "imbusszá" "imbusszá"
+nofor correct "ébusszá" "ébusszá"
+nofor correct "ambusszál" "ambuszszál"	For example bambuszszál, bambuszszálakból words
+nofor correct "ambusszen" "ambuszszen"	For example bambuszszenes word
+nofor correct "ambusszár" "ambuszszár"	For example bambuszszár word
 nofor correct "busszé" "buszszé"
-nofor correct "ányásszak" "ányászszak"	For example bányászszakszervezet word
+nofor correct "busszer" "buszszer"	For example buszszerencsétlenség word
+nofor correct "busszolg" "buszszolg"	For example buszszolgáltatásban word
+nofor correct "ányásszaksz" "ányászszaksz"	For example bányászszakszervezet word
 nofor correct "ányásszek" "ányászszek"	For example bányászszektor word back translation related
 nofor correct "ányásszob" "ányászszob"	For example bányászszobraival, Bányászszobraival word backtranslation related
+nofor correct "ányásszoftv" "ányászszoftv"	
 nofor correct "ányásszt" "ányászszt"	For example bányászsztrájk word backtranslation correction
 nofor correct "oksszöv" "okszszöv"	For example bokszszövetség word backtranslation correction
+nofor correct "oksszak" "okszszak"	For example bokszszakértő, bokszszakmában words
+nofor correct "oksszerv" "okszszerv"	For example bokszszervezet word
+nofor correct "okssztá" "okszsztá"	For example bokszsztár word
 nofor correct "ónusszám" "ónuszszám"	For example bónuszszám, bónuszszámaik backtranslation correction
+nofor correct "russzínház" *	For example kórusszínház word related not need apply the next correction rule
+nofor correct "usszabvá" "uszszabvá"	For example buszszabványok word
 nofor correct "usszínház" "uszszínház"	For example buszszínház word backtranslation output correction
+nofor correct "ipésszakm" "ipészszakm"	For example cipészszakmájának word backtranslation correction
 nofor correct "ipésszöv" "ipészszöv"	For example cipészszövetkezet backtranslation correction
 nofor correct "ukrásszak" "ukrászszak"	For example cukrásszakmában word
+nofor correct "ukrásszöv" "ukrászszöv"	For example Cukrászszövetségben word
+nofor correct "erkésszáll" "erkészszáll"	For example cserkészszállón word
 nofor correct "erkésszer" "erkészszer"	For example cserkészszervezet back translation correction
 nofor correct "erkésszob" "erkészszob"	For example cserkészszoba, cserkészszobájában backtranslation correction
 nofor correct "erkésszöv" "erkészszöv"	For example cserkészszövetkezet word backtranslation correction
-nofor correct "csillagássze" "csillagászsze"	For example csillagászszemmel word backtranslation correction
+nofor correct "illagássze" "illagászsze"	For example csillagászszemmel word backtranslation correction
 nofor correct "íssza" "íszsza"	For example díszszakasszal, díszszalaggal words backtranslation correction
 nofor correct "ísszeg" "íszszeg"	For example díszszegély, díszszegéllyel words backtranslation correction
 nofor correct "ísszek" "íszszek"	For example díszszekrény, díszszekrényében words backtranslation correction
 nofor correct "ísszem" "íszszem"	For example díszszemle, díszszemléhez words backtranslation correction
 nofor correct "ísszer" "íszszer"	For example díszszertartás word backtranslation correction
-nofor correct "ísszobá" "íszszobá"	For example díszszobában word backtranslation correction
-nofor correct "egésszám" "egészszám"	For example egészszám-végrehajtás backtranslation correction
+nofor correct "ísszob" "íszszob"	For example díszszobában word backtranslation correction
+nofor correct "egésszá" "egészszá"	For example egészszám-végrehajtás backtranslation correction
+nofor correct "Egésszá" "Egészszá"	Same the previous word, but uppercase version
 nofor correct "pítésszak" "pítészszak"	For example építészszakon, építészszakmában words backtranslation correction
 nofor correct "pítésszem" "pítészszem"	For example építésszemmel word backtranslation correction
 nofor correct "fesszab" "feszszab"	For example feszszabályzó word backtranslation correction
+nofor correct "Fesszab" "Feszszab"
 nofor correct "itnesszak" "itneszszak"	For example fitneszszakmában word backtranslation correction
+nofor correct "itnesszala" "itneszszala"	For example fitneszszalag word backtranslation correction
+nofor correct "itnessztá" "itneszsztá"	For example fitneszsztár word
+nofor correct "itnesszolg" "itneszszolg"	For example fitneszszolgáltatás word backtranslation correction
+nofor correct "itnesszö" "itneszszö"	For example fitneszszövetség word
 nofor correct "itnesszüne" "itneszszüne"	For example fitneszszünet word backtranslation correction
 nofor correct "odrásszak" "odrászszak"	For example fodrászszakszervezet, fodrászszakmában words backtranslation correction
 nofor correct "odrásszalo" "odrászszalo"	For example fodrászszalon, fodrászszalonból words backtranslation correction
 nofor correct "odrásszé" "odrászszé"	For example fodrászszék word backtranslation correction
-nofor correct "gépésszak" "gépészszak"	For example gépészszakterület backtranslation correction
+nofor correct "épésszak" "épészszak"	For example gépészszakterület backtranslation correction
 nofor correct "gyásszala" "gyászszala"	For example gyászszalaggal backtranslation correction
+nofor correct "Gyásszala" "Gyászszala"	For example Gyászszalaggal word
 nofor correct "gyásszab" "gyászszab"	For example gyászszabadság word backtranslation correction
+nofor correct "Gyásszab" "Gyászszab"	Uppercase version the previous word
+nofor correct "ogyásszakértő" *	For example fogyásszakértő word not need apply the next correction rule
+nofor correct "gyásszakért" "gyászszakért"	For example gyászszakértő word
+nofor correct "Gyásszakért" "Gyászszakért"	For example Gyászszakértő word
+nofor correct "Gyásszaka" "Gyászszaka"	For example Gyászszakaszai word
+nofor correct "gyásszaka" "gyászszaka"	For example Gyászszakaszai word
+nofor correct "gyásszem" "gyászszem"	For example gyászszemle word
+nofor correct "Gyásszem" "Gyászszem"	For example Gyászszemle word
 nofor correct "gyásszer" "gyászszer"	For example gyászszertartás backtranslation correction
+nofor correct "Gyásszer" "Gyászszer"	For example Gyászszertartás backtranslation correction
 nofor correct "gyásszün" "gyászszün"	For example gyászszünet word backtranslation correction
 nofor correct "Gyásszün" "Gyászszün"
-nofor correct "gipsszob" "gipszszob"	For example gipszszoborként, gipszszoborral, gipszszobraival words backtranslation correction
+nofor correct "ipsszob" "ipszszob"	For example gipszszoborként, gipszszoborral, gipszszobraival words backtranslation correction
+nofor correct "ipsszuszp" "ipszszuszp"	For example gipszszuszpenziós word
+nofor correct "halásszáko" "halászszáko"	For example halászszákot word
+nofor correct "Halásszáko" "Halászszáko"
+nofor correct "halásszám" "halászszám"	For example halászszámlálás, halászszámot words
+nofor correct "Halásszám" "Halászszám"
 nofor correct "halássze" "halászsze"	For example halászszervezet, halászszerszámaikkal backtranslation correction
+nofor correct "Halássze" "Halászsze"
 nofor correct "halásszi" "halászszi"	For example halászszigony, halászszigonyokkal backtranslation correction
+nofor correct "Halásszi" "Halászszi"
+nofor correct "orgásszállá" "orgászszállá"	For example horgászszállás word
 nofor correct "orgásszé" "orgászszé"	For example horgászszék backtranslation correction
+nofor correct "morgássze" *	For example the morgásszerű word not need apply the next backtranslation correction
 nofor correct "orgássze" "orgászsze"	For example horgászszempontból backtranslation correction
 nofor correct "umusszin" "umuszszin"	For example humuszszint word backtranslation correction
-nofor correct "hússzáz" "húszszáz"	For example húszszázalék word backtranslation correction
+nofor correct "ússzáz" "úszszáz"	For example húszszázalék word backtranslation correction
+nofor correct "ússzemé" "úszszemé"	For example húszszemélyes word backtranslation correction
 nofor correct "jásszak" "jászszak"	For example íjászszakkör word backtranslation correction
 nofor correct "jásszá" "jászszá"	For example íjászszázad word backtranslation correction
 nofor correct "jásszöv" "jászszöv"	for example íjászszövetség word backtranslation correction
 nofor correct "risszken" "riszszken"	For example íriszszkenner word backtranslation correction
 nofor correct "Jásszent" "Jászszent"	For example Jászszentlászló town name backtranslation correction
+nofor correct "jásszent" "jászszent"	For example jászszentandrási word
 nofor correct "ogásszak" "ogászszak"	For example jogászszakmában, jogászszakon backtranslation correction
 nofor correct "ogássze" "ogászsze"	For example jogászszemmel word backtranslation correction
-nofor correct "kaktusszá" "kaktuszszá"	for example kaktuszszár backtranslation correction
-nofor correct "kertésszen" "kertészszen"	For example kertészszenvedély word backtranslation correction
+nofor correct "kaktusszár" "kaktuszszár"	for example kaktuszszár backtranslation correction
+nofor correct "Kaktusszár" "Kaktuszszár"	For example Kaktuszszár word correction
+nofor correct "KAKTUSSZÁR" "KAKTUSZSZÁR"	For example KAKTUSZSZÁR word correction
+nofor correct "ertésszakm" "ertészszakm"	For example kertészszakma word
+nofor correct "ertésszen" "ertészszen"	For example kertészszenvedély word backtranslation correction
+nofor correct "sertésszemm" *	For example sertésszemmel word not need apply the general ertészszemm backtranslation exception
+nofor correct "Sertésszemm" *	Same the previous line, but uppercase word pair
+nofor correct "ertésszemm" "ertészszemm"	For example kertészszemmel word
+nofor correct "kertésszem" "kertészszem"	For example kertészszem word
+nofor correct "Kertésszem" "Kertészszem"	For example uppercase Kertésszem word
+nofor correct "ertésszig" "ertészszig"	For example kertészszigeti word
+nofor correct "ertésszokny" "ertészszokny"	For example kertészszoknyás word backtranslation correction
 nofor correct "ókusszi" "ókuszszi"	For example kókuszszirup word backtranslation correction
+nofor correct "ókusszö" "ókuszszö"	For example fókuszszövettel word
 nofor correct "kókussző" "kókuszsző"	For example kókusszőnyeg word backtranslation correction
+nofor correct "Kókussző" "Kókuszsző"	For example Kókuszszőnyeggel word
 nofor correct "olesszo" "oleszszo"	For example koleszszobájából word backtranslation correction
 nofor correct "azdásszak" "azdászszak"	For example közgazdászszakmában word backtranslation correction
 nofor correct "azdássze" "azdászsze"	For example közgazdászszemmel word backtranslation correction
 nofor correct "ultusszo" "ultuszszo"	For example kultuszszoborral word backtranslation correction
 nofor correct "ótusszi" "ótuszszi"	For example lótuszszirmokból word backtranslation correction
 nofor correct "összakad" "öszszakad"	For example löszszakadékokból word backtranslation correction
-nofor correct "övéssza" "övészsza"	For example lövészszakasz, lövészszakazban, lövészszakazhoz words backtranslation correction
+nofor correct "övéssza" "övészsza"	For example lövészszakasz, lövészszakaszban, lövészszakazhoz words backtranslation correction
+nofor correct "övésszám" *	For example lövésszámmal word
 nofor correct "övésszá" "övészszá"	For example lövészszázad, lövészszállító words backtranslation correction
 nofor correct "elassze" "elaszsze"	For example melaszszesz word backtranslation correction
+nofor correct "késszeg" *
+nofor correct "Késszeg" *
 nofor correct "désszeg" *	For example szerződésszegés word not need apply the next line rule
 nofor correct "ésszeg" "észszeg"	For example mészszegény word backtranslation correction
 nofor correct "mésszi" "mészszi"	For example mészsziklákból word backtranslation correction
 nofor correct "ésszu" "észszu"	For example mészszurony word backtranslation correction
-nofor correct "űvésszak" "űvészszak"	For example művészszakszervezet, művészszakmában words backtranslation corrections
+nofor correct "űvésszá" "űvészszá"	For example művészszálló,  bűvésszámok words
+nofor correct "űvéssza" "űvészsza"	For example bűvészszakma, művészszakszervezet, művészszakmában words backtranslation corrections
 nofor correct "űvésszem" "űvészszem"	For example művészszemmel word backtranslation correction
 nofor correct "űvésszí" "űvészszí"	For example művészszínház word backtranslation correction
+nofor correct "űvésszob" "űvészszob"	For example művészszoba word
+nofor correct "űvésszöv" "űvészszöv"	For example művészszövetséggel, Művészszövetséggel word
 nofor correct "násszert" "nászszert"	For example nászszertartás word backtranslation correction
 nofor correct "omdásszak" "omdászszak"	For example nyomdászszakmában, nyomdászszakszervezetben words backtranslation corrections
 nofor correct "omdássztr" "omdászsztr"	For example nyomdászsztrájk word backtranslation correction
-nofor correct "panasszó" "panaszszó"	For example panaszszó, panaszszóval words backtranslation correction
-nofor correct "papirusszá" "papiruszszá"	For example papiruszszár, papiruszszárral words backtranslation correction
-nofor correct "penéssza" "penészsza"	For example penészszagban, penészszaggal words backtranslation corrections
-nofor correct "penésszöv" "penészszöv"	For example penészszövedék words backtranslation correction
+nofor correct "anasszó" "anaszszó"	For example panaszszó, panaszszóval words backtranslation correction
+nofor correct "apirusszá" "apiruszszá"	For example papiruszszár, papiruszszárral words backtranslation correction
+nofor correct "enéssza" "enészsza"	For example penészszagban, penészszaggal words backtranslation corrections
+nofor correct "enésszöv" "enészszöv"	For example penészszövedék words backtranslation correction
+nofor correct "iklusszab" *	For example ciklusszabályozás word need copying the original word the backtranslated output
+nofor correct "tílusszab" *	For example stílusszabályainak word backtranslation part not need apply the next correction rule
 nofor correct "lusszab" "luszszab"	For example pluszszabadság, pluszszabadsággal words backtranslation correction
 nofor correct "lusszav" "luszszav"	For example pluszszavazat word backtranslation correction
+nofor correct "lusszáll" "luszszáll"	For example pluszszállítás word
+nofor correct "lusszáz" "luszszáz"	For example pluszszázalékot, pluszszázalékokat words
+nofor correct "ílusszer" *	For example stílusszerű word
+nofor correct "lusszerv" "luszszerv"	For example pluszszervek word
+nofor correct "lusszig" "luszszig"	For example pluszszigetelés word
 nofor correct "plusszin" "pluszszin"	For example pluszszint, pluszszinttel words backtranslation correction
+nofor correct "lusszín" "luszszín"	For example pluszszínterek word
 nofor correct "lusszo" "luszszo"	For example pluszszolgáltatás, pluszszoba words backtranslation correction
+nofor correct "lusszöv" "luszszöv"	for example pluszszöveget word
 nofor correct "poggyásszál" "poggyászszál"	For example poggyászszállító word backtranslation correction
+nofor correct "Poggyásszál" "Poggyászszál"
 nofor correct "poggyássze" "poggyászsze"	For example poggyászszekerek, poggyászszett words backtranslation correction
+nofor correct "Poggyássze" "Poggyászsze"
 nofor correct "poggyásszí" "poggyászszí"	For example poggyászszíj word backtranslation correction
+nofor correct "Poggyásszí" "Poggyászszí"
 nofor correct "régésszak" "régészszak"	For example régészszakon, régészszakmában words backtranslation correction
+nofor correct "Régésszak" "Régészszak"
 nofor correct "régésszem" "régészszem"	For example régészszemmel word backtranslation correction
-nofor correct "rekesszerk" "rekeszszerk"	For example rekeszszerkezet word backtranslation correction
-nofor correct "rinocérosszar" "rinocéroszszar"	For example rinocéroszszarv, rinocéroszszarvakkal words backtranslation correction
-nofor correct "státusszim" "státuszszim"	For example státuszszimbólum word
-nofor correct "szakasszin" "szakaszszin"	For example szakaszszinttel word backtranslation correction
+nofor correct "Rekesszem" "Rekeszszem"
+nofor correct "ekesszerk" "ekeszszerk"	For example rekeszszerkezet word backtranslation correction
+nofor correct "ekesszár" "ekeszszár"	For example rekeszszár word
+nofor correct "inocérosszar" "inocéroszszar"	For example rinocéroszszarv, rinocéroszszarvakkal words backtranslation correction
+nofor correct "tátusszer" "tátuszszer"	For example státuszszerzési word
+nofor correct "tátusszim" "tátuszszim"	For example státuszszimbólum word
+nofor correct "tátusszor" "tátuszszor"	For example státuszszorongás word
+nofor correct "akasszám" "akaszszám"	For example szakaszszám word
+nofor correct "akasszin" "akaszszin"	For example szakaszszinttel word backtranslation correction
 nofor correct "eméssza" "emészsza"	For example szemésszakember word backtranslation correction
 nofor correct "ínésszak" "ínészszak"	For example színészszakmában, színészszakszervezet words
+nofor correct "ínésszáll" "ínészszáll"	For example színészszállást word
 nofor correct "ínésszelí" "ínészszelí"	For example színészszelídítő word backtranslation correction
 nofor correct "ínésszöv" "ínészszöv"	For example színészszövetség word
 nofor correct "ámasszin" "ámaszszin"	For example támaszszint, támaszszintről word
 nofor correct "téesszer" "téeszszer"	For example téeszszervezés word
+nofor correct "Téesszer" "Téeszszer"
 nofor correct "enisszöv" "eniszszöv"	For example Teniszszövetség, Teniszszövetségtől word correction
 nofor correct "enissztá" "eniszsztá"	For example teniszsztár word backtranslation correction
+nofor correct "erasszép" "eraszszép"	For example teraszszépítés word
 nofor correct "erasszin" "eraszszin"	For example teraszszint, teraszszinttel word
 nofor correct "ojássz" *	For example tojásszán word not need applying special backtranslation
-nofor correct "történéssza" "történészsza"	For example történészszakmában word
+nofor correct "örténéssza" "örténészsza"	For example történészszakmában word
 nofor correct "ranssze" "ranszsze"	For example transzszexuális, transzszexualitás word backtranslation correction
+nofor correct "Hússzabó" "Hússzabó"
 nofor correct "ússzab" "úszszab"	For example túszszabadítás, túszszabadító word backtranslation
+nofor correct "hússzed"	* For example hússzedő word not need apply the general szsz letter pair translation
+nofor correct "Hússzed"	* For example hússzedő word not need apply the general szsz letter pair translation
 nofor correct "ússzed" "úszszed"	For example túszszedő word backtranslation correction
 nofor correct "utásszak" "utászszak"	For example utászszakasz, utászszakaszaiban words backtranslation correction
 nofor correct "tásszáz" "tászszáz"	For example utásszázad, utászszázadból words backtranslation correction
-nofor correct "vadásszá" "vadászszá"	For example vadászszázad, vadászszázadban words backtranslation correction
-nofor correct "vadássze" "vadászsze"	For example vadászszemmel word backtranslation correction
+nofor correct "vadásszáz" "vadászszáz"	For example vadászszázad, vadászszázadban words backtranslation correction
+nofor correct "Vadásszáz" "Vadászszáz"	For example Vadászszázad word
+nofor correct "VADÁSSZÁZ" "VADÁSZSZÁZ"	For example VADÁSZSZÁZAD word
+nofor correct "vadásszem" "vadászszem"	For example vadászszemmel word backtranslation correction
+nofor correct "Vadásszem" "Vadászszem"	For example Vadászszemmel word backtranslation correction
+nofor correct "vadásszenv" "vadászszenv"	For example vadászszenvedéllyel vord
+nofor correct "Vadásszenv" "Vadászszenv"	For example vadászszenvedéllyel vord
+nofor correct "ervadásszer" *	For example the hervadásszerű word not need apply the general szsz backtranslation related correction, see the next line general exception
+nofor correct "vadásszer" "vadászszer"	For example vadászszerencse word
+nofor correct "Vadásszer" "Vadászszer"	For example Vadászszerelésben word
+nofor correct "vadásszez" "vadászszez"	For example vadászszezon word
+nofor correct "Vadásszez" "Vadászszez"	For example Vadászszezon word
 nofor correct "vadásszob" "vadászszob"	For example vadászszobában word backtranslation correction
-nofor correct "vésszavá" "vészszavá"	For example vészszavával word backtranslation correction
-nofor correct "vésszáz" "vészszáz"	For example vészszázad word backtranslation correction
+nofor correct "Vadásszob" "Vadászszob"	For example Vadászszobában word
+nofor correct "adásszövet" "adászszövet"	For example vadászszövetség, Vadászszövetség words
+nofor correct "ésszavá" "észszavá"	For example vészszavával word backtranslation correction
+nofor correct "ésszáz" "észszáz"	For example vészszázad word backtranslation correction
 nofor correct "vésszele" "vészszele"	For example vészszelep word backtranslation correction
+nofor correct "Vésszele" "Vészszele"
+nofor correct "iasszála" "iaszszála"	For example viaszszálakból word
+nofor correct "iasszárm" "iaszszárm"	For example viaszszármazékok word
+nofor correct "iasszár" "iaszszár"	For example viaszszárnyak word
 nofor correct "iassze" "iaszsze"	for example viasszegély word backtranslation correction
 nofor correct "iasszob" "iaszszob"	For example viaszszobor, viaszszobrával words backtranslation correction
 nofor correct "enésszak" "enészszak"	For example Zenészszakszervezetében word backtranslation correction
-
+nofor correct "enésszem" "enészszem"	For example zenészszemmel, zenészszemélyiség words
+nofor correct "rmésszi" *
+nofor correct "alansszab" "alanszszab"	For example balanszszabályzás, Balanszszabályzó, balanszszabályzót words
+nofor correct "álasszank" "álaszszank"	For example válaszszankció, válaszszankciókat word
+nofor correct "ásszerváj" "ászszerváj"	For example ászszerva, ászszerváikat, ászszervája words
+nofor correct "inocérossze" "inocéroszsze"	For example rinocéroszszerű word
+nofor correct "elmérésszer" *	For example felmérésszerű word
+nofor correct "aritásszer" "aritászszer"	For example karitászszervezet, karitászszervezethez words
+nofor correct "ísszáza" "íszszáza"	For example díszszázadának word
+nofor correct "gyásszám" "gyászszám"	For example gyászszámait word
+nofor correct "Gyásszám" "Gyászszám"
+nofor correct "tornásszám" "tornászszám"
+nofor correct "Tornásszám" "Tornászszám"
+nofor correct "ornássztá" "ornászsztá"	For example tornászsztár word
+nofor correct "leggyermetegebb" *
+nofor correct "elkésszent" "elkészszent"	For example lelkészszentelés word
+nofor correct "elkésszem" "elkészszem"	For example lelkészszemmel word
+nofor correct "elkésszolg" "elkészszolg"	For example lelkészszolgálat word
+nofor correct "lkusszövet" "lkuszszövet"	For example alkuszszövetség word
+nofor correct "empésszala" "empészszala"	For example csempészszalamandrák, csempészszalamandrákat words
+nofor correct "gásszöv" "gászszöv"	For example Horgászszövetség, horgászszövetségek, horgászszövetségnek, Jogászszövetségtől words
+nofor correct "umusször" "umuszször"	For example humuszszörny word
+nofor correct "umusszer" "umuszszer"	For example humuszszerű word
+nofor correct "amasszob" "amaszszob"	For example kamaszszoba, kamaszszobában words
+nofor correct "amasszer" "amaszszer"	For example kamaszszerelem word
+nofor correct "amasszok" "amaszszok"	For example kamaszszokás word
+nofor correct "amassztor" "amaszsztor"	For example kamaszsztori word
+nofor correct "rekesszám" "rekeszszám"	For example rekeszszám word
+nofor correct "Rekesszám" "Rekeszszám"	For example Rekeszszám word
+nofor correct "vadásszínp" "vadászszínp"	for example vadászszínpad word
+nofor correct "Vadásszínp" "Vadászszínp"	For example Vadászszínpad word
+nofor correct "egyésszakért" "egyészszakért"	For example vegyészszakértő word
+nofor correct "válaszszák" "válasszák"
+nofor correct "Válaszszák" "Válasszák"
+nofor correct "ónusszab" "ónuszszab"	For example bónuszszabályzat word
+nofor correct "ísszö" "íszszö"	For example díszszökőkút word
+nofor correct "ipsszilá" "ipszszilá"	For example gipszszilánk word
+nofor correct "ipsszerű" "ipszszerű"	For example gipszszerű word
+nofor correct "enisszokny" "eniszszokny"	For example teniszszoknya word
+nofor correct "avasszag" "avaszszag"	For example tavaszszag word
+nofor correct "úlélészszerű" "úlélésszerű"	For example túlélésszerű word not need apply the szsz backtranslation related correction
+nofor correct "vadásszoká" "vadászszoká"	For example vadászszokásainak word
+nofor correct "csörömpölészszerű" "csörömpölésszerű"	For example csörömpölésszerű word not need apply the szsz backtranslation correction
+nofor correct "ultusszer" "ultuszszer"	For example kultuszszerzőket word
+nofor correct "elvésszak" "elvészszak"	For example nyelvészszakértő word
+nofor correct "Résszámlá" "Részszámlá"	For example Részszámlához word
+nofor correct "ebésszakm" "ebészszakm"	For example sebészszakma, sebészszakmában words
+nofor correct "szülésszak" "szülészszak"	For example szülészszakmában word
+nofor correct "Szülésszak" "Szülészszak"
+nofor correct "asszopó" "aszszopó"
+nofor correct "asszopá" "aszszopá"	For example faszszopása word
+nofor correct "erasszez" "eraszszez"	For example teraszszezonra word
+nofor correct "enisszup" "eniszszup"	For example teniszszupersztár word
+nofor correct "enisszabá" "eniszszabá"	For example teniszszabályt word
+nofor correct "ókusszab" "ókuszszab"	For example fókuszszabályzást word
+nofor correct "enésszt" "enészszt"	For example zenészsztár, zenészsztori words
+nofor correct "Zenéssztá" "Zenészsztá"
+nofor correct "ússzám" "úszszám"	For example húszszámos word
+nofor correct "koksszen" "kokszszen"	For example kokszszene word
+nofor correct "Koksszen" "Kokszszen"
+nofor correct "kosszemcsé" "koszszemcsé"	For example koszszemcsék word
+nofor correct "Kosszemcsé" "Koszszemcsé"	For example Koszszemcsék word
+nofor correct "anasszav" "anaszszav"	For example panaszszava word
+nofor correct "anasszám" "anaszszám"	For example panaszszám, panaszszámmal word
+nofor correct "enyésszeml" "enyészszeml"	For example tenyészszemle word
+nofor correct "örténésszem" "örténészszem"	For example történészszemmel word
+nofor correct "alásszed" "alászszed"	For example kalászszedő, Kalászszedő words
+nofor correct "alásszí" "alászszí"	For example kalászszívvel word
+nofor correct "övésszövet" "övészszövet"	For example Lövészszövetség word
+nofor correct "iklusszámot" *	For example ciklusszámot word not need applying the next correction rule
+nofor correct "lusszámot" "luszszámot"	For example pluszszámot word
+nofor correct "gyésszoftver" "gyészszoftver"	For example ügyészszoftver word
+nofor correct "erpesszerű" "erpeszszerű"	For example herpeszszerű word
+nofor correct "késszurká" *
+nofor correct "Késszurká" *
+nofor correct "olbásszala" "olbászszala"	For example kolbászszalagot word
+nofor correct "olbássze" "olbászsze"	For example kolbászszeletkékkel word
+nofor correct "ölcsésszlen" "ölcsészszlen"	For example bölcsészszleng, bölcsészszlengben words
+nofor correct "irkusszakm" "irkuszszakm"	For example cirkuszszakmában word
+nofor correct "pítésszövets" "pítészszövets"	For example építészszövetség word
+nofor correct "pítésszoft" "pítészszoft"	For example építészszoftvert word
+nofor correct "gyásszoká" "gyászszoká"	For example gyászszokások word
+nofor correct "Gyásszoká" "Gyászszoká"
+nofor correct "ílusszem" *	For example stílusszemmel, stílusszemle words
+nofor correct "lusszem" "luszszem"	For example pluszszemélyzetet word
+nofor correct "ínéssztá" "ínészsztá"	For example színészsztár word
+nofor correct "vezeklészszer" "vezeklésszer"
+nofor correct "eresszegé" "ereszszegé"	For example ereszszegélyt word
+nofor correct "Eresszegé" "Ereszszegé"
+nofor correct "ányásszámí" "ányászszámí"	For example bányászszámítógépek word
+nofor correct "énisszobr" "éniszszobr"	For example péniszszobrot word
+nofor correct "oksszén" "okszszén"	For example kokszszénen word
+nofor correct "inoszaurusszer" "inoszauruszszer"	For example dinoszauruszszerű word
+nofor correct "inoszaurusszob" "inoszauruszszob"	For example dinoszauruszszobor word
+nofor correct "macesszabá" "maceszszabá"	For example maceszszabály word
+nofor correct "Macesszabá" "Maceszszabá"
+nofor correct "rkásszáz" "rkászszáz"	For example árkászszázad word
+nofor correct "rkásszak" "rkászszak"	For example árkászszakasz word
+nofor correct "eksszalá" "ekszszalá"	For example kekszszalámi word
+nofor correct "eksszele" "ekszszele"	For example kekszszelet word
+nofor correct "eksszend" "ekszszend"	For example kekszszendvics word
+nofor correct "ipsszele" "ipszszele"	For example csipszszelet word
+nofor correct "ersbussz" *	For example Versbusszá word
+nofor correct "ísszlovák" "íszszlovák"	For example díszszlovákot word backtranslation correction
+nofor correct "oposszerű" "oposzszerű"	For example toposzszerű word
+nofor correct "adásszéke" "adászszéke"	For example vadászszéke, vadászszékek words
+nofor correct "adásszékh" "adászszékh"	For example vadászszékhelye word
+nofor correct "ányásszer" "ányászszer"	For example bányászszerszámok word
+nofor correct "ónusszala" "ónuszszala"	For example bónuszszalagot word
+nofor correct "orásszakm" "orászszakm"	For example borászszakma word
+nofor correct "ombásszer" "ombászszer"	For example gombászszerenncse word
+nofor correct "kalásszám" "kalászszám"	For example kalászszám word
+nofor correct "Kalásszám" "Kalászszám"	For example Kalászszám word
+nofor correct "áosszer" "áoszszer"	For example káoszszerű word
+nofor correct "kusszel" "kuszszel"	For example kókuszszelet word
+nofor correct "tussznob" "tuszsznob"	For example kultuszsznobizmus word
+nofor correct "szerésszöv" "szerészszöv"	For example Látszerészszövetség word
+nofor correct "niszkusszak" "niszkuszszak"	For example meniszkuszszakadással word
+nofor correct "tosszál" "toszszál"	For example mítoszszál word
+nofor correct "ákásszak" "ákászszak"	For example rákászszakmáról word
+nofor correct "sósszer" "sószszer"	For example sószszerű word
+nofor correct "enisszer" "eniszszer"	For example teniszszerelésben, teniszszervezetek words
+nofor correct "enisszez" "eniszszez"	for example teniszszezon word
+nofor correct "ransszi" "ranszszi"	For example transzszibériai, Transzszibériai, transzszimfónia words
+nofor correct "ansszí" "anszszí"	For example transzszínész word
+nofor correct "adásszakm" "adászszakm"	For example vadászszakmai, Vadászszakmai words
+nofor correct "ohásszer" "ohászszer"	For example vaskohászszervezetének word
+nofor correct "iasszí" "iaszszí"	For example viaszszívű word
+nofor correct "szósszer" "szószszer"	For example szószszerű word
+nofor correct "ésszir" "észszir"	For example vészszirénázás word
+nofor correct "ésszárma" "észszárma"	For example mészszármazék word
+nofor correct "ölcséssza" "ölcsészsza"	For example bölcsészszagú,  bölcsészszak word
+nofor correct "ísszárny" "íszszárny"	For example díszszárnyas word
+nofor correct "alásszöv" "alászszöv"	For example Halászszövetség word
+nofor correct "ajesször" "ajeszször"	For example pajesszörnyeké word
+nofor correct "obrásszak" "obrászszak"	For example szobrászszakkörében word
+nofor correct "avasszent" "avaszszent"	For example Tavaszszentelő word
+nofor correct "ívverésszám" *
+nofor correct "ipesszer" "ipeszszer"	For example csipeszszerű word
+nofor correct "allosszimbólu" "alloszszimbólu"	For example falloszszimbólum word
+nofor correct "ússzobr" *	For example hússzobrokból word not need apply the szsz backtranslation rule
+nofor correct "ússzob" "úszszob"	For example húszszobás word
+nofor correct "amasszag" "amaszszag"	For example kamaszszaga word
+nofor correct "árcisszü" "árciszszü"	For example nárciszszüret word
+nofor correct "enisszak" "eniszszak"	For example teniszszakíró word
+nofor correct "enisszív" "eniszszív"	For example teniszszív word
+nofor correct "vésszellő" "vészszellő"	For example vészszellőző, vészszellőző-nyílással
+nofor correct "everéssze" *	For example heverésszen word
+nofor correct "átossze" "átoszsze"	For example pátoszszerű word
+nofor correct "ürkésszemm" "ürkészszemm"	For example fürkészszemmel word
+nofor correct "eksszere" "ekszszere"	For example kekszszerető word
+nofor correct "ógyásszak" "ógyászszak"	For example belgyógyászszakorvosa word
+nofor correct "résszin" "részszin"	For example részszintidőt word
+nofor correct "Résszin" "Részszin"	For example Részszintidőt word
+nofor correct "ífusszer" "ífuszszer"	For example tífuszszerű word
+nofor correct "éhésszer" "éhészszer"	For example méhészszervezet word
+nofor correct "éhésszöv" "éhészszöv"	For example méhészszövetség word
+nofor correct "Szesszigo" "Szeszszigo"	For example Szeszszigorításról word
+nofor correct "szesszigo" "szeszszigo"	For example szeszszigorításról
+nofor correct "ransszű" "ranszszű"	For example transzszűz word
+nofor correct "elérésszá" *	For example elérésszám word
+nofor correct "Elérésszá" *	For example Elérésszám word
+nofor correct "ónusszolg" "ónuszszolg"	For example bónuszszolgáltatás word
+nofor correct "iabétesszű" "iabéteszszű"	For example diabéteszszűrés, diabéteszszűrését words
+nofor correct "ekesszin" "ekeszszin"	For example rekeszszindróma, rekeszszintézis words
+nofor correct "enisszurk" "eniszszurk"	For example teniszszurkolók word
+nofor correct "résszüne" "részszüne"	For example részszünet word
+nofor correct "Résszüne" "Részszüne"	For example Részszünet word
+nofor correct "RÉSSZÜNE" "RÉSZSZÜNE"	For example RÉSZSZÜNET word
+nofor correct "ítosszal" *
+nofor correct "ítossza" "ítoszsza"	For example mítoszszag word
+nofor correct "ísszón" "íszszón"	For example díszszónokának word
+nofor correct "vadásszáll" "vadászszáll"	For example vadászszállás word
+nofor correct "Vadásszáll" "Vadászszáll"	For example Vadászszállás word
+nofor correct "VADÁSSZÁLL" "VADÁSZSZÁLL"	For example VADÁSSZÁLLÁS word
+nofor correct "tátusszt" "tátuszszt"	For example státuszsztori word
+nofor correct "busszám" "buszszám"	For example buszszámot word
+nofor correct "Busszám" "Buszszám"	For example Buszszám word
+nofor correct "BUSSZÁM" "BUSZSZÁM"	For example BUSZSZÁMOT word
+nofor correct "ókusszür" "ókuszszür"	For example kókuszszüretelő word
+nofor correct "észösszegé" *	For example részösszegé word
+nofor correct "ÉSZÖSSZEGÉ" *	For example RÉSZÖSSZEGÉ word
+nofor correct "szösszegé" "szöszszegé"	For example szöszszegénysége word
+nofor correct "Szösszegé" "Szöszszegé"	For example Szöszszegénysége word
+nofor correct "SZÖSSZEGÉ" "SZÖSZSZEGÉ"	For example SZÖSZSZEGÉNYSÉGE word
+nofor correct "űvésszt" "űvészszt"	For example művészsztár word
+nofor correct "enisszen" "eniszszen"	For example teniszszentély word
+nofor correct "vésszi" "vészszi"	for example vészszirénázás, vészszituáció word
+nofor correct "imnussze" "imnuszsze"	For example himnuszszerű word
+nofor correct "szösszent" "szöszszent"	For example szöszszentben word
+nofor correct "pítésszt" "pítészszt"	For example építészsztár, Építészsztár words
+nofor correct "ányásszáz" "ányászszáz"	For example bányászszázad word
+nofor correct "obrásszek" "obrászszek"	For example szobrászszekció word
+nofor correct "akmusszer" "akmuszszer"	For example lakmuszszerepéről word
+nofor correct "résszöv" "részszöv"	For example részszövetségben word
+nofor correct "neisszikl" "neiszszikl"	For example gneissziklákat word
+nofor correct "űrésszá" *	For example szűrészszám word
+nofor correct "rdésszak" "rdészszak"	For example erdészszakmai word
+nofor correct "örténésszöv" "örténészszöv"	For example történészszövetség word
+nofor correct "rverésszá" *
+nofor correct "ősszakál" "őszszakál"	For example őszszakállú word
+nofor correct "Ősszakál" "Őszszakál"	For example Őszszakállú word
+nofor correct "ŐSSZAKÁL" "ŐSZSZAKÁL"	For example ŐSZSZAKÁLLÚ word
+nofor correct "uhásszáll" "uhászszáll"	For example juhászszállások word
+nofor correct "éhésszak" "éhészszak"	For example méhészszakkör word
+nofor correct "enisszet" "eniszszet"	For example teniszszettje word
+nofor correct "ornásszez" "ornászszez"	For example tornászszezon word
+nofor correct "fasszar" "faszszar"	For example faszszar word
+nofor correct "Fasszar" "Faszszar"	For example Faszszar word
+nofor correct "FASSZAR" "FASZSZAR"	For example FASZSZAR word
+nofor correct "oksszen" "okszszen"	For example bokszszentháromság word
+nofor correct "boksszab" "bokszszab"	For example bokszszabály word
+nofor correct "Boksszab" "Bokszszab"	For example Bokszszabály word
+nofor correct "BOKSSZAB" "BOKSZSZAB"	For example BOKSZSZABÁLY word
+nofor correct "alásszem" "alászszem"	For example kalászszemle word
+nofor correct "lusszé" "luszszé"	For example pluszszéket word
+nofor correct "ihalásszer" *	For example kihalászszerű word
+nofor correct "Busszer" "Buszszer"	For example Buszszerű, Buszszerencsétlenség word
+nofor correct "BUSSZER" "BUSZSZER"	For example BUSZSZERŰ word
+nofor correct "génnyom" *	For example oxigénnyomás word
+nofor correct "imnusszöv" "imnuszszöv"	For example himnuszszöveg word
+nofor correct "iasszöv" "iaszszöv"	For example viaszszövedékből word
+nofor correct "hajtássz" *	For example hajtásszázalék word
+nofor correct "Hajtássz" *	For example Hajtásszázalék word
+nofor correct "HAJTÁSSZ" *	For example HAJTÁSSZÁZALÉK word
+nofor correct "horgásszab" "horgászszab"	For example horgászszabályok word
+nofor correct "Horgásszab" "Horgászszab"	For example Horgászszabályok word
+nofor correct "HORGÁSSZAB" "HORGÁSZSZAB"	For example HORGÁSZSZABÁLYOK word
+nofor correct "ókusszap" "ókuszszap"	For example kókuszszappan word
+nofor correct "lkusszoft" "lkuszszoft"	For example alkuszszoftverek word
+nofor correct "LKUSSZOFT" "LKUSZSZOFT"	For example ALKUSZSZOFTWEREK word
+nofor correct "ísszől" "íszszől"	For example díszszőlők word
+nofor correct "szülésszakér" *	For example szülésszakértő word
+nofor correct "Szülésszakér" *	For example Szülésszakértő word
+nofor correct "SZÜLÉSSZAKÉR" *	For example SZÜLÉSSZAKÉRTŐ word
+nofor correct "ússzintes" "úszszintes"	For example húszszintes word
 
 #zszs backtranslated word corrections
 nofor correct "törzszsel" "törzzsel"
+nofor correct "törzszsé" "törzzsé"
+nofor correct "Törzszsel" "Törzzsel"
+nofor correct "Törzszsé" "Törzzsé"
 nofor correct "rizzsá" "rizszsá"	For example rizszsák, rizszsákokban words backtranslation correction
 
 
@@ -87301,6 +87758,7 @@
 
 #non hungarian words corrections (for example town names)
 nofor correct "Budejovic" "Budějovic"	For example Budějovicében, Budějovicéből words
+nofor correct "Bölga-korpu" "Bëlga-korpu"
 nofor correct "Caön" "Caën" For example Caënban, Caënból town names
 nofor correct "Citroön" "Citroën"	For example Citroën, Citroënben words
 nofor correct "citroön" "citroën"
@@ -95378,6 +95836,7 @@
 numsign 3456  number sign, just a dots operand
 numericmodechars .,
 nocontractsign 56
+nonumsign 56
 numericnocontchars ابدِبهيجabcdefghij
 
 noback multind 56-6 letsign capsletter
@@ -99622,6 +100081,7 @@
 	de-g2-core.cti \
 	de-g2-core-patterns.dic \
 	de-g2.ctb \
+	de-g2-detailed.ctb \
 	devanagari.cti \
 	digits6DotsPlusDot6.uti \
 	digits6Dots.uti \
@@ -99825,7 +100285,6 @@
 	no-no-generic.ctb \
 	no-no-generic.dis \
 	no-no-latinLetterDef6Dots_diacritics.uti \
-	no.tbl \
 	np-in-g1.utb \
 	nso-za-g1.utb \
 	nso-za-g2.ctb \
@@ -99864,12 +100323,9 @@
 	si-in-g1.utb \
 	sin.cti \
 	sin.utb \
-	sk-chardefs.cti \
 	sk-g1.ctb \
 	sk-sk-g1.utb \
 	sk-sk.utb \
-	sk.tbl \
-	sk-translation.cti \
 	sl-si-comp8.ctb \
 	sl-si-g1.utb \
 	sl.tbl \
@@ -99984,12 +100440,18 @@
 
 include bengali.cti
 include en-in-g1.ctb
-# This table is meant to be used for forward translation only.
-# The generated Braille is primarily meant for embossing and is not
-# suitable for back-translation.
-# Please, use the detailed tables in contexts where
-# back-translation is an issue.
-# -----------
+# liblouis: German Grade 2 Braille
+#
+# This table is primarily meant to be used for forward translation.
+#
+# It can be used for back-translation but due to the lack of
+# consideration for back-translation of the German standard for
+# Kurzschrift (grade 2) it is nearly impossible to do accurate
+# back-translation of German grade 2 braille.
+
+# The detailed table helps somewhat in that provides more detailed
+# braille representation but the fundamental problems of the standard
+# remain.
 
 #-name: Deutsche Kurzschrift
 #-index-name: German, contracted
@@ -101716,6 +102178,7 @@
 numsign 3456  number sign, just a dots operand
 numericmodechars .,
 nocontractsign 56
+nonumsign 56
 numericnocontchars abcdefghij
 
 # Correct order of dot and numeric indicator
@@ -102895,7 +103358,8 @@
 include hu-backtranslate-word-corrections.cti
 include braille-patterns.cti
 #Braille indicators
-nocontractsign 6
+#nocontractsign 6
+#nonumsign 6
 numsign 3456
 capsletter 46a
 noback pass4 @46a ? # remove in a second pass because single capital letters are not indicated
@@ -102911,10 +103375,6 @@
 endemph underline 236
 begcomp 456-346
 endcomp 456-156
-always ' 6-3
-always : 25
-always ! 235
-always • 6-35
 # literary forms of the decimal digits
 include litdigits6Dots.uti
 include hu-hu-g1_braille_input.cti
@@ -102936,10 +103396,12 @@
 #wrong backtranslated output is „20-19”, „25-59”
 nofor pass2 $D[@36-36]%notaccentedletters @36-36-6
 nofor pass2 $D[@36]%notaccentedletters @36-6
-nofor correct $D1-30["--annyi"] "--ai"
-nofor correct $D1-30["--ennyi"] "--ei"
-nofor correct $D1-30["-annyi"] "-ai"
-nofor correct $D1-30["-ennyi"] "-ei"
+nofor correct "—'annyi" "--ai"
+nofor correct "—'ennyi" "--ei"
+nofor correct $D1-30["--'annyi"] "--ai"
+nofor correct $D1-30["--'ennyi"] "--ei"
+nofor correct $D1-30["-'annyi"] "-ai"
+nofor correct $D1-30["-'ennyi"] "-ei"
 #Special letter replacements
 always cs 146	General need replacing cs letters with a combined dot combination
 always ccs 146-146	General need replacing ccs letters with a combined dot combination
@@ -104262,9 +104724,17 @@
 # Include hyphenation file as the last thing
 include hyph_brl_da_dk.dic
 # This table contains braille codes and rules for Gurumukhi script.
-#
-# Copyright (C) 2014 National Institute for Visually Handicapped, 116, Rajpur Road, Dehradun, Uttrakhand, India
-#
+
+#-author-name: Dipendra Manocha
+#-author-email: dmanocha@daisy.org
+#-author-name: Jake Kyle
+#-maintainer-name: Dipendra Manocha
+#-maintainer-email: dmanocha@daisy.org
+#-updated: 2022-05-23
+
+# Copyright (C) 2014,2022 National Institute for Visually Handicapped, 116, Rajpur Road, Dehradun, Uttrakhand, India
+# Copyright (C) 2021-2022 by Compass Braille http://www.compassbraille.org
+
 # This file is part of liblouis.
 #
 # liblouis is free software: you can redistribute it and/or modify it
@@ -104282,195 +104752,372 @@
 # <http://www.gnu.org/licenses/>.
 
 # This table is built and maintained under an activity of Braille Council of India
-# Contributors: Dipendra Manocha, Sreeja, Dinesh Kaushal, Mesar Hameed
-# Last updated on May 5, 2014
-# To report any bugs or any suggestion, please write to d@saksham.org and sreeja.param@gmail.com
+# Contributors: Dipendra Manocha, Sreeja, Dinesh Kaushal, Mesar Hameed, Jake Kyle (Compass Braille, UK)
+# Updated on May 23, 2022
+# To report any bugs or any suggestion, please write to dmanocha@daisy.org and sreeja.param@gmail.com
 
 include braille-patterns.cti
 
-# generated by ttbtest
-letter      \x0A01		3	# GURUMUKHI SIGN ADAK B1NDI
-letter      \x0A02	56	# GURUMUKHI SIGN BINDI
-letter      \x0A03	6	# GURUMUKHI SIGN VISARGA
-letter      \x0A05	1	# GURUMUKHI LETTER A = aira
-letter      \x0A06	345	# GURUMUKHI LETTER AA
-letter      \x0A07	24	# GURUMUKHI LETTER I
-letter      \x0A08	35	# GURUMUKHI LETTER II
-letter      \x0A09	136	# GURUMUKHI LETTER U
-letter      \x0A0A	1256	# GURUMUKHI LETTER UU
-letter      \x0A0F	15	# GURUMUKHI LETTER EE
-letter      \x0A10	34	# GURUMUKHI LETTER Al
-letter      \x0A13	135	# GURUMUKHI LETTER OO
-letter      \x0A14	246	# GURUMUKHI LETTER AU
-
 replace \x200D
-# consonants 
- 
-letter      \x0A15	13	# GURUMUKHI LETTER KA
-letter      \x0A16	24	# GURUMUKHI LETTER KHA
-letter      \x0A17	1245	# GURUMUKHI LETTER GA
-letter      \x0A18	126	# GURUMUKHI LETTER GHA
-letter      \x0A19	346	# GURUMUKHI LETTER NGA
-letter      \x0A1A	14	# GURUMUKHI LETTER CA
-letter      \x0A1B	16	# GURUMUKHI LETTER CHA
-letter      \x0A1C	245	# GURUMUKHI LETTER JA
-letter      \x0A1D	356	# GURUMUKHI LETTER JHA
-letter      \x0A1E	25	# GURUMUKHI LETTER NYA
-letter      \x0A1F	23456	# GURUMUKHI LETTER TTA
-letter      \x0A20	2456	# GURUMUKHI LETTER TTHA
-letter      \x0A21	1246	# GURUMUKHI LETTER DDA
-letter      \x0A22	123456	   # GURUMUKHI LETTER DDHA
-letter      \x0A23	3456	# GURUMUKHI LETTER NNA
-letter      \x0A24	2345	# GURUMUKHI LETTER TA
-letter      \x0A25	1456	# GURUMUKHI LETTER THA
-letter      \x0A26	145	# GURUMUKHI LETTER DA
-letter      \x0A27	2346	# GURUMUKHI LETTER DHA
-letter      \x0A28	1345	# GURUMUKHI LETTER NA
-letter      \x0A2A	1234	# GURUMUKHI LETTER PA
-letter      \x0A2B	235	# GURUMUKHI LETTER PHA
-letter      \x0A2C	12	# GURUMUKHI LETTER BA
-letter      \x0A2D	45	# GURUMUKHI LETTER BHA
-letter      \x0A2E	134	# GURUMUKHI LETTER MA
-letter      \x0A2F	13456	# GURUMUKHI LETTER YA
-letter      \x0A30	1235	# GURUMUKHI LETTER RA
-letter      \x0A32	123	# GURUMUKHI LETTER LA
-letter      \x0A33	456	# GURUMUKHI LETTER LLA =  0A32 AND 0A3C
-letter      \x0A35	1236	# GURUMUKHI LETTER VA
-letter      \x0A36	146	# GURUMUKHI LETTER SHA - 0A38 AND 0A3C
-letter      \x0A38	234	# GURUMUKHI LETTER SA
-letter      \x0A39	125	# GURUMUKHI LETTER HA
 
-# vowels 
+# Punctuation
+noback punctuation \x0A64 256 RESERVED- 0964 #DEVANAGARI DANDA
+noback punctuation \x0A65 256-256 RESERVED- 0965 # DEVANAGARI DOUBLE DANDA
+punctuation \x0964 256 # DEVANAGARI DANDA
+noback punctuation \x0965 256-256 # DEVANAGARI DOUBLE DANDA
+postpunc \x0964 256
+postpunc \x0964\x0964 256-256
 
-letter      \x0A3E	345	# GURUMUKHI VOWEL SIGN AA= kanna
-letter      \x0A3F	24	# GURUMUKHI VOWEL SIGN I= sihari stands to the left of the consonant
-letter      \x0A40	35	# GURUMUKHI VOWEL SIGN II = BIHARI
-letter      \x0A41	136	# GURUMUKHI VOWEL SIGN U = AUNKAR
-letter      \x0A42	1256	# GURUMUKHI VOWEL SIGN UU = DULAINKAR
-letter      \x0A47	15	# GURUMUKHI VOWEL SIGN EE
-letter      \x0A48	34	# GURUMUKHI VOWEL SIGN AI = DULAINKAR
-letter      \x0A4B	135	# GURUMUKHI VOWEL SIGN OO =HORA
-letter      \x0A4C	246	# GURUMUKHI VOWEL SIGN AU = KANAURA
- 
-letter      \x0A59		46	# GURUMUKHI LETTER KHHA= 0A16 AND 0A3C
-letter      \x0A5B	245	# GURUMUKHI LETTER ZA = 0A1C AND 0A3C
-letter      \x0A5C	12456	# GURUMUKHI LETTER RRA
-letter      \x0A5E	235	# GURUMUKHI LETTER FA
-letter      \x0A64	256	RESERVED- 0964 #DEVANAGARI DANDA
-letter      \x0A65	256-256	RESERVED- 0965 # DEVANAGARI DOUBLE DANDA
+# Signs
+letter \x0A01 56 # GURUMUKHI SIGN ADAK BINDI
+letter \x0A02 3 # GURUMUKHI SIGN BINDI
+letter \x0A03 6 # GURUMUKHI SIGN VISARGA
+
+# Vowel letters
+letter \x0A05 1 # GURUMUKHI LETTER A = aira
+letter \x0A06 345 # GURUMUKHI LETTER AA
+letter \x0A07 24 # GURUMUKHI LETTER I
+letter \x0A08 35 # GURUMUKHI LETTER II
+letter \x0A09 136 # GURUMUKHI LETTER U
+letter \x0A0A 1256 # GURUMUKHI LETTER UU
+letter \x0A0F 15 # GURUMUKHI LETTER EE
+letter \x0A10 34 # GURUMUKHI LETTER Al
+letter \x0A13 135 # GURUMUKHI LETTER OO
+letter \x0A14 246 # GURUMUKHI LETTER AU
+
+# Consonants
+letter \x0A15 13 # GURUMUKHI LETTER KA
+letter \x0A16 46 # GURUMUKHI LETTER KHA
+letter \x0A17 1245 # GURUMUKHI LETTER GA
+letter \x0A18 126 # GURUMUKHI LETTER GHA
+letter \x0A19 346 # GURUMUKHI LETTER NGA
+letter \x0A1A 14 # GURUMUKHI LETTER CA
+letter \x0A1B 16 # GURUMUKHI LETTER CHA
+letter \x0A1C 245 # GURUMUKHI LETTER JA
+letter \x0A1D 356 # GURUMUKHI LETTER JHA
+letter \x0A1E 25 # GURUMUKHI LETTER NYA
+letter \x0A1F 23456 # GURUMUKHI LETTER TTA
+letter \x0A20 2456 # GURUMUKHI LETTER TTHA
+letter \x0A21 1246 # GURUMUKHI LETTER DDA
+letter \x0A22 123456 # GURUMUKHI LETTER DDHA
+letter \x0A23 3456 # GURUMUKHI LETTER NNA
+letter \x0A24 2345 # GURUMUKHI LETTER TA
+letter \x0A25 1456 # GURUMUKHI LETTER THA
+letter \x0A26 145 # GURUMUKHI LETTER DA
+letter \x0A27 2346 # GURUMUKHI LETTER DHA
+letter \x0A28 1345 # GURUMUKHI LETTER NA
+letter \x0A2A 1234 # GURUMUKHI LETTER PA
+letter \x0A2B 235 # GURUMUKHI LETTER PHA
+letter \x0A2C 12 # GURUMUKHI LETTER BA
+letter \x0A2D 45 # GURUMUKHI LETTER BHA
+letter \x0A2E 134 # GURUMUKHI LETTER MA
+letter \x0A2F 13456 # GURUMUKHI LETTER YA
+letter \x0A30 1235 # GURUMUKHI LETTER RA
+letter \x0A32 123 # GURUMUKHI LETTER LA
+letter \x0A33 456 # GURUMUKHI LETTER LLA = 0A32 AND 0A3C
+letter \x0A35 1236 # GURUMUKHI LETTER VA
+letter \x0A36 146 # GURUMUKHI LETTER SHA - 0A38 AND 0A3C
+letter \x0A38 234 # GURUMUKHI LETTER SA
+letter \x0A39 125 # GURUMUKHI LETTER HA
+
+attribute letter \x0A3C letter \x0A3C # GURMUKHI SIGN NUKTA: pairin bindi
+
+# Vowel Signs
+letter \x0A3E 345 # GURUMUKHI VOWEL SIGN AA= kanna
+letter \x0A3F 24 # GURUMUKHI VOWEL SIGN I= sihari stands to the left of the consonant
+letter \x0A40 35 # GURUMUKHI VOWEL SIGN II = BIHARI
+letter \x0A41 136 # GURUMUKHI VOWEL SIGN U = AUNKAR
+letter \x0A42 1256 # GURUMUKHI VOWEL SIGN UU = DULAINKAR
+letter \x0A47 15 # GURUMUKHI VOWEL SIGN EE
+letter \x0A48 34 # GURUMUKHI VOWEL SIGN AI = DULAINKAR
+letter \x0A4B 135 # GURUMUKHI VOWEL SIGN OO =HORA
+letter \x0A4C 246 # GURUMUKHI VOWEL SIGN AU = KANAURA
+
+letter \x0A4D 4 # GURMUKHI SIGN VIRAMA
+
+# Additional letters
+letter \x0A59 1346 # GURUMUKHI LETTER KHHA = 0A16 AND 0A3C
+letter \x0A5A 5-1245 # GURMUKHI LETTER GHHA
+letter \x0A5B 1356 # GURUMUKHI LETTER ZA = 0A1C AND 0A3C
+letter \x0A5C 12456 # GURUMUKHI LETTER RRA
+letter \x0A5E 124 # GURUMUKHI LETTER FA
 
 # Digits
+digit \x0A66 245 # GURUMUKHI DIGIT ZERO
+digit \x0A67 1 # GURUMUKHI DIGIT ONE
+digit \x0A68 12 # GURUMUKHI DIGIT TWO
+digit \x0A69 14 # GURUMUKHI DIGIT THREE
+digit \x0A6A 145 # GURUMUKHI DIGIT FOUR
+digit \x0A6B 15 # GURUMUKHI DIGIT FIVE
+digit \x0A6C 124 # GURUMUKHI DIGIT SIX
+digit \x0A6D 1245 # GURUMUKHI DIGIT SEVEN
+digit \x0A6E 125 # GURUMUKHI DIGIT EIGHT
+digit \x0A6F 24 # GURUMUKHI DIGIT NINE
 
-litdigit      \x0A66	245	# GURUMUKHI DIGIT ZERO
-litdigit      \x0A67	1	# GURUMUKHI DIGIT ONE
-litdigit      \x0A68	12	# GURUMUKHI DIGIT TWO
-litdigit      \x0A69	14	# GURUMUKHI DIGIT THREE
-litdigit      \x0A6A	145	# GURUMUKHI DIGIT FOUR
-litdigit      \x0A6B	15	# GURUMUKHI DIGIT FIVE
-litdigit      \x0A6C	124	# GURUMUKHI DIGIT SIX
-litdigit      \x0A6D	1245	# GURUMUKHI DIGIT SEVEN
-litdigit      \x0A6E	125	# GURUMUKHI DIGIT EIGHT
-litdigit      \x0A6F	24	# GURUMUKHI DIGIT NINE
+litdigit \x0A66 245 # GURUMUKHI DIGIT ZERO
+litdigit \x0A67 1 # GURUMUKHI DIGIT ONE
+litdigit \x0A68 12 # GURUMUKHI DIGIT TWO
+litdigit \x0A69 14 # GURUMUKHI DIGIT THREE
+litdigit \x0A6A 145 # GURUMUKHI DIGIT FOUR
+litdigit \x0A6B 15 # GURUMUKHI DIGIT FIVE
+litdigit \x0A6C 124 # GURUMUKHI DIGIT SIX
+litdigit \x0A6D 1245 # GURUMUKHI DIGIT SEVEN
+litdigit \x0A6E 125 # GURUMUKHI DIGIT EIGHT
+litdigit \x0A6F 24 # GURUMUKHI DIGIT NINE
 
-letter      \x0A70	56	# GURUMUKHI TIPPI • nasalization
-letter      \x0A71	4	# GURUMUKHI ADDAK • doubles following consonant
-letter      \x0A74	3456-1-136	# GURUMUKHI EK ONKAR • God is One
+# Additional characters
+letter \x0A70 56 # GURUMUKHI TIPPI - nasalization
+letter \x0A71 4 # GURUMUKHI ADDAK - doubles following consonant
+letter \x0A74 3456-1-136 # GURUMUKHI EK ONKAR - God is One
+
+always \x0A70 56 # TIPPI - to override ADAK BINDI in backtranslation
+always \x0A71 4 # ADDAK - to override virama in backtranslation
+
+# Compound letters
+noback always \x0A16\x0A3C 1346 # KHA + NUKTA = KHHA (same as \x0A59)
+noback always \x0A17\x0A3C 5-1245 # GA + NUKTA = GHHA
+noback always \x0A1C\x0A3C 1356 # JA + NUKTA = ZA (same as \x0A5B)
+always \x0A24\x0A3C 5-2345 # TA + NUKTA
+noback always \x0A2B\x0A3C 124 # PHA + NUKTA = FA (same as \x0A5E)
+noback always \x0A2B\x0A3F\x0A3C 124-24 # PHA + SIGN I + NUKTA
+noback always \x0A32\x0A3C 456 # LA + NUKTA = LLA (same as \x0A33)
+noback always \x0A38\x0A3C 146 # SA + NUKTA = SHA (same as \x0A36)
+
+# Numbers in backtranslation
+midendword \x0A23 3456 # GURUMUKHI LETTER NNA
+
+# Vowels in backtranslation
+# generally vowels use letters if on their own or at start of word
+sufword \x0A05 1 # GURUMUKHI LETTER A = aira
+sufword \x0A06 345 # GURUMUKHI LETTER AA
+sufword \x0A07 24 # GURUMUKHI LETTER I
+sufword \x0A08 35 # GURUMUKHI LETTER II
+sufword \x0A09 136 # GURUMUKHI LETTER U
+sufword \x0A0A 1256 # GURUMUKHI LETTER UU
+sufword \x0A0F 15 # GURUMUKHI LETTER EE
+sufword \x0A10 34 # GURUMUKHI LETTER Al
+sufword \x0A13 135 # GURUMUKHI LETTER OO
+sufword \x0A14 246 # GURUMUKHI LETTER AU
+
+# generally vowels use signs in middle of word
+always \x0A3E 345 # GURUMUKHI VOWEL SIGN AA= kanna
+always \x0A3F 24 # GURUMUKHI VOWEL SIGN I= sihari stands to the left of the consonant
+always \x0A40 35 # GURUMUKHI VOWEL SIGN II = BIHARI
+always \x0A41 136 # GURUMUKHI VOWEL SIGN U = AUNKAR
+always \x0A42 1256 # GURUMUKHI VOWEL SIGN UU = DULAINKAR
+always \x0A47 15 # GURUMUKHI VOWEL SIGN EE
+always \x0A48 34 # GURUMUKHI VOWEL SIGN AI = DULAINKAR
+always \x0A4B 135 # GURUMUKHI VOWEL SIGN OO =HORA
+always \x0A4C 246 # GURUMUKHI VOWEL SIGN AU = KANAURA
+
+# exceptions to general rules above when sequences of vowels together
+sufword \x0A06\x0A08 345-35 # LETTER AA + LETTER II
+sufword \x0A06\x0A07\x0A06 345-24-345 # LETTER AA + LETTER I + LETTER AA
+sufword \x0A06\x0A08\x0A06 345-35-345 # LETTER AA + LETTER II + LETTER AA
+sufword \x0A06\x0A0F 345-15 # LETTER AA + LETTER EE
+sufword \x0A06\x0A13 345-135 # LETTER AA + LETTER OO
+sufword \x0A06\x0A09 345-136 # LETTER AA + LETTER U
+sufword \x0A07\x0A09 24-136 # LETTER I + LETTER U
+sufword \x0A10\x0A09 34-136 # LETTER AI +LETTER U
+midendword \x0A3E\x0A02\x0A09 345-3-136 # SIGN AA + BINDI + LETTER U
+midendword \x0A3E\x0A0F 345-15 # SIGN AA + LETTER EE
+midendword \x0A3E\x0A07 345-24 # SIGN AA + LETTER I
+midendword \x0A3E\x0A08 345-35 # SIGN AA + LETTER II
+midendword \x0A3E\x0A07\x0A06 345-24-345 # SIGN AA + LETTER I + LETTER AA
+midendword \x0A3E\x0A08\x0A06 345-35-345 # SIGN AA + LETTER II + LETTER AA
+midendword \x0A3E\x0A08\x0A0F 345-35-15 # SIGN AA + LETTER II + LETTER EE
+midendword \x0A3E\x0A09 345-136 # SIGN AA + LETTER U
+midendword \x0A3E\x0A0A 345-1256 # SIGN AA + LETTER UU
+midendword \x0A3E\x0A13 345-135 # SIGN AA + LETTER OO
+midendword \x0A3F\x0A06 24-345 # SIGN I + LETTER AA
+midendword \x0A3F\x0A06\x0A07\x0A06 24-345-24-345 # SIGN I + LETTER AA + LETTER I + LETTER AA
+midendword \x0A3F\x0A06\x0A08 24-345-35 # SIGN I + LETTER AA + LETTER II
+midendword \x0A3F\x0A06\x0A09 24-345-136 # SIGN I + LETTER AA + LETTER U
+midendword \x0A3F\x0A06\x0A0F 24-345-15 # SIGN I + LETTER AA + LETTER EE
+midendword \x0A3F\x0A06\x0A13 24-345-135 # SIGN I + LETTER AA + LETTER OO
+midendword \x0A3F\x0A13 24-135 # SIGN I + LETTER OO
+midendword \x0A3F\x0A09 24-136 # SIGN I + LETTER U
+midendword \x0A40\x0A06 35-345 # SIGN II + LETTER AA
+midendword \x0A40\x0A09 35-136 # SIGN II + LETTER U
+midendword \x0A40\x0A0F 35-15 # SIGN II + LETTER EE
+midendword \x0A40\x0A13 35-135 # SIGN II + LETTER OO
+midendword \x0A41\x0A06 136-345 # SIGN U + LETTER AA
+midendword \x0A41\x0A06\x0A0F 136-345-15 # SIGN U + LETTER AA + LETTER EE
+midendword \x0A41\x0A06\x0A08\x0A0F 136-345-35-15 # SIGN U + LETTER AA + LETTER II + LETTER EE
+midendword \x0A41\x0A06\x0A09 136-345-136 # SIGN U + LETTER AA + LETTER U
+midendword \x0A42\x0A06 1256-345 # SIGN UU + LETTER AA
+midendword \x0A42\x0A08 1256-35 # SIGN UU + LETTER II
+midendword \x0A42\x0A0F 1256-15 # SIGN UU + LETTER EE
+midendword \x0A47\x0A06 15-345 # SIGN EE + LETTER AA
+midendword \x0A47\x0A08 15-35 # SIGN EE + LETTER II
+midendword \x0A47\x0A08\x0A0F 15-35-15 # SIGN EE + LETTER II + LETTER EE
+midendword \x0A47\x0A0A 15-1256 # SIGN EE + LETTER UU
+midendword \x0A47\x0A13 15-135 # SIGN EE + LETTER OO
+midendword \x0A48\x0A06 34-345 # SIGN AI + LETTER AA
+midendword \x0A4B\x0A0F 135-15 # SIGN OO + LETTER EE
+midendword \x0A4B\x0A07\x0A06 135-24-345 # SIGN OO + LETTER I + LETTER AA
+midendword \x0A4B\x0A08 135-35 # SIGN OO + LETTER II
+midendword \x0A4B\x0A08\x0A06 135-35-345 # SIGN OO + LETTER II + LETTER AA
+midendword \x0A4B\x0A0A 135-1256 # SIGN OO + LETTER UU
+midendword \x0A4C\x0A09 246-136 # SIGN AU + LETTER U
+
+# when vowel letters used after consonants we need dot 1 between
+always \x0A15\x0A08 13-1-35 # LETTER KA + LETTER II
+always \x0A15\x0A08\x0A06 13-1-35-345 # LETTER KA + LETTER II + LETTER AA
+always \x0A17\x0A08 1245-1-35 # LETTER GA + LETTER II
+always \x0A17\x0A08\x0A06 1245-1-35-345 # LETTER GA + LETTER II + LETTER AA
+sufword \x0A17\x0A0F 1245-1-15 # LETTER GA + LETTER EE
+always \x0A2A\x0A08 1234-1-35 # LETTER PA + LETTER II
+always \x0A2A\x0A09 1234-1-136 # LETTER PA + LETTER U
+always \x0A2A\x0A0F 1234-1-15 # LETTER PA + LETTER EE
+always \x0A2A\x0A13 1234-1-135 # LETTER PA + LETTER OO
+always \x0A2D\x0A08 45-1-35 # LETTER BHA + LETTER II
+always \x0A2E\x0A08 134-1-35 # LETTER MA + LETTER II
+always \x0A2E\x0A0A 134-1-1256 # LETTER MA + LETTER UU
+always \x0A30\x0A08 1235-1-35 # LETTER RA + LETTER II
+always \x0A32\x0A08\x0A06 123-1-35-345 # LETTER LA + LETTER II + LETTER AA
+always \x0A32\x0A13 123-1-135 # LETTER LA + LETTER OO
+word \x0A32\x0A08 123-1-35 # LETTER LA + LETTER II
+
+# for dot 3 backtranslation to bindi
+always \x0A02 3 # GURUMUKHI SIGN BINDI
+
+# when dot 4 is \x0A71 'Addak' in backtranslation
+midendword \x0A71\x0A15 4-13 # ADDAK + KA
+midendword \x0A71\x0A17 4-1245 # ADDAK + GA
+midendword \x0A71\x0A1A 4-14 # ADDAK + CA
+always \x0A71\x0A1B 4-16 # ADDAK + CHA
+always \x0A71\x0A1C 4-245 # ADDAK + JA
+always \x0A71\x0A1D 4-356 # ADDAK + JHA
+always \x0A71\x0A1F 4-23456 # ADDAK + TTA
+always \x0A71\x0A20 4-2456 # ADDAK + TTHA
+always \x0A71\x0A21 4-1246 # ADDAK + DDA
+always \x0A71\x0A22 4-123456 # ADDAK + DDHA
+always \x0A71\x0A24 4-2345 # ADDAK + TA
+always \x0A71\x0A24\x0A4D 4-4-2345 # ADDAK + TA + VIRAMA
+midendword \x0A71\x0A25 4-1456 # ADDAK + THA
+midendword \x0A71\x0A26 4-145 # ADDAK + DA
+always \x0A71\x0A28 4-1345 # ADDAK + NA
+always \x0A71\x0A26\x0A3E 4-145-345 # ADDAK + DA + AA
+always \x0A71\x0A27 4-2346 # ADDAK + DHA
+midendword \x0A71\x0A2A 4-1234 # ADDAK + PA
+always \x0A71\x0A2B 4-235 # ADDAK + PHA
+always \x0A71\x0A2C 4-12 # ADDAK + BA
+midendword \x0A71\x0A2D 4-45 # ADDAK + BHA
+endword \x0A71\x0A30 4-1235 # ADDAK + RA
+always \x0A71\x0A30\x0A41 4-1235-136 # ADDAK + RA + U
+always \x0A71\x0A32 4-123 # ADDAK + LA
+always \x0A71\x0A32\x0A4D 4-4-123 # ADDAK + LA + VIRAMA
+always \x0A71\x0A38 4-234 # ADDAK + SA
+always \x0A71\x0A5C 4-12456 # ADDAK + RRA
+always \x0A5C\x0A4D\x0A39 4-12456-125 # RRA + VIRAMA + HA
+always \x0A71\x0A5C\x0A4D\x0A39 4-4-12456-125 # ADDAK + RRA + VIRAMA + HA
+always \x0A71\x0A16 4-46 # ADDAK + KHA
+always \x0A71\x0A26\x0A08 4-145-1-35 # ADDAK + DA + LETTER II
+
 
 # half characters
-always      \x0A15\x0A71	4-13	# GURUMUKHI LETTER KA
-always      \x0A16\x0A71	4-24	# GURUMUKHI LETTER KHA
-always      \x0A17\x0A71	4-1245	# GURUMUKHI LETTER GA
-always      \x0A18\x0A71	4-126	# GURUMUKHI LETTER GHA
-always      \x0A19\x0A71	4-346	# GURUMUKHI LETTER NGA
+always \x0A15\x0A4D 4-13 # GURUMUKHI LETTER KA
+always \x0A16\x0A4D 4-24 # GURUMUKHI LETTER KHA
+always \x0A17\x0A4D 4-1245 # GURUMUKHI LETTER GA
+always \x0A18\x0A4D 4-126 # GURUMUKHI LETTER GHA
+always \x0A19\x0A4D 4-346 # GURUMUKHI LETTER NGA
 
-always      \x0A1A\x0A71	4-14	# GURUMUKHI LETTER CA
-always      \x0A1B\x0A71	4-16	# GURUMUKHI LETTER CHA
-always      \x0A1C\x0A71	4-245	# GURUMUKHI LETTER JA
-always      \x0A1D\x0A71	4-356	# GURUMUKHI LETTER JHA
-always      \x0A1E\x0A71	4-25	# GURUMUKHI LETTER NYA
+always \x0A1A\x0A4D 4-14 # GURUMUKHI LETTER CA
+always \x0A1B\x0A4D 4-16 # GURUMUKHI LETTER CHA
+always \x0A1C\x0A4D 4-245 # GURUMUKHI LETTER JA
+always \x0A1D\x0A4D 4-356 # GURUMUKHI LETTER JHA
+always \x0A1E\x0A4D 4-25 # GURUMUKHI LETTER NYA
 
-always      \x0A1F\x0A71	4-23456	# GURUMUKHI LETTER TTA
-always      \x0A20\x0A71	4-2456	# GURUMUKHI LETTER TTHA
-always      \x0A21\x0A71	4-1246	# GURUMUKHI LETTER DDA
-always      \x0A22\x0A71	4-123456	   # GURUMUKHI LETTER DDHA
-always      \x0A23\x0A71	4-3456	# GURUMUKHI LETTER NNA
+always \x0A1F\x0A4D 4-23456 # GURUMUKHI LETTER TTA
+always \x0A20\x0A4D 4-2456 # GURUMUKHI LETTER TTHA
+always \x0A21\x0A4D 4-1246 # GURUMUKHI LETTER DDA
+always \x0A22\x0A4D 4-123456 # GURUMUKHI LETTER DDHA
+always \x0A23\x0A4D 4-3456 # GURUMUKHI LETTER NNA
 
-always      \x0A24\x0A71	4-2345	# GURUMUKHI LETTER TA
-always      \x0A25\x0A71	4-1456	# GURUMUKHI LETTER THA
-always      \x0A26\x0A71	4-145	# GURUMUKHI LETTER DA
-always      \x0A27\x0A71	4-2346	# GURUMUKHI LETTER DHA
-always      \x0A28\x0A71	4-1345	# GURUMUKHI LETTER NA
+always \x0A24\x0A4D 4-2345 # GURUMUKHI LETTER TA
+always \x0A25\x0A4D 4-1456 # GURUMUKHI LETTER THA
+always \x0A26\x0A4D 4-145 # GURUMUKHI LETTER DA
+always \x0A27\x0A4D 4-2346 # GURUMUKHI LETTER DHA
+always \x0A28\x0A4D 4-1345 # GURUMUKHI LETTER NA
 
-always      \x0A2A\x0A71	4-1234	# GURUMUKHI LETTER PA
-always      \x0A2B\x0A71	4-235	# GURUMUKHI LETTER PHA
-always      \x0A2C\x0A71	4-12	# GURUMUKHI LETTER BA
-always      \x0A2D\x0A71	4-45	# GURUMUKHI LETTER BHA
-always      \x0A2E\x0A71	4-134	# GURUMUKHI LETTER MA
+always \x0A2A\x0A4D 4-1234 # GURUMUKHI LETTER PA
+always \x0A2B\x0A4D 4-235 # GURUMUKHI LETTER PHA
+always \x0A2C\x0A4D 4-12 # GURUMUKHI LETTER BA
+always \x0A2D\x0A4D 4-45 # GURUMUKHI LETTER BHA
+always \x0A2E\x0A4D 4-134 # GURUMUKHI LETTER MA
 
-always      \x0A2F\x0A71	4-13456	# GURUMUKHI LETTER YA
-always      \x0A30\x0A71	4-1235	# GURUMUKHI LETTER RA
-always      \x0A32\x0A71	4-123	# GURUMUKHI LETTER LA
-always      \x0A33\x0A71	4-456	# GURUMUKHI LETTER LLA =  0A32 AND 0A3C
-always      \x0A35\x0A71	4-1236	# GURUMUKHI LETTER VA
-always      \x0A36\x0A71	4-146	# GURUMUKHI LETTER SHA - 0A38 AND 0A3C
-always      \x0A38\x0A71	4-234	# GURUMUKHI LETTER SA
-always      \x0A39\x0A71	4-125	# GURUMUKHI LETTER HA
+always \x0A2F\x0A4D 4-13456 # GURUMUKHI LETTER YA
+always \x0A30\x0A4D 4-1235 # GURUMUKHI LETTER RA
+always \x0A32\x0A4D 4-123 # GURUMUKHI LETTER LA
+always \x0A33\x0A4D 4-456 # GURUMUKHI LETTER LLA = 0A32 AND 0A3C
+always \x0A35\x0A4D 4-1236 # GURUMUKHI LETTER VA
+always \x0A36\x0A4D 4-146 # GURUMUKHI LETTER SHA - 0A38 AND 0A3C
+always \x0A38\x0A4D 4-234 # GURUMUKHI LETTER SA
+always \x0A39\x0A4D 4-125 # GURUMUKHI LETTER HA
 
-always      \x0A59\x0A71		4-46	# GURUMUKHI LETTER KHHA= 0A16 AND 0A3C
-always      \x0A5B\x0A71	4-245	# GURUMUKHI LETTER ZA = 0A1C AND 0A3C
-always      \x0A5C\x0A71	4-12456	# GURUMUKHI LETTER RRA
-always      \x0A5E\x0A71	4-235	# GURUMUKHI LETTER FA
-always      \x0A64\x0A71	4-256	RESERVED- 0964 #DEVANAGARI DANDA
-always        \x0A65\x0A71	4-256-256	RESERVED- 0965 # DEVANAGARI DOUBLE DANDA
+always \x0A59\x0A4D 4-46 # GURUMUKHI LETTER KHHA = 0A16 AND 0A3C
+always \x0A5B\x0A4D 4-245 # GURUMUKHI LETTER ZA = 0A1C AND 0A3C
+always \x0A5C\x0A4D 4-12456 # GURUMUKHI LETTER RRA
+always \x0A5E\x0A4D 4-235 # GURUMUKHI LETTER FA
+always \x0A64\x0A4D 4-256 RESERVED- 0964 #DEVANAGARI DANDA
+always \x0A65\x0A4D 4-256-256 RESERVED- 0965 # DEVANAGARI DOUBLE DANDA
 
-# consonent followed by vowel but not matra
+# To prevent ADDAK in backtranslation
+always \x0A26\x0A4D\x0A30 4-145-1235 # DA + VIRAMA + RA
+always \x0A28\x0A4D\x0A39 4-1345-125 # NA + VIRAMA + HA
+always \x0A32\x0A4D\x0A39 4-123-125 # LA + VIRAMA + HA
+always \x0A38\x0A24\x0A4D\x0A30 234-4-2345-1235 # SA + TA + VIRAMA + RA
+
+
+# consonent followed by vowel letter not matra (vowel sign)
 
 attribute GurumukiVowel \x0A07\x0A08\x0A09\x0A0A\x0A0F\x0A10\x0A13\x0A14
-attribute Halant \x0A71
+attribute Halant \x0A4D
 
-before GurumukiVowel always      \x0A15 	  13-1	# GURUMUKHI LETTER KA
-before GurumukiVowel always      \x0A16 	  24-1	# GURUMUKHI LETTER KHA
-before GurumukiVowel always      \x0A17 	  1245-1	# GURUMUKHI LETTER GA
-before GurumukiVowel always      \x0A18 	  126-1	# GURUMUKHI LETTER GHA
+before GurumukiVowel always \x0A15 13-1 # GURUMUKHI LETTER KA
+before GurumukiVowel always \x0A16 24-1 # GURUMUKHI LETTER KHA
+before GurumukiVowel always \x0A17 1245-1 # GURUMUKHI LETTER GA
+before GurumukiVowel always \x0A18 126-1 # GURUMUKHI LETTER GHA
 
-before GurumukiVowel always      \x0A1A 	  14-1	# GURUMUKHI LETTER CA
-before GurumukiVowel always      \x0A1B 	  16-1	# GURUMUKHI LETTER CHA
-before GurumukiVowel always      \x0A1C 	  245-1	# GURUMUKHI LETTER JA
-before GurumukiVowel always      \x0A1D 	  356-1	# GURUMUKHI LETTER JHA
+before GurumukiVowel always \x0A1A 14-1 # GURUMUKHI LETTER CA
+before GurumukiVowel always \x0A1B 16-1 # GURUMUKHI LETTER CHA
+before GurumukiVowel always \x0A1C 245-1 # GURUMUKHI LETTER JA
+before GurumukiVowel always \x0A1D 356-1 # GURUMUKHI LETTER JHA
 
-before GurumukiVowel always      \x0A1F 	  23456-1 	# GURUMUKHI LETTER TTA
-before GurumukiVowel always      \x0A20 	  2456-1	# GURUMUKHI LETTER TTHA
-before GurumukiVowel always      \x0A21 	  1246-1	# GURUMUKHI LETTER DDA
-before GurumukiVowel always      \x0A22 	  123456-1	   # GURUMUKHI LETTER DDHA
-before GurumukiVowel always      \x0A23 	  3456-1	# GURUMUKHI LETTER NNA
+before GurumukiVowel always \x0A1F 23456-1 # GURUMUKHI LETTER TTA
+before GurumukiVowel always \x0A20 2456-1 # GURUMUKHI LETTER TTHA
+before GurumukiVowel always \x0A21 1246-1 # GURUMUKHI LETTER DDA
+before GurumukiVowel always \x0A22 123456-1 # GURUMUKHI LETTER DDHA
+before GurumukiVowel always \x0A23 3456-1 # GURUMUKHI LETTER NNA
 
-before GurumukiVowel always      \x0A24 	  2345-1	# GURUMUKHI LETTER TA
-before GurumukiVowel always      \x0A25 	  1456-1	# GURUMUKHI LETTER THA
-before GurumukiVowel always      \x0A26 	  145-1	            # GURUMUKHI LETTER DA
-before GurumukiVowel always      \x0A27 	  2346-1	# GURUMUKHI LETTER DHA
-before GurumukiVowel always      \x0A28 	  1345-1	# GURUMUKHI LETTER NA
+before GurumukiVowel always \x0A24 2345-1 # GURUMUKHI LETTER TA
+before GurumukiVowel always \x0A25 1456-1 # GURUMUKHI LETTER THA
+before GurumukiVowel always \x0A26 145-1 # GURUMUKHI LETTER DA
+before GurumukiVowel always \x0A27 2346-1 # GURUMUKHI LETTER DHA
+before GurumukiVowel always \x0A28 1345-1 # GURUMUKHI LETTER NA
 
-before GurumukiVowel always      \x0A2A 	  1234-1	# GURUMUKHI LETTER PA
-before GurumukiVowel always      \x0A2B 	  235-1   	# GURUMUKHI LETTER PHA
-before GurumukiVowel always      \x0A2C 	  12-1	# GURUMUKHI LETTER BA
-before GurumukiVowel always      \x0A2D 	  45-1	# GURUMUKHI LETTER BHA
-before GurumukiVowel always      \x0A2E 	  134-1	# GURUMUKHI LETTER MA
+before GurumukiVowel always \x0A2A 1234-1 # GURUMUKHI LETTER PA
+before GurumukiVowel always \x0A2B 235-1 # GURUMUKHI LETTER PHA
+before GurumukiVowel always \x0A2C 12-1 # GURUMUKHI LETTER BA
+before GurumukiVowel always \x0A2D 45-1 # GURUMUKHI LETTER BHA
+before GurumukiVowel always \x0A2E 134-1 # GURUMUKHI LETTER MA
 
-before GurumukiVowel always      \x0A2F 	  13456-1	# GURUMUKHI LETTER YA
-before GurumukiVowel always      \x0A30 	  1235-1	# GURUMUKHI LETTER RA
-before GurumukiVowel always      \x0A32 	  123-1	# GURUMUKHI LETTER LA
-before GurumukiVowel always      \x0A33 	  456-1	# GURUMUKHI LETTER LLA =  0A32 AND 0A3C
-before GurumukiVowel always      \x0A35 	  1236-1	# GURUMUKHI LETTER VA
-before GurumukiVowel always      \x0A36 	  146-1	# GURUMUKHI LETTER SHA - 0A38 AND 0A3C
-before GurumukiVowel always      \x0A38 	  234-1	# GURUMUKHI LETTER SA
-before GurumukiVowel always      \x0A39 	  125-1	# GURUMUKHI LETTER HA
+before GurumukiVowel always \x0A2F 13456-1 # GURUMUKHI LETTER YA
+before GurumukiVowel always \x0A30 1235-1 # GURUMUKHI LETTER RA
+before GurumukiVowel always \x0A32 123-1 # GURUMUKHI LETTER LA
+before GurumukiVowel always \x0A33 456-1 # GURUMUKHI LETTER LLA = 0A32 AND 0A3C
+before GurumukiVowel always \x0A35 1236-1 # GURUMUKHI LETTER VA
+before GurumukiVowel always \x0A36 146-1 # GURUMUKHI LETTER SHA - 0A38 AND 0A3C
+before GurumukiVowel always \x0A38 234-1 # GURUMUKHI LETTER SA
+before GurumukiVowel always \x0A39 125-1 # GURUMUKHI LETTER HA
 
-before GurumukiVowel always      \x0A59 		  46-1	# GURUMUKHI LETTER KHHA= 0A16 AND 0A3C
-before GurumukiVowel always      \x0A5B 	  245-1	# GURUMUKHI LETTER ZA = 0A1C AND 0A3C
-before GurumukiVowel always      \x0A5C 	  12456-1	# GURUMUKHI LETTER RRA
-before GurumukiVowel always      \x0A5E 	  235-1	# GURUMUKHI LETTER FA
-before GurumukiVowel always      \x0A64 	  256-1	RESERVED- 0964 #DEVANAGARI DANDA
-before GurumukiVowel always      \x0A65 	  256-256-1	RESERVED- 0965 # DEVANAGARI DOUBLE DANDA
+before GurumukiVowel always \x0A59 46-1 # GURUMUKHI LETTER KHHA= 0A16 AND 0A3C
+before GurumukiVowel always \x0A5B 245-1 # GURUMUKHI LETTER ZA = 0A1C AND 0A3C
+before GurumukiVowel always \x0A5C 12456-1 # GURUMUKHI LETTER RRA
+before GurumukiVowel always \x0A5E 235-1 # GURUMUKHI LETTER FA
+before GurumukiVowel always \x0A64 256-1 RESERVED- 0964 #DEVANAGARI DANDA
+before GurumukiVowel always \x0A65 256-256-1 RESERVED- 0965 # DEVANAGARI DOUBLE DANDA
 # This table contains braille codes and rules for Telugu  script.
 #
 # Copyright (C) 2014 National Institute for Visually Handicapped, 116, Rajpur Road, Dehradun, Uttrakhand, India
@@ -105497,8 +106144,8 @@
 #uppercase Æ 4-1 LATIN CAPITAL LETTER AE LATIN CAPITAL LETTER A E 
 sign € 56-15
 letter ff 0
-letter fi 0
-letter fl 0
+noback lowercase fi 124-24
+noback lowercase fl 124-123
 letter ffi 0
 letter ffl 0
 letter ţ 2345
@@ -105514,7 +106161,8 @@
 letter ğ 1245
 letter ń 1345
 letter ň 1345
-letter š 234
+lowercase š 234
+#letter Š 234
 letter ž 126
 
 noback always \x0075\x030b 23456
@@ -105534,6 +106182,7 @@
 base uppercase Ö ö
 base uppercase Õ õ
 base uppercase Û û
+base uppercase Š š
 # liblouis: Spanish, Grade 2 table
 #
 # -----------
@@ -128605,6 +129254,10 @@
 partword karcsoroz 13-1-1235-14-234-135-1235-135-126	For example rézkarcsorozat, rézkarcsorozatokat words related need this general exception
 partword ujjperc =	For example ujjperccsont, ujjperccsontok words related need this general exception
 begword daróc =	For example daróccsuháját, daróccsuha words related need this general exception
+partword kapoccsal 13-1-1234-135-146-146-1-123
+word kapoccsal 13-1-1234-135-146-146-1-123
+partword kapoccsá 13-1-1234-135-146-146-4
+word kapoccsá 13-1-1234-135-146-146-4
 partword kapocs 13-1-1234-135-146	For example kapocszár word
 partword ifjonc =	For example ifjonccsapat word
 partword párduc =	For example párduccsík word
@@ -128622,6 +129275,7 @@
 partword csúcszen 146-346-146-126-15-1345	For example csúcszenekarának word related need this exception
 partword narancs 1345-1-1235-1-1345-146	For example narancszombi word related need this general exception
 partword platán =	For example platáncsoport, platáncsere word  need this general exception
+always csokilencs 146-135-13-24-123-15-1345-146	For example csokilencse, csokilencsét words
 
 #gy, ggy related exceptions
 #This exception section containing word parts and full words with need marking ggy letter pairs with single g and gy braille dot combination
@@ -128803,6 +129457,16 @@
 partword ostobaság =	For example ostobasággyűjtemény word
 partword többség =	For example többséggyűlölet, többséggyűlölő words related need this general exception
 partword zöldséggy 126-12345-123-145-234-16-1245-1456	For example zöldséggyümölcs word
+always valósággy 1236-1-123-246-234-4-1245-1456	For example valósággyártás word
+always sötétséggy 234-12345-2345-16-2345-234-16-1245-1456	For example sötétséggyümölccsel word
+always világgy 1236-24-123-4-1245-1456	For example túlvilággyümölcsöt word
+always egységgy 15-1456-234-16-1245-1456	For example egységgyertya word
+always szükséggy 156-12356-13-234-16-1245-1456	For example szükséggyertya word
+always jéggyer 245-16-1245-1456-15-1235	For example jéggyertya word
+always herceggy 125-15-1235-14-15-1245-1456	For example herceggyermeket word
+always köteggy 13-12345-2345-15-1245-1456	For example köteggyár word
+always közeggy 13-12345-126-15-1245-1456	For example közeggyár word
+always zsineggy 345-24-1345-15-1245-1456	For example zsineggyártó word
 
 #ny, nny related exceptions
 #Following exception parts need marking nny letter pairs with single n and nny braille dot combinations
@@ -128959,7 +129623,14 @@
 partword ugasszer 136-1245-1-234-156-15-1235	For example lugasszerű word
 partword sszekrény 234-156-15-13-1235-16-1246	For example kisszekrény word
 begword pilis =	For example Pilisszántó, Pilisszentkereszt town names
+always hússzab 125-346-234-156-1-12	For example Hússzabóság word
 always hússzag 125-346-234-156-1-1245	For example hússzag, hússzagú, emberhússzagot words
+always hússzár 125-346-234-156-4-1235	For example hússzárítás word
+always hússzed 125-346-234-156-15-145	For example hússzedő word
+always hússzé 125-346-234-156-16	For example hússzégyenítés, hússzél words
+always hússzobr 125-346-234-156-135-12-1235	For example hússzobrokból word
+always hússzó 125-346-234-156-246	For example hússzósz word
+always hússzez 125-346-234-156-15-126	For example hússzezon, vadhússzezon words
 partword szacsk 234-126-1-146-13	For example chipseszacskó, liszteszacskó, vizeleteszacskó words
 partword keresés =	For example keresésszolgáltató word
 partword szorongássz 156-135-1235-135-1345-1245-4-234-156	For example szorongásszerű, szorongásszint word
@@ -129116,7 +129787,6 @@
 partword nenssze 1345-15-1345-234-156-15
 partword törlessz 2345-12345-1235-123-15-156-156	For example törlesszem word
 partword lesszem 123-15-234-156-15-134	For example élesszemű word
-word élesszem 16-123-15-156-156-15-134
 partword fölélessze 124-12345-123-16-123-15-156-156-15	For example fölélesszem word
 partword dássze 145-4-234-156-15	For example adásszerű word
 partword dásszám 145-4-234-156-4-134	For example előadásszám word
@@ -129238,6 +129908,7 @@
 partword hússzekr 125-346-234-156-15-13-1235	For example hússzekrény word
 partword jelenéssze 245-15-123-15-1345-16-234-156-15	For example jelenésszerű wordd
 partword jelzésszer 245-15-123-126-16-234-156-15-1235	For example jelzésszerű, hangjelzésszerű word
+partword jelzésszin 245-15-123-126-16-234-156-24-1345	For example jelzésszintű word
 partword kasszé 13-1-234-156-16	For example kasszék word part containing wordsneed this exception
 partword katalógussz 13-1-2345-1-123-246-1245-136-234-156	For example katalógusszám word
 partword kavarásszer 13-1-1236-1-1235-4-234-156-15-1235	For example kavarásszerű word
@@ -129249,7 +129920,7 @@
 always könyvesszé 13-12345-1246-1236-15-156-156-16	For example könyvesszé, könyvesszéje word
 partword másszer 134-4-234-156-15-1235	For example látomásszerű, nyomásszerű, képmásszerű words
 partword frisszöld 124-1235-24-234-234-126-12345-123-145
-partword ésszög 2345-16-234-156-12345-1245	For example lejtésszöge word
+partword tésszög 2345-16-234-156-12345-1245	For example lejtésszöge word
 partword lengéssz 123-15-1345-1245-16-234-156	For example lengésszabály word
 partword lépés 123-16-1234-16-234	For example lépésszám, lépészaj words
 partword lökéssz 123-12345-13-16-234-156	For example lökésszám, lökésszerű words
@@ -129312,6 +129983,7 @@
 partword őrsszerű 12456-1235-234-156-15-1235-23456	For example előőrsszerű
 nofor begword ésszer 16-156-156-15-1235	For example old grammar check rule ésszerű word
 nofor pass2 @1235-16-156-156-15-1235-1236 *	For example résszervekkel is an another exception
+#nofor pass2 @1235-16-156-156-15-1235-126 *
 nofor pass2 @16-156-156-15-1235 @16-156-5-156-15-1235
 begword észszer 16-156-5-156-15-1235	The newest grammar check rule form, similar the old ésszerű word
 always fásszín 124-4-234-156-34-1345
@@ -129567,6 +130239,7 @@
 partword állomás =	For example állomásszám word
 partword analízissz 1-1345-1-123-34-126-24-234-156	For example Analízisszintű word
 partword áramlás =	For example áramlásszabályozás word
+partword veréssza 1236-15-1235-16-234-156-1	For example árverésszagú word
 partword árverés =	For example árverésszerű word
 partword asztalossz 1-156-2345-1-123-135-234-156	For example asztalosszakmát word
 partword áttörés =	For example áttörésszerepe word
@@ -129813,6 +130486,127 @@
 partword kukássz 13-136-13-4-234-156	For example kukássztrájk, kukásszakszervezet words
 partword hasszorít 125-1-234-156-135-1235-34-2345	For example hasszorító, hasszorítóval words
 begword oltássz 135-123-2345-4-234-156	For example oltásszakértő, oltásszkeptikus words
+begword kosarassz 13-135-234-1-1235-1-234-156	For example kosarassztár, kosarassztárok words
+nofor begword résszerző 1235-16-156-156-15-1235-126-12456	For example résszerződés word
+begword felméréssz 124-15-123-134-16-1235-16-234-156	For example felmérésszerű word
+begword megelőzésszer 134-15-1245-15-123-12456-126-16-234-156-15-1235	For example megelőzésszerű word
+always költésszi 13-12345-123-2345-16-234-156-24	For example költésszint word
+always repedés =	For example repedésszámlálás, Repedésszigetelés word
+always résszig 1235-16-234-156-24-1245	For example résszigetelés word
+always rezgéssz 1235-15-126-1245-16-234-156	For example rezgésszint, rezgésszintje, rezgésszintet words
+always telepítéssz 2345-15-123-15-1234-34-2345-16-234-156	For example telepítésszimuláción word
+partword kelésszer 13-15-123-16-234-156-15-1235	For example felkelésszerű word
+partword osásszer 135-234-4-234-156-15-1235	For example agymosásszerűen word
+begword aktusszer 1-13-2345-136-234-156-15-1235	For example aktusszerűségtől word
+always csörömpölés 146-12345-1235-12345-134-1234-12345-123-16-234	For example csörömpölésszerűen word
+always apásszer 1-1234-4-234-156-15-1235	For example arccsapásszerűen word
+always zásszer 126-4-234-156-15-1235	For example csiklandozásszerűen word
+always indulássz 24-1345-145-136-123-4-234-156	For example földindulásszerűen word
+always sorolássz 234-135-1235-135-123-4-234-156	For example fölsorolásszerűen word
+always lásszer 123-4-234-156-15-1235	For example fuldoklásszerű word
+always gügyögéssz 1245-12356-1456-12345-1245-16-234-156	For example gügyögésszerű word
+always képzelődéssz 13-16-1234-126-15-123-12456-145-16-234-156	For example képzelődésszerű word
+always keresztvasszer 13-15-1235-15-156-2345-1236-1-234-156-15-1235	For example keresztvasszerű word
+always költésszi 13-12345-123-2345-16-234-156-24	For example költésszint word
+always messiássz 134-15-234-234-24-4-234-156	For example messiásszerep, messiásszindróma words
+always násszer 1345-4-234-156-15-1235	For example pattanásszerű, pukkanásszerű word
+always sorsszám 234-135-1235-234-156-4-134	For example sorsszám word
+always szülésszak 156-12356-123-16-156-156-1-13	For example szülésszakorvos word
+always túléléssz 2345-346-123-16-123-16-234-156	For example túlélésszerű word
+always valósszer 1236-1-123-246-234-156-15-1235	For example valósszerű word
+always vezekléssz 1236-15-126-15-13-123-16-234-156	For example vezeklésszerű word
+always függésszerű 124-12356-1245-1245-16-234-156-15-1235-23456	For example függésszerű word
+always verésszám 1236-15-1235-16-234-156-4-134	For example szívverésszámot word
+always köpéssz 13-12345-1234-16-234-156	For example köpésszerű word
+always rövidítéssz 1235-12345-1236-24-145-34-2345-16-234-156	For example rövidítésszótárnak word
+always résszív 1235-16-234-156-34-1236	For example résszívó word
+always részszí 1235-16-156-5-156-34	For example részszívót word
+always passzolg 1234-1-156-156-135-123-1245	For example passzolgat word
+always sszolg 234-156-135-123-1245	For example archiválásszolgáltató word
+always szekussz 156-15-13-136-234-156	For example szekusszázados word
+always beruházássz 12-15-1235-136-125-4-126-4-234-156	For example beruházásszervező word
+always bevándorlássz 12-15-1236-4-1345-145-135-1235-123-4-234-156	For example bevándorlásszámláló word
+always előzéssz 15-123-12456-126-16-234-156	For example előzésszám word
+always elsőssz 15-123-234-12456-234-156	For example elsősszámú word
+always emlőssz 15-134-123-12456-234-156	For example emlősszámú word
+always halálozássz 125-1-123-4-123-135-126-4-234-156	For example halálozásszám word
+always helyezéssz 125-15-456-15-126-16-234-156	For example helyezésszámaival word
+always igényléssz 24-1245-16-1246-123-16-234-156	For example igénylésszám word
+always illeszkedéssz 24-123-123-15-156-13-15-145-16-234-156	For example illeszkedésszám word
+always ismerőssz 24-234-134-15-1235-12456-234-156	For example ismerősszámmal word
+always károgássz 13-4-1235-135-1245-4-234-156	For example károgásszámláló word
+always kezeléssz 13-15-126-15-123-16-234-156	For example kezelésszáma, kezelésszámmal word
+always körözéssz 13-12345-1235-12345-126-16-234-156	For example körözésszámmal word
+always látogatássz 123-4-2345-135-1245-1-2345-4-234-156	For example látogatásszámmal word
+always betegedéssz 12-15-2345-15-1245-15-145-16-234-156	For example megbetegedésszám word
+always jelenéssz 245-15-123-15-1345-16-234-156	For example megjelenésszám word
+always pislogássz 1234-24-234-123-135-1245-4-234-156	For example pislogásszám word
+always rétessz 1235-16-2345-15-234-156	For example rétesszakértőt, rétesszámlálót word
+always székeléssz 156-16-13-15-123-16-234-156	For example székelésszámuk word
+always üresedéssz 12356-1235-15-234-15-145-16-234-156	For example üresedésszám ord
+always üressz 12356-1235-15-234-156	For example üresszám word
+always villantássz 1236-24-123-123-1-1345-2345-4-234-156	For example villantásszámot word
+always virtussz 1236-24-1235-2345-136-234-156	For example virtusszámot word
+always bugyborékolássz 12-136-1456-12-135-1235-16-13-135-123-4-234-156	For example bugyborékolásszerű word
+always féréssz 124-16-1235-16-234-156	For example hozzáférésszerzés word
+always fogyasztássz 124-135-1456-1-156-2345-4-234-156	For example fogyasztásszemlélet word
+always integritássz 24-1345-2345-15-1245-1235-24-2345-4-234-156	For example integritásszemlélet
+always érésszab 16-1235-16-234-156-1-12	For example érésszabályozó word
+always késszeg 13-16-234-156-15-1245	For example késszegmens word
+always késszak 13-16-234-156-1-13	For example késszaküzlet word
+always eléréssz 15-123-16-1235-16-234-156	For example elérésszámmal word
+begword égéssz 16-1245-16-234-156	For example égésszerű word
+always evésszü 15-1236-16-234-156-12356	For example evésszünet word
+always döntéssz 145-12345-1345-2345-16-234-156	For example döntésszabadság, döntésszituáció word
+always könyveléssz 13-12345-1246-1236-15-123-16-234-156	For example könyvelésszervezési word
+always becsüssz 12-15-146-12356-234-156	For example becsüsszakma word
+always befektetéssz 12-15-124-15-13-2345-15-2345-16-234-156	For example befektetésszakmai word
+always dartsszak 145-1-1235-2345-234-156-1-13	For example dartsszakértő word
+always építésszak 16-1234-34-2345-16-234-156-1-13	For example építésszakmát word
+always fazekassz 124-1-126-15-13-1-234-156	For example fazekasszakkör word
+always filmesszak 124-24-123-134-15-234-156-1-13	For example filmesszakma word
+always grafikussz 1245-1235-1-124-24-13-136-234-156	For example grafikusszakkör, grafikusszakma word
+always hímzéssz 125-34-134-126-16-234-156	For example hímzésszakkör word
+always hipnózissz 125-24-1234-1345-246-126-24-234-156	For example hipnózisszakértő word
+always hússzakért 125-346-234-156-1-13-16-1235-2345	For example hússzakértő word
+always informatikussz 24-1345-124-135-1235-134-1-2345-24-13-136-234-156	For example informatikusszakértő word
+always iparossz 24-1234-1-1235-135-234-156	For example iparosszakma word
+always rablássz 1235-1-12-123-4-234-156	For example rablásszakértő word
+always kozmetikussz 13-135-126-134-15-2345-24-13-136-234-156	For example kozmetikusszakmát word
+always űtősszak 23456-2345-12456-234-156-1-13	For example műtősszakasszisztensi word
+always paragrafussz 1234-1-1235-1-1245-1235-1-124-136-234-156	For example paragrafusszám, paragrafusszakértő words
+always retusszak 1235-15-2345-136-234-156-1-13	For example retusszaktudással word
+always rontássz 1235-135-1345-2345-4-234-156	For example rontásszakértő word
+always ervezésszak 15-1235-1236-15-126-16-234-156-1-13	For example szervezésszakmai word
+always táncossz 2345-4-1345-14-135-234-156	For example táncosszakmai word
+always változássz 1236-4-123-2345-135-126-4-234-156	For example változásszakértő word
+always vegetáriánussz 1236-15-1245-15-2345-4-1235-24-4-1345-136-234-156	For example vegetáriánusszakértő word
+always fonásszak 124-135-1345-4-234-156-1-13	For example fonásszakkör word
+always vezetéssz 1236-15-126-15-2345-16-234-156	For example vezetésszakértő word
+always kiégéssz 13-24-16-1245-16-234-156	For example kiégésszerű word
+begword vokssz 1236-135-13-234-156	For example voksszabályok, voksszerzésre, voksszerű word
+always fújássz 124-346-245-4-234-156	For example fújásszerűséget word
+always fűzéssz 124-23456-126-16-234-156	For example fűzésszezon, fűzésszolgáltató words
+always énekléssz 16-1345-15-13-123-16-234-156	For example éneklésszerű word
+word élesszem 16-123-15-234-156-15-134	For example élesszem single word
+begword újraélessze 346-245-1235-1-16-123-15-156-156-15	For example újraélessze word a different exception, with marking different the previous word part exception
+partword baszássza 12-1-156-4-234-156-1	For example átbaszásszagot word
+partword lódássza 123-246-145-4-234-156-1	For example csalódásszagú word
+partword múlássza 134-346-123-4-234-156-1	For example elmúlásszagú word
+always emésztéssz 15-134-16-156-2345-16-234-156	For example emésztésszabályozó, emésztésszaga, emésztésszagú, emésztésszimuláció words
+always osássza 135-234-4-234-156-1	For example mosásszag word
+always ányolássza 4-1246-135-123-4-234-156-1	For example gányolásszagú word
+always análissza 1-1345-4-123-24-234-156-1	For example kanálisszag word
+always oppintássza 135-1234-1234-24-1345-2345-4-234-156-1	For example koppintásszagú word
+always járatássza 2345-4-1235-1-2345-4-234-156-1	For example lejáratásszagú word
+always ézissza 16-126-24-234-156-1	For example tézisszagú word
+always esztéssza 15-156-2345-16-234-156-1	For example vesztésszagot word
+always büdösszé 12-12356-145-12345-234-156-16	E.g. in hungary the name of the Büdösszék Lake
+always dünnyögéssz 145-12345-1246-1246-12345-1245-16-234-156	For example dünnyögésszerű word
+always égésszer 16-1245-16-234-156-15-1235	For example napégésszerű word
+always szülésszaké 156-12356-123-16-234-156-1-13-16	For example szülésszakértő word
+always hússzint 125-346-156-156-24-1345-2345	For example hússzintes word
+word kaktusszá 13-1-13-2345-136-156-156-4
 
 #ty, lly, tty related exceptions
 #This exception part containing english words with need presenting original english braille rules
@@ -129856,6 +130650,8 @@
 begword lacsny 123-1-146-1345-13456	Same exception for Lacsny Miklós name related if the name containing suffixes. Partword or always opcode too risk this exception
 word pesty 1234-15-234-2345-13456	For example Pesty László name related need this general exception
 begword pesty 1234-15-234-2345-13456	Same  exception with Pesty László name related if the name containing suffixes. Partword or always opcode too risk this exception
+endnum city =
+endnum -city =
 
 #zs, zzs related exceptions
 partword ínházsi 34-1345-125-4-345-24
@@ -129966,6 +130762,27 @@
 partword rizszab 1235-24-345-126-1-12	For example rizszabáló word
 partword masszázszuh 134-1-156-156-4-345-126-136-125	For example masszázszuhany, masszázszuhanyokkal words
 
+#Special letters, with need marking different after numbers
+endnum a 6-1
+endnum b 6-12
+endnum c 6-14
+endnum d 6-145
+endnum e 6-15
+endnum f 6-124
+endnum g 6-1245
+endnum h 6-125
+endnum i 6-24
+endnum j 6-245
+endnum -a 36-1
+endnum -b 36-12
+endnum -c 36-14
+endnum -d 36-145
+endnum -e 36-15
+endnum -f 36-124
+endnum -g 36-1245
+endnum -h 36-125
+endnum -i 36-24
+endnum -j 36-245
 
 UTF-8
 .а8
@@ -170313,70 +171130,6 @@
 
 include zh-tw.ctb
 include braille-patterns.cti
-#
-#  Copyright (C) 2014 by Branislav Mamojka <mamojka@unss.sk>
-#  Copyright (C) 2016 by Peter Vagner <peter.v@datagate.sk>
-#
-#  This file is part of liblouis.
-#
-#  liblouis is free software: you can redistribute it and/or modify it
-#  under the terms of the GNU Lesser General Public License as
-#  published by the Free Software Foundation, either version 2.1 of the
-#  License, or (at your option) any later version.
-#
-#  liblouis is distributed in the hope that it will be useful, but
-#  WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#  Lesser General Public License for more details.
-#
-#  You should have received a copy of the GNU Lesser General Public
-#  License along with liblouis. If not, see
-#  <http://www.gnu.org/licenses/>.
-#
-#--------------------------------------------------------------------------------
-#
-#  Slovak Braille
-#
-#     Created and maintained by Branislav Mamojka <mamojka@unss.sk>
-#                               Peter Vagner <peter.v@datagate.sk>
-#
-#     Based on the official Slovak Braille Standard
-#     Derived from the Czech braille table by Bert Frees and Jan Halousek
-# ----------------------------------------------------------------------------------------------
-
-# ----------------------------------------------------------------------------------------------
-# Braille indicator opcodes
-# ----------------------------------------------------------------------------------------------
-
-numsign  3456
-capsletter  6
-begcapsword  6-6
-endcapsword  56
-
-# ----------------------------------------------------------------------------------------------
-# Literary digits
-# ----------------------------------------------------------------------------------------------
-
-include litdigits6Dots.uti
-
-# ----------------------------------------------------------------------------------------------
-# Decimal points, hyphens
-# ----------------------------------------------------------------------------------------------
-
-decpoint  \x002C  2
-decpoint  \x002E  3
-hyphen    \x002D  36
-
-# ----------------------------------------------------------------------------------------------
-# Letter prefix in numbers
-# ----------------------------------------------------------------------------------------------
-
-attribute    digitletter                 abcdefghij
-noback context  $d[]%digitletter            @56
-noback context  $d"."[]%digitletter         @56
-noback context  $d","[]%digitletter         @56
-
-# ----------------------------------------------------------------------------------------------
 #-index-name: Estonian, computer
 #-display-name: Estonian computer braille
 
@@ -216850,6 +217603,19 @@
 å2t2v
 # liblouis: Norwegian literary braille, 6-dot, contracted level 3
 #
+#-name: Norsk litterær punktskrift, 6-punkt, kortskrift nivå 3
+#-index-name: Norwegian, contracted, grade 3
+#-display-name: Norwegian grade 3 contracted braille
+#
+#+language:no
+#+language:nb
+#+language:nn
+#+type:literary
+#+direction:both
+#+dots:6
+#+grade:3
+#+contraction:full
+#
 #  Copyright (C) 2005 ViewPlus Technologies, Inc. www.viewplus.com
 #  Copyright (C) 2005-2012 Lars Bjørndal <lars@lamasti.net>
 #
@@ -216876,6 +217642,7 @@
 # This specific table is based on the document "Kortskrift i norsk punktskrift - KS04".
 #
 # Input is expected to be normalized to NFC (fully composed)
+#-unicode-form:nfc
 #
 #  This file is part of liblouis.
 #
@@ -217481,6 +218248,7 @@
 #
 #  Copyright (C) 2018 SBS Schweizerische Bibliothek für Blinde, Seh- und Lesebehinderte
 #  Copyright (C) 2020 Bue Vester-Andersen
+#  Copyright (C) 2022 Bert Frees
 #
 #  This file is part of liblouis.
 #
@@ -217502,14 +218270,36 @@
 
 include de-g1-core-patterns.dic
 
-noback nocross always   au         16                           # 1 #
-noback nocross always   eu         126                          # 2 #
-noback nocross always   ei         146                          # 3 #
-noback nocross always   ch         1456                         # 4 #
-noback nocross always   sch        156                          # 5 #
-noback nocross always   st         23456                        # ] #
-noback nocross always   äu         34                           # \ #
-noback nocross always   ie         346                          # 0 #
+# Attach virtual dot "a" and letter attribute to contraction
+# patterns. Private use area U+F800 - U+F83F is used to represent
+# these.
+
+letter  \xF821  16a            # 1 #
+letter  \xF823  126a           # 2 #
+letter  \xF829  146a           # 3 #
+letter  \xF839  1456a          # 4 #
+letter  \xF831  156a           # 5 #
+letter  \xF83E  23456a         # ] #
+letter  \xF80C  34a            # \ #
+letter  \xF82C  346a           # 0 #
+
+noback pass2 @16a @16
+noback pass2 @126a @126
+noback pass2 @146a @146
+noback pass2 @1456a @1456
+noback pass2 @156a @156
+noback pass2 @23456a @23456
+noback pass2 @34a @34
+noback pass2 @346a @346
+
+noback nocross always   au         16a                           # 1 #
+noback nocross always   eu         126a                          # 2 #
+noback nocross always   ei         146a                          # 3 #
+noback nocross always   ch         1456a                         # 4 #
+noback nocross always   sch        156a                          # 5 #
+noback nocross always   st         23456a                        # ] #
+noback nocross always   äu         34a                           # \ #
+noback nocross always   ie         346a                          # 0 #
 
 nofor partword    au         16                           # 1 #
 nofor partword    eu         126                          # 2 #
@@ -218737,7 +219527,7 @@
 
 # Other alphabets
 
-attribute foreign ñ # defined in nl-chardefs.uti
+attribute foreign Ññ # ñ defined in nl-chardefs.uti
 
 lowercase ł 126
 base uppercase Ł ł
@@ -224920,17 +225710,25 @@
 postpunc ! 235
 punctuation ! 235
 endnum % 3456-245-356
+endnum -% 36-3456-245-356
 endnum / 5-2
 prepunc / 5-2
 postpunc / 5-2
+always ' 6-3
+midnum : 3
+endnum : 6-25
+always : 25
+always ! 235
+always • 6-35
 
 #numbers related braille input rules, trying temporary fixing with numericnocontchars abcdefghij related issue (the #615 issue), link is following:
 #https://github.com/liblouis/liblouis/issues/615
 #cs related number exceptions
 endnum cs 146
 endnum -cs 36-146
-prepunc cs 146
+#prepunc cs 146
 postpunc cs 146
+always cs 146
 noback pass2 $D@6%notaccentedletters[@14-234] @146
 noback pass2 $D@6%notaccentedletters%accentedletters.[@14-234] @146
 noback pass2 $D@46%notaccentedletters[@14-234] @146
@@ -224949,7 +225747,7 @@
 noback pass3 $D[@36-146-6]%notaccentedletters. @36-146
 nofor pass3 $D[@36-146]%notaccentedletters. @36-146
 
-endnum ccsz 14-14-156
+endnum ccsz 6-14-14-156
 endnum -ccsz 36-14-14-156
 prepunc ccsz 14-14-156
 postpunc ccsz 14-14-156
@@ -224965,7 +225763,7 @@
 noback pass2 $D@6%notaccentedletters[@14-14-234] @146-146
 noback pass2 $D%accentedletters1-30[@14-14-234] @146-146
 
-endnum csz 14-156
+endnum csz 6-14-156
 endnum -csz 36-14-156
 prepunc csz 14-156
 postpunc csz 14-156
@@ -225166,7 +225964,16 @@
 postpunc ä 5-1
 noback context $l$p["–"] @36-36
 noback context $l["–"] @36-36
+nofor correct $D1-30["--annyi"] "--ai"
+nofor correct $D1-30["--ennyi"] "--ei"
+#nofor correct $D1-30["-'annyi"] "-ai"
+nofor correct $D1-30["-ennyi"] "-ei"
 
+nofor correct $D["'"]$l ?
+nofor correct $D["'"]$U ?
+nofor correct ["—'"]$l "--"
+nofor correct ["--'"]$l "-"
+nofor correct ["-'"]$l "-"
 # -----------
 #-index-name: Portuguese, computer
 #-display-name: Portuguese computer braille
@@ -226612,6 +227419,7 @@
 begcapsword 456
 endcapsword 6
 nocontractsign 6
+nonumsign 6
 numericnocontchars abcdefghij
 numericmodechars .,:
 
@@ -227818,7 +228626,7 @@
 
 # This table contains braille codes and rules for Punjabi Grade 1 and includes English grade 1
 #
-# Copyright (C) 2014 National Institute for Visually Handicapped, 116, Rajpur Road, Dehradun, Uttrakhand, India
+# Copyright (C) 2014,2022 National Institute for Visually Handicapped, 116, Rajpur Road, Dehradun, Uttrakhand, India
 #
 # This file is part of liblouis.
 #
@@ -227837,11 +228645,26 @@
 # <http://www.gnu.org/licenses/>.
 
 # This table is built and maintained under an activity of Braille Council of India
-# Contributors: Dipendra Manocha, Sreeja, Dinesh Kaushal, Mesar Hameed
-# Last updated on May 5, 2014
-# To report any bugs or any suggestion, please write to d@saksham.org and sreeja.param@gmail.com
+# Contributors: Dipendra Manocha, Sreeja, Dinesh Kaushal, Mesar Hameed, Jake Kyle (Compass Braille, UK)
+# Last updated on May 23, 2022
+# To report any bugs or any suggestion, please write to dmanocha@daisy.org and sreeja.param@gmail.com
 
 include gurumuki.cti
+
+# Following needed to override rules in en-in-g1.ctb table
+
+word “ 236
+word ? 236
+always , 2
+
+always \x0A2D\x0A30  45-1235  # BHA + RA - needed to override ® in en-in-g1.ctb table
+always \x0A16    46    # KHA - needed to override . (period) in en-in-g1.ctb table
+always \x0A1D    356    # JHA - needed to override " (close quotes) in en-in-g1.ctb table
+always \x0A16\x002C   46-2  # needed to override > (less-than sign) in en-in-g1.ctb table
+always \x0A16\x0A2E   46-134  # needed to override µ (micro sign) in en-in-g1.ctb table
+always \x0A2B 235 # needed to override ! in en-in-g1.ctb table
+
+
 include en-in-g1.ctb
 # liblouis: Hungarian Grade 2 table
 #
@@ -227983,6 +228806,10 @@
 word után 136
 word úgy 346
 word van 1236
+partword kapoccsal 13-1-1234-135-146-1236
+word kapoccsal 13-1-1234-135-146-1236
+partword kapoccsá 13-1-1234-135-146-146-4
+word kapoccsá 13-1-1234-135-146-146-4
 
 #abbreviations, with possible using word parts too
 always csakis 146-24
@@ -228053,6 +228880,8 @@
 always ennyi 15-24
 always gond 1245-145
 always függ 1245-1245
+always függésszerű 1245-1245-156-15-1235-23456	For example függésszerű word
+
 always gyors 1456-234
 always keresz 13-15-1235-15-156
 always keres 13-234
@@ -228069,6 +228898,7 @@
 always pontty 1234-135-1345-1256-1256
 always pénz 1234-126
 always rövid 1235-145
+always rövidítéssz 1235-145-234-156	For example rövidítésszótárnak word
 always forr 1235-1235
 always szabad 156-145
 always tanács 2345-146
@@ -228596,7 +229426,7 @@
 endnum ből 6-12-123
 endnum ából 4-12-123
 endnum -ából 36-4-12-123
-endnum ból 12-123
+endnum ból 6-12-123
 endnum öséből 12345-234-16-12-123
 endnum -öséből 36-12345-234-16-12-123
 endnum -ból 36-12-123
@@ -228606,7 +229436,7 @@
 endnum hoz 6-125-126
 endnum hez 6-125-126
 endnum höz 6-125-126
-noback pass2 @6-6-125 @6-125
+#noback pass2 @6-6-125 @6-125
 endnum -hoz 36-125-126
 endnum éhez 16-125-126
 endnum -éhez 36-16-125-126
@@ -228634,18 +229464,18 @@
 endnum -től 36-2345-123
 noback correct $D.["-TŐL"] "-től"
 noback correct $D.["-Től"] "-től"
-endnum asával 1-234-4-1236
+endnum asával 6-1-234-4-1236
 endnum -asával 36-1-234-4-1236
 endnum ával 4-1236
 endnum -ával 36-4-1236
-endnum esével 15-234-16-1236
+endnum esével 6-15-234-16-1236
 endnum -esével 36-15-234-16-1236
 endnum ével 16-1236
 endnum -ével 36-16-1236
 endnum val 1236
 endnum -val 36-1236
 endnum -vel 36-1236
-endnum a\sés 1-0-16
+endnum a\sés 6-1-0-16
 endnum cal 1236
 endnum cel 1236
 endnum -cal 36-1236
@@ -228722,17 +229552,17 @@
 endnum §-tól 3456-1236-36-2345-123
 endnum %ként 3456-245-356-13-2345
 endnum %-ként 3456-245-356-36-13-2345
-endnum jében 245-16-12
+endnum jében 6-245-16-12
 endnum -jében 36-245-16-12
-endnum jéből 245-16-12-123
+endnum jéből 6-245-16-12-123
 endnum -jéből 36-245-16-12-123
-endnum jéhez 245-16-125-126
+endnum jéhez 6-245-16-125-126
 endnum -jéhez 36-245-16-125-126
-endnum jéről 245-16-1235-123
+endnum jéről 6-245-16-1235-123
 endnum -jéről 36-245-16-1235-123
-endnum jétől 245-16-2345-123
+endnum jétől 6-245-16-2345-123
 endnum -jétől 36-245-16-2345-123
-endnum jével 245-16-1236
+endnum jével 6-245-16-1236
 endnum -jével 36-245-16-1236
 noback pass3 @36-6-245-16-1236-15-123 @36-245-16-1236
 noback pass3 @36-46a-6-245-16-1236-15-123 @36-245-16-1236
@@ -228744,25 +229574,25 @@
 noback correct $D.%notaccentedletters.["ként"] "kt"
 noback correct $D.%notaccentedletters.["ban"] "b"
 noback correct $D.%notaccentedletters.["-ban"] "-b"
-endnum asában 1-234-4-12
-endnum asából 1-234-4-12-123
+endnum asában 6-1-234-4-12
+endnum asából 6-1-234-4-12-123
 endnum -asában 36-1-234-4-12
 endnum -asából 36-1-234-4-12-123
-endnum asához 1-234-4-125-126
+endnum asához 6-1-234-4-125-126
 endnum -asához 36-1-234-4-125-126
-endnum asáról 1-234-4-1235-123
+endnum asáról 6-1-234-4-1235-123
 endnum -asáról 1-234-4-1235-123
-endnum asától 1-234-4-2345-123
+endnum asától 6-1-234-4-2345-123
 endnum -asától 36-1-234-4-2345-123
-endnum esben 15-234-12
+endnum esben 6-15-234-12
 endnum -esben 36-15-234-12
-endnum esből 15-234-12-123
+endnum esből 6-15-234-12-123
 endnum -esből 36-15-234-12-123
-endnum essel 15-234-1236
+endnum essel 6-15-234-1236
 endnum -essel 36-15-234-1236
-endnum eshez 15-234-125-126
+endnum eshez 6-15-234-125-126
 endnum -eshez 36-15-234-125-126
-endnum esről 15-234-1235-123
+endnum esről 6-15-234-1235-123
 endnum -esről 36-15-234-1235-123
 endnum sz 156
 endnum szeresében 156-15-1235-15-234-16-12
@@ -228787,41 +229617,41 @@
 endnum -szorosától 36-156-135-1235-135-234-4-2345-123
 endnum szorosával 156-135-1235-135-234-4-1236
 endnum -szorosával 36-156-135-1235-135-234-4-1236
-endnum estől 15-234-2345-123
+endnum estől 6-15-234-2345-123
 endnum -estől 36-15-234-2345-123
-endnum esekében 15-234-16-12
+endnum esekében 6-15-234-16-12
 endnum -esekében 36-15-234-16-12
-endnum esekéből 15-234-15-13-16-12-123
+endnum esekéből 6-15-234-15-13-16-12-123
 endnum -esekéből 36-15-234-15-13-16-12-123
-endnum esekéhez 15-234-15-13-16-125-126
+endnum esekéhez 6-15-234-15-13-16-125-126
 endnum -esekéhez 36-15-234-15-13-16-125-126
-endnum esekéről 15-234-15-13-16-1235-123
+endnum esekéről 6-15-234-15-13-16-1235-123
 endnum -esekéről 36-15-234-15-13-16-1235-123
-endnum esekétől 15-234-15-13-16-2345-123
+endnum esekétől 6-15-234-15-13-16-2345-123
 endnum -esekétől 36-15-234-15-13-16-2345-123
-endnum esekkel 15-234-15-13-1236
+endnum esekkel 6-15-234-15-13-1236
 endnum -esekkel 36-15-234-15-13-1236
-endnum esekben 15-234-15-13-12
+endnum esekben 6-15-234-15-13-12
 endnum -esekben 36-15-234-15-13-12
-endnum esekből 15-234-15-13-12-123
+endnum esekből 6-15-234-15-13-12-123
 endnum -esekből 36-15-234-15-13-12-123
-endnum esekhez 15-234-15-13-125-126
+endnum esekhez 6-15-234-15-13-125-126
 endnum -esekhez 36-15-234-15-13-125-126
-endnum esekről 15-234-15-13-1235-123
+endnum esekről 6-15-234-15-13-1235-123
 endnum -esekről 36-15-234-15-13-1235-123
-endnum esektől 15-234-15-13-2345-123
+endnum esektől 6-15-234-15-13-2345-123
 endnum -esektől 36-15-234-15-13-2345-123
-endnum esekével 15-234-15-13-16-1236
+endnum esekével 6-15-234-15-13-16-1236
 endnum -esekével 36-15-234-15-13-16-1236
-endnum esében 15-234-16-12
+endnum esében 6-15-234-16-12
 endnum -esében 36-15-234-16-12
-endnum eséből 15-234-16-12-123
+endnum eséből 6-15-234-16-12-123
 endnum -eséből 36-15-234-16-12-123
-endnum eséhez 15-234-16-125-126
+endnum eséhez 6-15-234-16-125-126
 endnum -eséhez 36-15-234-16-125-126
-endnum eséről 15-234-16-1235-123
+endnum eséről 6-15-234-16-1235-123
 endnum -eséről 36-15-234-16-1235-123
-endnum esétől 15-234-16-2345-123
+endnum esétől 6-15-234-16-2345-123
 endnum -esétől 36-15-234-16-2345-123
 endnum szeres 156-15-1235-15-234
 endnum -szeres 36-156-15-1235-15-234
@@ -228837,9 +229667,9 @@
 endnum -szeressel 36-156-15-1235-15-234-1236
 endnum szerestől 156-15-1235-15-234-2345-123
 endnum -szerestől 36-156-15-1235-15-234-2345-123
-endnum asként 1-234-13-2345
+endnum asként 6-1-234-13-2345
 endnum -asként 36-1-234-13-2345
-endnum esként 15-234-13-2345
+endnum esként 6-15-234-13-2345
 endnum -esként 36-15-234-13-2345
 endnum részesben 1235-16-156-15-234-12
 endnum -részesben 36-1235-16-156-15-234-12
@@ -228853,17 +229683,17 @@
 endnum -részestől 36-1235-16-156-15-234-2345-123
 endnum részessel 1235-16-156-15-234-1236
 endnum -részessel 36-1235-16-156-15-234-1236
-endnum hetesben 125-15-2345-15-234-12
+endnum hetesben 6-125-15-2345-15-234-12
 endnum -hetesben 36-125-15-2345-15-234-12
-endnum hetesből 125-15-2345-15-234-12-123
+endnum hetesből 6-125-15-2345-15-234-12-123
 endnum -hetesből 36-125-15-2345-15-234-12-123
-endnum heteshez 125-15-2345-15-234-125-126
+endnum heteshez 6-125-15-2345-15-234-125-126
 endnum -heteshez 36-125-15-2345-15-234-125-126
-endnum hetesről 125-15-2345-15-234-1235-123
+endnum hetesről 6-125-15-2345-15-234-1235-123
 endnum -hetesről 36-125-15-2345-15-234-1235-123
-endnum hetestől 125-15-2345-15-234-2345-123
+endnum hetestől 6-125-15-2345-15-234-2345-123
 endnum -hetestől 36-125-15-2345-15-234-2345-123
-endnum hetessel 125-15-2345-15-234-1236
+endnum hetessel 6-125-15-2345-15-234-1236
 endnum -hetessel 36-125-15-2345-15-234-1236
 endnum évessel 16-1236-15-234-1236
 endnum -évessel 36-16-1236-15-234-1236
@@ -229001,6 +229831,20 @@
 always boldog-liget =
 always boldogon =
 always boldog-szigetek 12-135-123-145-135-1245-36-156-24-1245-15-2345-15-13
+
+#Special letters, with need marking different after numbers
+
+endnum A 46a-6-1
+endnum B 46a-6-12
+endnum C 46a-6-14
+endnum D 46a-6-145
+endnum E 46a-6-15
+endnum F 46a-6-124
+endnum G 46a-6-1245
+endnum H 46a-6-125
+endnum I 46a-6-24
+endnum J 46a-6-245
+
 # This table contains braille codes and rules for Bengali  script.
 #
 # Copyright (C) 2014 National Institute for Visually Handicapped, 116, Rajpur Road, Dehradun, Uttrakhand, India
@@ -248278,6 +249122,7 @@
 ETAGS = etags
 EXEEXT = 
 FGREP = /usr/bin/grep -F
+FILECMD = file
 GETOPT_CDEFS_H = getopt-cdefs.h
 GETOPT_H = getopt.h
 GL_CFLAG_ALLOW_WARNINGS = -Wno-error
@@ -249082,7 +249927,7 @@
 LIBINTL = 
 LIBLOUIS_AGE = 0
 LIBLOUIS_CURRENT = 20
-LIBLOUIS_REVISION = 9
+LIBLOUIS_REVISION = 10
 LIBOBJS = 
 LIBS = 
 LIBTOOL = $(SHELL) $(top_builddir)/libtool
@@ -249141,10 +249986,10 @@
 PACKAGE = liblouis
 PACKAGE_BUGREPORT = liblouis-liblouisxml@freelists.org
 PACKAGE_NAME = Liblouis
-PACKAGE_STRING = Liblouis 3.21.0
+PACKAGE_STRING = Liblouis 3.22.0
 PACKAGE_TARNAME = liblouis
 PACKAGE_URL = http://www.liblouis.org
-PACKAGE_VERSION = 3.21.0
+PACKAGE_VERSION = 3.22.0
 PATH_SEPARATOR = :
 PRAGMA_COLUMNS = 
 PRAGMA_SYSTEM_HEADER = #pragma GCC system_header
@@ -249343,7 +250188,7 @@
 UNISTD_H_HAVE_SYS_RANDOM_H = 0
 UNISTD_H_HAVE_WINSOCK2_H = 0
 UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS = 0
-VERSION = 3.21.0
+VERSION = 3.22.0
 WARN_CFLAGS =  -fno-common -Wall -Warith-conversion -Wbad-function-cast -Wcast-align=strict -Wdate-time -Wdisabled-optimization -Wdouble-promotion -Wduplicated-branches -Wduplicated-cond -Wextra -Wformat-signedness -Winit-self -Winline -Winvalid-pch -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wmissing-prototypes -Wnested-externs -Wnull-dereference -Wold-style-definition -Wopenmp-simd -Woverlength-strings -Wpacked -Wpointer-arith -Wshadow -Wstack-protector -Wstrict-overflow -Wstrict-prototypes -Wsuggest-attribute=cold -Wsuggest-attribute=malloc -Wsuggest-final-methods -Wsuggest-final-types -Wsync-nand -Wtrampolines -Wuninitialized -Wunknown-pragmas -Wunsafe-loop-optimizations -Wunused-macros -Wvariadic-macros -Wvector-operation-performance -Wvla -Wwrite-strings -Warray-bounds=2 -Wattribute-alias=2 -Wformat-overflow=2 -Wformat=2 -Wformat-truncation=2 -Wimplicit-fallthrough=5 -Wshift-overflow=2 -Wunused-const-variable=2 -Wvla-larger-than=4031 -Wno-analyzer-malloc-leak
 WCHAR_T_SUFFIX = 
 WIDECHAR_TYPE = unsigned short int
@@ -249507,6 +250352,7 @@
 	de-g2-core.cti \
 	de-g2-core-patterns.dic \
 	de-g2.ctb \
+	de-g2-detailed.ctb \
 	devanagari.cti \
 	digits6DotsPlusDot6.uti \
 	digits6Dots.uti \
@@ -249710,7 +250556,6 @@
 	no-no-generic.ctb \
 	no-no-generic.dis \
 	no-no-latinLetterDef6Dots_diacritics.uti \
-	no.tbl \
 	np-in-g1.utb \
 	nso-za-g1.utb \
 	nso-za-g2.ctb \
@@ -249749,12 +250594,9 @@
 	si-in-g1.utb \
 	sin.cti \
 	sin.utb \
-	sk-chardefs.cti \
 	sk-g1.ctb \
 	sk-sk-g1.utb \
 	sk-sk.utb \
-	sk.tbl \
-	sk-translation.cti \
 	sl-si-comp8.ctb \
 	sl-si-g1.utb \
 	sl.tbl \
@@ -250078,9 +250920,18 @@
 
 include bengali.cti
 include en-in-g1.ctb
+# liblouis: Slovak (grade 1) text braille translation table
 #
-#  Copyright (C) 2014 by Branislav Mamojka <mamojka@unss.sk>
-#  Copyright (C) 2016 by Peter Vagner <peter.v@datagate.sk>
+#-index-name: Slovak
+#-display-name: Slovak braille
+#
+#+language: sk
+#+type: literary
+#+dots: 6
+#+contraction: no
+#+direction: both
+#
+#  Copyright (C) 2014,2016,2021-2022 the Braille Authority of Slovakia <https://www.skn.sk/en/slovak-authority-for-the-braille-code>
 #
 #  This file is part of liblouis.
 #
@@ -250098,22 +250949,945 @@
 #  License along with liblouis. If not, see
 #  <http://www.gnu.org/licenses/>.
 #
-#--------------------------------------------------------------------------------
-#
-#  Slovak Braille
-#
-#     Created and maintained by Branislav Mamojka <mamojka@unss.sk>
-#                               Peter Vagner <peter.v@datagate.sk>
-#
-#     Based on the official Slovak Braille Standard
-#     Derived from the Czech braille table by Bert Frees and Jan Halousek
-# -------------------------------------------------------------------------------
+# Based on "Pravidlá písania a používania Braillovho písma
+# v Slovenskej republike" (Braille writing and usage guidelines
+# in Slovak republic)
+# <http://liblouis.org/braille-specs/slovak/>.
 
-include sk-chardefs.cti
+space               \x0009          9                                     <control> HORIZONTAL TABULATION
+space               \x001B          1b                                    <control> ESCAPE
+space               \x00A0          a                                     NO-BREAK SPACE
+include spaces.uti
+
+# ==============================================================================================
+# SINGLE-CELL
+# ==============================================================================================
+
+lowercase           \x0061          1                                 a   LATIN SMALL LETTER A
+lowercase           \x0062          12                                b   LATIN SMALL LETTER B
+lowercase           \x0063          14                                c   LATIN SMALL LETTER C
+lowercase           \x0064          145                               d   LATIN SMALL LETTER D
+lowercase           \x0065          15                                e   LATIN SMALL LETTER E
+lowercase           \x0066          124                               f   LATIN SMALL LETTER F
+lowercase           \x0067          1245                              g   LATIN SMALL LETTER G
+lowercase           \x0068          125                               h   LATIN SMALL LETTER H
+lowercase           \x0069          24                                i   LATIN SMALL LETTER I
+lowercase           \x006A          245                               j   LATIN SMALL LETTER J
+lowercase           \x006B          13                                k   LATIN SMALL LETTER K
+lowercase           \x006C          123                               l   LATIN SMALL LETTER L
+lowercase           \x006D          134                               m   LATIN SMALL LETTER M
+lowercase           \x006E          1345                              n   LATIN SMALL LETTER N
+lowercase           \x006F          135                               o   LATIN SMALL LETTER O
+lowercase           \x0070          1234                              p   LATIN SMALL LETTER P
+lowercase           \x0071          12345                             q   LATIN SMALL LETTER Q
+lowercase           \x0072          1235                              r   LATIN SMALL LETTER R
+lowercase           \x0073          234                               s   LATIN SMALL LETTER S
+lowercase           \x0074          2345                              t   LATIN SMALL LETTER T
+lowercase           \x0075          136                               u   LATIN SMALL LETTER U
+lowercase           \x0076          1236                              v   LATIN SMALL LETTER V
+lowercase           \x0077          2456                              w   LATIN SMALL LETTER W
+lowercase           \x0078          1346                              x   LATIN SMALL LETTER X
+lowercase           \x0079          13456                             y   LATIN SMALL LETTER Y
+lowercase           \x007A          1356                              z   LATIN SMALL LETTER Z
+lowercase           \x00E1          16                                á   LATIN SMALL LETTER A WITH ACUTE
+lowercase           \x00E4          4                                 ä   LATIN SMALL LETTER A WITH DIARESIS
+lowercase           \x00E9          345                               é   LATIN SMALL LETTER E WITH ACUTE
+lowercase           \x00ED          34                                í   LATIN SMALL LETTER I WITH ACUTE
+lowercase           \x00F3          246                               ó   LATIN SMALL LETTER O WITH ACUTE
+lowercase           \x00FA          346                               ú   LATIN SMALL LETTER U WITH ACUTE
+lowercase           \x00FD          12346                             ý   LATIN SMALL LETTER Y WITH ACUTE
+lowercase           \x010D          146                               č   LATIN SMALL LETTER C WITH CARON
+lowercase           \x010F          1456                              ď   LATIN SMALL LETTER D WITH CARON
+lowercase           \x013E          456                               ľ   LATIN SMALL LETTER L WITH CARON
+lowercase           \x013A          46                                ĺ   LATIN SMALL LETTER L WITH Acute
+lowercase           \x0148          1246                              ň   LATIN SMALL LETTER N WITH CARON
+lowercase           \x00F4          23456                             ô   LATIN SMALL LETTER O WITH CARET
+lowercase           \x0155          12356                             ŕ   LATIN SMALL LETTER R WITH ACUTE
+lowercase           \x0161          156                               š   LATIN SMALL LETTER S WITH CARON
+lowercase           \x0165          1256                              ť   LATIN SMALL LETTER T WITH CARON
+lowercase           \x017E          2346                              ž   LATIN SMALL LETTER Z WITH CARON
+
+base uppercase      \x0041          \x0061                            Aa  LATIN CAPITAL LETTER A - LATIN SMALL LETTER A
+base uppercase      \x0042          \x0062                            Bb  LATIN CAPITAL LETTER B - LATIN SMALL LETTER B
+base uppercase      \x0043          \x0063                            Cc  LATIN CAPITAL LETTER C - LATIN SMALL LETTER C
+base uppercase      \x0044          \x0064                            Dd  LATIN CAPITAL LETTER D - LATIN SMALL LETTER D
+base uppercase      \x0045          \x0065                            Ee  LATIN CAPITAL LETTER E - LATIN SMALL LETTER E
+base uppercase      \x0046          \x0066                            Ff  LATIN CAPITAL LETTER F - LATIN SMALL LETTER F
+base uppercase      \x0047          \x0067                            Gg  LATIN CAPITAL LETTER G - LATIN SMALL LETTER G
+base uppercase      \x0048          \x0068                            Hh  LATIN CAPITAL LETTER H - LATIN SMALL LETTER H
+base uppercase      \x0049          \x0069                            Ii  LATIN CAPITAL LETTER I - LATIN SMALL LETTER I
+base uppercase      \x004A          \x006A                            Jj  LATIN CAPITAL LETTER J - LATIN SMALL LETTER J
+base uppercase      \x004B          \x006B                            Kk  LATIN CAPITAL LETTER K - LATIN SMALL LETTER K
+base uppercase      \x004C          \x006C                            Ll  LATIN CAPITAL LETTER L - LATIN SMALL LETTER L
+base uppercase      \x004D          \x006D                            Mm  LATIN CAPITAL LETTER M - LATIN SMALL LETTER M
+base uppercase      \x004E          \x006E                            Nn  LATIN CAPITAL LETTER N - LATIN SMALL LETTER N
+base uppercase      \x004F          \x006F                            Oo  LATIN CAPITAL LETTER O - LATIN SMALL LETTER O
+base uppercase      \x0050          \x0070                            Pp  LATIN CAPITAL LETTER P - LATIN SMALL LETTER P
+base uppercase      \x0051          \x0071                            Qq  LATIN CAPITAL LETTER Q - LATIN SMALL LETTER Q
+base uppercase      \x0052          \x0072                            Rr  LATIN CAPITAL LETTER R - LATIN SMALL LETTER R
+base uppercase      \x0053          \x0073                            Ss  LATIN CAPITAL LETTER S - LATIN SMALL LETTER S
+base uppercase      \x0054          \x0074                            Tt  LATIN CAPITAL LETTER T - LATIN SMALL LETTER T
+base uppercase      \x0055          \x0075                            Uu  LATIN CAPITAL LETTER U - LATIN SMALL LETTER U
+base uppercase      \x0056          \x0076                            Vv  LATIN CAPITAL LETTER V - LATIN SMALL LETTER V
+base uppercase      \x0057          \x0077                            Ww  LATIN CAPITAL LETTER W - LATIN SMALL LETTER W
+base uppercase      \x0058          \x0078                            Xx  LATIN CAPITAL LETTER X - LATIN SMALL LETTER X
+base uppercase      \x0059          \x0079                            Yy  LATIN CAPITAL LETTER Y - LATIN SMALL LETTER Y
+base uppercase      \x005A          \x007A                            Zz  LATIN CAPITAL LETTER Z - LATIN SMALL LETTER Z
+base uppercase      \x00C1          \x00E1                            Áá  LATIN CAPITAL LETTER A WITH ACUTE - LATIN SMALL LETTER A WITH ACUTE
+base uppercase      \x00C4          \x00E4                            Ää  LATIN CAPITAL LETTER A WITH DIARESIS - LATIN SMALL LETTER A WITH DIARESIS
+base uppercase      \x00C9          \x00E9                            Éé  LATIN CAPITAL LETTER E WITH ACUTE - LATIN SMALL LETTER E WITH ACUTE
+base uppercase      \x00CD          \x00ED                            Íí  LATIN CAPITAL LETTER I WITH ACUTE - LATIN SMALL LETTER I WITH ACUTE
+base uppercase      \x00D3          \x00F3                            Óó  LATIN CAPITAL LETTER O WITH ACUTE - LATIN SMALL LETTER O WITH ACUTE
+base uppercase      \x00DA          \x00FA                            Úú  LATIN CAPITAL LETTER U WITH ACUTE - LATIN SMALL LETTER U WITH ACUTE
+base uppercase      \x00DD          \x00FD                            Ýý  LATIN CAPITAL LETTER Y WITH ACUTE - LATIN SMALL LETTER Y WITH ACUTE
+base uppercase      \x010C          \x010D                            Čč  LATIN CAPITAL LETTER C WITH CARON - LATIN SMALL LETTER C WITH CARON
+base uppercase      \x010E          \x010F                            Ďď  LATIN CAPITAL LETTER D WITH CARON - LATIN SMALL LETTER D WITH CARON
+base uppercase      \x013D          \x013E                            Ľľ  LATIN CAPITAL LETTER L WITH CARON - LATIN SMALL LETTER L WITH CARON
+base uppercase      \x0139          \x013A                            Ĺĺ  LATIN CAPITAL LETTER L WITH Acute - LATIN SMALL LETTER L WITH Acute
+base uppercase      \x0147          \x0148                            Ňň  LATIN CAPITAL LETTER N WITH CARON - LATIN SMALL LETTER N WITH CARON
+base uppercase      \x00D4          \x00F4                            Ôô  LATIN CAPITAL LETTER O WITH CARET - LATIN SMALL LETTER O WITH CARET
+base uppercase      \x0154          \x0155                            Ŕŕ  LATIN CAPITAL LETTER R WITH ACUTE - LATIN SMALL LETTER R WITH ACUTE
+base uppercase      \x0160          \x0161                            Šš  LATIN CAPITAL LETTER S WITH CARON - LATIN SMALL LETTER S WITH CARON
+base uppercase      \x0164          \x0165                            Ťť  LATIN CAPITAL LETTER T WITH CARON - LATIN SMALL LETTER T WITH CARON
+base uppercase      \x017D          \x017E                            Žž  LATIN CAPITAL LETTER Z WITH CARON - LATIN SMALL LETTER Z WITH CARON
+
+# ----------------------------------------------------------------------------------------------
+# Unicode 0000..007F  C0 Controls and Basic Latin
+# ----------------------------------------------------------------------------------------------
+
+punctuation         \x0021          235                               !   EXCLAMATION MARK
+punctuation         \x0022          2356                              "
+#noback sign        \x0023          3456                              #
+noback sign         \x0023          46-3456                           #
+punctuation         \x0027          3                                 '
+punctuation         \x0028          236                               (
+punctuation         \x0029          356                               )
+sign                \x002A          35                                *
+noback math         \x002B          235                               +
+math                \x002B          56-235                            +
+punctuation         \x002C          2                                 ,
+punctuation         \x002D          36                                -
+punctuation         \x002E          256                               .
+math                \x002F          12456                             /
+include             digits6Dots.uti
+punctuation         \x003A          25                                :
+punctuation         \x003B          23                                ;
+math                \x003C          126                               <
+math                \x003C          56-126                            <
+noback math         \x003D          2356                              =
+math                \x003D          56-2356                           =
+noback math         \x003E          345                               >
+math                \x003E          56-345                            >
+punctuation         \x003F          26                                ?
+noback sign         \x005F          46-36                             _
+noback punctuation  \x0060          6                                 `
+#noback punctuation \x0060          46-6                              `
+
+
+# ----------------------------------------------------------------------------------------------
+# Unicode 0080..00FF  C1 Controls and Latin-1 Supplement
+# ----------------------------------------------------------------------------------------------
+
+noback punctuation  \x00A1          46-235                            ¡   INVERTED EXCLAIM
+noback sign         \x00A2          4-14                              ¢   CENT SIGN
+noback sign         \x00A3          4-123                             £   POUND SIGN
+noback sign         \x00A4          4-136                             ¤   CURRENCY SIGN
+noback sign         \x00A5          4-13456                           ¥   yen SIGN
+noback sign         \x00A6          46-46                             ¦   BROKEN VERTICAL LINE
+noback punctuation  \x00A7          46-346                            §   SECTION
+noback sign         \x00A9          46-14                             ©   COPIRIGHT
+noback math         \x00AB          46-236                            «   LEFT DOUBLE ANGLE BRACKET
+punctuation         \x00AD          36                                ­   SOFT HYPHEN
+noback sign         \x00AE          46-1235                           ®   REGISTERED
+noback math         \x00B2          34-3456-12-156                    ²   SUPERSCRIPT 2
+noback math         \x00B3          34-3456-14-156                    ³   SUPERSCRIPT 3
+noback sign         \x00B4          45                                ´   ACUTE ACCENT
+noback sign         \x00B8          3456                              ¸   CEDILLA
+noback math         \x00D7          346                               ×   MULTIPLICATION SIGN
+math                \x00D7          56-346                            ×   MULTIPLICATION SIGN
+noback math         \x00F7          25                                ÷   DIVISION SIGN
+math                \x00F7          56-25                             ÷   DIVISION SIGN
+noback letter       \x00FE          5-235                             þ
+
+
+# ----------------------------------------------------------------------------------------------
+# Unicode 2000..206F  General Punctuation
+# ----------------------------------------------------------------------------------------------
+
+punctuation         \x2010          36                                ‐   HYPHEN
+punctuation         \x2011          36                                ‑   NON-BREAKING HYPHEN
+punctuation         \x2012          36                                ‒   FIGURE DASH
+punctuation         \x2013          36                                –   EN DASH
+punctuation         \x2014          36                                —   EM DASH
+punctuation         \x2015          36                                ―   HORIZONTAL BAR
+punctuation         \x2018          3                                 ‘   LEFT SINGLE QUOTATION MARK
+punctuation         \x2019          3                                 ’   RIGHT SINGLE QUOTATION MARK
+punctuation         \x201A          3                                 ‚   SINGLE LOW-9 QUOTATION MARK
+punctuation         \x201B          3                                 ‛   SINGLE HIGH-REVERSED-9 QUOTATION MARK
+punctuation         \x201C          2356                              “   LEFT DOUBLE QUOTATION MARK
+punctuation         \x201D          2356                              ”   RIGHT DOUBLE QUOTATION MARK
+punctuation         \x201E          2356                              „   DOUBLE LOW-9 QUOTATION MARK
+punctuation         \x201F          2356                              ‟   DOUBLE HIGH-REVERSED-9 QUOTATION MARK
+noback sign         \x2020          46-6-3456                         †   DAGGER
+noback sign         \x2021          46-6-3456-23                      ‡   DOUBLE DAGGER
+noback punctuation  \x2022          46-2356                           •   BULLET
+punctuation         \x2026          256-256-256                       …   HORIZONTAL ELLIPSIS
+noback sign         \x20A0          4-136-4-15                        ₠   EURO CURRENCY SIGN
+noback sign         \x20A3          4-124                             ₣   FRANC SIGN
+noback sign         \x20A4          4-123-1235                        ₤   LIRA SIGN
+noback sign         \x20AC          4-15                              €   EURO SIGN
+noback sign         \x20B4          4-125                             ₴   HRIVNA SIGN
+noback sign         \x20BD          4-1235                            ₽   RUBLE SIGN
+noback sign         \x20BF          4-12                              ₿   BITCOIN SIGN
+
+
+# ==============================================================================================
+# MULTI-CELL
+# ==============================================================================================
+
+# ----------------------------------------------------------------------------------------------
+# Unicode 0000..007F  C0 Controls and Basic Latin
+# ----------------------------------------------------------------------------------------------
+
+noback sign         \x0024          4-234                             $   DOLLAR SIGN
+math                \x0025          3456-1234                         %   PERCENT SIGN
+sign                \x0026          3456-12346                        &   AMPERSAND
+sign                \x0040          3456-12456                        @   COMMERCIAL AT
+punctuation         \x005B          6-236                             [   LEFT SQUARE BRACKET
+sign                \x005C          3456-1256                         \   REVERSE SOLIDUS
+punctuation         \x005D          6-356                             ]   RIGHT SQUARE BRACKET
+noback sign         \x005E          46-45                             ^   CIRCUMFLEX ACCENT
+noback math         \x007B          5-236                             {   LEFT CURLY BRACKET
+noback sign         \x007C          46-456                            |   VERTICAL LINE
+noback math         \x007D          5-356                             }   RIGHT CURLY BRACKET
+noback math         \x007E          46-26                             ~   TILDE
+
+
+# ----------------------------------------------------------------------------------------------
+# Unicode 0080..00FF  C1 Controls and Latin-1 Supplement
+# ----------------------------------------------------------------------------------------------
+
+sign                \x00B0          3456-234                          °   DEGREE SIGN
+math                \x00B1          235-36                            ±   PLUS-MINUS SIGN
+noback sign         \x00B5          45-134                            µ   MICRO SIGN
+noback sign         \x00B6          46-345                            ¶   PARAGRAPH MARKER
+noback punctuation  \x00B7          46-256                            ·   MIDDLE DOT
+noback math         \x00B9          34-3456-1-156                     ¹   SUPERSCRIPT ONE
+noback math         \x00BB          46-356                            »   RIGHT DOUBLE ANGLE BRACKET
+noback math         \x00BC          3456-1-256                        ¼   ONE QUARTER
+noback math         \x00BD          3456-1-23                         ½   ONE HALF
+noback math         \x00BE          3456-14-256                       ¾   THREE QUARTERS
+noback sign         \x00BF          46-35                             ¿   INVERTED QUESTION
+
+
+# ----------------------------------------------------------------------------------------------
+# Unicode 0370..03FF  Greek and Coptic
+# ----------------------------------------------------------------------------------------------
+
+noback lowercase    \x03b1          1                                 α   
+noback lowercase    \x03b2          12                                β   
+noback lowercase    \x03b3          1245                              γ   
+noback lowercase    \x03b4          145                               δ   
+noback lowercase    \x03b5          15                                ε   
+noback lowercase    \x03b6          1356                              ζ   
+noback lowercase    \x03b7          156                               η   
+noback lowercase    \x03b8          1456                              θ   
+noback lowercase    \x03b9          24                                ι   
+noback lowercase    \x03ba          13                                κ   
+noback lowercase    \x03bb          123                               λ   
+noback lowercase    \x03bc          134                               μ   
+noback lowercase    \x03bd          1345                              ν   
+noback lowercase    \x03be          1346                              ξ   
+noback lowercase    \x03bf          135                               ο   
+noback lowercase    \x03c0          1234                              π   
+noback lowercase    \x03c1          1235                              ρ   
+noback lowercase    \x03c3          234                               σ   
+noback lowercase    \x03c2          234                               ς   
+noback lowercase    \x03c4          2345                              τ   
+noback lowercase    \x03c5          136                               υ   
+noback lowercase    \x03c6          124                               φ   
+noback lowercase    \x03c7          12346                             χ   
+noback lowercase    \x03c8          13456                             ψ   
+noback lowercase    \x03c9          2456                              ω   
+noback lowercase    \x03ac          345                               ά   
+noback lowercase    \x1fb6          16                                ᾶ   
+noback lowercase    \x1f70          12356                             ὰ   
+noback lowercase    \x03ad          1246                              έ   
+noback lowercase    \x1f72          14                                ὲ   
+noback lowercase    \x03ae          123456                            ή   
+noback lowercase    \x1fc6          126                               ῆ   
+noback lowercase    \x1f74          2346                              ὴ   
+noback lowercase    \x03af          12456                             ί   
+noback lowercase    \x1fd6          146                               ῖ   
+noback lowercase    \x1f76          34                                ὶ   
+noback lowercase    \x03cc          246                               ό   
+noback lowercase    \x1f78          346                               ὸ   
+noback lowercase    \x03cd          1256                              ύ   
+noback lowercase    \x1fe6          1236                              ῦ   
+noback lowercase    \x1f7a          23456                             ὺ   
+noback lowercase    \x03ce          245                               ώ   
+noback lowercase    \x1ff6          3456                              ῶ   
+noback lowercase    \x1f7c          12345                             ὼ   
+noback lowercase    \x03dd          1236                              ϝ   
+noback lowercase    \x03df          12345                             ϟ   
+noback lowercase    \x03e1          2346                              ϡ   
+
+noback letter       \x0391          1                                 Α   
+noback letter       \x0392          12                                Β   
+noback letter       \x0393          1245                              Γ   
+noback letter       \x0394          145                               Δ   
+noback letter       \x0395          15                                Ε   
+noback letter       \x0396          1356                              Ζ   
+noback letter       \x0397          156                               Η   
+noback letter       \x0398          1456                              Θ   
+noback letter       \x0399          24                                Ι   
+noback letter       \x039a          13                                Κ   
+noback letter       \x039b          123                               Λ   
+noback letter       \x039c          134                               Μ   
+noback letter       \x039d          1345                              Ν   
+noback letter       \x039e          1346                              Ξ   
+noback letter       \x039f          135                               Ο   
+noback letter       \x03a0          1234                              Π   
+noback letter       \x03a1          1235                              Ρ   
+noback letter       \x03a3          234                               Σ   
+noback letter       \x03a3          234                               Σ   
+noback letter       \x03a4          2345                              Τ   
+noback letter       \x03a5          136                               Υ   
+noback letter       \x03a6          124                               Φ   
+noback letter       \x03a7          12346                             Χ   
+noback letter       \x03a8          13456                             Ψ   
+noback letter       \x03a9          2456                              Ω   
+noback letter       \x0386          345                               Ά   
+noback letter       \x1fba          12356                             Ὰ   
+noback letter       \x0388          1246                              Έ   
+noback letter       \x1fc8          14                                Ὲ   
+noback letter       \x0389          123456                            Ή   
+noback letter       \x1fca          2346                              Ὴ   
+noback letter       \x038a          12456                             Ί   
+noback letter       \x1fda          34                                Ὶ   
+noback letter       \x038c          246                               Ό   
+noback letter       \x1ff8          346                               Ὸ   
+noback letter       \x038e          1256                              Ύ   
+noback letter       \x1fea          23456                             Ὺ   
+noback letter       \x038f          245                               Ώ   
+noback letter       \x1ffa          12345                             Ὼ   
+noback letter       \x03dc          1236                              Ϝ   
+noback letter       \x03de          12345                             Ϟ   
+noback letter       \x03e0          2346                              Ϡ   
+
+
+# ----------------------------------------------------------------------------------------------
+# Other Unicode characters
+# ----------------------------------------------------------------------------------------------
+
+noback math         \x00AC          1345-135-1345                     ¬   LOGICAL NOT
+noback sign         \x02C7          56                                ˇ   CARON
+noback sign         \x02D9          5                                 ˙   DOT ABOVE
+noback sign         \x02DD          123456                            ˝   DOUBLE ACUTE
+noback math         \x0609          3456-134                          ؉   ARABIC-INDIC PER MILLE SIGN
+math                \x2030          3456-1235                         ‰   PER MILLE SIGN
+noback math         \x2032          34-2                              ′   PRIME SIGN
+noback math         \x2033          34-2-2                            ″   DOUBLE PRIME SIGN
+noback math         \x2034          34-2-2-2                          ‴   TRIPLE PRIME SIGN
+noback math         \x203E          156                               ‾
+noback math         \x2070          34-3456-245-156                   ⁰   SUPERSCRIPT 0
+noback math         \x2074          34-3456-145-156                   ⁴   SUPERSCRIPT 4
+noback math         \x2075          34-3456-15-156                    ⁵   SUPERSCRIPT 5
+noback math         \x2076          34-3456-124-156                   ⁶   SUPERSCRIPT 6
+noback math         \x2077          34-3456-1245-156                  ⁷   SUPERSCRIPT 7
+noback math         \x2078          34-3456-125-156                   ⁸   SUPERSCRIPT 8
+noback math         \x2079          34-3456-24-156                    ⁹   SUPERSCRIPT 9
+noback math         \x207A          34-235-156                        ⁺   SUPERSCRIPT PLUS
+noback math         \x207B          34-36-156                         ⁻   SUPERSCRIPT MINUS
+noback math         \x207C          34-2356-156                       ⁼   SUPERSCRIPT EQUALS
+noback math         \x207D          34-236-156                        ⁽   SUPERSCRIPT LEFT PAREN
+noback math         \x207E          34-356-156                        ⁾   SUPERSCRIPT RIGHT PAREN
+noback math         \x207F          34-1345-156                       ⁿ   SUPERSCRIPT N
+noback math         \x2080          16-3456-245-156                   ₀   SUBSCRIPT 0
+noback math         \x2081          16-3456-1-156                     ₁   SUBSCRIPT 1
+noback math         \x2082          16-3456-12-156                    ₂   SUBSCRIPT 2
+noback math         \x2083          16-3456-14-156                    ₃   SUBSCRIPT 3
+noback math         \x2084          16-3456-145-156                   ₄   SUBSCRIPT 4
+noback math         \x2085          16-3456-15-156                    ₅   SUBSCRIPT 5
+noback math         \x2086          16-3456-124-156                   ₆   SUBSCRIPT 6
+noback math         \x2087          16-3456-1245-156                  ₇   SUBSCRIPT 7
+noback math         \x2088          16-3456-125-156                   ₈   SUBSCRIPT 8
+noback math         \x2089          16-3456-24-156                    ₉   SUBSCRIPT 9
+noback math         \x208A          16-235-156                        ₊   SUBSCRIPT PLUS
+noback math         \x208B          16-36-156                         ₋   SUBSCRIPT MINUS
+noback math         \x208C          16-2356-156                       ₌   SUBSCRIPT EQUALS
+noback math         \x208D          16-236-156                        ₍   SUBSCRIPT LEFT PAREN
+noback math         \x208E          16-356-156                        ₎   SUBSCRIPT RIGHT PAREN
+noback math         \x2099          16-1345-156                       ₙ   SUBSCRIPT N
+sign                \x2103          3456-234-6-14                     ℃   degrees celsius
+sign                \x2109          3456-234-6-124                    ℉   degrees fahrenhei
+noback sign         \x2122          46-2345                           ™   TRADEMARK
+noback math         \x2150          3456-1-2356                       ⅐   ONE SEVENTH
+noback math         \x2151          3456-1-35                         ⅑   ONE nineth
+noback math         \x2152          3456-1-2-356                      ⅒   ONE TENTH
+noback math         \x2153          3456-1-25                         ⅓   ONE THIRD
+noback math         \x2154          3456-12-25                        ⅔   TWO THIRDS
+noback math         \x2155          3456-1-26                         ⅕   ONE fifth
+noback math         \x2159          3456-1-235                        ⅙   ONE SIXTH
+noback math         \x215B          3456-1-236                        ⅛   ONE EIGHTH
+noback math         \x2190          1256-246                          ←   left arrow
+noback math         \x2191          1256-346                          ↑   up arrow
+noback math         \x2192          1256-135                          →   right arrow
+noback math         \x2193          1256-146                          ↓   down down arrow
+noback math         \x2194          1256-2456-1235-135                ↔   left right arrow
+noback math         \x2195          1256-2456-1235-346                ↕   up down arrow
+noback math         \x2196          1256-156                          ↖   left up arrow
+noback math         \x2197          1256-234                          ↗   right up arrow
+noback math         \x2198          1256-126                          ↘   right down arrow
+noback math         \x2199          1256-345                          ↙   left down arrow
+noback math         \x219A          1256-246-4-156                    ↚   left checkmarked arrow
+noback math         \x219B          1256-135-4-156                    ↛   right checkmarked arrow
+noback math         \x219C          1256-35-26-246                    ↜
+noback math         \x219D          1256-26-35-135                    ↝
+noback math         \x219E          1256-25-1235-1235-136             ↞
+noback math         \x219F          1256-25-1235-1235-346             ↟
+noback math         \x21A0          1256-25-1235-1235-135             ↠
+noback math         \x21A1          1256-25-1235-1235-146             ↡
+noback math         \x21A2          1256-1235-1235-246                ↢
+noback math         \x21A3          1256-1235-1235-135                ↣
+noback math         \x21A4          1256-1256-1235-246                ↤
+noback math         \x21A5          1256-1256-1235-346                ↥
+noback math         \x21A6          1256-1256-1235-135                ↦
+noback math         \x21A7          1256-1256-1235-146                ↦
+noback math         \x21AD          1256-2456-35-26-1235-135          ↭
+noback math         \x21AE          1256-2456-1235-135-4-156          ↮
+noback math         \x21B0          1256-356-346                      ↰
+noback math         \x21B1          1256-256-346                      ↱
+noback math         \x21B2          1256-256-146                      ↲
+noback math         \x21B3          1256-356-146                      ↳
+noback math         \x21B6          1256-35-35-246                    ↶
+noback math         \x21B7          1256-26-26-135                    ↷
+noback math         \x21BC          1256-4-1235-246                   ↼
+noback math         \x21BD          1256-6-1235-246                   ↽
+noback math         \x21BE          1256-6-1235-346                   ↾
+noback math         \x21BF          1256-4-1235-346                   ↿
+noback math         \x21C0          1256-4-1235-135                   ⇀
+noback math         \x21C1          1256-6-1235-135                   ⇁
+noback math         \x21C2          1256-4-1235-146                   ⇂
+noback math         \x21C3          1256-6-1235-146                   ⇃
+noback math         \x21C4          1256-135-12456-1256-246           ⇄
+noback math         \x21C5          1256-346-123456-1256-146          ⇄
+noback math         \x21CC          45-456-2356                       ⇌
+noback math         \x21D0          1256-2356-246                     ⇐
+noback math         \x21D1          1256-2356-346                     ⇑
+noback math         \x21D2          1256-2356-135                     ⇒
+noback math         \x21D3          1256-2356-146                     ⇓
+noback math         \x21D4          1256-2456-2356-1235-135           ⇔
+noback math         \x21D4          1256-2456-2356-1235-135           ⇔
+noback math         \x21D5          1256-2456-2356-1235-346           ⇕
+noback math         \x21E0          1256-2-246                        ⇠
+noback math         \x21E1          1256-2-346                        ⇡
+noback math         \x21E2          1256-2-135                        ⇢
+noback math         \x21E3          1256-2-146                        ⇣
+noback math         \x21E3          1256-2-146                        ⇣
+math                \x2200          3456-1236                         ∀   FOR ALL
+noback math         \x2202          34-2                              ∂   PARTIAL DIFFERENTIAL
+math                \x2203          3456-134                          ∃   THERE EXISTS
+math                \x2205          3456-13456                        ∅   EMPTY SET
+noback math         \x2208          45-15                             ∈   ELEMENT OF
+noback math         \x2209          4-45-15                           ∉   NOT AN ELEMENT OF
+noback math         \x220B          45-45-15                          ∋   CONTAINS AS MEMBER
+noback math         \x220F          46-1234                           ∏   TIMES
+noback math         \x2211          46-124                            ∑   SUM
+noback math         \x2212          36                                −   MINUS
+math                \x2213          36-235                            ∓   MINUS OR PLUS
+math                \x2212          56-36                             −   MINUS
+noback math         \x2215          12456                             ∕   DIVIDED BY
+math                \x2215          56-12456                          ∕   DIVIDED BY
+noback math         \x2216          45-36                             ∖   SET MINUS
+math                \x2217          35                                ∗   ASTERISK OPERATOR
+noback math         \x2218          5-135                             ∘   RING OPERATOR
+noback math         \x2219          3                                 ∙   DOT OPERATOR
+noback math         \x221A          146                               √   SQUARE ROOT
+math                \x221E          3456-1345                         ∞   INFINITY
+noback math         \x2220          456-246                           ∠   ANGLE
+noback math         \x2221          46-456-246                        ∡   MEASURED ANGLE
+noback math         \x2223          456                               ∣   DIVIDES
+math                \x2223          56-456                            ∣   DIVIDES
+noback math         \x2224          4-456                             ∤   DOES NOT DIVIDE
+noback math         \x2225          3456-123                          ∥∥  PARALEL TO
+noback math         \x2226          4-3456-123                        ∥∦  NOT PARALEL TO
+noback math         \x2227          15-2345                           ∧   LOGICAL AND
+noback math         \x22BF          1246-3456-3456-14                 ⊿   TRIANGLE
+noback math         \x22C0          15-2345                           ∧   LOGICAL AND
+noback math         \x2228          145-24-234                        ⋁   LOGICAL OR
+noback math         \x22C1          145-24-234                        ⋁   LOGICAL OR
+noback math         \x2229          45-156                            ∩   INTERSECTION
+noback math         \x222A          45-3456                           ∪   UNION
+noback math         \x222B          2346                              ∫   INTEGRAL
+noback math         \x2236          25                                ∶   RATIO
+noback math         \x2245          45-2356                           ≅   APROXIMATELLY EQUAL TO
+noback math         \x2248          5-2356                            ≈   ALMOST EQUAL TO
+noback math         \x2249          4-5-2356                          ≉   ALMOST NOT EQUAL TO
+noback math         \x224A          5-2356-2356                       ≊   ALMOST OR EQUAL TO
+noback math         \x2251          46-5-2356                         ≑   GEOMETRICALLY EQUAL TO
+noback math         \x2261          456-2356                          ≡   IDENTICAL TO
+noback math         \x2262          4-456-2356                        ≢   NOT IDENTICAL TO
+noback math         \x2260          4-2356                            ≠   NOT EQUAL TO
+noback math         \x2250          5-2356                            ≐   APPROACHES THE LIMIT
+math                \x2264          126-2356                          ≤   LESS-THAN OR EQUAL TO
+math                \x2265          345-2356                          ≥   GREATER-THAN OR EQUAL TO
+noback math         \x226A          46-126                            ≪   MUCH SMALLER THAN
+noback math         \x226B          46-345                            ≫   MUCH GREATER THAN
+noback math         \x226E          4-126                             ≮   NOT LESS than
+noback math         \x226F          4-345                             ≯   NOT GREATER than
+math                \x2276          126-345                           ≶   LESS-THAN OR GREATER-THAN
+math                \x2277          345-126                           ≷   GREATER-THAN OR LESS-THAN
+noback math         \x2282          45-12346                          ⊂   SUBSET OF
+noback math         \x2283          45-13456                          ⊃   SUPERSET OF
+noback math         \x2284          4-45-12346                        ⊄   NOT A SUBSET OF
+noback math         \x2285          4-45-13456                        ⊅   NOT A SUPERSET OF
+noback math         \x2286          45-12346-2356                     ⊆   SUBSET OF OR EQUAL TO
+noback math         \x2287          45-13456-2356                     ⊇   SUPERSET OR EQUAL TO
+noback math         \x2288          4-45-12346-4-2356                 ⊈   NEITHER A SUBSET OF NOR EQUAL TO
+noback math         \x2289          4-45-13456-4-2356                 ⊉   NEITHER A SUPERSET OF NOR EQUAL TO
+noback math         \x228A          45-12346-4-2356                   ⊊   SUBSET OF AND DOES NOT EQUAL TO
+noback math         \x228B          45-13456-4-2356                   ⊋   SUPERSET OF AND DOES NOT EQUAL TO
+noback math         \x22A5          3456-36                           ⊥   PERPENDICULAR TO
+noback math         \x22C5          3                                 ⋅   DOT OPERATOR
+noback math         \x2312          35-26                             ⌒
+noback math         \x25A0          1246-3456-145                     ■   BLACK SQUARE
+noback math         \x25A1          1246-3456-145                     □   WHITE SQUARE
+noback math         \x25A4          46-1246-3456-145                  ▤
+noback math         \x25AA          456-1246-3456-145                 ▪   SQUARE
+noback math         \x25AD          1246-3456-3456-145                ▭   RECTANGLE
+noback math         \x25B2          456-1246-3456-14                  ▲   FULL TRIANGLE
+noback math         \x25B3          1246-3456-14                      △   TRIANGLE
+noback math         \x25D9          456-1246-3456-145-246-1246-123456 ◙
+noback math         \x25E6          46-2356                           ◦   WHITE BULLET
+noback math         \x25CA          1246-4-3456-145                   ◊   LOZENGE
+noback math         \x25CF          456-1246-123456                   ●   BLACK CIRCLE
+noback math         \x25AC          456-1246-3456-3456-145            ▬   FILLED RECTANGLE
+noback math         \x2640          46-1346                           ♀   FEMININE SYMBOL
+noback math         \x2642          46-13456                          ♂   MARS SYMBOL
+noback sign         \x2713          4-146                             ✓   CHECKMARK SIGN
+noback math         \x27E8          5-126                             ⟨   MATHEMATICAL LEFT ANGLE BRACKET
+noback math         \x27E9          5-345                             ⟩   MATHEMATICAL RIGHT ANGLE BRACKET
+noback math         \x27EA          46-236                            ⟪   MATHEMATICAL DOUBLE LEFT ANGLE BRACKET
+noback math         \x27EB          46-356                            ⟫   MATHEMATICAL DOUBLE RIGHT ANGLE BRACKET
+noback math         \x27F5          1256-25-25-25-246                 ⟵
+noback math         \x27F6          1256-25-25-25-135                 ⟶
+sign                \x283F          123456                            ⠿   FULL BRAILLE SIGN
+noback math         \x2942          5-456-2356                        ⥂
+noback math         \x2944          4-456-2356                        ⥄
+noback math         \x2991          256-236                           ⦑   MATHEMATICAL LEFT ANGLE BRACKET WITH DOT
+noback math         \x2992          256-356                           ⦒   MATHEMATICAL RIGHT ANGLE BRACKET WITH DOT
+noback math         \x299C          3456-456-246                      ⦜   RIGHT ANGLE
+noback math         \x299D          46-3456-456-246                   ⦜   MEASURED RIGHT ANGLE
+noback math         \x2B20          1246-3456-15                      ⬠
+noback math         \x2B22          1246-3456-124                     ⬢
+noback math         \x2BC3          1246-3456-125                     ⯃
+noback sign         \x2E4B          46-6-3456-25                      ⹋   TRIPLE DAGGER
+noback math         \x3008          5-126                             〈   MATHEMATICAL LEFT ANGLE BRACKET
+noback math         \x3009          5-345                             〉   MATHEMATICAL RIGHT ANGLE BRACKET
+noback always       \xD83C\xDF11    2456-1235                         🌑   NEW MOON
+noback always       \xD83C\xDF13    135-135                           🌓   FIRST QUARTER OF A MOON
+noback always       \xD83C\xDF15    246-135                           🌕   FULL MOON
+noback always       \xD83C\xDF17    246-246                           🌗   LAST QUARTER OF A MOON
+#noback sign        \y1F311         2456-1235                         🌑   NEW MOON
+#noback sign        \y1F313         135-135                           🌓   FIRST QUARTER OF A MOON
+#noback sign        \y1F315         246-135                           🌕   FULL MOON
+#noback sign        \y1F317         246-246                           🌗   LAST QUARTER OF A MOON
+noback lowercase    \x00F0          5-145                             ð
+base uppercase      \x00D0          \x00F0                            Ðð
+
+
+# ----------------------------------------------------------------------------------------------
+# Foreign latin characters
+# ----------------------------------------------------------------------------------------------
+
+noback lowercase    \x00e0          12356                             à
+noback lowercase    \x00e2          16                                â
+noback lowercase    \x0105          156                               ą
+noback lowercase    \x00e5          1                                 å
+noback lowercase    \x0101          2                                 ā
+noback lowercase    \x0107          146                               ć
+noback lowercase    \x00e7          12346                             ç
+noback lowercase    \x0111          1456                              đ
+noback lowercase    \x00e6          345                               æ
+noback lowercase    \x00e8          1246                              è
+noback lowercase    \x011b          126                               ě
+noback lowercase    \x0119          1256                              ę
+noback lowercase    \x00eb          15                                ë
+noback lowercase    \x00ea          26                                ê
+noback lowercase    \x0113          256                               ē
+noback lowercase    \x0123          1245                              ģ
+noback lowercase    \x0121          2356                              ġ
+noback lowercase    \x00ef          24                                ï
+noback lowercase    \x012b          35                                ī
+noback lowercase    \x00ee          34                                î
+noback lowercase    \x0137          13                                ķ
+noback lowercase    \x0142          123                               ł
+noback lowercase    \x013c          1236                              ļ
+noback lowercase    \x0144          1345                              ń
+noback lowercase    \x00f1          12456                             ñ
+noback lowercase    \x0146          23                                ņ
+noback lowercase    \x00f6          135                               ö
+noback lowercase    \x00f8          246                               ø
+noback lowercase    \x0153          135-15                            œ
+noback lowercase    \x00f2          356                               ò
+noback lowercase    \x014d          236                               ō
+noback lowercase    \x0151          12345                             ő
+noback lowercase    \x0159          2456                              ř
+noback lowercase    \x015b          234                               ś
+noback lowercase    \x00df          2346                              ß
+noback lowercase    \x016f          23456                             ů
+noback lowercase    \x00fc          136                               ü
+noback lowercase    \x00f9          346                               ù
+noback lowercase    \x00fb          3456                              û
+noback lowercase    \x0171          123456                            ű
+noback lowercase    \x016b          36                                ū
+noback lowercase    \x017c          13456                             ż
+noback lowercase    \x017a          1356                              ź
+
+
+noback letter       \x00c0          12356                             À
+noback letter       \x00c2          16                                Â
+noback letter       \x0104          156                               Ą
+noback letter       \x00c5          1                                 Å
+noback letter       \x0100          2                                 Ā
+noback letter       \x0106          146                               Ć
+noback letter       \x00c7          12346                             Ç
+noback letter       \x0110          1456                              Đ
+noback letter       \x00c6          345                               Æ
+noback letter       \x00c8          1246                              È
+noback letter       \x011a          126                               Ě
+noback letter       \x0118          1256                              Ę
+noback letter       \x00cb          15                                Ë
+noback letter       \x00ca          26                                Ê
+noback letter       \x0112          256                               Ē
+noback letter       \x0122          1245                              Ģ
+noback letter       \x0120          2356                              Ġ
+noback letter       \x00cf          24                                Ï
+noback letter       \x012a          35                                Ī
+noback letter       \x00ce          34                                Î
+noback letter       \x0136          13                                Ķ
+noback letter       \x0141          123                               Ł
+noback letter       \x013b          1236                              Ļ
+noback letter       \x0143          1345                              Ń
+noback letter       \x00d1          12456                             Ñ
+noback letter       \x0145          23                                Ņ
+noback letter       \x00d6          135                               Ö
+noback letter       \x00d8          246                               Ø
+noback letter       \x0152          135-15                            Œ
+noback letter       \x00d2          356                               Ò
+noback letter       \x014c          236                               Ō
+noback letter       \x0150          12345                             Ő
+noback letter       \x0158          2456                              Ř
+noback letter       \x015a          234                               Ś
+noback letter       \x1E9E          2346                              ẞ
+noback letter       \x016e          23456                             Ů
+noback letter       \x00dc          136                               Ü
+noback letter       \x00d9          346                               Ù
+noback letter       \x00db          3456                              Û
+noback letter       \x0170          123456                            Ű
+noback letter       \x016a          36                                Ū
+noback letter       \x017b          13456                             Ż
+noback letter       \x0179          1356                              Ź
+
+
+# ----------------------------------------------------------------------------------------------
+# Braille indicator opcodes
+# ----------------------------------------------------------------------------------------------
+
+numsign  3456
+midendnumericmodechars ,.-':/()
+capsletter  6
+begcapsword  6-6
+endcapsword  56
+capsmodechars -
+
+# ----------------------------------------------------------------------------------------------
+# Literary digits
+# ----------------------------------------------------------------------------------------------
+
+include litdigits6Dots.uti
+
+# ----------------------------------------------------------------------------------------------
+# Decimal points, hyphens
+# ----------------------------------------------------------------------------------------------
+
+decpoint  \x002C  2
+#decpoint  \x002E  3
+noback hyphen    \x002D  36
+
+# ----------------------------------------------------------------------------------------------
+# Letter prefix in numbers
+# ----------------------------------------------------------------------------------------------
+
+attribute    digitletter                 abcdefghij
+noback context  $d[]%digitletter            @56
+noback context  $d"."[]%digitletter         @56
+noback context  $d","[]%digitletter         @56
+noback context  $d"-"[]%digitletter         @56
+noback context  $d"\x0027"[]%digitletter         @56 # apostrophe
+noback context  $d":"[]%digitletter         @56
+noback context  $d"\x002F"[]%digitletter         @56 # slash
+noback context  $d"\x0028"[]%digitletter         @56 # left paren
+noback context  $d"\x0029"[]%digitletter         @56 # right paren
+
+
+nofor context       [@5]            #1=1                              # Input some slovak special characters
+
+nofor context       #1=1@236        "\x007B"#1=0                      # LEFT CURLY BRACKET
+nofor context       #1=1@356        "\x007D"#1=0                      # RIGHT CURLY BRACKET
+nofor context       #1=1@145        "\x00F0"#1=0                      # ð
+nofor context       #1=1@235        "\x00FE"#1=0                      # THORN
+
+nofor context       [@5-5]          "\x2810"#2=0                      # ⠐
+nofor context       [@5-45]         "\x2818"#2=0                      # ⠘
+nofor context       [@5-6]          "\x2820"#2=0                      # ⠠
+nofor context       [@5-56]         "\x2830"#2=0                      # ⠰
+
+nofor context       [@5-46]         #2=2                              # Input some slovak special characters
+
+nofor context       #2=2@3456       "\x0023"#2=0                      # NUMBER SIGN
+nofor context       #2=2@36         "\x005F"#2=0                      # LOW LINE
+nofor context       #2=2@6          "\x0060"#2=0                      # GRAVE ACCENT
+nofor context       #2=2@235        "\x00A1"#2=0                      # INVERTED EXCLAIM
+nofor context       #2=2@46         "\x00A6"#2=0                      # BROKEN VERTICAL BAR
+nofor context       #2=2@346        "\x00A7"#2=0                      # SECTION
+nofor context       #2=2@14         "\x00A9"#2=0                      # COPIRIGHT
+nofor context       #2=2@236        "\x00AB"#2=0                      # LEFT DOUBLE ANGLE BRACKET
+nofor context       #2=2@1235       "\x00AE"#2=0                      # REGISTERED
+nofor context       #2=2@45         "\x005E"#2=0                      # CIRCUMFLEX
+nofor context       #2=2@456        "\x007C"#2=0                      # VERTICAL LINE
+nofor context       #2=2@26         "\x007E"#2=0                      # TILDE
+nofor context       #2=2@345        "\x00B6"#2=0                      # PARAGRAPH MARKER
+nofor context       #2=2@256        "\x00B7"#2=0                      # MIDDLE DOT
+nofor context       #2=2@356        "\x00BB"#2=0                      # RIGHT DOUBLE ANGLE BRACKET
+nofor context       #2=2@35         "\x00BF"#2=0                      # INVERTED QUESTION
+nofor context       #2=2@2356       "\x2022"#2=0                      # BULLET
+nofor context       #2=2@2345       "\x2122"#2=0                      # TRADEMARK
+nofor context       #2=2@1234       "\x220F"#2=0                      # TIMES
+nofor context       #2=2@234        "\x2211"#2=0                      # SUM
+nofor context       #2=2@1346       "\x2640"#2=0                      # FEMININE SYMBOL
+nofor context       #2=2@13456      "\x2642"#2=0                      # MARS SYMBOL
+
+nofor context       [@5-4]          #3=2                              # Input some slovak special characters
+
+nofor context       #3=2@14         "\x00A2"#3=0                      # CENT SIGN
+nofor context       #3=2@123        "\x00A3"#3=0                      # POUND SIGN
+nofor context       #3=2@136        "\x00A4"#3=0                      # CURRENCY SIGN
+nofor context       #3=2@13456      "\x00A5"#3=0                      # YEN SIGN
+nofor context       #3=2@124        "\x20A3"#3=0                      # FRANC SIGN
+nofor context       #3=2@15         "\x20AC"#3=0                      # EURO SIGN
+nofor context       #3=2@125        "\x20B4"#3=0                      # HRIVNA SIGN
+nofor context       #3=2@1235       "\x20BD"#3=0                      # RUBLE SIGN
+nofor context       #3=2@12         "\x20BF"#3=0                      # BITCOIN SIGN
+nofor context       #3=2@234        "\x0024"#3=0                      # DOLLAR SIGN
+nofor context       #3=2@456        "\x2224"#3=0                      # DOES NOT DIVIDE
+nofor context       #3=2@2356       "\x2260"#3=0                      # NOT EQUALS TO
+nofor context       #3=2@146        "\x2713"#3=0                      # CHECKMARK SIGN
+
+nofor context       #1=1@12356      "\x00e0"#1=0                      # à
+nofor context       #1=1@16         "\x00e2"#1=0                      # â
+nofor context       #1=1@156        "\x0105"#1=0                      # ą
+nofor context       #1=1@1          "\x00e5"#1=0                      # å
+nofor context       #1=1@2          "\x0101"#1=0                      # ā
+nofor context       #1=1@146        "\x0107"#1=0                      # ć
+nofor context       #1=1@12346      "\x00e7"#1=0                      # ç
+nofor context       #1=1@1456       "\x0111"#1=0                      # đ
+nofor context       #1=1@345        "\x00e6"#1=0                      # æ
+nofor context       #1=1@1246       "\x00e8"#1=0                      # è
+nofor context       #1=1@126        "\x011b"#1=0                      # ě
+nofor context       #1=1@1256       "\x0119"#1=0                      # ę
+nofor context       #1=1@15         "\x00eb"#1=0                      # ë
+nofor context       #1=1@26         "\x00ea"#1=0                      # ê
+nofor context       #1=1@256        "\x0113"#1=0                      # ē
+nofor context       #1=1@1245       "\x0123"#1=0                      # ģ
+nofor context       #1=1@2356       "\x0121"#1=0                      # ġ
+nofor context       #1=1@24         "\x00ef"#1=0                      # ï
+nofor context       #1=1@35         "\x012b"#1=0                      # ī
+nofor context       #1=1@34         "\x00ee"#1=0                      # î
+nofor context       #1=1@13         "\x0137"#1=0                      # ķ
+nofor context       #1=1@123        "\x0142"#1=0                      # ł
+nofor context       #1=1@1236       "\x013c"#1=0                      # ļ
+nofor context       #1=1@1345       "\x0144"#1=0                      # ń
+nofor context       #1=1@12456      "\x00f1"#1=0                      # ñ
+nofor context       #1=1@23         "\x0146"#1=0                      # ņ
+nofor context       #1=1@135        "\x00f6"#1=0                      # ö
+nofor context       #1=1@246        "\x00f8"#1=0                      # ø
+nofor context       #1=1@135-15     "\x0153"#1=0                      # œ
+#nofor context      #1=1@356        "\x00f2"#1=0                      # ò
+#nofor context      #1=1@236        "\x014d"#1=0                      # ō
+nofor context       #1=1@12345      "\x0151"#1=0                      # ő
+nofor context       #1=1@2456       "\x0159"#1=0                      # ř
+nofor context       #1=1@234        "\x015b"#1=0                      # ś
+nofor context       #1=1@2346       "\x00df"#1=0                      # ß
+nofor context       #1=1@23456      "\x016f"#1=0                      # ů
+nofor context       #1=1@136        "\x00fc"#1=0                      # ü
+nofor context       #1=1@346        "\x00f9"#1=0                      # ù
+nofor context       #1=1@3456       "\x00fb"#1=0                      # û
+nofor context       #1=1@123456     "\x0171"#1=0                      # ű
+nofor context       #1=1@36         "\x016b"#1=0                      # ū
+nofor context       #1=1@13456      "\x017c"#1=0                      # ż
+nofor context       #1=1@1356       "\x017a"#1=0                      # ź
+
+nofor context       [@6-5]          #4=2                              # Input some latin foreign uppercase characters
+
+nofor context       #4=2@12356      "\x00c0"#4=0                      # À
+nofor context       #4=2@16         "\x00c2"#4=0                      # Â
+nofor context       #4=2@156        "\x0104"#4=0                      # Ą
+nofor context       #4=2@1          "\x00c5"#4=0                      # Å
+nofor context       #4=2@2          "\x0100"#4=0                      # Ā
+nofor context       #4=2@146        "\x0106"#4=0                      # Ć
+nofor context       #4=2@12346      "\x00c7"#4=0                      # Ç
+nofor context       #4=2@1456       "\x0110"#4=0                      # Đ
+nofor context       #4=2@345        "\x00c6"#4=0                      # Æ
+nofor context       #4=2@1246       "\x00c8"#4=0                      # È
+nofor context       #4=2@126        "\x011a"#4=0                      # Ě
+nofor context       #4=2@1256       "\x0118"#4=0                      # Ę
+nofor context       #4=2@15         "\x00cb"#4=0                      # Ë
+nofor context       #4=2@26         "\x00ca"#4=0                      # Ê
+nofor context       #4=2@256        "\x0112"#4=0                      # Ē
+nofor context       #4=2@1245       "\x0122"#4=0                      # Ģ
+nofor context       #4=2@2356       "\x0120"#4=0                      # Ġ
+nofor context       #4=2@24         "\x00cf"#4=0                      # Ï
+nofor context       #4=2@35         "\x012a"#4=0                      # Ī
+nofor context       #4=2@34         "\x00ce"#4=0                      # Î
+nofor context       #4=2@13         "\x0136"#4=0                      # Ķ
+nofor context       #4=2@123        "\x0141"#4=0                      # Ł
+nofor context       #4=2@1236       "\x013b"#4=0                      # Ļ
+nofor context       #4=2@1345       "\x0143"#4=0                      # Ń
+nofor context       #4=2@12456      "\x00d1"#4=0                      # Ñ
+nofor context       #4=2@23         "\x0145"#4=0                      # Ņ
+nofor context       #4=2@135        "\x00d6"#4=0                      # Ö
+nofor context       #4=2@246        "\x00d8"#4=0                      # Ø
+nofor context       #4=2@135-15     "\x0152"#4=0                      # Œ
+#nofor context      #4=2@356        "\x00d2"#4=0                      # Ò
+#nofor context      #4=2@236        "\x014c"#4=0                      # Ō
+nofor context       #4=2@12345      "\x0150"#4=0                      # Ő
+nofor context       #4=2@2456       "\x0158"#4=0                      # Ř
+nofor context       #4=2@234        "\x015a"#4=0                      # Ś
+nofor context       #4=2@2346       "\x1E9E"#4=0                      # ẞ
+nofor context       #4=2@23456      "\x016e"#4=0                      # Ů
+nofor context       #4=2@136        "\x00dc"#4=0                      # Ü
+nofor context       #4=2@346        "\x00d9"#4=0                      # Ù
+nofor context       #4=2@3456       "\x00db"#4=0                      # Û
+nofor context       #4=2@123456     "\x0170"#4=0                      # Ű
+nofor context       #4=2@36         "\x016a"#4=0                      # Ū
+nofor context       #4=2@13456      "\x017b"#4=0                      # Ż
+nofor context       #4=2@1356       "\x0179"#4=0                      # Ź
+
+attribute upperforeign \x00c0\x00c2\x0104\x00c5\x0100\x0106\x00c7\x0110\x00c6\x00c8\x011a\x0118\x00cb\x00ca\x0112\x0122\x0120\x00cf\x012a\x00ce\x0136\x0141\x013b\x0143\x00d1\x0145\x00d6\x00d8\x0152\x00d2\x014c\x0150\x0158\x015a\x016e\x00dc\x00d9\x00db\x0170\x016a\x017b\x0179
+attribute lowerforeign \x00e0\x00e2\x0105\x00e5\x0101\x0107\x00e7\x0111\x00e6\x00e8\x011b\x0119\x00eb\x00ea\x0113\x0123\x0121\x00ef\x012b\x00ee\x0137\x0142\x013c\x0144\x00f1\x0146\x00f6\x00f8\x0153\x00f2\x014d\x0151\x0159\x015b\x00df\x016f\x00fc\x00f9\x00fb\x0171\x016b\x017c\x017a
+
+noback context      !#1=1[]%lowerforeign @5#1=5#2=0                       # prefix latin foreign letters with dot 5
+noback context      !#1=4[]%upperforeign @6-5#2=4#3=0                     # prefix foreign latin letters with dot 5
+
+attribute uppergreek \x0391\x0392\x0393\x0394\x0395\x0396\x0397\x0398\x0399\x039a\x039b\x039c\x039d\x039e\x039f\x03a0\x03a1\x03a3\x03a3\x03a4\x03a5\x03a6\x03a7\x03a8\x03a9\x0386\x1fba\x0388\x1fc8\x0389\x1fca\x038a\x1fda\x038c\x1ff8\x038e\x1fea\x038f\x1ffa\x03dc\x03de\x03e0
+attribute lowergreek \x03b1\x03b2\x03b3\x03b4\x03b5\x03b6\x03b7\x03b8\x03b9\x03ba\x03bb\x03bc\x03bd\x03be\x03bf\x03c0\x03c1\x03c3\x03c2\x03c4\x03c5\x03c6\x03c7\x03c8\x03c9\x03ac\x1fb6\x1f70\x03ad\x1f72\x03ae\x1fc6\x1f74\x03af\x1fd6\x1f76\x03cc\x1f78\x03cd\x1fe6\x1f7a\x03ce\x1ff6\x1f7c\x03dd\x03df\x03e1
+
+nofor context       [@45]           #5=1                                  # Input small greek characters
+
+nofor context       #5=1@1          "\x03b1"#5=0                      # α
+nofor context       #5=1@12         "\x03b2"#5=0                      # β
+nofor context       #5=1@1245       "\x03b3"#5=0                      # γ
+nofor context       #5=1@145        "\x03b4"#5=0                      # δ
+nofor context       #5=1@15         "\x03b5"#5=0                      # ε
+nofor context       #5=1@1356       "\x03b6"#5=0                      # ζ
+nofor context       #5=1@156        "\x03b7"#5=0                      # η
+nofor context       #5=1@1456       "\x03b8"#5=0                      # θ
+nofor context       #5=1@24         "\x03b9"#5=0                      # ι
+nofor context       #5=1@13         "\x03ba"#5=0                      # κ
+nofor context       #5=1@123        "\x03bb"#5=0                      # λ
+nofor context       #5=1@134        "\x03bc"#5=0                      # μ
+nofor context       #5=1@1345       "\x03bd"#5=0                      # ν
+nofor context       #5=1@1346       "\x03be"#5=0                      # ξ
+nofor context       #5=1@135        "\x03bf"#5=0                      # ο
+nofor context       #5=1@1234       "\x03c0"#5=0                      # π
+nofor context       #5=1@1235       "\x03c1"#5=0                      # ρ
+nofor context       #5=1@234        "\x03c3"#5=0                      # σ
+nofor context       #5=1@234        "\x03c2"#5=0                      # ς
+nofor context       #5=1@2345       "\x03c4"#5=0                      # τ
+nofor context       #5=1@136        "\x03c5"#5=0                      # υ
+nofor context       #5=1@124        "\x03c6"#5=0                      # φ
+nofor context       #5=1@12346      "\x03c7"#5=0                      # χ
+nofor context       #5=1@13456      "\x03c8"#5=0                      # ψ
+nofor context       #5=1@2456       "\x03c9"#5=0                      # ω
+nofor context       #5=1@345        "\x03ac"#5=0                      # ά
+nofor context       #5=1@16          "\x1fb6"#5=0                     # ᾶ
+nofor context       #5=1@12356      "\x1f70"#5=0                      # ὰ
+nofor context       #5=1@1246      "\x03ad"#5=0                       # έ
+nofor context       #5=1@14         "\x1f72"#5=0                      # ὲ
+nofor context       #5=1@123456     "\x03ae"#5=0                      # ή
+nofor context       #5=1@126        "\x1fc6"#5=0                      # ῆ
+nofor context       #5=1@2346       "\x1f74"#5=0                      # ὴ
+nofor context       #5=1@12456      "\x03af"#5=0                      # ί
+nofor context       #5=1@146        "\x1fd6"#5=0                      # ῖ
+nofor context       #5=1@34         "\x1f76"#5=0                      # ὶ
+nofor context       #5=1@246        "\x03cc"#5=0                      # ό
+nofor context       #5=1@346        "\x1f78"#5=0                      # ὸ
+nofor context       #5=1@1256       "\x03cd"#5=0                      # ύ
+nofor context       #5=1@1236       "\x1fe6"#5=0                      # ῦ
+nofor context       #5=1@23456      "\x1f7a"#5=0                      # ὺ
+nofor context       #5=1@245        "\x03ce"#5=0                      # ώ
+nofor context       #5=1@3456       "\x1ff6"#5=0                      # ῶ
+nofor context       #5=1@12345      "\x1f7c"#5=0                      # ὼ
+nofor context       #5=1@1236       "\x03dd"#5=0                      # ϝ
+nofor context       #5=1@12345      "\x03df"#5=0                      # ϟ
+nofor context       #5=1@2346        "\x03e1"#5=0                     # ϡ
+
+nofor context       [@6-45]         #6=2                                  # Input some greek uppercase characters
+
+nofor context       #6=2@1          "\x0391"#6=0                      # Α
+nofor context       #6=2@12         "\x0392"#6=0                      # Β
+nofor context       #6=2@1245       "\x0393"#6=0                      # Γ
+nofor context       #6=2@145        "\x0394"#6=0                      # Δ
+nofor context       #6=2@15         "\x0395"#6=0                      # Ε
+nofor context             #6=2@1356 "\x0396"#6=0                      # Ζ
+nofor context                       #6=2@156                          "\x0397"#6=0                      # Η
+nofor context       #6=2@1456       "\x0398"#6=0                      # Θ
+nofor context       #6=2@24         "\x0399"#6=0                      # Ι
+nofor context       #6=2@13         "\x039a"#6=0                      # Κ
+nofor context       #6=2@123        "\x039b"#6=0                      # Λ
+nofor context       #6=2@134        "\x039c"#6=0                      # Μ
+nofor context       #6=2@1345       "\x039d"#6=0                      # Ν
+nofor context       #6=2@1346       "\x039e"#6=0                      # Ξ
+nofor context       #6=2@135        "\x039f"#6=0 # Ο
+nofor context       #6=2@1234       "\x03a0"#6=0                      # Π
+nofor context       #6=2@1235       "\x03a1"#6=0                      # Ρ
+nofor context       #6=2@234        "\x03a3"#6=0                      # Σ
+nofor context       #6=2@234        "\x03a3"#6=0                      # Σ
+nofor context       #6=2@2345       "\x03a4"#6=0                      # Τ
+nofor context       #6=2@136        "\x03a5"#6=0                      # Υ
+nofor context       #6=2@124                "\x03a6"#6=0              # Φ
+nofor context       #6=2@12346      "\x03a7"#6=0                      # Χ
+nofor context       #6=2@13456      "\x03a8"#6=0                      # Ψ
+nofor context       #6=2@2456       "\x03a9"#6=0                      # Ω
+nofor context       #6=2@345        "\x0386"#6=0                      # Ά
+nofor context       #6=2@12356      "\x1fba"#6=0                      # Ὰ
+nofor context       #6=2@1246       "\x0388"#6=0                      # Έ
+nofor context       #6=2@14         "\x1fc8"#6=0                      # Ὲ
+nofor context       #6=2@123456     "\x0389"#6=0                      # Ή
+nofor context       #6=2@2346       "\x1fca"#6=0                      # Ὴ
+nofor context       #6=2@12456      "\x038a"#6=0                      # Ί
+nofor context       #6=2@34         "\x1fda"#6=0                      # Ὶ
+nofor context       #6=2@246        "\x038c"#6=0                      # Ό
+nofor context       #6=2@346        "\x1ff8"#6=0                      # Ὸ
+nofor context       #6=2@1256       "\x038e"#6=0                      # Ύ
+nofor context       #6=2@23456      "\x1fea"#6=0                      # Ὺ
+nofor context       #6=2@245        "\x038f"#6=0                      # Ώ
+nofor context       #6=2@12345      "\x1ffa"#6=0                      # Ὼ
+nofor context       #6=2@1236       "\x03dc"#6=0                      # Ϝ
+nofor context       #6=2@12345      "\x03de"#6=0                      # Ϟ
+nofor context       #6=2@2346       "\x03e0"#6=0                      # Ϡ
+
+noback context      !#1=6[]%uppergreek @6-45#1=6#2=0                      # prefix capital greek letters with dot 45
+noback context      !#1=5[]%lowergreek!%lowergreek @45#1=5#2=0            # prefix greek letters with dot 45
+noback context      !#1=5[]%lowergreek~ @45#1=5#2=0                       # prefix greek letters with dot 45
+noback context      !#1=5[]%lowergreek @45-45#1=5#2=0                     # prefix greek letters with dot 45
+noback context      %lowergreek[]!%lowergreek @56#1=5#2=0                 # prefix latin letters after greek letter with dot 56
+
 include braille-patterns.cti
-include sk-translation.cti
 
-# -------------------------------------------------------------------------------
+# ----------------------------------------------------------------------------------------------
 ISO8859-1
 1b2ra
 1b2rá
@@ -280969,7 +282743,7 @@
 #+direction: both
 #
 #  Copyright (C) 2013 Igor B. Poretsky <poretsky@mlbox.ru>
-#  Copyright (C) 2020-2021 Andrey Yakuboy <andrewia2002@yandex.ru>
+#  Copyright (C) 2020-2022 Andrey Yakuboy <andrewia2002@yandex.ru>
 #  Copyright (C) 2020 Bert Frees <bertfrees@gmail.com>
 #
 #  This file is part of liblouis.
@@ -280992,6 +282766,8 @@
 # распространения" (Guidelines for edition of mass-distribution
 # braille publications) <http://liblouis.org/braille-specs/russian/>.
 
+include ru-unicode.dis
+
 # Braille indicators:
 numsign 3456  number sign, just a dots operand
 
@@ -281110,6 +282886,83 @@
 lowercase \x044f 1246
 lowercase \x0451 16
 
+# We're using "base uppercase" rather than "uppercase" because
+# ru-litbrl.ctb is also included in ru-ru-g1.ctb which defines capital
+# signs.
+
+# The "noback always" rules were added because Cyrillic characters are
+# expected to get a dot 9. Without these rules, the uppercase letters
+# would not get a dot 9 because the "lowercase" rules (included for
+# back-translation, see above) would have precedence.
+
+base uppercase \x0410 \x0430
+base uppercase \x0411 \x0431
+base uppercase \x0412 \x0432
+base uppercase \x0413 \x0433
+base uppercase \x0414 \x0434
+base uppercase \x0415 \x0435
+base uppercase \x0416 \x0436
+base uppercase \x0417 \x0437
+base uppercase \x0418 \x0438
+base uppercase \x0419 \x0439
+base uppercase \x041a \x043a
+base uppercase \x041b \x043b
+base uppercase \x041c \x043c
+base uppercase \x041d \x043d
+base uppercase \x041e \x043e
+base uppercase \x041f \x043f
+base uppercase \x0420 \x0440
+base uppercase \x0421 \x0441
+base uppercase \x0422 \x0442
+base uppercase \x0423 \x0443
+base uppercase \x0424 \x0444
+base uppercase \x0425 \x0445
+base uppercase \x0426 \x0446
+base uppercase \x0427 \x0447
+base uppercase \x0428 \x0448
+base uppercase \x0429 \x0449
+base uppercase \x042a \x044a
+base uppercase \x042b \x044b
+base uppercase \x042c \x044c
+base uppercase \x042d \x044d
+base uppercase \x042e \x044e
+base uppercase \x042f \x044f
+base uppercase \x0401 \x0451
+
+noback always \x0401 169
+noback always \x0410 19
+noback always \x0411 129
+noback always \x0412 24569
+noback always \x0413 12459
+noback always \x0414 1459
+noback always \x0415 159
+noback always \x0416 2459
+noback always \x0417 13569
+noback always \x0418 249
+noback always \x0419 123469
+noback always \x041a 139
+noback always \x041b 1239
+noback always \x041c 1349
+noback always \x041d 13459
+noback always \x041e 1359
+noback always \x041f 12349
+noback always \x0420 12359
+noback always \x0421 2349
+noback always \x0422 23459
+noback always \x0423 1369
+noback always \x0424 1249
+noback always \x0425 1259
+noback always \x0426 149
+noback always \x0427 123459
+noback always \x0428 1569
+noback always \x0429 13469
+noback always \x042a 123569
+noback always \x042b 23469
+noback always \x042c 234569
+noback always \x042d 2469
+noback always \x042e 12569
+noback always \x042f 12469
+
 # Fix the problem with double code points for 'Ё' and 'Й'
 noback always \x0415\x0308 169
 noback always \x0435\x0308 169
@@ -281151,6 +283004,35 @@
 noback lowercase \xa653 123469-159-34
 noback lowercase \xa657 123469-19
 noback lowercase \xa64b 1359-1369-34
+base uppercase \x0404 \x0454
+base uppercase \x0405 \x0455
+base uppercase \x0406 \x0456
+base uppercase \x0407 \x0457
+base uppercase \x040b \x045b
+base uppercase \x0460 \x0461
+base uppercase \x0462 \x0463
+base uppercase \x0464 \x0465
+base uppercase \x0466 \x0467
+base uppercase \x0468 \x0469
+base uppercase \x046a \x046b
+base uppercase \x046c \x046d
+base uppercase \x046e \x046f
+base uppercase \x0470 \x0471
+base uppercase \x0472 \x0473
+base uppercase \x0474 \x0475
+base uppercase \x0476 \x0477
+base uppercase \x0478 \x0479
+base uppercase \x047a \x047b
+base uppercase \x047e \x047f
+base uppercase \x0480 \x0481
+base uppercase \x04e0 \x04e1
+base uppercase \xa640 \xa641
+base uppercase \xa642 \xa643
+base uppercase \xa648 \xa649
+base uppercase \xa64a \xa64b
+base uppercase \xa650 \xa651
+base uppercase \xa652 \xa653
+base uppercase \xa656 \xa657
 
 # Punctuation
 noback punctuation [ 12356				# 91
@@ -281270,6 +283152,30 @@
 lowercase	\x03c8	13456	# ψ Psi
 lowercase	\x03c9	2456	# ω Omega
 noback math	\x03d5	56-124	# GREEK PHI SYMBOL
+base uppercase \x0391 \x03B1   # Αα Alpha
+base uppercase \x0392 \x03B2   # Ββ Beta
+base uppercase \x0393 \x03B3   # Γγ Gamma
+base uppercase \x0394 \x03B4   # Δδ Delta
+base uppercase \x0395 \x03B5   # Εε Epsilon
+base uppercase \x0396 \x03B6   # Ζζ Zeta
+base uppercase \x0397 \x03B7   # Ηη Eta
+base uppercase \x0398 \x03B8   # Θθ Theta
+base uppercase \x0399 \x03B9   # Ιι Iota
+base uppercase \x039a \x03ba   # Κκ Kappa
+base uppercase \x039b \x03bb   # Λλ Lambda
+base uppercase \x039c \x03bc   # Μμ Mu
+base uppercase \x039d \x03bd   # Νν Nu
+base uppercase \x039e \x03be   # Ξξ Xi
+base uppercase \x039f \x03bf   # Οο Omicron
+base uppercase \x03a0 \x03c0   # Ππ Pi
+base uppercase \x03a1 \x03c1   # Ρρ Rho
+base uppercase \x03a3 \x03c3   # Σσ Sigma
+base uppercase \x03a4 \x03c4   # Ττ Tau
+base uppercase \x03a5 \x03c5   # Υυ Upsilon
+base uppercase \x03a6 \x03c6   # Φφ Phi
+base uppercase \x03a7 \x03c7   # Χχ Chi
+base uppercase \x03a8 \x03c8   # Ψψ Psi
+base uppercase \x03a9 \x03c9   # Ωω Omega
 
 # Other math symbols
 
@@ -281397,12 +283303,15 @@
 noback lowercase \x00e1 4-1			# a with acute
 noback lowercase \x00e2 16				# a with circumflex
 noback lowercase \x00e4 345				# a with dieresis
+noback lowercase \x00e6 345				# ae
 noback lowercase \x00e7 12346				# c with cedilla
 noback lowercase \x00e8 2346				# e with grave
 noback lowercase \x00e9 123456				# e with acute
 noback lowercase \x00ea 126				# e with circumflex
-noback lowercase \x00ed 4-24			# i with acute
+noback lowercase \x00eb 1246				# e with diaeresis
+noback lowercase \x00ed 4-24				# i with acute
 noback lowercase \x00ee 146				# i with circumflex
+noback lowercase \x00ef 12456				# i with diaeresis
 noback lowercase \x00f3 4-135			# o with acute
 noback lowercase \x00f4 1456			# o with circumflex
 noback lowercase \x00f6 246			# o with dieresis
@@ -281424,32 +283333,19 @@
 noback lowercase \x1e55 4-1234			# p with acute
 noback lowercase \x1e83 4-2456			# w with acute
 noback lowercase \x00df 2346				sharp s
-
-# Cyrillic
-noback lowercase \x0450 4-159			# Cyrillic e with grave
-noback lowercase \x045d 4-249			# Cyrillic i with grave
-
-# Uppercase letter
-
-# We're using "base uppercase" rather than "uppercase" because
-# ru-litbrl.ctb is also included in ru-ru-g1.ctb which defines capital
-# signs.
-
-# The "noback always" rules were added because Cyrillic characters are
-# expected to get a dot 9. Without these rules, the uppercase letters
-# would not get a dot 9 because the "lowercase" rules (included for
-# back-translation, see above) would have precedence.
-
 base uppercase \x00c0 \x00e0   # a with grave
 base uppercase \x00c1 \x00e1   # a with acute
 base uppercase \x00c2 \x00e2   # a with circumflex
 base uppercase \x00c4 \x00e4   # a with dieresis
+base uppercase \x00c6 \x00e6   # ae
 base uppercase \x00c7 \x00e7   # c with cedilla
 base uppercase \x00c8 \x00e8   # e with grave
 base uppercase \x00c9 \x00e9   # e with acute
 base uppercase \x00ca \x00ea   # e with circumflex
+base uppercase \x00cb \x00eb   # e with diaeresis
 base uppercase \x00cd \x00ed   # i with acute
 base uppercase \x00ce \x00ee   # i with circumflex
+base uppercase \x00cf \x00ef   # i with diaeresis
 base uppercase \x00d3 \x00f3   # o with acute
 base uppercase \x00d4 \x00f4   # o with circumflex
 base uppercase \x00d6 \x00f6   # o with dieresis
@@ -281466,133 +283362,17 @@
 base uppercase \x015a \x015b   # s with acute
 base uppercase \x0179 \x017a   # z with acute
 base uppercase \x01f4 \x01f5   # g with acute
-base uppercase \x0391 \x03B1   # Αα Alpha
-base uppercase \x0392 \x03B2   # Ββ Beta
-base uppercase \x0393 \x03B3   # Γγ Gamma
-base uppercase \x0394 \x03B4   # Δδ Delta
-base uppercase \x0395 \x03B5   # Εε Epsilon
-base uppercase \x0396 \x03B6   # Ζζ Zeta
-base uppercase \x0397 \x03B7   # Ηη Eta
-base uppercase \x0398 \x03B8   # Θθ Theta
-base uppercase \x0399 \x03B9   # Ιι Iota
-base uppercase \x039a \x03ba   # Κκ Kappa
-base uppercase \x039b \x03bb   # Λλ Lambda
-base uppercase \x039c \x03bc   # Μμ Mu
-base uppercase \x039d \x03bd   # Νν Nu
-base uppercase \x039e \x03be   # Ξξ Xi
-base uppercase \x039f \x03bf   # Οο Omicron
-base uppercase \x03a0 \x03c0   # Ππ Pi
-base uppercase \x03a1 \x03c1   # Ρρ Rho
-base uppercase \x03a3 \x03c3   # Σσ Sigma
-base uppercase \x03a4 \x03c4   # Ττ Tau
-base uppercase \x03a5 \x03c5   # Υυ Upsilon
-base uppercase \x03a6 \x03c6   # Φφ Phi
-base uppercase \x03a7 \x03c7   # Χχ Chi
-base uppercase \x03a8 \x03c8   # Ψψ Psi
-base uppercase \x03a9 \x03c9   # Ωω Omega
-base uppercase \x0400 \x0450   # Cyrillic e with grave
-base uppercase \x0401 \x0451
-base uppercase \x0404 \x0454
-base uppercase \x0405 \x0455
-base uppercase \x0406 \x0456
-base uppercase \x0407 \x0457
-base uppercase \x040b \x045b
-base uppercase \x040d \x045d   # Cyrillic i with grave
-base uppercase \x0410 \x0430
-base uppercase \x0411 \x0431
-base uppercase \x0412 \x0432
-base uppercase \x0413 \x0433
-base uppercase \x0414 \x0434
-base uppercase \x0415 \x0435
-base uppercase \x0416 \x0436
-base uppercase \x0417 \x0437
-base uppercase \x0418 \x0438
-base uppercase \x0419 \x0439
-base uppercase \x041a \x043a
-base uppercase \x041b \x043b
-base uppercase \x041c \x043c
-base uppercase \x041d \x043d
-base uppercase \x041e \x043e
-base uppercase \x041f \x043f
-base uppercase \x0420 \x0440
-base uppercase \x0421 \x0441
-base uppercase \x0422 \x0442
-base uppercase \x0423 \x0443
-base uppercase \x0424 \x0444
-base uppercase \x0425 \x0445
-base uppercase \x0426 \x0446
-base uppercase \x0427 \x0447
-base uppercase \x0428 \x0448
-base uppercase \x0429 \x0449
-base uppercase \x042a \x044a
-base uppercase \x042b \x044b
-base uppercase \x042c \x044c
-base uppercase \x042d \x044d
-base uppercase \x042e \x044e
-base uppercase \x042f \x044f
-base uppercase \x0460 \x0461
-base uppercase \x0462 \x0463
-base uppercase \x0464 \x0465
-base uppercase \x0466 \x0467
-base uppercase \x0468 \x0469
-base uppercase \x046a \x046b
-base uppercase \x046c \x046d
-base uppercase \x046e \x046f
-base uppercase \x0470 \x0471
-base uppercase \x0472 \x0473
-base uppercase \x0474 \x0475
-base uppercase \x0476 \x0477
-base uppercase \x0478 \x0479
-base uppercase \x047a \x047b
-base uppercase \x047e \x047f
-base uppercase \x0480 \x0481
-base uppercase \x04e0 \x04e1
 base uppercase \x1e30 \x1e31   # k with acute
 base uppercase \x1e3e \x1e3f   # m with acute
 base uppercase \x1e54 \x1e55   # p with acute
 base uppercase \x1e82 \x1e83   # w with acute
 base uppercase \x1e9e \x00df   sharp s
-base uppercase \xa640 \xa641
-base uppercase \xa642 \xa643
-base uppercase \xa648 \xa649
-base uppercase \xa64a \xa64b
-base uppercase \xa650 \xa651
-base uppercase \xa652 \xa653
-base uppercase \xa656 \xa657
 
-noback always \x0401 169
-noback always \x0410 19
-noback always \x0411 129
-noback always \x0412 24569
-noback always \x0413 12459
-noback always \x0414 1459
-noback always \x0415 159
-noback always \x0416 2459
-noback always \x0417 13569
-noback always \x0418 249
-noback always \x0419 123469
-noback always \x041a 139
-noback always \x041b 1239
-noback always \x041c 1349
-noback always \x041d 13459
-noback always \x041e 1359
-noback always \x041f 12349
-noback always \x0420 12359
-noback always \x0421 2349
-noback always \x0422 23459
-noback always \x0423 1369
-noback always \x0424 1249
-noback always \x0425 1259
-noback always \x0426 149
-noback always \x0427 123459
-noback always \x0428 1569
-noback always \x0429 13469
-noback always \x042a 123569
-noback always \x042b 23469
-noback always \x042c 234569
-noback always \x042d 2469
-noback always \x042e 12569
-noback always \x042f 12469
+# Cyrillic
+noback lowercase \x0450 4-159			# Cyrillic e with grave
+noback lowercase \x045d 4-249			# Cyrillic i with grave
+base uppercase \x0400 \x0450   # Cyrillic e with grave
+base uppercase \x040d \x045d   # Cyrillic i with grave
 
 # punctuation
 noback prepunc " 236
@@ -281626,8 +283406,8 @@
 # sign \y1f975 126-25-456
 
 # Symbol attributes for special rules below:
-attribute upperlatin ABCDEFGHIJKLMNOPQRSTUVWXYZ\x00c0\x00c1\x00c2\x00c4\x00c7\x00c8\x00c9\x00ca\x00cd\x00ce\x00d3\x00d4\x00d6\x00d9\x00da\x00db\x00dc\x00dd\x0106\x0139\x0143\x0152\x0154\x015a\x0179\x01f4\x1e30\x1e3e\x1e54\x1e82\x1e9e
-attribute lowerlatin abcdefghijklmnopqrstuvwxyz\x00df\x00e0\x00e1\x00e2\x00e4\x00e7\x00e8\x00e9\x00ea\x00ed\x00ee\x00f3\x00f4\x00f6\x00f9\x00fa\x00fb\x00fc\x00fd\x0107\x013a\x0144\x0153\x0155\x015b\x017a\x01f5\x1e31\x1e3f\x1e55\x1e83
+attribute upperlatin ABCDEFGHIJKLMNOPQRSTUVWXYZ\x00c0\x00c1\x00c2\x00c4\x00c6\x00c7\x00c8\x00c9\x00ca\x00cb\x00cd\x00ce\x00cf\x00d3\x00d4\x00d6\x00d9\x00da\x00db\x00dc\x00dd\x0106\x0139\x0143\x0152\x0154\x015a\x0179\x01f4\x1e30\x1e3e\x1e54\x1e82\x1e9e
+attribute lowerlatin abcdefghijklmnopqrstuvwxyz\x00df\x00e0\x00e1\x00e2\x00e4\x00e6\x00e7\x00e8\x00e9\x00ea\x00eb\x00ed\x00ee\x00ef\x00f3\x00f4\x00f6\x00f9\x00fa\x00fb\x00fc\x00fd\x0107\x013a\x0144\x0153\x0155\x015b\x017a\x01f5\x1e31\x1e3f\x1e55\x1e83
 attribute uppercyrillic \x0401\x0410\x0411\x0412\x0413\x0414\x0415\x0416\x0417\x0418\x0419\x041a\x041b\x041c\x041d\x041e\x041f\x0420\x0421\x0422\x0423\x0424\x0425\x0426\x0427\x0428\x0429\x042a\x042b\x042c\x042d\x042e\x042f\x0400\x0404\x0405\x0406\x0407\x040b\x040d\x0460\x0462\x0464\x0466\x0468\x046a\x046c\x046e\x0470\x0472\x0474\x0476\x0478\x047a\x047e\x0480\x04e0\xa640\xa642\xa648\xa650\xa652\xa656\xa64a
 attribute lowercyrillic \x0430\x0431\x0432\x0433\x0434\x0435\x0436\x0437\x0438\x0439\x043a\x043b\x043c\x043d\x043e\x043f\x0440\x0441\x0442\x0443\x0444\x0445\x0446\x0447\x0448\x0449\x044a\x044b\x044c\x044d\x044e\x044f\x0451\x0450\x0454\x0455\x0456\x0457\x045b\x045d\x0461\x0463\x0465\x0467\x0469\x046b\x046d\x046f\x0471\x0473\x0475\x0477\x0479\x047b\x047f\x0481\x04e1\xa641\xa643\xa649\xa651\xa653\xa657\xa64b
 attribute uppergreek \x0391\x0392\x0393\x0394\x0395\x0396\x0397\x0398\x0399\x039a\x039b\x039c\x039d\x039e\x039f\x03a0\x03a1\x03a3\x03a4\x03a5\x03a6\x03a7\x03a8\x03a9
@@ -336041,6 +337821,66 @@
 after digit always 힎 0-245-135-25-356
 
 # For grade 2 versions of these dots, see ko2.ctb.
+# liblouis: German Grade 2 Braille (with capitals)
+#
+# Das System der deutschen Blindenschrift (2015/2018)
+# http://www.bskdl.org/textschrift.html
+#
+# Unlike `de-g2.utb` this table provides more detailed braille
+# representation so back-translation is more exact. This can be
+# beneficial when used with screen readers and note-takers for both
+# forward and backward translation.
+#
+# However due to the lack of consideration for back-translation of the
+# German standard for grade 2 it is nearly impossible to do proper
+# back-translation of German grade 2 braille. This table is slightly
+# better than `de-g2.utb` but it cannot in good conscience be
+# recommended for back-translation. Use it at your own risk or help
+# improving it.
+
+# Forward translation differs from that of de-g2.utb in the following ways.
+# 1. All capital letters are marked.
+# 2. Accented letters are translated using de-accents-detailed.cti
+#    to make the translation as detailed as possible.
+# 3. "`" and "´" are translated as "'" so they won't cover other
+#    characters.
+#
+# -----------
+#-name: Deutsche Kurzschrift
+#-index-name: German, contracted, with capitals
+#-display-name: German contracted braille with indication of capitals
+#
+#+language: de
+#+type: literary
+#+contraction: full
+#+grade: 2
+#+direction: forward
+#+variant: detailed
+#-has-nocross: yes
+# -----------
+#
+#  Copyright (C) 2022 SBS Schweizerische Bibliothek für Blinde, Seh- und Lesebehinderte
+#
+#  This file is part of liblouis.
+#
+#  liblouis is free software: you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as
+#  published by the Free Software Foundation, either version 2.1 of the
+#  License, or (at your option) any later version.
+#
+#  liblouis is distributed in the hope that it will be useful, but
+#  WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with liblouis. If not, see
+#  <http://www.gnu.org/licenses/>.
+#
+#-------------------------------------------------------------------------------
+
+include de-g1-detailed.ctb
+include de-g2-core.cti
 #-name: U.S. English grade 2 (EBAE)
 #-index-name: English, U.S., contracted
 #-display-name: English contracted braille as used in the U.S.
@@ -336392,6 +338232,7 @@
 #
 #  Copyright (C) 2018 SBS Schweizerische Bibliothek für Blinde, Seh- und Lesebehinderte
 #  Copyright (C) 2020 Bue Vester-Andersen
+#  Copyright (C) 2022 Bert Frees
 #
 #  This file is part of liblouis.
 #
@@ -336545,6 +338386,7 @@
 
 numsign 3456
 numericmodechars .,:
+nonumsign 6
 
 # --- Emphasis opcodes ---------------------------------------------------------
 
@@ -336555,20 +338397,30 @@
 begemphphrase italic 456-456
 endemphphrase italic after 6-3
 lenemphphrase italic 2
-begemphword italic 456
+begemphword italic 456b
 endemphword italic 6-3
+emphmodechars italic *()[]{}<>
+noemphchars italic \s\t\n\r\x00a0/-\x2013\x2014*()[]{}<>„“‚‘«»‹›.,:;!¡?¿
 
 begemphphrase bold 456-456
 endemphphrase bold after 6-3
 lenemphphrase bold 2
-begemphword bold 456
+begemphword bold 456b
 endemphword bold 6-3
+emphmodechars bold *()[]{}<>
+noemphchars bold \s\t\n\r/x00a0/-\x2013\x2014*()[]{}<>„“‚‘«»‹›.,:;!¡?¿
 
 begemphphrase underline 456-456
 endemphphrase underline after 6-3
 lenemphphrase underline 2
-begemphword underline 456
+begemphword underline 456b
 endemphword underline 6-3
+emphmodechars underline *()[]{}<>
+noemphchars underline \s\t\n\r\x00a0/-\x2013\x2014*()[]{}<>„“‚‘«»‹›.,:;!¡?¿
+
+# dot 6 required before emphasis indicator when inside word
+noback pass2 _$l@456b @6-456
+noback pass2 @456b @456
 
 emphclass downgrade
 begemphphrase downgrade 36-3
@@ -337333,6 +339185,7 @@
 numsign                       3456                         Number start indicator in text
 
 nocontractsign                56
+nonumsign                     56
 numericnocontchars     abcdefghij
 midendnumericmodechars .,:
 #  Copyright (C) 2010 Leon Ungier <Leon.Ungier@ViewPlus.com>, 
@@ -348480,6 +350333,7 @@
 numsign 3456  number sign, just a dots operand
 numericmodechars .,
 nocontractsign 6
+nonumsign 6
 numericnocontchars abcdefghij
 
 #   add numeric space indicator
@@ -348707,27 +350561,6 @@
 noback pass3 [@46]@1236 ?   # remove capital sign from o and g with accent
 noback pass3 @5 ?
 noback pass3 @45 ?
-#-index-name: Slovak
-#-display-name: Slovak braille
-
-#+language:sk
-#+type:literary
-#+grade:1
-# Marked as "direction:forward" by Bue Vester-Andersen
-# as tests only run forward.
-#+direction:forward
-
-# TODO: Please correct the metadata above. It is not meant to be
-# accurate nor complete. It hasn't been verified by the table
-# author yet. It is merely an attempt by the liblouis maintainers
-# to get some sensible initial values in place.
-
-# TODO: Please add a reference to official documentation about
-# the implemented braille code. Preferably submit the documents
-# to https://github.com/liblouis/braille-specs.
-
-include sk-g1.ctb
-include braille-patterns.cti
 # This table contains braille codes and rules for Sinhala script.
 #
 # Copyright (C) 2017 Access to success, 248/1 A, New city land, Kadawela, Katana, Sri Lanka
@@ -415019,6 +416852,7 @@
 include litdigits6Dots.uti
 numericmodechars .,
 nocontractsign 6
+nonumsign 6
 numericnocontchars abcdefghij
 
 endnum \s% 3456-245-356 Postotak
@@ -415277,23 +417111,6 @@
 literal .zipx
 
 
-#-name: Norsk litterær punktskrift, 6-punkt, kortskrift nivå 3
-#-index-name: Norwegian, contracted, grade 3
-#-display-name: Norwegian grade 3 contracted braille
-#
-#+language:no
-#+language:nb
-#+language:nn
-#+type:literary
-#+direction:both
-#+dots:6
-#+contraction:full
-#+grade:3
-#
-#-unicode-form:nfc
-
-include no-no-g3.ctb
-include braille-patterns.cti
 #-index-name: Romanian, computer
 #-display-name: Romanian computer braille
 
@@ -415379,7 +417196,7 @@
 # choice between a table with indication of capital letters a table without.
 
 # Symbol attributes for special rules below (duplicated in ru-litbrl.ctb):
-attribute upperlatin ABCDEFGHIJKLMNOPQRSTUVWXYZ\x00c0\x00c1\x00c2\x00c4\x00c7\x00c8\x00c9\x00ca\x00cd\x00ce\x00d3\x00d4\x00d6\x00da\x00db\x00dc\x00dd\x0106\x0139\x0143\x0152\x0154\x015a\x0179\x01f4\x1e30\x1e3e\x1e54\x1e82\x1e9e
+attribute upperlatin ABCDEFGHIJKLMNOPQRSTUVWXYZ\x00c0\x00c1\x00c2\x00c4\x00c6\x00c7\x00c8\x00c9\x00ca\x00cb\x00cd\x00ce\x00cf\x00d3\x00d4\x00d6\x00d9\x00da\x00db\x00dc\x00dd\x0106\x0139\x0143\x0152\x0154\x015a\x0179\x01f4\x1e30\x1e3e\x1e54\x1e82\x1e9e
 attribute uppercyrillic \x0401\x0410\x0411\x0412\x0413\x0414\x0415\x0416\x0417\x0418\x0419\x041a\x041b\x041c\x041d\x041e\x041f\x0420\x0421\x0422\x0423\x0424\x0425\x0426\x0427\x0428\x0429\x042a\x042b\x042c\x042d\x042e\x042f\x0400\x0404\x0405\x0406\x0407\x040b\x040d\x0460\x0462\x0464\x0466\x0468\x046a\x046c\x046e\x0470\x0472\x0474\x0476\x0478\x047a\x047e\x0480\x04e0\xa640\xa642\xa648\xa650\xa652\xa656\xa64a
 attribute uppergreek \x0391\x0392\x0393\x0394\x0395\x0396\x0397\x0398\x0399\x039a\x039b\x039c\x039d\x039e\x039f\x03a0\x03a1\x03a3\x03a4\x03a5\x03a6\x03a7\x03a8\x03a9
 
@@ -441985,10 +443802,9 @@
 # author yet. It is merely an attempt by the liblouis maintainers
 # to get some sensible initial values in place.
 #
-# TODO: Please add a reference to official documentation about
-# the implemented braille code. Preferably submit the documents
-# to https://github.com/liblouis/braille-specs.
-# -----------
+# This table implements the specifications for Braille Computer
+# Notation from the UK Association for Accessible Formats, see
+# https://www.ukaaf.org/wp-content/uploads/2020/03/Braille-Computer-Notation-PDF.pdf
 #
 #  Copyright (C) 2013 David Reynolds <dkreynolds@ntlworld.com>
 #
@@ -442029,7 +443845,7 @@
 math = 2356
 math > 356
 sign % 46
-math + 236
+math + 235
 math ~ 14567
 sign ` 3457
 sign £ 467
@@ -442930,6 +444746,7 @@
 #  liblouis: German Grade 2 Braille
 #
 #  Copyright (C) 2018 SBS Schweizerische Bibliothek für Blinde, Seh- und Lesebehinderte
+#  Copyright (C) 2022 Bert Frees
 #
 #  This file is part of liblouis.
 #
@@ -442986,52 +444803,149 @@
 always \s(!      0-2356-6-235
 always \s[!      0-6-2356-6-235
 
+# The letsign opcode makes sure that single letters that are also a
+# contraction are indicated with dot 6. However if these single
+# letters are followed by a single quote this mechanism doesn't work.
+# But dot 6 is also needed in that situation
+word	a'	6-1-6
+word	b'	6-12-6
+word	d'	6-145-6
+word	e'	6-15-6
+word	f'	6-124-6
+word	g'	6-1245-6
+word	i'	6-24-6
+word	j'	6-245-6
+word	k'	6-13-6
+word	l'	6-123-6
+word	m'	6-134-6
+word	n'	6-1345-6
+word	o'	6-135-6
+word	p'	6-1234-6
+word	r'	6-1235-6
+word	t'	6-2345-6
+word	u'	6-136-6
+word	v'	6-1236-6
+word	w'	6-2456-6
+word	z'	6-1356-6
+
 include de-g2-core-patterns.dic
 
+# Attach virtual dot "a" and letter attribute to contraction
+# patterns. Private use area U+F800 - U+F83F is used to represent
+# these.
+
+letter  \xF804  3a             # . #
+letter  \xF806  23a            # ; #
+letter  \xF80C  34a            # \ #
+letter  \xF812  25a            # : #
+letter  \xF814  35a            # * #
+letter  \xF816  235a           # + #
+letter  \xF818  45a            # > #
+letter  \xF81C  345a           # @ #
+letter  \xF821  16a            # 1 #
+letter  \xF822  26a            # ? #
+letter  \xF823  126a           # 2 #
+letter  \xF824  36b            # - #
+letter  \xF826  236a           # ( #
+letter  \xF828  46a            # $ #
+letter  \xF829  146a           # 3 #
+letter  \xF82A  246a           # 9 #
+letter  \xF82B  1246a          # 6 #
+letter  \xF82C  346a           # 0 #
+letter  \xF82E  2346a          # ^ #
+letter  \xF82F  12346a         # & #
+letter  \xF830  56a            # < #
+letter  \xF831  156a           # 5 #
+letter  \xF832  256a           # / #
+letter  \xF833  1256a          # 8 #
+letter  \xF834  356a           # ) #
+letter  \xF836  2356a          # = #
+letter  \xF837  12356a         # [ #
+letter  \xF838  456a           # _ #
+letter  \xF839  1456a          # 4 #
+letter  \xF83B  12456a         # 7 #
+letter  \xF83C  3456a          # # #
+letter  \xF83E  23456a         # ] #
+letter  \xF83F  123456a        # % #
+
+noback pass2 @3a @3
+noback pass2 @23a @23
+noback pass2 @34a @34
+noback pass2 @25a @25
+noback pass2 @35a @35
+noback pass2 @235a @235
+noback pass2 @45a @45
+noback pass2 @345a @345
+noback pass2 @16a @16
+noback pass2 @26a @26
+noback pass2 @126a @126
+noback pass2 @36b @36a
+noback pass2 @236a @236
+noback pass2 @46a @46
+noback pass2 @146a @146
+noback pass2 @246a @246
+noback pass2 @1246a @1246
+noback pass2 @346a @346
+noback pass2 @2346a @2346
+noback pass2 @12346a @12346
+noback pass2 @56a @56
+noback pass2 @156a @156
+noback pass2 @256a @256
+noback pass2 @1256a @1256
+noback pass2 @356a @356
+noback pass2 @2356a @2356
+noback pass2 @12356a @12356
+noback pass2 @456a @456
+noback pass2 @1456a @1456
+noback pass2 @12456a @12456
+noback pass2 @3456a @3456
+noback pass2 @23456a @23456
+noback pass2 @123456a @123456
+
 word       aber       1                            #  A  #
 nocross always    aber       2-1                          #  ,A  #
-nocross midendword    ach        56                           #  <  #
-nocross begmidword    al         25                           #  :  #
-nocross always    al'        25-6                         #  :'  #
+nocross midendword    ach        56a                           #  <  #
+nocross begmidword    al         25a                           #  :  #
+nocross always    al'        25a-6                         #  :'  #
 prfword    all        1-12345                      #  AQ  #
 nocross always    all        1                            #  A  #
-word       als        146                          #  3  #
+word       als        146a                          #  3  #
 nocross always    also       1-135                        #  AO  #
 endword    an         1-1345                       #  AN  #
-nocross always    an         235                          #  +  #
-nocross always    an'        235-6                        #  +'  #
-nocross always    ander      2-12456                      #  ,7  #
-nocross begmidword    ar         356                          #  )  #
-nocross always    ar'        356-6                        #  )'  #
-nocross always    arbeit     356-12                       #  )B  #
+nocross always    an         235a                          #  +  #
+nocross always    an'        235a-6                        #  +'  #
+nocross always    ander      2-12456a                      #  ,7  #
+nocross begmidword    ar         356a                          #  )  #
+nocross always    ar'        356a-6                        #  )'  #
+nocross always    arbeit     356a-12                       #  )B  #
 nocross always    ation      5-1345                       #  !N  #
 nocross always    ativ       5-1236                       #  !V  #
-word       au         6-16                         #  '1  #
-nocross always    au         16                           #  1  #
-word       auch       34                           #  \  #
-word       auf        16                           #  1  #
-nocross always    auf        2-16                         #  ,1  #
-nocross begmidword    be         23                           #  ;  #
-nocross always    be'        23-6                         #  ;'  #
+word       au         6-16a                         #  '1  #
+nocross always    au         16a                           #  1  #
+word       auch       34a                           #  \  #
+word       auf        16a                           #  1  #
+nocross always    auf        2-16a                         #  ,1  #
+nocross begmidword    be         23a                           #  ;  #
+nocross always    be'        23a-6                         #  ;'  #
 word       bei        12                           #  B  #
 nocross always    bei        2-12                         #  ,B  #
 nocross always    beid       12-145                       #  BD  #
 word       beim       12-134                       #  BM  #
-nocross always    besonder   23                           #  ;  #
+nocross always    besonder   23a                           #  ;  #
 nocross always    besser     234-234                      #  SS  #
 nocross always    bis        12-234                       #  BS  #
-nocross always    bist       12-23456                     #  B]  #
+nocross always    bist       12-23456a                     #  B]  #
 nocross always    bleib      12-12                        #  BB  #
-nocross always    brauch     2-34                         #  ,\  #
+nocross always    brauch     2-34a                         #  ,\  #
 nocross always    brief      12-124                       #  BF  #
 nocross always    bring      12-1245                      #  BG  #
-nocross always    bräuch     5-34                         #  !\  #
+nocross always    bräuch     5-34a                         #  !\  #
 always     c          6-14                         #  'C  #
 always     C          6-14                         #  'C  #
-word       ch         6-1456                       #  '4  #
-nocross always    ch         1456                         #  4  #
-nocross always    charakter  1456-13                      #  4K  #
-nocross midendword    ck         46                           #  $  #
+word       ch         6-1456a                       #  '4  #
+nocross always    ch         1456a                         #  4  #
+nocross always    charakter  1456a-13                      #  4K  #
+nocross midendword    ck         46a                           #  $  #
 nocross always    dabei      145-12                       #  DB  #
 word       dadurch    145-145                      #  DD  #
 nocross always    dafür      145-124                      #  DF  #
@@ -443039,55 +444953,55 @@
 nocross always    daher      145-125                      #  DH  #
 nocross always    damit      145-134                      #  DM  #
 nocross always    dank       145-13                       #  DK  #
-nocross always    darauf     145-16                       #  D1  #
-nocross always    darüber    145-1256                     #  D8  #
+nocross always    darauf     145-16a                       #  D1  #
+nocross always    darüber    145-1256a                     #  D8  #
 word       das        145                          #  D  #
-word       dass       2346                         #  ^  #
+word       dass       2346a                         #  ^  #
 nocross always    davon      145-1236                     #  DV  #
 nocross always    dazu       145-1356                     #  DZ  #
-word       dem        12356                        #  [  #
-nocross always    dem        2-12356                      #  ,[  #
+word       dem        12356a                        #  [  #
+nocross always    dem        2-12356a                      #  ,[  #
 nocross always    demokrat   145-2345                     #  DT  #
 word       den        15                           #  E  #
 word       denen      15-14                        #  EC  #
 nocross always    denn       145-1345                     #  DN  #
 word       der        1235                         #  R  #
-word       des        3                            #  .  #
-nocross always    dessen     145-2346                     #  D^  #
-nocross always    deutsch    145-156                      #  D5  #
-word       die        346                          #  0  #
-nocross begmidword    dies       346                          #  0  #
+word       des        3a                            #  .  #
+nocross always    dessen     145-2346a                     #  D^  #
+nocross always    deutsch    145-156a                      #  D5  #
+word       die        346a                          #  0  #
+nocross begmidword    dies       346a                          #  0  #
 word       dir        145-1235                     #  DR  #
-word       doch       145-1456                     #  D4  #
-nocross always    druck      145-46                       #  D$  #
-nocross always    drück      5-145-46                     #  !D$  #
-word       durch      1456                         #  4  #
-nocross always    durch      2-1456                       #  ,4  #
+word       doch       145-1456a                     #  D4  #
+nocross always    druck      145-46a                       #  D$  #
+nocross always    drück      5-145-46a                     #  !D$  #
+word       durch      1456a                         #  4  #
+nocross always    durch      2-1456a                       #  ,4  #
 nocross always    dürf       2-145                        #  ,D  #
 nocross always    ebenso     15-135                       #  EO  #
-nocross midword    eh         2356                         #  =  #
-word       ei         6-146                        #  '3  #
-nocross always    ei         146                          #  3  #
-nocross always    ein        1246                         #  6  #
-nocross always    einander   2-1246                       #  ,6  #
+nocross midword    eh         2356a                         #  =  #
+word       ei         6-146a                        #  '3  #
+nocross always    ei         146a                          #  3  #
+nocross always    ein        1246a                         #  6  #
+nocross always    einander   2-1246a                       #  ,6  #
 nocross always    el         13456                        #  Y  #
-nocross always    em         12356                        #  [  #
+nocross always    em         12356a                        #  [  #
 nocross always    en         14                           #  C  #
-nocross begword   ent        2346                         #  ^  #
+nocross begword   ent        2346a                         #  ^  #
 word       eo         6-15-135                     #  'EO  #
-nocross always    er         12456                        #  7  #
-nocross always    es         123456                       #  %  #
+nocross always    er         12456a                        #  7  #
+nocross always    es         123456a                       #  %  #
 nocross always    etwa       15-1                         #  EA  #
 nocross always    etwas      2345-2456                    #  TW  #
-word       eu         6-126                        #  '2  #
-nocross always    eu         126                          #  2  #
+word       eu         6-126a                        #  '2  #
+nocross always    eu         126a                          #  2  #
 begword    ex         1346                         #  X  #
 nocross always    'ex        6-15-6-1346                  #  'E'X  #
 nocross always    fall       124-12345                    #  FQ  #
 nocross always    fahr       2-1235                       #  ,R  #
 word       falls      124-12345-234                #  FQS  #
 nocross midendword    falls      124                          #  F  #
-nocross always    fertig     124-45                       #  F>  #
+nocross always    fertig     124-45a                       #  F>  #
 nocross always    folg       124-1245                     #  FG  #
 nocross always    freund     124-145                      #  FD  #
 nocross always    fäll       5-124-12345                  #  !FQ  #
@@ -443096,32 +445010,32 @@
 word       für        124                          #  F  #
 nocross always    für        2-124                        #  ,F  #
 nocross always    ganz       1245-1356                    #  GZ  #
-nocross always    ge         12346                        #  &  #
+nocross always    ge         12346a                        #  &  #
 word       gegen      1245                         #  G  #
 nocross always    gegen      2-1245                       #  ,G  #
 nocross always    gegenwart  1245-2456                    #  GW  #
 nocross always    gegenwärt  5-1245-2456                  #  !GW  #
-nocross always    gegenüber  1245-1256                    #  G8  #
+nocross always    gegenüber  1245-1256a                    #  G8  #
 nocross always    gelegen       1245-1245                    #  GG  #
 nocross always    geschäft     1245-124                     #  GF  #
-nocross always    gesellschaft  1245-156                  #  G5  #
-word       gewesen    12346                        #  &  #
-nocross always    gewesen    2-12346                      #  ,&  #
-nocross always    geworden   12346-2456                   #  &W  #
+nocross always    gesellschaft  1245-156a                  #  G5  #
+word       gewesen    12346a                        #  &  #
+nocross always    gewesen    2-12346a                      #  ,&  #
+nocross always    geworden   12346a-2456                   #  &W  #
 nocross always    gibt       1245-12                      #  GB  #
-nocross always    gleich     1245-1456                    #  G4  #
-nocross always    glück      1245-46                      #  G$  #
-nocross always    gross      1245-2346                    #  G^  #
-nocross always    groß       1245-2346                    #  G^  #
+nocross always    gleich     1245-1456a                    #  G4  #
+nocross always    glück      1245-46a                      #  G$  #
+nocross always    gross      1245-2346a                    #  G^  #
+nocross always    groß       1245-2346a                    #  G^  #
 nocross always    grund      1245-145                     #  GD  #
-nocross always    gröss      5-1245-2346                  #  !G^  #
-nocross always    größ       5-1245-2346                  #  !G^  #
+nocross always    gröss      5-1245-2346a                  #  !G^  #
+nocross always    größ       5-1245-2346a                  #  !G^  #
 nocross always    gründ      5-1245-145                   #  !GD  #
 nocross always    gänz       5-1245-1356                  #  !GZ  #
 nocross always    hab        2-125                        #  ,H  #
 nocross always    haft       125-124                      #  HF  #
 nocross always    hand       125-145                      #  HD  #
-nocross always    hast       125-23456                    #  H]  #
+nocross always    hast       125-23456a                    #  H]  #
 nocross always    hat        125-2345                     #  HT  #
 nocross always    hatt       125                          #  H  #
 nocross always    haupt      125-1234                     #  HP  #
@@ -443133,46 +445047,46 @@
 nocross always    hoff       124-124                      #  FF  #
 nocross always    häft       5-125-124                    #  !HF  #
 nocross always    händ       5-125-145                    #  !HD  #
-nocross always    hätt       345                          #  @  #
+nocross always    hätt       345a                          #  @  #
 nocross always    häupt      5-125-1234                   #  !HP  #
-nocross always    ich        3456                         #  #  #
-word       ich,       24-1456-2                    #  I4,  #
-word       ich;       24-1456-23                   #  I4;  #
-word       ich:       24-1456-25                   #  I4:  #
-word       ich?       24-1456-26                   #  I4?  #
-word       ich!       24-1456-235                  #  I4+  #
-word       ich)       24-1456-2356                 #  I4=  #
-word       ich"       24-1456-356                  #  I4)  #
-word       ich«       24-1456-356                  #  I4)  #
-word       ich»       24-1456-356                  #  I4)  #
-word       ich┊       24-1456-abcdef               #  I4x  #
-nocross midendword    ie         346                          #  0  #
-nocross midendword    ig         45                           #  >  #
-word       ihm        236                          #  (  #
+nocross always    ich        3456a                         #  #  #
+word       ich,       24-1456a-2                    #  I4,  #
+word       ich;       24-1456a-23                   #  I4;  #
+word       ich:       24-1456a-25                   #  I4:  #
+word       ich?       24-1456a-26                   #  I4?  #
+word       ich!       24-1456a-235                  #  I4+  #
+word       ich)       24-1456a-2356                 #  I4=  #
+word       ich"       24-1456a-356                  #  I4)  #
+word       ich«       24-1456a-356                  #  I4)  #
+word       ich»       24-1456a-356                  #  I4)  #
+word       ich┊       24-1456a-abcdef               #  I4x  #
+nocross midendword    ie         346a                          #  0  #
+nocross midendword    ig         45a                           #  >  #
+word       ihm        236a                          #  (  #
 nocross always    ihn        24-125                       #  IH  #
 nocross sufword   ihr        24                           #  I  #
-word       im         36a                          #  -  #
+word       im         36b                          #  -  #
 word       immer      1346                         #  X  #
 nocross always    immer      2-1346                       #  ,X  #
-nocross always    in         35                           #  *  #
-nocross always    interess   2-35                         #  ,*  #
+nocross always    in         35a                           #  *  #
+nocross always    interess   2-35a                         #  ,*  #
 nocross always    irgend     24-1245                      #  IG  #
 nocross always    ismus      5-24                         #  !I  #
-word       ist        23456                        #  ]  #
-nocross always    istisch    5-156                        #  !5  #
-nocross always    ität       5-345                        #  !@  #
+word       ist        23456a                        #  ]  #
+nocross always    istisch    5-156a                        #  !5  #
+nocross always    ität       5-345a                        #  !@  #
 nocross always    jahr       245-1235                     #  JR  #
 nocross always    jahrhundert 245-125                     #  JH  #
 nocross always    jahrtausend 245-2345                    #  JT  #
 nocross always    jahrzehnt  245-1356                     #  JZ  #
 nocross always    jed        245-145                      #  JD  #
-word       jedoch     245-1456                     #  J4  #
-nocross always    jetzig     245-45                       #  J>  #
+word       jedoch     245-1456a                     #  J4  #
+nocross always    jetzig     245-45a                       #  J>  #
 word       jetzt      245                          #  J  #
 nocross always    jetzt      2-245                        #  ,J  #
 nocross always    jähr       5-245-1235                   #  !JR  #
 word       kann       13                           #  K  #
-nocross always    kannst     13-23456                     #  K]  #
+nocross always    kannst     13-23456a                     #  K]  #
 nocross always    kapital    13-1234                      #  KP  #
 nocross midendword keit      13                           #  K  #
 nocross always    komm       13-1346                      #  KX  #
@@ -443186,20 +445100,20 @@
 nocross always    lang       123-1245                     #  LG  #
 nocross always    lass       2-123                        #  ,L  #
 nocross always    leb        123-12                       #  LB  #
-nocross always    leicht     123-1456                     #  L4  #
+nocross always    leicht     123-1456a                     #  L4  #
 nocross always    letzt      123-2345                     #  LT  #
-nocross midendword lich      456                          #  _  #
+nocross midendword lich      456a                          #  _  #
 nocross midendword ll        12345                        #  Q  #
 nocross always    läng       5-123-1245                   #  !LG  #
 nocross always    läss       5-123                        #  !L  #
 word       lässt      123                          #  L  #
 nocross midendword mal       134                          #  M  #
 word       man        134                          #  M  #
-nocross always    maschin    134-156                      #  M5  #
+nocross always    maschin    134-156a                      #  M5  #
 nocross always    material   134-123                      #  ML  #
 nocross always    materiell  134-12345                    #  MQ  #
-word       mehr       2356                         #  =  #
-nocross always    mehr       2-2356                       #  ,=  #
+word       mehr       2356a                         #  =  #
+nocross always    mehr       2-2356a                       #  ,=  #
 nocross always    mir        134-1235                     #  MR  #
 word       mit        2345                         #  T  #
 nocross always    mit        2-2345                       #  ,T  #
@@ -443210,41 +445124,41 @@
 word       mrs        6-134-1235-234               #  'MRS  #
 always     mrs.       134-1235-234-3               #  MRS.  #
 nocross always    musik      134-13                       #  MK  #
-nocross always    muss       134-2346                     #  M^  #
-nocross begmidword möcht     1456                         #  4  #
-nocross always    mög        2-246                        #  ,9  #
-nocross always    möglich    134-456                      #  M_  #
+nocross always    muss       134-2346a                     #  M^  #
+nocross begmidword möcht     1456a                         #  4  #
+nocross always    mög        2-246a                        #  ,9  #
+nocross always    möglich    134-456a                      #  M_  #
 nocross always    müss       2-134                        #  ,M  #
 nocross always    nachdem    1345-145                     #  ND  #
 nocross always    nahm       1345-134                     #  NM  #
 nocross always    natur      1345-2345                    #  NT  #
-nocross always    natürlich  1345-456                     #  N_  #
+nocross always    natürlich  1345-456a                     #  N_  #
 nocross always    neben      1345-12                      #  NB  #
 nocross always    nehm       1345-125                     #  NH  #
 word       nicht      1345                         #  N  #
 nocross always    nicht      2-1345                       #  ,N  #
 nocross always    nichts     1345-234                     #  NS  #
 nocross midendword nis       1346                         #  X  #
-nocross always    noch       1345-1456                    #  N4  #
+nocross always    noch       1345-1456a                    #  N4  #
 nocross always    nommen     1345-1346                    #  NX  #
 nocross always    notwendig  1345-2456                    #  NW  #
 nocross always    nur        1345-1235                    #  NR  #
 nocross always    nutz       1345-1356                    #  NZ  #
-nocross always    nächst     1345-23456                   #  N]  #
+nocross always    nächst     1345-23456a                   #  N]  #
 nocross always    nähm       5-1345-134                   #  !NM  #
 nocross always    nütz       5-1345-1356                  #  !NZ  #
 word       oder       135                          #  O  #
 nocross always    ohne       135-15                       #  OE  #
-nocross begmidword or        26                           #  ?  #
-nocross always    or'        26-6                         #  ?'  #
+nocross begmidword or        26a                           #  ?  #
+nocross always    or'        26a-6                         #  ?'  #
 nocross always    paragraf   1234-1245                    #  PG  #
 nocross always    person     1234-1345                    #  PN  #
 nocross always    persön     5-1234-1345                  #  !PN  #
 nocross always    platz      1234-1356                    #  PZ  #
 nocross always    plätz      5-1234-1356                  #  !PZ  #
-nocross always    plötzlich  1234-456                     #  P_  #
+nocross always    plötzlich  1234-456a                     #  P_  #
 nocross always    politik    1234-13                      #  PK  #
-nocross always    politisch  1234-156                     #  P5  #
+nocross always    politisch  1234-156a                     #  P5  #
 begword    pro        12345                        #  Q  #
 nocross always    prou       1234-1235-135-136            #  PROU  #
 nocross always    punkt      1234-2345                    #  PT  #
@@ -443255,27 +445169,27 @@
 nocross always    regier     1235-1245                    #  RG  #
 nocross always    rehabilit  1235-12                      #  RB  #
 nocross always    republik   1235-13                      #  RK  #
-nocross always    richt      2-3456                       #  ,#  #
-nocross always    rück       1235-46                      #  R$  #
+nocross always    richt      2-3456a                       #  ,#  #
+nocross always    rück       1235-46a                      #  R$  #
 nocross always    sag        234-1245                     #  SG  #
-nocross midendword sam       2346                         #  ^  #
+nocross midendword sam       2346a                         #  ^  #
 nocross always    satz       234-1356                     #  SZ  #
-word       sch        6-156                        #  '5  #
-nocross always    sch        156                          #  5  #
-sufword    schaft     156-1-124-2345               #  5AFT  #
-nocross midendword schaft    156                          #  5  #
-nocross always    schlag     156-1245                     #  5G  #
-nocross always    schliess   156-2346                     #  5^  #
-nocross always    schließ    156-2346                     #  5^  #
-nocross always    schläg     5-156-1245                   #  !5G  #
-word       schon      156                          #  5  #
-nocross always    schreib    156-12                       #  5B  #
-nocross always    schrieb    2-156                        #  ,5  #
-nocross always    schrift    156-2345                     #  5T  #
-nocross always    schwierig  156-45                       #  5>  #
+word       sch        6-156a                        #  '5  #
+nocross always    sch        156a                          #  5  #
+sufword    schaft     156a-1-124-2345               #  5AFT  #
+nocross midendword schaft    156a                          #  5  #
+nocross always    schlag     156a-1245                     #  5G  #
+nocross always    schliess   156a-2346a                     #  5^  #
+nocross always    schließ    156a-2346a                     #  5^  #
+nocross always    schläg     5-156a-1245                   #  !5G  #
+word       schon      156a                          #  5  #
+nocross always    schreib    156a-12                       #  5B  #
+nocross always    schrieb    2-156a                        #  ,5  #
+nocross always    schrift    156a-2345                     #  5T  #
+nocross always    schwierig  156a-45a                       #  5>  #
 nocross always    sehr       234-1235                     #  SR  #
-nocross sufword   sein       246                          #  9  #
-nocross always    selbst     234-23456                    #  S]  #
+nocross sufword   sein       246a                          #  9  #
+nocross always    selbst     234-23456a                    #  S]  #
 nocross always    setz       2-15                         #  ,E  #
 word       sich       14                           #  C  #
 word       sie        234                          #  S  #
@@ -443283,39 +445197,39 @@
 nocross always    sitz       2-24                         #  ,I  #
 word       so         1234                         #  P  #
 nocross always    so         2-1234                       #  ,P  #
-nocross always    solch      234-1456                     #  S4  #
+nocross always    solch      234-1456a                     #  S4  #
 nocross always    soll       2-234                        #  ,S  #
 nocross always    sondern    234-1345                     #  SN  #
 nocross always    sozial     234-123                      #  SL  #
-nocross always    spiel      2-346                        #  ,0  #
-nocross always    sprech     2-2346                       #  ,^  #
+nocross always    spiel      2-346a                        #  ,0  #
+nocross always    sprech     2-2346a                       #  ,^  #
 word       ss         6-234-234                    #  'SS  #
-nocross midendword ss        2346                         #  ^  #
+nocross midendword ss        2346a                         #  ^  #
 word       st         234-2345                     #  ST  #
-nocross always    st         23456                        #  ]  #
-nocross always    staat      23456-2345                   #  ]T  #
-nocross always    stand      2-23456                      #  ,]  #
+nocross always    st         23456a                        #  ]  #
+nocross always    staat      23456a-2345                   #  ]T  #
+nocross always    stand      2-23456a                      #  ,]  #
 nocross always    stell      2-13456                      #  ,Y  #
-nocross always    stets      23456-234                    #  ]S  #
-nocross always    ständ      5-23456                      #  !]  #
+nocross always    stets      23456a-234                    #  ]S  #
+nocross always    ständ      5-23456a                      #  !]  #
 nocross always    säg        5-234-1245                   #  !SG  #
 nocross always    sätz       5-234-1356                   #  !SZ  #
-nocross midendword te        236                          #  (  #
+nocross midendword te        236a                          #  (  #
 nocross always    technik    2345-13                      #  TK  #
-nocross always    technisch  2345-156                     #  T5  #
+nocross always    technisch  2345-156a                     #  T5  #
 nocross always    trag       2345-1245                    #  TG  #
 nocross always    treff      2345-124                     #  TF  #
 nocross always    trotz      2345-1356                    #  TZ  #
 nocross always    träg       5-2345-1245                  #  !TG  #
 word       tz         6-2345-1356                  #  'TZ  #
 word       un         136-1345                     #  UN  #
-nocross always    un         256                          #  /  #
+nocross always    un         256a                          #  /  #
 word       und        136                          #  U  #
 nocross always    und        2-136                        #  ,U  #
 nocross midendword ung       136                          #  U  #
-word       unter      256                          #  /  #
-nocross always    unter      2-256                        #  ,/  #
-nocross begword   ver        36a                          #  -  #
+word       unter      256a                          #  /  #
+nocross always    unter      2-256a                        #  ,/  #
+nocross begword   ver        36b                          #  -  #
 nocross always    verhältnis 1236-125                     #  VH  #
 nocross always    viel       1236-123                     #  VL  #
 nocross always    vielleicht 1236-2345                    #  VT  #
@@ -443325,42 +445239,42 @@
 nocross always    vom        1236-134                     #  VM  #
 word       von        1236                         #  V  #
 nocross always    von        2-1236                       #  ,V  #
-word       vor        26                           #  ?  #
-nocross always    vor        2-26                         #  ,?  #
+word       vor        26a                           #  ?  #
+nocross always    vor        2-26a                         #  ,?  #
 nocross always    völk       5-1236-13                    #  !VK  #
 nocross always    völl       5-12345                      #  !Q  #
 nocross always    wahr       2456-125                     #  WH  #
-nocross sufword   war        356                          #  )  #
+nocross sufword   war        356a                          #  )  #
 word       was        2456                         #  W  #
 nocross always    weg        2456-1245                    #  WG  #
 nocross always    weit       2456-2345                    #  WT  #
-nocross always    weis       2-146                        #  ,3  #
+nocross always    weis       2-146a                        #  ,3  #
 nocross always    welch      13456                        #  Y  #
-nocross always    wenig      2456-45                      #  W>  #
+nocross always    wenig      2456-45a                      #  W>  #
 nocross always    wenn       2456-1345                    #  WN  #
 nocross always    werd       2-2456                       #  ,W  #
-nocross always    wesentlich 2456-456                     #  W_  #
-nocross always    wiss       2456-2346                    #  W^  #
-word       wie        126                          #  2  #
-nocross always    wie        2-126                        #  ,2  #
-nocross always    wieder     346-145                      #  0D  #
+nocross always    wesentlich 2456-456a                     #  W_  #
+nocross always    wiss       2456-2346a                    #  W^  #
+word       wie        126a                          #  2  #
+nocross always    wie        2-126a                        #  ,2  #
+nocross always    wieder     346a-145                      #  0D  #
 nocross always    will       2456-12345                   #  WQ  #
 nocross always    wir        2456-1235                    #  WR  #
 nocross always    wird       2456-145                     #  WD  #
 nocross always    wirk       2456-13                      #  WK  #
-nocross always    wirst      2456-23456                   #  W]  #
-nocross always    wirtschaft 2456-156                     #  W5  #
+nocross always    wirst      2456-23456a                   #  W]  #
+nocross always    wirtschaft 2456-156a                     #  W5  #
 nocross always    wohl       2456-123                     #  WL  #
 nocross always    woll       2-135                        #  ,O  #
 nocross always    worden     135-14                       #  OC  #
 word       wurd       2456-136-1235-145            #  WURD  #
 nocross always    wurd       136                          #  U  #
 nocross always    währ       5-2456-125                   #  !WH  #
-nocross always    während    345-145                      #  @D  #
-nocross always    wär        5-356                        #  !)  #
+nocross always    während    345a-145                      #  @D  #
+nocross always    wär        5-356a                        #  !)  #
 endword    wärts      2456                         #  W  #
-word       würd       2456-1256-1235-145           #  W8RD  #
-nocross always    würd       1256                         #  8  #
+word       würd       2456-1256a-1235-145           #  W8RD  #
+nocross always    würd       1256a                         #  8  #
 always     x          6-1346                       #  'X  #
 always     X          6-1346                       #  'X  #
 always     y          6-13456                      #  'Y  #
@@ -443372,20 +445286,20 @@
 nocross always    zum        1356-134                     #  ZM  #
 nocross always    zunächst   1356-1345                    #  ZN  #
 nocross always    zur        1356-1235                    #  ZR  #
-nocross always    zurück     1356-46                      #  Z$  #
+nocross always    zurück     1356-46a                      #  Z$  #
 nocross always    zusammen   1356-234                     #  ZS  #
 nocross always    zwischen   1356-2456                    #  ZW  #
 nocross always    zähl       5-1356-123                   #  !ZL  #
-always     ß          6-2346                       #  '^  #
-nocross always    ähnlich    345-456                      #  @_  #
-nocross always    änder      5-12456                      #  !7  #
-nocross always    äu         34                           #  \  #
-nocross always    öffentlich 246-456                      #  9_  #
-word       über       1256                         #  8  #
-nocross always    über       2-1256                       #  ,8  #
-word       überhaupt  1256-125                     #  8H  #
-nocross always    übrig      1256-45                      #  8>  #
-endword    'st        6-23456                      #  ']  #
+always     ß          6-2346a                       #  '^  #
+nocross always    ähnlich    345a-456a                      #  @_  #
+nocross always    änder      5-12456a                      #  !7  #
+nocross always    äu         34a                           #  \  #
+nocross always    öffentlich 246a-456a                      #  9_  #
+word       über       1256a                         #  8  #
+nocross always    über       2-1256a                       #  ,8  #
+word       überhaupt  1256a-125                     #  8H  #
+nocross always    übrig      1256a-45a                      #  8>  #
+endword    'st        6-23456a                      #  ']  #
 
 # Ausnahmen:
 word       'e         6-6-15                         #  ''E  #
@@ -443394,48 +445308,48 @@
 word       'n         6-6-1345                       #  ''N  #
 word       's         6-6-234                        #  ''S  #
 word       't         6-6-2345                       #  ''T  #
-word       'tsch      6-6-2345-156                   #  ''T5  #
+word       'tsch      6-6-2345-156a                   #  ''T5  #
 word       'u         6-6-136                        #  ''U  #
 word       ao         6-1-135                        #  'AO  #
 word       aos        6-1-135-234                    #  'AOS  #
-word       che        6-1456-15                      #  '4E  #
-word       chen       6-1456-14                      #  '4C  #
-word       chens      6-1456-14-234                  #  '4CS  #
-word       chet       6-1456-15-2345                 #  '4ET  #
-word       chets      6-1456-15-2345-234             #  '4ETS  #
-word       dau        6-145-16                       #  'D1  #
+word       che        6-1456a-15                      #  '4E  #
+word       chen       6-1456a-14                      #  '4C  #
+word       chens      6-1456a-14-234                  #  '4CS  #
+word       chet       6-1456a-15-2345                 #  '4ET  #
+word       chets      6-1456a-15-2345-234             #  '4ETS  #
+word       dau        6-145-16a                       #  'D1  #
 word       dirs       145-1235-234                   #  DRS  #
 word       dr         6-145-1235                     #  'DR  #
 always     dr.        145-1235-3                     #  DR.  #
-word       dschem     6-145-156-12356                #  'D5[  #
-word       dü         6-145-1256                    #   'D8  #
-word       gsch       6-1245-156                     #  'G5  #
+word       dschem     6-145-156a-12356a                #  'D5[  #
+word       dü         6-145-1256a                    #   'D8  #
+word       gsch       6-1245-156a                     #  'G5  #
 word       he'd       125-15-6-145                   #  HE'D  #
 word       he's       125-15-6-234                   #  HE'S  #
 word       ih         6-24-125                       #  'IH  #
 word       ihm's      24-125-134-6-234               #  IHM'S  #
-word       mal'ach    134-25-6-1-1456                #  M:'A4  #
-word       mal'n      134-25-6-1345                  #  M:'N  #
+word       mal'ach    134-25a-6-1-1456a                #  M:'A4  #
+word       mal'n      134-25a-6-1345                  #  M:'N  #
 word       mlle       6-134-12345-15                 #  'MQE  #
 word       nta        6-1345-2345-1                  #  'NTA  #
 word       nu'man     1345-136-6-134-1-1345          #  NU'MAN  #
-word       pnin       6-1234-1345-35                 #  'PN*  #
-word       psch       6-1234-156                     #  'P5  #
-word       pschscht   6-1234-156-156-2345            #  'P55T  #
-word       pscht      6-1234-156-2345                #  'P5T  #
+word       pnin       6-1234-1345-35a                 #  'PN*  #
+word       psch       6-1234-156a                     #  'P5  #
+word       pschscht   6-1234-156a-156-2345            #  'P55T  #
+word       pscht      6-1234-156a-2345                #  'P5T  #
 word       qa'im      6-12345-1-6-24-134             #  'QA'IM  #
 word       rrm        6-1235-1235-134                #  'RRM  #
 word       s'         6-234-6                        #  'S'  #
-word       sch'chinah 156-6-1456-35-1-125            #  5'4*AH  #
-word       scht       6-156-2345                     #  '5T  #
+word       sch'chinah 156a-6-1456a-35a-1-125            #  5'4*AH  #
+word       scht       6-156a-2345                     #  '5T  #
 word       sgt        6-234-1245-2345                #  'SGT  #
-word       tsch       6-2345-156                     #  'T5  #
-word       tschk      6-2345-156-13                  #  'T5K  #
-word       un'ora     256-6-135-1235-1               #  /'ORA  #
+word       tsch       6-2345-156a                     #  'T5  #
+word       tschk      6-2345-156a-13                  #  'T5K  #
+word       un'ora     256a-6-135-1235-1               #  /'ORA  #
 word       when       2456-125-14-1345                  #  'WHC  #
-word       where      6-2456-125-12456-15            #  'WH7E  #
+word       where      6-2456-125-12456a-15            #  'WH7E  #
 word       whu        6-2456-125-136                 #  'WHU  #
-word       über'aupt  2-1256-6-16-1234-2345          #  ,8'1PT  #
+word       über'aupt  2-1256a-6-16a-1234-2345          #  ,8'1PT  #
 # LibLouis: Greek international braille - common symbols
 #
 # Copyright (C) 2019 Dave Mielke: <dave@mielke.cc>, [http://mielke.cc/]
@@ -445009,334 +446923,6 @@
 midendword e’er 15-3-12456
 word goin' 1245-135-35-3
 word goin’ 1245-135-35-3
-#
-#  Copyright (C) 2014 by Branislav Mamojka <mamojka@unss.sk>
-#  Copyright (C) 2016 by Peter Vagner <peter.v@datagate.sk>
-#
-#  This file is part of liblouis.
-#
-#  liblouis is free software: you can redistribute it and/or modify it
-#  under the terms of the GNU Lesser General Public License as
-#  published by the Free Software Foundation, either version 2.1 of the
-#  License, or (at your option) any later version.
-#
-#  liblouis is distributed in the hope that it will be useful, but
-#  WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-#  Lesser General Public License for more details.
-#
-#  You should have received a copy of the GNU Lesser General Public
-#  License along with liblouis. If not, see
-#  <http://www.gnu.org/licenses/>.
-#
-#--------------------------------------------------------------------------------
-#
-#  Slovak Braille
-#
-#     Created and maintained by Branislav Mamojka <mamojka@unss.sk>
-#                               Peter Vagner <peter.v@datagate.sk>
-#
-#     Based on the official Slovak Braille Standard
-#     Derived from the Czech braille table by Bert Frees and Jan Halousek
-# ----------------------------------------------------------------------------------------------
-
-space       \x0009        9                                       <control> HORIZONTAL TABULATION
-space       \x001B        1b                                      <control> ESCAPE
-space       \x00A0        a                                       NO-BREAK SPACE
-include spaces.uti
-
-# ==============================================================================================
-# SINGLE-CELL
-# ==============================================================================================
-
-lowercase      \x0061  1                   a                  LATIN SMALL LETTER A
-lowercase      \x0062  12                  b                  LATIN SMALL LETTER B
-lowercase      \x0063  14                  c                  LATIN SMALL LETTER C
-lowercase      \x0064  145                 d                  LATIN SMALL LETTER D
-lowercase      \x0065  15                  e                  LATIN SMALL LETTER E
-lowercase      \x0066  124                 f                  LATIN SMALL LETTER F
-lowercase      \x0067  1245                g                  LATIN SMALL LETTER G
-lowercase      \x0068  125                 h                  LATIN SMALL LETTER H
-lowercase      \x0069  24                  i                  LATIN SMALL LETTER I
-lowercase      \x006A  245                 j                  LATIN SMALL LETTER J
-lowercase      \x006B  13                  k                  LATIN SMALL LETTER K
-lowercase      \x006C  123                 l                  LATIN SMALL LETTER L
-lowercase      \x006D  134                 m                  LATIN SMALL LETTER M
-lowercase      \x006E  1345                n                  LATIN SMALL LETTER N
-lowercase      \x006F  135                 o                  LATIN SMALL LETTER O
-lowercase      \x0070  1234                p                  LATIN SMALL LETTER P
-lowercase      \x0071  12345               q                  LATIN SMALL LETTER Q
-lowercase      \x0072  1235                r                  LATIN SMALL LETTER R
-lowercase      \x0073  234                 s                  LATIN SMALL LETTER S
-lowercase      \x0074  2345                t                  LATIN SMALL LETTER T
-lowercase      \x0075  136                 u                  LATIN SMALL LETTER U
-lowercase      \x0076  1236                v                  LATIN SMALL LETTER V
-lowercase      \x0077  2456                w                  LATIN SMALL LETTER W
-lowercase      \x0078  1346                x                  LATIN SMALL LETTER X
-lowercase      \x0079  13456               y                  LATIN SMALL LETTER Y
-lowercase      \x007A  1356                z                  LATIN SMALL LETTER Z
-lowercase      \x00E1  16                  á                  LATIN SMALL LETTER A WITH ACUTE
-lowercase      \x00E4  4                   ä                  LATIN SMALL LETTER A WITH DIARESIS
-lowercase      \x00E9  345                 é                  LATIN SMALL LETTER E WITH ACUTE
-lowercase      \x00ED  34                  í                  LATIN SMALL LETTER I WITH ACUTE
-lowercase      \x00F3  246                 ó                  LATIN SMALL LETTER O WITH ACUTE
-lowercase      \x00FA  346                 ú                  LATIN SMALL LETTER U WITH ACUTE
-lowercase      \x00FD  12346               ý                  LATIN SMALL LETTER Y WITH ACUTE
-lowercase      \x010D  146                 č                  LATIN SMALL LETTER C WITH CARON
-lowercase      \x010F  1456                ď                  LATIN SMALL LETTER D WITH CARON
-lowercase      \x011B  126                 ě                  LATIN SMALL LETTER E WITH CARON
-lowercase      \x013E  456                 ľ                  LATIN SMALL LETTER L WITH CARON
-lowercase      \x013A  46                  ĺ                  LATIN SMALL LETTER L WITH Acute
-lowercase      \x0148  1246                ň                  LATIN SMALL LETTER N WITH CARON
-lowercase      \x00F4  23456               ô                  LATIN SMALL LETTER O WITH CARET
-lowercase      \x0155  12356               ŕ                  LATIN SMALL LETTER R WITH ACUTE
-lowercase      \x0161  156                 š                  LATIN SMALL LETTER S WITH CARON
-lowercase      \x0165  1256                ť                  LATIN SMALL LETTER T WITH CARON
-lowercase      \x016F  23456               ů                  LATIN SMALL LETTER U WITH RING ABOVE
-lowercase      \x017E  2346                ž                  LATIN SMALL LETTER Z WITH CARON
-
-base uppercase \x0041 \x0061               Aa                 LATIN CAPITAL LETTER A - LATIN SMALL LETTER A
-base uppercase \x0042 \x0062               Bb                 LATIN CAPITAL LETTER B - LATIN SMALL LETTER B
-base uppercase \x0043 \x0063               Cc                 LATIN CAPITAL LETTER C - LATIN SMALL LETTER C
-base uppercase \x0044 \x0064               Dd                 LATIN CAPITAL LETTER D - LATIN SMALL LETTER D
-base uppercase \x0045 \x0065               Ee                 LATIN CAPITAL LETTER E - LATIN SMALL LETTER E
-base uppercase \x0046 \x0066               Ff                 LATIN CAPITAL LETTER F - LATIN SMALL LETTER F
-base uppercase \x0047 \x0067               Gg                 LATIN CAPITAL LETTER G - LATIN SMALL LETTER G
-base uppercase \x0048 \x0068               Hh                 LATIN CAPITAL LETTER H - LATIN SMALL LETTER H
-base uppercase \x0049 \x0069               Ii                 LATIN CAPITAL LETTER I - LATIN SMALL LETTER I
-base uppercase \x004A \x006A               Jj                 LATIN CAPITAL LETTER J - LATIN SMALL LETTER J
-base uppercase \x004B \x006B               Kk                 LATIN CAPITAL LETTER K - LATIN SMALL LETTER K
-base uppercase \x004C \x006C               Ll                 LATIN CAPITAL LETTER L - LATIN SMALL LETTER L
-base uppercase \x004D \x006D               Mm                 LATIN CAPITAL LETTER M - LATIN SMALL LETTER M
-base uppercase \x004E \x006E               Nn                 LATIN CAPITAL LETTER N - LATIN SMALL LETTER N
-base uppercase \x004F \x006F               Oo                 LATIN CAPITAL LETTER O - LATIN SMALL LETTER O
-base uppercase \x0050 \x0070               Pp                 LATIN CAPITAL LETTER P - LATIN SMALL LETTER P
-base uppercase \x0051 \x0071               Qq                 LATIN CAPITAL LETTER Q - LATIN SMALL LETTER Q
-base uppercase \x0052 \x0072               Rr                 LATIN CAPITAL LETTER R - LATIN SMALL LETTER R
-base uppercase \x0053 \x0073               Ss                 LATIN CAPITAL LETTER S - LATIN SMALL LETTER S
-base uppercase \x0054 \x0074               Tt                 LATIN CAPITAL LETTER T - LATIN SMALL LETTER T
-base uppercase \x0055 \x0075               Uu                 LATIN CAPITAL LETTER U - LATIN SMALL LETTER U
-base uppercase \x0056 \x0076               Vv                 LATIN CAPITAL LETTER V - LATIN SMALL LETTER V
-base uppercase \x0057 \x0077               Ww                 LATIN CAPITAL LETTER W - LATIN SMALL LETTER W
-base uppercase \x0058 \x0078               Xx                 LATIN CAPITAL LETTER X - LATIN SMALL LETTER X
-base uppercase \x0059 \x0079               Yy                 LATIN CAPITAL LETTER Y - LATIN SMALL LETTER Y
-base uppercase \x005A \x007A               Zz                 LATIN CAPITAL LETTER Z - LATIN SMALL LETTER Z
-base uppercase \x00C1 \x00E1               Áá                 LATIN CAPITAL LETTER A WITH ACUTE - LATIN SMALL LETTER A WITH ACUTE
-base uppercase \x00C4 \x00E4               Ää                 LATIN CAPITAL LETTER A WITH DIARESIS - LATIN SMALL LETTER A WITH DIARESIS
-base uppercase \x00C9 \x00E9               Éé                 LATIN CAPITAL LETTER E WITH ACUTE - LATIN SMALL LETTER E WITH ACUTE
-base uppercase \x00CD \x00ED               Íí                 LATIN CAPITAL LETTER I WITH ACUTE - LATIN SMALL LETTER I WITH ACUTE
-base uppercase \x00D3 \x00F3               Óó                 LATIN CAPITAL LETTER O WITH ACUTE - LATIN SMALL LETTER O WITH ACUTE
-base uppercase \x00DA \x00FA               Úú                 LATIN CAPITAL LETTER U WITH ACUTE - LATIN SMALL LETTER U WITH ACUTE
-base uppercase \x00DD \x00FD               Ýý                 LATIN CAPITAL LETTER Y WITH ACUTE - LATIN SMALL LETTER Y WITH ACUTE
-base uppercase \x010C \x010D               Čč                 LATIN CAPITAL LETTER C WITH CARON - LATIN SMALL LETTER C WITH CARON
-base uppercase \x010E \x010F               Ďď                 LATIN CAPITAL LETTER D WITH CARON - LATIN SMALL LETTER D WITH CARON
-base uppercase \x011A \x011B               Ěě                 LATIN CAPITAL LETTER E WITH CARON - LATIN SMALL LETTER E WITH CARON
-base uppercase \x013D \x013E               Ľľ                 LATIN CAPITAL LETTER L WITH CARON - LATIN SMALL LETTER L WITH CARON
-base uppercase \x0139 \x013A               Ĺĺ                 LATIN CAPITAL LETTER L WITH Acute - LATIN SMALL LETTER L WITH Acute
-base uppercase \x0147 \x0148               Ňň                 LATIN CAPITAL LETTER N WITH CARON - LATIN SMALL LETTER N WITH CARON
-base uppercase \x00D4 \x00F4               Ôô                 LATIN CAPITAL LETTER O WITH CARET - LATIN SMALL LETTER O WITH CARET
-base uppercase \x0154 \x0155               Ŕŕ                 LATIN CAPITAL LETTER R WITH ACUTE - LATIN SMALL LETTER R WITH ACUTE
-base uppercase \x0160 \x0161               Šš                 LATIN CAPITAL LETTER S WITH CARON - LATIN SMALL LETTER S WITH CARON
-base uppercase \x0164 \x0165               Ťť                 LATIN CAPITAL LETTER T WITH CARON - LATIN SMALL LETTER T WITH CARON
-base uppercase \x016E \x016F               Ůů                 LATIN CAPITAL LETTER U WITH RING ABOVE - LATIN SMALL LETTER U WITH RING ABOVE
-base uppercase \x017D \x017E               Žž                 LATIN CAPITAL LETTER Z WITH CARON - LATIN SMALL LETTER Z WITH CARON
-
-# ----------------------------------------------------------------------------------------------
-# Unicode 0000..007F  C0 Controls and Basic Latin
-# ----------------------------------------------------------------------------------------------
-
-punctuation \x0021        235                 !                   EXCLAMATION MARK
-punctuation \x0022        2356                "                   QUOTATION MARK
-sign        \x0023        3456                #                   NUMBER SIGN
-sign        \x0023        56-3456                #                   NUMBER SIGN
-punctuation \x0027        3                   '                   APOSTROPHE
-punctuation \x0028        236                 (                   LEFT PARENTHESIS
-punctuation \x0029        356                 )                   RIGHT PARENTHESIS
-sign        \x002A        35                  *                   ASTERISK
-noback math \x002B        235                 +                   PLUS SIGN
-math        \x002B        56-235                 +                   PLUS SIGN
-punctuation \x002C        2                   ,                   COMMA
-punctuation \x002D        36                  -                   HYPHEN-MINUS
-punctuation \x002E        256                   .                   FULL STOP
-math        \x002F        12456               /                   SOLIDUS
-include digits6Dots.uti
-punctuation \x003A        25                  :                   COLON
-punctuation \x003B        23                  ;                   SEMICOLON
-noback math \x003C        126                 <                   LESS-THAN SIGN
-math        \x003C        56-126                 <                   LESS-THAN SIGN
-noback math \x003D        2356                =                   EQUALS SIGN
-math        \x003D        56-2356             =                   EQUALS SIGN
-noback math \x003E        345                 >                   GREATER-THAN SIGN
-math        \x003E        56-345                 >                   GREATER-THAN SIGN
-punctuation \x003F        26                  ?                   QUESTION MARK
-noback sign \x005F        456                _                   LOW LINE
-sign        \x005F        56-456                _                   LOW LINE
-punctuation \x0060        6                   `                   GRAVE ACCENT
-
-
-# ----------------------------------------------------------------------------------------------
-# Unicode 0080..00FF  C1 Controls and Latin-1 Supplement
-# ----------------------------------------------------------------------------------------------
-
-punctuation \x00A7        346                 §                   SECTION
-punctuation \x00AD        36                  ­                   SOFT HYPHEN
-sign        \x00B4        4                   ´                   ACUTE ACCENT
-noback math \x00D7        3                  ×                   MULTIPLICATION SIGN
-math        \x00D7        25-3               ×                   MULTIPLICATION SIGN
-math        \x00F7        25                  ÷                   DIVISION SIGN
-sign        \x00A9        236-6-14-356        ©                   COPYRIGHT
-
-
-# ----------------------------------------------------------------------------------------------
-# Unicode 2000..206F  General Punctuation
-# ----------------------------------------------------------------------------------------------
-
-punctuation \x2010        36                  ‐                   HYPHEN
-punctuation \x2011        36                  ‑                   NON-BREAKING HYPHEN
-punctuation \x2012        36                  ‒                   FIGURE DASH
-punctuation \x2013        36                  –                   EN DASH
-punctuation \x2014        36                  —                   EM DASH
-punctuation \x2015        36                  ―                   HORIZONTAL BAR
-punctuation \x2018        3                   ‘                   LEFT SINGLE QUOTATION MARK
-punctuation \x2019        3                   ’                   RIGHT SINGLE QUOTATION MARK
-punctuation \x201A        3                   ‚                   SINGLE LOW-9 QUOTATION MARK
-punctuation \x201B        3                   ‛                   SINGLE HIGH-REVERSED-9 QUOTATION MARK
-punctuation \x201C        2356                “                   LEFT DOUBLE QUOTATION MARK
-punctuation \x201D        2356                ”                   RIGHT DOUBLE QUOTATION MARK
-punctuation \x201E        2356                „                   DOUBLE LOW-9 QUOTATION MARK
-punctuation \x201F        2356                ‟                   DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-punctuation \x2026        256-256-256         …                   HORIZONTAL ELLIPSIS
-sign        \x20AC        15-136-1235-135     €                   EURO SIGN
-
-
-# ==============================================================================================
-# MULTI-CELL
-# ==============================================================================================
-
-# ----------------------------------------------------------------------------------------------
-# Unicode 0000..007F  C0 Controls and Basic Latin
-# ----------------------------------------------------------------------------------------------
-
-sign        \x0024        3456-1246           $                   DOLLAR SIGN
-math        \x0025        3456-1234           %                   PERCENT SIGN
-sign        \x0026        3456-12346          &                   AMPERSAND
-sign        \x0040        3456-12456          @                   COMMERCIAL AT
-punctuation \x005B        6-236               [                   LEFT SQUARE BRACKET
-sign        \x005C        3456-1256           \                   REVERSE SOLIDUS
-punctuation \x005D        6-356               ]                   RIGHT SQUARE BRACKET
-sign        \x005E        6-45                ^                   CIRCUMFLEX ACCENT
-math        \x007B        5-236              {                   LEFT CURLY BRACKET
-sign        \x007C        456-123                 |                   VERTICAL LINE
-math        \x007D        5-356              }                   RIGHT CURLY BRACKET
-math        \x007E        56-26                 ~                   TILDE
-
-
-# ----------------------------------------------------------------------------------------------
-# Unicode 0080..00FF  C1 Controls and Latin-1 Supplement
-# ----------------------------------------------------------------------------------------------
-
-sign        \x00B0        3456-234            °                   DEGREE SIGN
-math        \x00B1        56-256-36              ±                   PLUS-MINUS SIGN
-sign        \x00B5        45-134               µ                   MICRO SIGN
-math        \x00B9        34-1                ¹                   SUPERSCRIPT ONE
-noback lowercase \x00F6 135-15              ö                  LATIN SMALL LETTER O WITH DIAERESIS
-noback lowercase \x00FC 136-15              ü                  LATIN SMALL LETTER U WITH DIAERESIS
-
-base uppercase   \x00D6 \x00F6              Öö                 LATIN CAPITAL LETTER O WITH DIAERESIS - LATIN SMALL LETTER O WITH DIAERESIS
-base uppercase   \x00DC \x00FC              Üü                 LATIN CAPITAL LETTER U WITH DIAERESIS - LATIN SMALL LETTER U WITH DIAERESIS
-
-# ----------------------------------------------------------------------------------------------
-# Unicode 0370..03FF  Greek and Coptic
-# ----------------------------------------------------------------------------------------------
-
-letter      \x0391        6-45-1                Α                   GREEK CAPITAL LETTER ALPHA
-letter      \x0392        6-45-12               Β                   GREEK CAPITAL LETTER BETA
-letter      \x0393        6-45-1245             Γ                   GREEK CAPITAL LETTER GAMMA
-letter      \x0394        6-45-145              Δ                   GREEK CAPITAL LETTER DELTA
-letter      \x0395        6-45-15               Ε                   GREEK CAPITAL LETTER EPSILON
-letter      \x0398        6-45-1456             Θ                   GREEK CAPITAL LETTER THETA
-letter      \x0399        6-45-24               Ι                   GREEK CAPITAL LETTER IOTA
-letter      \x039A        6-45-13               Κ                   GREEK CAPITAL LETTER KAPPA
-letter      \x039B        6-45-123              Λ                   GREEK CAPITAL LETTER LAMDA
-letter      \x039C        6-45-134              Μ                   GREEK CAPITAL LETTER MU
-letter      \x039D        6-45-1345             Ν                   GREEK CAPITAL LETTER NU
-letter      \x039E        6-45-1346             Ξ                   GREEK CAPITAL LETTER XI
-letter      \x039F        6-45-135              Ο                   GREEK CAPITAL LETTER OMICRON
-letter      \x03A0        6-45-1234             Π                   GREEK CAPITAL LETTER PI
-letter      \x03A1        6-45-1235             Ρ                   GREEK CAPITAL LETTER RHO
-letter      \x03A3        6-45-234              Σ                   GREEK CAPITAL LETTER SIGMA
-letter      \x03A4        6-45-2345             Τ                   GREEK CAPITAL LETTER TAU
-letter      \x03A5        6-45-13456            Υ                   GREEK CAPITAL LETTER UPSILON
-letter      \x03A6        6-45-124              Φ                   GREEK CAPITAL LETTER PHI
-letter      \x03A7        6-45-125              Χ                   GREEK CAPITAL LETTER CHI
-letter      \x03A8        6-45-12346            Ψ                   GREEK CAPITAL LETTER PSI
-letter      \x03A9        6-45-2456             Ω                   GREEK CAPITAL LETTER OMEGA
-
-lowercase   \x03B1        45-1                α                   GREEK SMALL LETTER ALPHA
-lowercase   \x03B2        45-12               β                   GREEK SMALL LETTER BETA
-lowercase   \x03B3        45-1245             γ                   GREEK SMALL LETTER GAMMA
-lowercase   \x03B4        45-145              δ                   GREEK SMALL LETTER DELTA
-lowercase   \x03B5        45-15               ε                   GREEK SMALL LETTER EPSILON
-lowercase   \x03B8        45-1456             θ                   GREEK SMALL LETTER THETA
-lowercase   \x03B9        45-24               ι                   GREEK SMALL LETTER IOTA
-lowercase   \x03BA        45-13               κ                   GREEK SMALL LETTER KAPPA
-lowercase   \x03BB        45-123              λ                   GREEK SMALL LETTER LAMDA
-lowercase   \x03BC        45-134              μ                   GREEK SMALL LETTER MU
-lowercase   \x03BD        45-1345             ν                   GREEK SMALL LETTER NU
-lowercase   \x03BE        45-1346             ξ                   GREEK SMALL LETTER XI
-lowercase   \x03BF        45-135              ο                   GREEK SMALL LETTER OMICRON
-lowercase   \x03C0        45-1234             π                   GREEK SMALL LETTER PI
-lowercase   \x03C1        45-1235             ρ                   GREEK SMALL LETTER RHO
-lowercase   \x03C3        45-234              σ                   GREEK SMALL LETTER SIGMA
-lowercase   \x03C4        45-2345             τ                   GREEK SMALL LETTER TAU
-lowercase   \x03C5        45-13456            υ                   GREEK SMALL LETTER UPSILON
-lowercase   \x03C6        45-124              φ                   GREEK SMALL LETTER PHI
-lowercase   \x03C7        45-125              χ                   GREEK SMALL LETTER CHI
-lowercase   \x03C8        45-12346            ψ                   GREEK SMALL LETTER PSI
-lowercase   \x03C9        45-2456             ω                   GREEK SMALL LETTER OMEGA
-
-# ----------------------------------------------------------------------------------------------
-# Other Unicode characters
-# ----------------------------------------------------------------------------------------------
-
-noback math \x00AC        1345-135-1345       ¬                   LOGICAL NOT
-math        \x0609        3456-1235           ؉                   ARABIC-INDIC PER MILLE SIGN
-math        \x2030        3456-1235           ‰                   PER MILLE SIGN
-math        \x2190        246-25               ←                   left arrow
-math        \x2191        3456-12356               ↑                   up arrow
-math        \x2192        25-135               →                   right arrow
-math        \x2193        3456-12345               ↓                   down down arrow
-math        \x2194        246-25-135               ↔                   left right arrow
-math        \x2200        3456-1236           ∀                   FOR ALL
-math        \x2202        34-2                ∂                   PARTIAL DIFFERENTIAL
-math        \x2203        3456-134            ∃                   THERE EXISTS
-math        \x2205        3456-13456          ∅                   EMPTY SET
-math        \x2208        45-15               ∈                   ELEMENT OF
-math        \x2209        4-45-15             ∉                   NOT AN ELEMENT OF
-math        \x2211        46-234              ∑                   SUM
-math        \x2217        35                  ∗                   ASTERISK OPERATOR
-math        \x221E        3456-1345            ∞                   INFINITY
-math        \x2223        456                  ∣                   DIVIDES
-noback math \x2225        456-456              ∥                   PARALEL TO
-noback math \x2227        15-2345              ∧                   LOGICAL AND
-noback math \x22C0        15-2345              ∧                   LOGICAL AND
-noback math \x2228        145-24-234           ⋁                   LOGICAL OR
-noback math \x22C1        145-24-234           ⋁                   LOGICAL OR
-math        \x2229        45-156               ∩                   INTERSECTION
-math        \x222A        45-3456              ∪                   UNION
-math        \x222B        246                  ∫                   INTEGRAL
-math        \x2236        25                  ∶                   RATIO
-math        \x2260        4-2356              ≠                   NOT EQUAL TO
-math        \x2250        5-2356              ≐                   APPROACHES THE LIMIT
-math        \x2264        126-2356            ≤                   LESS-THAN OR EQUAL TO
-math        \x2265        345-2356            ≥                   GREATER-THAN OR EQUAL TO
-math        \x2282        12346               ⊂                   SUBSET OF
-math        \x2284        4-12346             ⊄                   NOT A SUBSET OF
-math        \x22C5        3                   ⋅                   DOT OPERATOR
-math        \x27E8        4-126               ⟨                   MATHEMATICAL LEFT ANGLE BRACKET
-math        \x27E8        4-345               ⟩                   MATHEMATICAL RIGHT ANGLE BRACKET
-
-# ----------------------------------------------------------------------------------------------
 #-index-name: Portuguese, contracted
 #-display-name: Portuguese contracted braille
 
@@ -543930,17 +545516,14 @@
 endemph underline 236
 begcomp 456-346
 endcomp 456-156
-always ' 6-3
-always : 25
-always ! 235
-always • 6-35
 # literary forms of the decimal digits
 include litdigits6Dots.uti
 attribute notaccentedletters abcdefghijABCDEFGHIJ-
 attribute accentedletters áéíöőüűúóklmnopqrstuvwxyz
 include hu-hu-g1_braille_input.cti
 midendnumericmodechars ,:.-
-nocontractsign 6
+#nocontractsign 6
+#nonumsign 6
 numericnocontchars abcdefghij-
 #Correcting forward translation when after a number following a hyphen
 #character, and a literary digit letter (for example a, b, c, etc).
diff --git a/third_party/liblouis/wasm/liblouis_wasm.js b/third_party/liblouis/wasm/liblouis_wasm.js
index 7616db7b..2c4a19c 100644
--- a/third_party/liblouis/wasm/liblouis_wasm.js
+++ b/third_party/liblouis/wasm/liblouis_wasm.js
@@ -392,9 +392,9 @@
       }
     
 
-      if (!Object.getOwnPropertyDescriptor(Module['ready'], '___stdio_exit')) {
-        Object.defineProperty(Module['ready'], '___stdio_exit', { configurable: true, get: function() { abort('You are getting ___stdio_exit on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js') } });
-        Object.defineProperty(Module['ready'], '___stdio_exit', { configurable: true, set: function() { abort('You are setting ___stdio_exit on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js') } });
+      if (!Object.getOwnPropertyDescriptor(Module['ready'], '_fflush')) {
+        Object.defineProperty(Module['ready'], '_fflush', { configurable: true, get: function() { abort('You are getting _fflush on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js') } });
+        Object.defineProperty(Module['ready'], '_fflush', { configurable: true, set: function() { abort('You are setting _fflush on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js') } });
       }
     
 
@@ -432,9 +432,7 @@
         err('warning: you defined Module.locateFilePackage, that has been renamed to Module.locateFile (using your locateFilePackage for now)');
       }
       var REMOTE_PACKAGE_NAME = Module['locateFile'] ? Module['locateFile'](REMOTE_PACKAGE_BASE, '') : REMOTE_PACKAGE_BASE;
-
-      var REMOTE_PACKAGE_SIZE = metadata['remote_package_size'];
-      var PACKAGE_UUID = metadata['package_uuid'];
+var REMOTE_PACKAGE_SIZE = metadata['remote_package_size'];
 
       function fetchRemotePackage(packageName, packageSize, callback, errback) {
         if (typeof process === 'object' && typeof process.versions === 'object' && typeof process.versions.node === 'string') {
@@ -582,7 +580,7 @@
     }
 
     }
-    loadPackage({"files": [{"filename": "/en-nabcc.utb", "start": 0, "end": 7265}, {"filename": "/no-no-generic.ctb", "start": 7265, "end": 20743}, {"filename": "/Se-Se-g1.utb", "start": 20743, "end": 25784}, {"filename": "/afr-za-g1.ctb", "start": 25784, "end": 28442}, {"filename": "/afr-za-g2.ctb", "start": 28442, "end": 58577}, {"filename": "/de-eurobrl6.dis", "start": 58577, "end": 60841}, {"filename": "/bh.tbl", "start": 60841, "end": 61387}, {"filename": "/Makefile.in", "start": 61387, "end": 134191}, {"filename": "/et-g0.utb", "start": 134191, "end": 139227}, {"filename": "/en-in-g1.ctb", "start": 139227, "end": 142652}, {"filename": "/es.tbl", "start": 142652, "end": 143257}, {"filename": "/he-IL.utb", "start": 143257, "end": 154046}, {"filename": "/fr-bfu-comp68.cti", "start": 154046, "end": 164731}, {"filename": "/mn-MN-common.cti", "start": 164731, "end": 168021}, {"filename": "/lt.tbl", "start": 168021, "end": 168724}, {"filename": "/Pl-Pl-g1.utb", "start": 168724, "end": 179094}, {"filename": "/ca-chardefs.cti", "start": 179094, "end": 195212}, {"filename": "/hyph_nb_NO.dic", "start": 195212, "end": 381890}, {"filename": "/ja-kantenji.utb", "start": 381890, "end": 644766}, {"filename": "/si-in-g1.utb", "start": 644766, "end": 645983}, {"filename": "/da-dk-octobraille.dis", "start": 645983, "end": 659208}, {"filename": "/gon.tbl", "start": 659208, "end": 659754}, {"filename": "/gez.tbl", "start": 659754, "end": 660431}, {"filename": "/cvox-common.cti", "start": 660431, "end": 661036}, {"filename": "/ko-2006-g1.ctb", "start": 661036, "end": 662815}, {"filename": "/bh.ctb", "start": 662815, "end": 663741}, {"filename": "/sl-si-comp8.ctb", "start": 663741, "end": 672618}, {"filename": "/wordcx.dis", "start": 672618, "end": 676472}, {"filename": "/gu-in-g1.utb", "start": 676472, "end": 677689}, {"filename": "/my-g2.ctb", "start": 677689, "end": 808013}, {"filename": "/de-g1-core-patterns.dic", "start": 808013, "end": 844108}, {"filename": "/ks-in-g1.utb", "start": 844108, "end": 845327}, {"filename": "/kok.tbl", "start": 845327, "end": 845877}, {"filename": "/sv-1996.ctb", "start": 845877, "end": 853617}, {"filename": "/de-g1-detailed.ctb", "start": 853617, "end": 856461}, {"filename": "/fi.utb", "start": 856461, "end": 865363}, {"filename": "/litdigits6DotsPlusDot6.uti", "start": 865363, "end": 866344}, {"filename": "/de-g0.utb", "start": 866344, "end": 868192}, {"filename": "/fi-fi-8dot.ctb", "start": 868192, "end": 876733}, {"filename": "/unicode.dis", "start": 876733, "end": 882707}, {"filename": "/kk.utb", "start": 882707, "end": 885301}, {"filename": "/litdigits6Dots.uti", "start": 885301, "end": 886258}, {"filename": "/kha.tbl", "start": 886258, "end": 886819}, {"filename": "/bel-comp.utb", "start": 886819, "end": 888352}, {"filename": "/hyph_eo.dic", "start": 888352, "end": 899019}, {"filename": "/gd.ctb", "start": 899019, "end": 904308}, {"filename": "/tr-g2.tbl", "start": 904308, "end": 904898}, {"filename": "/ukmaths_single_cell_defs.cti", "start": 904898, "end": 906430}, {"filename": "/no-no-8dot.utb", "start": 906430, "end": 926592}, {"filename": "/da-dk-g18.ctb", "start": 926592, "end": 951802}, {"filename": "/ckb.tbl", "start": 951802, "end": 952365}, {"filename": "/zhcn-g2.ctb", "start": 952365, "end": 1569838}, {"filename": "/ve-za-g1.utb", "start": 1569838, "end": 1572550}, {"filename": "/ru-letters.dis", "start": 1572550, "end": 1575382}, {"filename": "/zh_HK.tbl", "start": 1575382, "end": 1575712}, {"filename": "/hr-digits.uti", "start": 1575712, "end": 1577155}, {"filename": "/ukmaths_unicode_defs.cti", "start": 1577155, "end": 1592704}, {"filename": "/ethio-g1.ctb", "start": 1592704, "end": 1600476}, {"filename": "/he-IL-comp8.utb", "start": 1600476, "end": 1619778}, {"filename": "/sk-sk.utb", "start": 1619778, "end": 1622826}, {"filename": "/pi.tbl", "start": 1622826, "end": 1623368}, {"filename": "/no-no-8dot-fallback-6dot-g0.utb", "start": 1623368, "end": 1625726}, {"filename": "/ar-ar-comp8.utb", "start": 1625726, "end": 1634942}, {"filename": "/pl.tbl", "start": 1634942, "end": 1635173}, {"filename": "/bg.tbl", "start": 1635173, "end": 1635859}, {"filename": "/ko-2006.cti", "start": 1635859, "end": 1639818}, {"filename": "/hi-in-g1.utb", "start": 1639818, "end": 1641034}, {"filename": "/ru-brf.dis", "start": 1641034, "end": 1643564}, {"filename": "/no-no-chardefs6.uti", "start": 1643564, "end": 1656513}, {"filename": "/hr-g1.tbl", "start": 1656513, "end": 1656661}, {"filename": "/mr-in-g1.utb", "start": 1656661, "end": 1657879}, {"filename": "/us-table.dis", "start": 1657879, "end": 1659441}, {"filename": "/maketablelist.sh", "start": 1659441, "end": 1660459}, {"filename": "/hr-comp8.tbl", "start": 1660459, "end": 1661149}, {"filename": "/is.ctb", "start": 1661149, "end": 1667050}, {"filename": "/ka-in-g1.utb", "start": 1667050, "end": 1668265}, {"filename": "/pa.tbl", "start": 1668265, "end": 1668829}, {"filename": "/marburg_single_cell_defs.cti", "start": 1668829, "end": 1670344}, {"filename": "/README", "start": 1670344, "end": 1670505}, {"filename": "/countries.cti", "start": 1670505, "end": 1677208}, {"filename": "/latinLetterDef6Dots.uti", "start": 1677208, "end": 1679201}, {"filename": "/zh_CHN.tbl", "start": 1679201, "end": 1680081}, {"filename": "/en-chess.ctb", "start": 1680081, "end": 1681579}, {"filename": "/hu-backtranslate-word-corrections.cti", "start": 1681579, "end": 1702104}, {"filename": "/hyph_en_US.dic", "start": 1702104, "end": 1738571}, {"filename": "/ro.ctb", "start": 1738571, "end": 1743579}, {"filename": "/es-old.dis", "start": 1743579, "end": 1751184}, {"filename": "/hu.tbl", "start": 1751184, "end": 1751683}, {"filename": "/en-gb-g1.utb", "start": 1751683, "end": 1765929}, {"filename": "/tr.tbl", "start": 1765929, "end": 1766611}, {"filename": "/en-ueb-chardefs.uti", "start": 1766611, "end": 1794255}, {"filename": "/zu-za-g2.ctb", "start": 1794255, "end": 1795322}, {"filename": "/lt.ctb", "start": 1795322, "end": 1812435}, {"filename": "/ar-ar-g1-core.uti", "start": 1812435, "end": 1822914}, {"filename": "/el.ctb", "start": 1822914, "end": 1853717}, {"filename": "/ta.tbl", "start": 1853717, "end": 1854395}, {"filename": "/ne.ctb", "start": 1854395, "end": 1855287}, {"filename": "/ar.tbl", "start": 1855287, "end": 1855799}, {"filename": "/en-us-mathtext.ctb", "start": 1855799, "end": 1876247}, {"filename": "/digits6Dots.uti", "start": 1876247, "end": 1877165}, {"filename": "/bo.ctb", "start": 1877165, "end": 1884413}, {"filename": "/digits6DotsPlusDot6.uti", "start": 1884413, "end": 1885478}, {"filename": "/kn.tbl", "start": 1885478, "end": 1886042}, {"filename": "/dra.ctb", "start": 1886042, "end": 1886947}, {"filename": "/cs.tbl", "start": 1886947, "end": 1887579}, {"filename": "/sot-za-g1.ctb", "start": 1887579, "end": 1890309}, {"filename": "/ba.utb", "start": 1890309, "end": 1892774}, {"filename": "/de-accents-detailed.cti", "start": 1892774, "end": 1910196}, {"filename": "/fa-ir-g1.utb", "start": 1910196, "end": 1916367}, {"filename": "/tt.utb", "start": 1916367, "end": 1919055}, {"filename": "/sv.tbl", "start": 1919055, "end": 1919619}, {"filename": "/cs-comp8.utb", "start": 1919619, "end": 1933122}, {"filename": "/corrections.cti", "start": 1933122, "end": 1934614}, {"filename": "/uk-comp.utb", "start": 1934614, "end": 1936525}, {"filename": "/vi-lettersdef.uti", "start": 1936525, "end": 1945711}, {"filename": "/it-it-comp6.utb", "start": 1945711, "end": 1950586}, {"filename": "/grc-international-decomposed.uti", "start": 1950586, "end": 1967032}, {"filename": "/latinUppercaseComp6.uti", "start": 1967032, "end": 1968900}, {"filename": "/Makefile.am", "start": 1968900, "end": 1975709}, {"filename": "/hi.tbl", "start": 1975709, "end": 1976269}, {"filename": "/mn-in-g1.utb", "start": 1976269, "end": 1977485}, {"filename": "/de-g2.ctb", "start": 1977485, "end": 1979124}, {"filename": "/da-dk-g26l-lit.ctb", "start": 1979124, "end": 1995194}, {"filename": "/sl.tbl", "start": 1995194, "end": 1995837}, {"filename": "/de-eurobrl6u.dis", "start": 1995837, "end": 1997928}, {"filename": "/ko.cti", "start": 1997928, "end": 2003528}, {"filename": "/sr.tbl", "start": 2003528, "end": 2004193}, {"filename": "/tsn-za-g1.ctb", "start": 2004193, "end": 2005335}, {"filename": "/np-in-g1.utb", "start": 2005335, "end": 2006552}, {"filename": "/ko-g2.ctb", "start": 2006552, "end": 2008448}, {"filename": "/fa-ir-comp8.ctb", "start": 2008448, "end": 2014120}, {"filename": "/no-no-generic.dis", "start": 2014120, "end": 2028293}, {"filename": "/en-ueb-g1.ctb", "start": 2028293, "end": 2033296}, {"filename": "/pl-pl-comp8.ctb", "start": 2033296, "end": 2051633}, {"filename": "/IPA.utb", "start": 2051633, "end": 2058933}, {"filename": "/or.tbl", "start": 2058933, "end": 2059493}, {"filename": "/en_CA.ctb", "start": 2059493, "end": 2067597}, {"filename": "/gu.tbl", "start": 2067597, "end": 2068163}, {"filename": "/hu-hu-g2.ctb", "start": 2068163, "end": 2081633}, {"filename": "/da-dk-g26.ctb", "start": 2081633, "end": 2110931}, {"filename": "/gurumuki.cti", "start": 2110931, "end": 2121039}, {"filename": "/telugu.cti", "start": 2121039, "end": 2131016}, {"filename": "/ru-unicode.dis", "start": 2131016, "end": 2134041}, {"filename": "/dra.tbl", "start": 2134041, "end": 2134729}, {"filename": "/kmr.tbl", "start": 2134729, "end": 2139506}, {"filename": "/de-accents.cti", "start": 2139506, "end": 2156845}, {"filename": "/controlchars.cti", "start": 2156845, "end": 2157890}, {"filename": "/mun.ctb", "start": 2157890, "end": 2158812}, {"filename": "/mwr.ctb", "start": 2158812, "end": 2159739}, {"filename": "/hu-chardefs.cti", "start": 2159739, "end": 2172038}, {"filename": "/es-g2.ctb", "start": 2172038, "end": 2197714}, {"filename": "/hyph_nl_NL.dic", "start": 2197714, "end": 2281239}, {"filename": "/ve-za-g2.ctb", "start": 2281239, "end": 2286091}, {"filename": "/vi.ctb", "start": 2286091, "end": 2298232}, {"filename": "/cop-eg-comp8.utb", "start": 2298232, "end": 2306917}, {"filename": "/loweredDigits8Dots.uti", "start": 2306917, "end": 2307853}, {"filename": "/ml.tbl", "start": 2307853, "end": 2308531}, {"filename": "/hyph_de_DE.dic", "start": 2308531, "end": 2345332}, {"filename": "/no-no-g1.ctb", "start": 2345332, "end": 2349464}, {"filename": "/bo.tbl", "start": 2349464, "end": 2350146}, {"filename": "/en-us-brf.dis", "start": 2350146, "end": 2352584}, {"filename": "/de-g0-detailed.utb", "start": 2352584, "end": 2355617}, {"filename": "/mun.tbl", "start": 2355617, "end": 2356163}, {"filename": "/da-dk-g28l.ctb", "start": 2356163, "end": 2406493}, {"filename": "/hyph_it_IT.dic", "start": 2406493, "end": 2408699}, {"filename": "/Es-Es-G0.utb", "start": 2408699, "end": 2416931}, {"filename": "/hu-exceptionwords.cti", "start": 2416931, "end": 2533238}, {"filename": "/hyph_ru.dic", "start": 2533238, "end": 2600774}, {"filename": "/de-chess.ctb", "start": 2600774, "end": 2602263}, {"filename": "/tr-g1.ctb", "start": 2602263, "end": 2604897}, {"filename": "/braille-patterns.cti", "start": 2604897, "end": 2631328}, {"filename": "/bn.tbl", "start": 2631328, "end": 2631892}, {"filename": "/kh-in-g1.utb", "start": 2631892, "end": 2633084}, {"filename": "/awa.tbl", "start": 2633084, "end": 2633647}, {"filename": "/ckb-chardefs.cti", "start": 2633647, "end": 2642210}, {"filename": "/ar-ar-g2.ctb", "start": 2642210, "end": 2654009}, {"filename": "/es-chardefs.cti", "start": 2654009, "end": 2668605}, {"filename": "/es-g1.ctb", "start": 2668605, "end": 2669842}, {"filename": "/vi-vn-g1.ctb", "start": 2669842, "end": 2687477}, {"filename": "/pt-pt-g2.ctb", "start": 2687477, "end": 2709037}, {"filename": "/lv.tbl", "start": 2709037, "end": 2709714}, {"filename": "/eo-g1-x-system.ctb", "start": 2709714, "end": 2712675}, {"filename": "/zh-hk.ctb", "start": 2712675, "end": 2977594}, {"filename": "/mwr.tbl", "start": 2977594, "end": 2978144}, {"filename": "/hyph_brl_da_dk.dic", "start": 2978144, "end": 3039362}, {"filename": "/hyph_cs_CZ.dic", "start": 3039362, "end": 3061105}, {"filename": "/is-chardefs6.cti", "start": 3061105, "end": 3068646}, {"filename": "/eo.tbl", "start": 3068646, "end": 3069315}, {"filename": "/nl-chardefs.uti", "start": 3069315, "end": 3091394}, {"filename": "/ne.tbl", "start": 3091394, "end": 3091956}, {"filename": "/marburg_unicode_defs.cti", "start": 3091956, "end": 3107504}, {"filename": "/ur-pk-g2.ctb", "start": 3107504, "end": 3160401}, {"filename": "/et.ctb", "start": 3160401, "end": 3164506}, {"filename": "/sd.tbl", "start": 3164506, "end": 3165068}, {"filename": "/IPA-unicode-range.uti", "start": 3165068, "end": 3178256}, {"filename": "/wiskunde-chardefs.cti", "start": 3178256, "end": 3196815}, {"filename": "/nl-BE.dis", "start": 3196815, "end": 3198471}, {"filename": "/de-g1.ctb", "start": 3198471, "end": 3200259}, {"filename": "/as.tbl", "start": 3200259, "end": 3200825}, {"filename": "/fr-bfu-comp8.utb", "start": 3200825, "end": 3217284}, {"filename": "/ms-my-g2.ctb", "start": 3217284, "end": 3362674}, {"filename": "/ml-in-g1.utb", "start": 3362674, "end": 3363893}, {"filename": "/en-us-g1.ctb", "start": 3363893, "end": 3368200}, {"filename": "/zu-za-g1.utb", "start": 3368200, "end": 3369269}, {"filename": "/grc-international-composed.uti", "start": 3369269, "end": 3385573}, {"filename": "/is-chardefs8.cti", "start": 3385573, "end": 3406815}, {"filename": "/mn-MN-g2.ctb", "start": 3406815, "end": 3408758}, {"filename": "/km-g1.utb", "start": 3408758, "end": 3417979}, {"filename": "/bra.tbl", "start": 3417979, "end": 3418538}, {"filename": "/kannada.cti", "start": 3418538, "end": 3427249}, {"filename": "/se-se.dis", "start": 3427249, "end": 3441940}, {"filename": "/sk-sk-g1.utb", "start": 3441940, "end": 3447875}, {"filename": "/zh_TW.tbl", "start": 3447875, "end": 3448332}, {"filename": "/sk-translation.cti", "start": 3448332, "end": 3450941}, {"filename": "/et.tbl", "start": 3450941, "end": 3451625}, {"filename": "/br-in-g1.utb", "start": 3451625, "end": 3452839}, {"filename": "/tr-g2.ctb", "start": 3452839, "end": 3466074}, {"filename": "/en-us-compbrl.uti", "start": 3466074, "end": 3468241}, {"filename": "/cs-translation.cti", "start": 3468241, "end": 3470768}, {"filename": "/en-us-emphasis.uti", "start": 3470768, "end": 3472210}, {"filename": "/ru-ru-g1.ctb", "start": 3472210, "end": 3490790}, {"filename": "/xh-za-g1.utb", "start": 3490790, "end": 3493065}, {"filename": "/gr-pl-comp8.uti", "start": 3493065, "end": 3508555}, {"filename": "/hr-chardefs.cti", "start": 3508555, "end": 3525076}, {"filename": "/fr-bfu-comp6.utb", "start": 3525076, "end": 3543642}, {"filename": "/ta.ctb", "start": 3543642, "end": 3544562}, {"filename": "/sr-g1.ctb", "start": 3544562, "end": 3547894}, {"filename": "/lt-6dot.tbl", "start": 3547894, "end": 3548062}, {"filename": "/it-it-comp8.utb", "start": 3548062, "end": 3576911}, {"filename": "/ukchardefs.cti", "start": 3576911, "end": 3580145}, {"filename": "/te-in-g1.utb", "start": 3580145, "end": 3581358}, {"filename": "/hyph_nn_NO.dic", "start": 3581358, "end": 3768036}, {"filename": "/da-dk-g26-lit.ctb", "start": 3768036, "end": 3789852}, {"filename": "/hy.ctb", "start": 3789852, "end": 3795278}, {"filename": "/en-us-comp8-ext.utb", "start": 3795278, "end": 3821400}, {"filename": "/hyph_sv_SE.dic", "start": 3821400, "end": 3852659}, {"filename": "/se-se.ctb", "start": 3852659, "end": 3862900}, {"filename": "/is.tbl", "start": 3862900, "end": 3863471}, {"filename": "/boxes.ctb", "start": 3863471, "end": 3873776}, {"filename": "/hyph_da_DK.dic", "start": 3873776, "end": 3935174}, {"filename": "/no-no-g3.ctb", "start": 3935174, "end": 3954005}, {"filename": "/de-g1-core.cti", "start": 3954005, "end": 3956082}, {"filename": "/en_CA.tbl", "start": 3956082, "end": 3956807}, {"filename": "/ckb-g1.ctb", "start": 3956807, "end": 3958017}, {"filename": "/no-no-g2.ctb", "start": 3958017, "end": 3971030}, {"filename": "/bel.utb", "start": 3971030, "end": 3972594}, {"filename": "/da-dk-8miscChars.cti", "start": 3972594, "end": 3976474}, {"filename": "/ckb-translation.cti", "start": 3976474, "end": 3980015}, {"filename": "/nl-NL-g0.utb", "start": 3980015, "end": 3990994}, {"filename": "/ur-pk-g1.utb", "start": 3990994, "end": 4003300}, {"filename": "/devanagari.cti", "start": 4003300, "end": 4012607}, {"filename": "/be-in-g1.utb", "start": 4012607, "end": 4013822}, {"filename": "/ca.tbl", "start": 4013822, "end": 4014383}, {"filename": "/en-ueb-g2.ctb", "start": 4014383, "end": 4206151}, {"filename": "/tr.ctb", "start": 4206151, "end": 4210832}, {"filename": "/vi-vn-g2.ctb", "start": 4210832, "end": 4233514}, {"filename": "/de-de.dis", "start": 4233514, "end": 4248115}, {"filename": "/gd.tbl", "start": 4248115, "end": 4248813}, {"filename": "/hu-backtranslate-correction.dis", "start": 4248813, "end": 4250168}, {"filename": "/hu-hu-g1_braille_input.cti", "start": 4250168, "end": 4265357}, {"filename": "/pt-pt-comp8.ctb", "start": 4265357, "end": 4269042}, {"filename": "/or-in-g1.utb", "start": 4269042, "end": 4270253}, {"filename": "/no-no-comp8.ctb", "start": 4270253, "end": 4291185}, {"filename": "/cy.tbl", "start": 4291185, "end": 4291787}, {"filename": "/en-us-comp6.ctb", "start": 4291787, "end": 4296722}, {"filename": "/da-dk-g08.ctb", "start": 4296722, "end": 4310249}, {"filename": "/unicode-braille.utb", "start": 4310249, "end": 4335082}, {"filename": "/cs-g1.ctb", "start": 4335082, "end": 4336436}, {"filename": "/sl-si-g1.utb", "start": 4336436, "end": 4352645}, {"filename": "/ru-compbrl.ctb", "start": 4352645, "end": 4359955}, {"filename": "/grc-international-en.utb", "start": 4359955, "end": 4364677}, {"filename": "/sa.tbl", "start": 4364677, "end": 4365243}, {"filename": "/xh-za-g2.ctb", "start": 4365243, "end": 4374136}, {"filename": "/pu-in-g1.utb", "start": 4374136, "end": 4375352}, {"filename": "/hu-hu-g2_exceptions.cti", "start": 4375352, "end": 4417408}, {"filename": "/bengali.cti", "start": 4417408, "end": 4427745}, {"filename": "/no-no-braillo-047-01.dis", "start": 4427745, "end": 4435564}, {"filename": "/tsn-za-g2.ctb", "start": 4435564, "end": 4436703}, {"filename": "/uk.utb", "start": 4436703, "end": 4438905}, {"filename": "/printables.cti", "start": 4438905, "end": 4440739}, {"filename": "/uni-text.dis", "start": 4440739, "end": 4455355}, {"filename": "/pi.ctb", "start": 4455355, "end": 4456279}, {"filename": "/latinLetterDef8Dots.uti", "start": 4456279, "end": 4458067}, {"filename": "/bg.dis", "start": 4458067, "end": 4459816}, {"filename": "/de-comp6.utb", "start": 4459816, "end": 4474963}, {"filename": "/ko-chars.cti", "start": 4474963, "end": 4857311}, {"filename": "/digits8Dots.uti", "start": 4857311, "end": 4858239}, {"filename": "/pt-pt-g1.utb", "start": 4858239, "end": 4866606}, {"filename": "/ga-g2.ctb", "start": 4866606, "end": 4871392}, {"filename": "/en-us-g2.ctb", "start": 4871392, "end": 4899322}, {"filename": "/vi-saigon-g1.ctb", "start": 4899322, "end": 4920903}, {"filename": "/de-chardefs6.cti", "start": 4920903, "end": 4937810}, {"filename": "/mt.ctb", "start": 4937810, "end": 4942361}, {"filename": "/Makefile", "start": 4942361, "end": 4996246}, {"filename": "/as-in-g1.utb", "start": 4996246, "end": 4997462}, {"filename": "/sk-g1.ctb", "start": 4997462, "end": 4998894}, {"filename": "/hyph_es_ES.dic", "start": 4998894, "end": 5003111}, {"filename": "/en-us-comp8.ctb", "start": 5003111, "end": 5004743}, {"filename": "/en-chardefs.cti", "start": 5004743, "end": 5013053}, {"filename": "/spaces.uti", "start": 5013053, "end": 5015155}, {"filename": "/tamil.cti", "start": 5015155, "end": 5018373}, {"filename": "/de-de-comp8.ctb", "start": 5018373, "end": 5027706}, {"filename": "/hu-hu-comp8.ctb", "start": 5027706, "end": 5032509}, {"filename": "/ta-ta-g1.ctb", "start": 5032509, "end": 5037548}, {"filename": "/malayalam.cti", "start": 5037548, "end": 5056287}, {"filename": "/te.tbl", "start": 5056287, "end": 5056849}, {"filename": "/kok.ctb", "start": 5056849, "end": 5057776}, {"filename": "/de-g2-core-patterns.dic", "start": 5057776, "end": 5250344}, {"filename": "/hyph_fr_FR.dic", "start": 5250344, "end": 5258323}, {"filename": "/cy-cy-g1.utb", "start": 5258323, "end": 5272120}, {"filename": "/loweredDigits6Dots.uti", "start": 5272120, "end": 5273046}, {"filename": "/ru-litbrl.ctb", "start": 5273046, "end": 5308199}, {"filename": "/sah.utb", "start": 5308199, "end": 5310853}, {"filename": "/no-no.dis", "start": 5310853, "end": 5324497}, {"filename": "/bg.utb", "start": 5324497, "end": 5335958}, {"filename": "/ar-ar-math.uti", "start": 5335958, "end": 5356823}, {"filename": "/fr-bfu-g2.ctb", "start": 5356823, "end": 5501213}, {"filename": "/zh-chn.ctb", "start": 5501213, "end": 6764349}, {"filename": "/ar-ar-g1.utb", "start": 6764349, "end": 6765559}, {"filename": "/mr.tbl", "start": 6765559, "end": 6766123}, {"filename": "/mt.tbl", "start": 6766123, "end": 6766805}, {"filename": "/en-ueb-math.ctb", "start": 6766805, "end": 6768850}, {"filename": "/da-dk-g28.ctb", "start": 6768850, "end": 6830923}, {"filename": "/nso-za-g1.utb", "start": 6830923, "end": 6832317}, {"filename": "/en_GB.tbl", "start": 6832317, "end": 6833152}, {"filename": "/cs-chardefs.cti", "start": 6833152, "end": 6857411}, {"filename": "/ko-g1-rules.cti", "start": 6857411, "end": 6881092}, {"filename": "/en_US.tbl", "start": 6881092, "end": 6881931}, {"filename": "/no-no-g0.utb", "start": 6881931, "end": 6888642}, {"filename": "/mn-MN-g1.utb", "start": 6888642, "end": 6890589}, {"filename": "/de-g0-core.uti", "start": 6890589, "end": 6916945}, {"filename": "/lt-6dot.utb", "start": 6916945, "end": 6928897}, {"filename": "/gon.ctb", "start": 6928897, "end": 6929841}, {"filename": "/sv-1989.ctb", "start": 6929841, "end": 6939446}, {"filename": "/it.tbl", "start": 6939446, "end": 6940012}, {"filename": "/ko-2006-g2.ctb", "start": 6940012, "end": 6941987}, {"filename": "/vi-charsdef.uti", "start": 6941987, "end": 6944636}, {"filename": "/compress.cti", "start": 6944636, "end": 6946055}, {"filename": "/ru.ctb", "start": 6946055, "end": 6981825}, {"filename": "/hyph_pt_PT.dic", "start": 6981825, "end": 6983113}, {"filename": "/oriya.cti", "start": 6983113, "end": 6992646}, {"filename": "/hr-comp8.utb", "start": 6992646, "end": 7003163}, {"filename": "/eurodefs.cti", "start": 7003163, "end": 7006209}, {"filename": "/Lv-Lv-g1.utb", "start": 7006209, "end": 7026893}, {"filename": "/hyph_pl_PL.dic", "start": 7026893, "end": 7057444}, {"filename": "/unicode-without-blank.dis", "start": 7057444, "end": 7058346}, {"filename": "/my-g1.utb", "start": 7058346, "end": 7078579}, {"filename": "/sr-chardefs.cti", "start": 7078579, "end": 7087320}, {"filename": "/en-us-interline.ctb", "start": 7087320, "end": 7115296}, {"filename": "/sot-za-g2.ctb", "start": 7115296, "end": 7121582}, {"filename": "/da-dk-6miscChars.cti", "start": 7121582, "end": 7142208}, {"filename": "/ca-g1.ctb", "start": 7142208, "end": 7143966}, {"filename": "/iu-ca-g1.ctb", "start": 7143966, "end": 7152138}, {"filename": "/da-dk-g16.ctb", "start": 7152138, "end": 7160234}, {"filename": "/text_nabcc.dis", "start": 7160234, "end": 7181884}, {"filename": "/haw-us-g1.ctb", "start": 7181884, "end": 7183467}, {"filename": "/eo-g1.ctb", "start": 7183467, "end": 7186353}, {"filename": "/sin.utb", "start": 7186353, "end": 7187708}, {"filename": "/vi-puncsdef.uti", "start": 7187708, "end": 7205313}, {"filename": "/uz-g1.utb", "start": 7205313, "end": 7208870}, {"filename": "/sk.tbl", "start": 7208870, "end": 7209533}, {"filename": "/sin.cti", "start": 7209533, "end": 7220497}, {"filename": "/vi-vn-g0.utb", "start": 7220497, "end": 7223617}, {"filename": "/zh-tw.ctb", "start": 7223617, "end": 8684521}, {"filename": "/chr-us-g1.ctb", "start": 8684521, "end": 8690083}, {"filename": "/ga-g1.utb", "start": 8690083, "end": 8693346}, {"filename": "/kru.tbl", "start": 8693346, "end": 8693894}, {"filename": "/no-no-latinLetterDef6Dots_diacritics.uti", "start": 8693894, "end": 8742986}, {"filename": "/cy-cy-g2.ctb", "start": 8742986, "end": 8747291}, {"filename": "/da-dk-g16-lit.ctb", "start": 8747291, "end": 8756697}, {"filename": "/mni.tbl", "start": 8756697, "end": 8757264}, {"filename": "/es-new.dis", "start": 8757264, "end": 8764875}, {"filename": "/hy.tbl", "start": 8764875, "end": 8765559}, {"filename": "/mao-nz-g1.ctb", "start": 8765559, "end": 8767359}, {"filename": "/ko-g2-rules.cti", "start": 8767359, "end": 9042135}, {"filename": "/de-chardefs8.cti", "start": 9042135, "end": 9062133}, {"filename": "/latinLowercase.uti", "start": 9062133, "end": 9072579}, {"filename": "/nl-comp8.utb", "start": 9072579, "end": 9082600}, {"filename": "/aw-in-g1.utb", "start": 9082600, "end": 9083816}, {"filename": "/hr-translation.cti", "start": 9083816, "end": 9092274}, {"filename": "/no.tbl", "start": 9092274, "end": 9092624}, {"filename": "/ro.tbl", "start": 9092624, "end": 9093308}, {"filename": "/kru.ctb", "start": 9093308, "end": 9094234}, {"filename": "/ru-litbrl-detailed.utb", "start": 9094234, "end": 9098331}, {"filename": "/nso-za-g2.ctb", "start": 9098331, "end": 9099722}, {"filename": "/zhcn-g1.ctb", "start": 9099722, "end": 9775779}, {"filename": "/en-gb-comp8.ctb", "start": 9775779, "end": 9778038}, {"filename": "/gujarati.cti", "start": 9778038, "end": 9787641}, {"filename": "/da-dk-g26l.ctb", "start": 9787641, "end": 9802789}, {"filename": "/hr-g1.ctb", "start": 9802789, "end": 9804299}, {"filename": "/de-g2-core.cti", "start": 9804299, "end": 9835636}, {"filename": "/grc-international-common.uti", "start": 9835636, "end": 9839109}, {"filename": "/nemethdefs.cti", "start": 9839109, "end": 9855605}, {"filename": "/nl.tbl", "start": 9855605, "end": 9856319}, {"filename": "/bg.ctb", "start": 9856319, "end": 9863139}, {"filename": "/ko-g1.ctb", "start": 9863139, "end": 9864833}, {"filename": "/sa-in-g1.utb", "start": 9864833, "end": 9866052}, {"filename": "/en-GB-g2.ctb", "start": 9866052, "end": 9885706}, {"filename": "/sk-chardefs.cti", "start": 9885706, "end": 9911737}, {"filename": "/pt.tbl", "start": 9911737, "end": 9912349}, {"filename": "/en_US-comp8-ext.tbl", "start": 9912349, "end": 9912738}, {"filename": "/hyph_hu_HU.dic", "start": 9912738, "end": 10788842}, {"filename": "/hu-hu-g1.ctb", "start": 10788842, "end": 10793748}], "remote_package_size": 10793748, "package_uuid": "3ca34660-b0a9-49fc-a117-bc05482944c8"});
+    loadPackage({"files": [{"filename": "/en-nabcc.utb", "start": 0, "end": 7265}, {"filename": "/no-no-generic.ctb", "start": 7265, "end": 20743}, {"filename": "/Se-Se-g1.utb", "start": 20743, "end": 25784}, {"filename": "/afr-za-g1.ctb", "start": 25784, "end": 28442}, {"filename": "/afr-za-g2.ctb", "start": 28442, "end": 58577}, {"filename": "/de-eurobrl6.dis", "start": 58577, "end": 60841}, {"filename": "/bh.tbl", "start": 60841, "end": 61387}, {"filename": "/Makefile.in", "start": 61387, "end": 134172}, {"filename": "/et-g0.utb", "start": 134172, "end": 139208}, {"filename": "/en-in-g1.ctb", "start": 139208, "end": 142633}, {"filename": "/es.tbl", "start": 142633, "end": 143238}, {"filename": "/he-IL.utb", "start": 143238, "end": 154027}, {"filename": "/fr-bfu-comp68.cti", "start": 154027, "end": 164712}, {"filename": "/mn-MN-common.cti", "start": 164712, "end": 168002}, {"filename": "/lt.tbl", "start": 168002, "end": 168705}, {"filename": "/Pl-Pl-g1.utb", "start": 168705, "end": 179075}, {"filename": "/ca-chardefs.cti", "start": 179075, "end": 195193}, {"filename": "/hyph_nb_NO.dic", "start": 195193, "end": 381871}, {"filename": "/ja-kantenji.utb", "start": 381871, "end": 644747}, {"filename": "/si-in-g1.utb", "start": 644747, "end": 645964}, {"filename": "/da-dk-octobraille.dis", "start": 645964, "end": 659189}, {"filename": "/gon.tbl", "start": 659189, "end": 659735}, {"filename": "/gez.tbl", "start": 659735, "end": 660412}, {"filename": "/cvox-common.cti", "start": 660412, "end": 661017}, {"filename": "/ko-2006-g1.ctb", "start": 661017, "end": 662796}, {"filename": "/bh.ctb", "start": 662796, "end": 663722}, {"filename": "/sl-si-comp8.ctb", "start": 663722, "end": 672599}, {"filename": "/wordcx.dis", "start": 672599, "end": 676453}, {"filename": "/gu-in-g1.utb", "start": 676453, "end": 677670}, {"filename": "/my-g2.ctb", "start": 677670, "end": 807994}, {"filename": "/de-g1-core-patterns.dic", "start": 807994, "end": 844089}, {"filename": "/ks-in-g1.utb", "start": 844089, "end": 845308}, {"filename": "/kok.tbl", "start": 845308, "end": 845858}, {"filename": "/sv-1996.ctb", "start": 845858, "end": 853598}, {"filename": "/de-g1-detailed.ctb", "start": 853598, "end": 856442}, {"filename": "/fi.utb", "start": 856442, "end": 865344}, {"filename": "/litdigits6DotsPlusDot6.uti", "start": 865344, "end": 866325}, {"filename": "/de-g0.utb", "start": 866325, "end": 868173}, {"filename": "/fi-fi-8dot.ctb", "start": 868173, "end": 876714}, {"filename": "/unicode.dis", "start": 876714, "end": 882688}, {"filename": "/kk.utb", "start": 882688, "end": 885282}, {"filename": "/litdigits6Dots.uti", "start": 885282, "end": 886239}, {"filename": "/kha.tbl", "start": 886239, "end": 886800}, {"filename": "/bel-comp.utb", "start": 886800, "end": 888333}, {"filename": "/hyph_eo.dic", "start": 888333, "end": 899000}, {"filename": "/gd.ctb", "start": 899000, "end": 904289}, {"filename": "/tr-g2.tbl", "start": 904289, "end": 904879}, {"filename": "/ukmaths_single_cell_defs.cti", "start": 904879, "end": 906411}, {"filename": "/no-no-8dot.utb", "start": 906411, "end": 926573}, {"filename": "/da-dk-g18.ctb", "start": 926573, "end": 951783}, {"filename": "/ckb.tbl", "start": 951783, "end": 952346}, {"filename": "/zhcn-g2.ctb", "start": 952346, "end": 1569819}, {"filename": "/ve-za-g1.utb", "start": 1569819, "end": 1572531}, {"filename": "/ru-letters.dis", "start": 1572531, "end": 1575363}, {"filename": "/zh_HK.tbl", "start": 1575363, "end": 1575693}, {"filename": "/hr-digits.uti", "start": 1575693, "end": 1577136}, {"filename": "/ukmaths_unicode_defs.cti", "start": 1577136, "end": 1592685}, {"filename": "/ethio-g1.ctb", "start": 1592685, "end": 1600457}, {"filename": "/he-IL-comp8.utb", "start": 1600457, "end": 1619759}, {"filename": "/sk-sk.utb", "start": 1619759, "end": 1622807}, {"filename": "/pi.tbl", "start": 1622807, "end": 1623349}, {"filename": "/no-no-8dot-fallback-6dot-g0.utb", "start": 1623349, "end": 1625707}, {"filename": "/ar-ar-comp8.utb", "start": 1625707, "end": 1634923}, {"filename": "/pl.tbl", "start": 1634923, "end": 1635154}, {"filename": "/bg.tbl", "start": 1635154, "end": 1635840}, {"filename": "/ko-2006.cti", "start": 1635840, "end": 1639799}, {"filename": "/hi-in-g1.utb", "start": 1639799, "end": 1641015}, {"filename": "/ru-brf.dis", "start": 1641015, "end": 1643545}, {"filename": "/no-no-chardefs6.uti", "start": 1643545, "end": 1656494}, {"filename": "/hr-g1.tbl", "start": 1656494, "end": 1656642}, {"filename": "/mr-in-g1.utb", "start": 1656642, "end": 1657860}, {"filename": "/us-table.dis", "start": 1657860, "end": 1659422}, {"filename": "/maketablelist.sh", "start": 1659422, "end": 1660440}, {"filename": "/hr-comp8.tbl", "start": 1660440, "end": 1661130}, {"filename": "/is.ctb", "start": 1661130, "end": 1667031}, {"filename": "/ka-in-g1.utb", "start": 1667031, "end": 1668246}, {"filename": "/pa.tbl", "start": 1668246, "end": 1668810}, {"filename": "/marburg_single_cell_defs.cti", "start": 1668810, "end": 1670325}, {"filename": "/README", "start": 1670325, "end": 1670486}, {"filename": "/countries.cti", "start": 1670486, "end": 1677189}, {"filename": "/latinLetterDef6Dots.uti", "start": 1677189, "end": 1679182}, {"filename": "/zh_CHN.tbl", "start": 1679182, "end": 1680062}, {"filename": "/en-chess.ctb", "start": 1680062, "end": 1681560}, {"filename": "/hu-backtranslate-word-corrections.cti", "start": 1681560, "end": 1736003}, {"filename": "/hyph_en_US.dic", "start": 1736003, "end": 1772470}, {"filename": "/ro.ctb", "start": 1772470, "end": 1777478}, {"filename": "/es-old.dis", "start": 1777478, "end": 1785083}, {"filename": "/hu.tbl", "start": 1785083, "end": 1785582}, {"filename": "/en-gb-g1.utb", "start": 1785582, "end": 1799828}, {"filename": "/tr.tbl", "start": 1799828, "end": 1800510}, {"filename": "/en-ueb-chardefs.uti", "start": 1800510, "end": 1828154}, {"filename": "/zu-za-g2.ctb", "start": 1828154, "end": 1829221}, {"filename": "/lt.ctb", "start": 1829221, "end": 1846334}, {"filename": "/ar-ar-g1-core.uti", "start": 1846334, "end": 1856826}, {"filename": "/el.ctb", "start": 1856826, "end": 1887629}, {"filename": "/ta.tbl", "start": 1887629, "end": 1888307}, {"filename": "/ne.ctb", "start": 1888307, "end": 1889199}, {"filename": "/ar.tbl", "start": 1889199, "end": 1889711}, {"filename": "/en-us-mathtext.ctb", "start": 1889711, "end": 1910159}, {"filename": "/digits6Dots.uti", "start": 1910159, "end": 1911077}, {"filename": "/bo.ctb", "start": 1911077, "end": 1918325}, {"filename": "/digits6DotsPlusDot6.uti", "start": 1918325, "end": 1919390}, {"filename": "/kn.tbl", "start": 1919390, "end": 1919954}, {"filename": "/dra.ctb", "start": 1919954, "end": 1920859}, {"filename": "/cs.tbl", "start": 1920859, "end": 1921491}, {"filename": "/sot-za-g1.ctb", "start": 1921491, "end": 1924221}, {"filename": "/ba.utb", "start": 1924221, "end": 1926686}, {"filename": "/de-accents-detailed.cti", "start": 1926686, "end": 1944108}, {"filename": "/fa-ir-g1.utb", "start": 1944108, "end": 1950279}, {"filename": "/tt.utb", "start": 1950279, "end": 1952967}, {"filename": "/sv.tbl", "start": 1952967, "end": 1953531}, {"filename": "/cs-comp8.utb", "start": 1953531, "end": 1967034}, {"filename": "/corrections.cti", "start": 1967034, "end": 1968526}, {"filename": "/uk-comp.utb", "start": 1968526, "end": 1970437}, {"filename": "/vi-lettersdef.uti", "start": 1970437, "end": 1979623}, {"filename": "/it-it-comp6.utb", "start": 1979623, "end": 1984498}, {"filename": "/grc-international-decomposed.uti", "start": 1984498, "end": 2000944}, {"filename": "/latinUppercaseComp6.uti", "start": 2000944, "end": 2002812}, {"filename": "/Makefile.am", "start": 2002812, "end": 2009582}, {"filename": "/hi.tbl", "start": 2009582, "end": 2010142}, {"filename": "/mn-in-g1.utb", "start": 2010142, "end": 2011358}, {"filename": "/de-g2.ctb", "start": 2011358, "end": 2013224}, {"filename": "/da-dk-g26l-lit.ctb", "start": 2013224, "end": 2029294}, {"filename": "/sl.tbl", "start": 2029294, "end": 2029937}, {"filename": "/de-eurobrl6u.dis", "start": 2029937, "end": 2032028}, {"filename": "/ko.cti", "start": 2032028, "end": 2037628}, {"filename": "/sr.tbl", "start": 2037628, "end": 2038293}, {"filename": "/tsn-za-g1.ctb", "start": 2038293, "end": 2039435}, {"filename": "/np-in-g1.utb", "start": 2039435, "end": 2040652}, {"filename": "/ko-g2.ctb", "start": 2040652, "end": 2042548}, {"filename": "/fa-ir-comp8.ctb", "start": 2042548, "end": 2048220}, {"filename": "/no-no-generic.dis", "start": 2048220, "end": 2062393}, {"filename": "/en-ueb-g1.ctb", "start": 2062393, "end": 2067409}, {"filename": "/pl-pl-comp8.ctb", "start": 2067409, "end": 2085746}, {"filename": "/IPA.utb", "start": 2085746, "end": 2093046}, {"filename": "/or.tbl", "start": 2093046, "end": 2093606}, {"filename": "/en_CA.ctb", "start": 2093606, "end": 2101710}, {"filename": "/gu.tbl", "start": 2101710, "end": 2102276}, {"filename": "/hu-hu-g2.ctb", "start": 2102276, "end": 2115776}, {"filename": "/da-dk-g26.ctb", "start": 2115776, "end": 2145074}, {"filename": "/gurumuki.cti", "start": 2145074, "end": 2163029}, {"filename": "/telugu.cti", "start": 2163029, "end": 2173006}, {"filename": "/ru-unicode.dis", "start": 2173006, "end": 2176031}, {"filename": "/dra.tbl", "start": 2176031, "end": 2176719}, {"filename": "/kmr.tbl", "start": 2176719, "end": 2181496}, {"filename": "/de-accents.cti", "start": 2181496, "end": 2198835}, {"filename": "/controlchars.cti", "start": 2198835, "end": 2199880}, {"filename": "/mun.ctb", "start": 2199880, "end": 2200802}, {"filename": "/mwr.ctb", "start": 2200802, "end": 2201729}, {"filename": "/hu-chardefs.cti", "start": 2201729, "end": 2214098}, {"filename": "/es-g2.ctb", "start": 2214098, "end": 2239774}, {"filename": "/hyph_nl_NL.dic", "start": 2239774, "end": 2323299}, {"filename": "/ve-za-g2.ctb", "start": 2323299, "end": 2328151}, {"filename": "/vi.ctb", "start": 2328151, "end": 2340292}, {"filename": "/cop-eg-comp8.utb", "start": 2340292, "end": 2348977}, {"filename": "/loweredDigits8Dots.uti", "start": 2348977, "end": 2349913}, {"filename": "/ml.tbl", "start": 2349913, "end": 2350591}, {"filename": "/hyph_de_DE.dic", "start": 2350591, "end": 2387392}, {"filename": "/no-no-g1.ctb", "start": 2387392, "end": 2391524}, {"filename": "/bo.tbl", "start": 2391524, "end": 2392206}, {"filename": "/en-us-brf.dis", "start": 2392206, "end": 2394644}, {"filename": "/de-g0-detailed.utb", "start": 2394644, "end": 2397677}, {"filename": "/mun.tbl", "start": 2397677, "end": 2398223}, {"filename": "/da-dk-g28l.ctb", "start": 2398223, "end": 2448553}, {"filename": "/hyph_it_IT.dic", "start": 2448553, "end": 2450759}, {"filename": "/Es-Es-G0.utb", "start": 2450759, "end": 2458991}, {"filename": "/hu-exceptionwords.cti", "start": 2458991, "end": 2587917}, {"filename": "/hyph_ru.dic", "start": 2587917, "end": 2655453}, {"filename": "/de-chess.ctb", "start": 2655453, "end": 2656942}, {"filename": "/tr-g1.ctb", "start": 2656942, "end": 2659576}, {"filename": "/braille-patterns.cti", "start": 2659576, "end": 2686007}, {"filename": "/bn.tbl", "start": 2686007, "end": 2686571}, {"filename": "/kh-in-g1.utb", "start": 2686571, "end": 2687763}, {"filename": "/awa.tbl", "start": 2687763, "end": 2688326}, {"filename": "/ckb-chardefs.cti", "start": 2688326, "end": 2696889}, {"filename": "/ar-ar-g2.ctb", "start": 2696889, "end": 2708688}, {"filename": "/es-chardefs.cti", "start": 2708688, "end": 2723284}, {"filename": "/es-g1.ctb", "start": 2723284, "end": 2724521}, {"filename": "/vi-vn-g1.ctb", "start": 2724521, "end": 2742156}, {"filename": "/pt-pt-g2.ctb", "start": 2742156, "end": 2763716}, {"filename": "/lv.tbl", "start": 2763716, "end": 2764393}, {"filename": "/eo-g1-x-system.ctb", "start": 2764393, "end": 2767354}, {"filename": "/zh-hk.ctb", "start": 2767354, "end": 3032273}, {"filename": "/mwr.tbl", "start": 3032273, "end": 3032823}, {"filename": "/hyph_brl_da_dk.dic", "start": 3032823, "end": 3094041}, {"filename": "/hyph_cs_CZ.dic", "start": 3094041, "end": 3115784}, {"filename": "/is-chardefs6.cti", "start": 3115784, "end": 3123325}, {"filename": "/eo.tbl", "start": 3123325, "end": 3123994}, {"filename": "/nl-chardefs.uti", "start": 3123994, "end": 3146073}, {"filename": "/ne.tbl", "start": 3146073, "end": 3146635}, {"filename": "/marburg_unicode_defs.cti", "start": 3146635, "end": 3162183}, {"filename": "/ur-pk-g2.ctb", "start": 3162183, "end": 3215080}, {"filename": "/et.ctb", "start": 3215080, "end": 3219185}, {"filename": "/sd.tbl", "start": 3219185, "end": 3219747}, {"filename": "/IPA-unicode-range.uti", "start": 3219747, "end": 3232935}, {"filename": "/wiskunde-chardefs.cti", "start": 3232935, "end": 3251494}, {"filename": "/nl-BE.dis", "start": 3251494, "end": 3253150}, {"filename": "/de-g1.ctb", "start": 3253150, "end": 3254938}, {"filename": "/as.tbl", "start": 3254938, "end": 3255504}, {"filename": "/fr-bfu-comp8.utb", "start": 3255504, "end": 3271963}, {"filename": "/ms-my-g2.ctb", "start": 3271963, "end": 3417353}, {"filename": "/ml-in-g1.utb", "start": 3417353, "end": 3418572}, {"filename": "/en-us-g1.ctb", "start": 3418572, "end": 3422879}, {"filename": "/zu-za-g1.utb", "start": 3422879, "end": 3423948}, {"filename": "/grc-international-composed.uti", "start": 3423948, "end": 3440252}, {"filename": "/is-chardefs8.cti", "start": 3440252, "end": 3461494}, {"filename": "/mn-MN-g2.ctb", "start": 3461494, "end": 3463437}, {"filename": "/km-g1.utb", "start": 3463437, "end": 3472658}, {"filename": "/bra.tbl", "start": 3472658, "end": 3473217}, {"filename": "/kannada.cti", "start": 3473217, "end": 3481928}, {"filename": "/se-se.dis", "start": 3481928, "end": 3496619}, {"filename": "/sk-sk-g1.utb", "start": 3496619, "end": 3502554}, {"filename": "/zh_TW.tbl", "start": 3502554, "end": 3503011}, {"filename": "/et.tbl", "start": 3503011, "end": 3503695}, {"filename": "/br-in-g1.utb", "start": 3503695, "end": 3504909}, {"filename": "/tr-g2.ctb", "start": 3504909, "end": 3518144}, {"filename": "/en-us-compbrl.uti", "start": 3518144, "end": 3520311}, {"filename": "/cs-translation.cti", "start": 3520311, "end": 3522838}, {"filename": "/en-us-emphasis.uti", "start": 3522838, "end": 3524280}, {"filename": "/ru-ru-g1.ctb", "start": 3524280, "end": 3542860}, {"filename": "/xh-za-g1.utb", "start": 3542860, "end": 3545135}, {"filename": "/gr-pl-comp8.uti", "start": 3545135, "end": 3560625}, {"filename": "/hr-chardefs.cti", "start": 3560625, "end": 3577146}, {"filename": "/fr-bfu-comp6.utb", "start": 3577146, "end": 3595712}, {"filename": "/ta.ctb", "start": 3595712, "end": 3596632}, {"filename": "/sr-g1.ctb", "start": 3596632, "end": 3599964}, {"filename": "/lt-6dot.tbl", "start": 3599964, "end": 3600132}, {"filename": "/it-it-comp8.utb", "start": 3600132, "end": 3628981}, {"filename": "/ukchardefs.cti", "start": 3628981, "end": 3632215}, {"filename": "/te-in-g1.utb", "start": 3632215, "end": 3633428}, {"filename": "/hyph_nn_NO.dic", "start": 3633428, "end": 3820106}, {"filename": "/da-dk-g26-lit.ctb", "start": 3820106, "end": 3841922}, {"filename": "/hy.ctb", "start": 3841922, "end": 3847348}, {"filename": "/en-us-comp8-ext.utb", "start": 3847348, "end": 3873470}, {"filename": "/hyph_sv_SE.dic", "start": 3873470, "end": 3904729}, {"filename": "/se-se.ctb", "start": 3904729, "end": 3914970}, {"filename": "/is.tbl", "start": 3914970, "end": 3915541}, {"filename": "/boxes.ctb", "start": 3915541, "end": 3925846}, {"filename": "/hyph_da_DK.dic", "start": 3925846, "end": 3987244}, {"filename": "/no-no-g3.ctb", "start": 3987244, "end": 4006374}, {"filename": "/de-g1-core.cti", "start": 4006374, "end": 4009121}, {"filename": "/en_CA.tbl", "start": 4009121, "end": 4009846}, {"filename": "/ckb-g1.ctb", "start": 4009846, "end": 4011056}, {"filename": "/no-no-g2.ctb", "start": 4011056, "end": 4024069}, {"filename": "/bel.utb", "start": 4024069, "end": 4025633}, {"filename": "/da-dk-8miscChars.cti", "start": 4025633, "end": 4029513}, {"filename": "/ckb-translation.cti", "start": 4029513, "end": 4033054}, {"filename": "/nl-NL-g0.utb", "start": 4033054, "end": 4044038}, {"filename": "/ur-pk-g1.utb", "start": 4044038, "end": 4056344}, {"filename": "/devanagari.cti", "start": 4056344, "end": 4065651}, {"filename": "/be-in-g1.utb", "start": 4065651, "end": 4066866}, {"filename": "/ca.tbl", "start": 4066866, "end": 4067427}, {"filename": "/en-ueb-g2.ctb", "start": 4067427, "end": 4259195}, {"filename": "/tr.ctb", "start": 4259195, "end": 4263876}, {"filename": "/vi-vn-g2.ctb", "start": 4263876, "end": 4286558}, {"filename": "/de-de.dis", "start": 4286558, "end": 4301159}, {"filename": "/gd.tbl", "start": 4301159, "end": 4301857}, {"filename": "/hu-backtranslate-correction.dis", "start": 4301857, "end": 4303212}, {"filename": "/hu-hu-g1_braille_input.cti", "start": 4303212, "end": 4318816}, {"filename": "/pt-pt-comp8.ctb", "start": 4318816, "end": 4322501}, {"filename": "/or-in-g1.utb", "start": 4322501, "end": 4323712}, {"filename": "/no-no-comp8.ctb", "start": 4323712, "end": 4344644}, {"filename": "/cy.tbl", "start": 4344644, "end": 4345246}, {"filename": "/en-us-comp6.ctb", "start": 4345246, "end": 4350181}, {"filename": "/da-dk-g08.ctb", "start": 4350181, "end": 4363708}, {"filename": "/unicode-braille.utb", "start": 4363708, "end": 4388541}, {"filename": "/cs-g1.ctb", "start": 4388541, "end": 4389895}, {"filename": "/sl-si-g1.utb", "start": 4389895, "end": 4406116}, {"filename": "/ru-compbrl.ctb", "start": 4406116, "end": 4413426}, {"filename": "/grc-international-en.utb", "start": 4413426, "end": 4418148}, {"filename": "/sa.tbl", "start": 4418148, "end": 4418714}, {"filename": "/xh-za-g2.ctb", "start": 4418714, "end": 4427607}, {"filename": "/pu-in-g1.utb", "start": 4427607, "end": 4429468}, {"filename": "/hu-hu-g2_exceptions.cti", "start": 4429468, "end": 4472190}, {"filename": "/bengali.cti", "start": 4472190, "end": 4482527}, {"filename": "/no-no-braillo-047-01.dis", "start": 4482527, "end": 4490346}, {"filename": "/tsn-za-g2.ctb", "start": 4490346, "end": 4491485}, {"filename": "/uk.utb", "start": 4491485, "end": 4493687}, {"filename": "/printables.cti", "start": 4493687, "end": 4495521}, {"filename": "/uni-text.dis", "start": 4495521, "end": 4510137}, {"filename": "/pi.ctb", "start": 4510137, "end": 4511061}, {"filename": "/latinLetterDef8Dots.uti", "start": 4511061, "end": 4512849}, {"filename": "/bg.dis", "start": 4512849, "end": 4514598}, {"filename": "/de-comp6.utb", "start": 4514598, "end": 4529745}, {"filename": "/ko-chars.cti", "start": 4529745, "end": 4912093}, {"filename": "/digits8Dots.uti", "start": 4912093, "end": 4913021}, {"filename": "/pt-pt-g1.utb", "start": 4913021, "end": 4921388}, {"filename": "/ga-g2.ctb", "start": 4921388, "end": 4926174}, {"filename": "/en-us-g2.ctb", "start": 4926174, "end": 4954104}, {"filename": "/vi-saigon-g1.ctb", "start": 4954104, "end": 4975685}, {"filename": "/de-chardefs6.cti", "start": 4975685, "end": 4992592}, {"filename": "/mt.ctb", "start": 4992592, "end": 4997143}, {"filename": "/Makefile", "start": 4997143, "end": 5051005}, {"filename": "/as-in-g1.utb", "start": 5051005, "end": 5052221}, {"filename": "/sk-g1.ctb", "start": 5052221, "end": 5127038}, {"filename": "/hyph_es_ES.dic", "start": 5127038, "end": 5131255}, {"filename": "/en-us-comp8.ctb", "start": 5131255, "end": 5132887}, {"filename": "/en-chardefs.cti", "start": 5132887, "end": 5141197}, {"filename": "/spaces.uti", "start": 5141197, "end": 5143299}, {"filename": "/tamil.cti", "start": 5143299, "end": 5146517}, {"filename": "/de-de-comp8.ctb", "start": 5146517, "end": 5155850}, {"filename": "/hu-hu-comp8.ctb", "start": 5155850, "end": 5160653}, {"filename": "/ta-ta-g1.ctb", "start": 5160653, "end": 5165692}, {"filename": "/malayalam.cti", "start": 5165692, "end": 5184431}, {"filename": "/te.tbl", "start": 5184431, "end": 5184993}, {"filename": "/kok.ctb", "start": 5184993, "end": 5185920}, {"filename": "/de-g2-core-patterns.dic", "start": 5185920, "end": 5378488}, {"filename": "/hyph_fr_FR.dic", "start": 5378488, "end": 5386467}, {"filename": "/cy-cy-g1.utb", "start": 5386467, "end": 5400264}, {"filename": "/loweredDigits6Dots.uti", "start": 5400264, "end": 5401190}, {"filename": "/ru-litbrl.ctb", "start": 5401190, "end": 5436659}, {"filename": "/sah.utb", "start": 5436659, "end": 5439313}, {"filename": "/no-no.dis", "start": 5439313, "end": 5452957}, {"filename": "/bg.utb", "start": 5452957, "end": 5464418}, {"filename": "/ar-ar-math.uti", "start": 5464418, "end": 5485283}, {"filename": "/fr-bfu-g2.ctb", "start": 5485283, "end": 5629673}, {"filename": "/zh-chn.ctb", "start": 5629673, "end": 6892809}, {"filename": "/ar-ar-g1.utb", "start": 6892809, "end": 6894019}, {"filename": "/mr.tbl", "start": 6894019, "end": 6894583}, {"filename": "/mt.tbl", "start": 6894583, "end": 6895265}, {"filename": "/en-ueb-math.ctb", "start": 6895265, "end": 6897310}, {"filename": "/da-dk-g28.ctb", "start": 6897310, "end": 6959383}, {"filename": "/nso-za-g1.utb", "start": 6959383, "end": 6960777}, {"filename": "/en_GB.tbl", "start": 6960777, "end": 6961612}, {"filename": "/cs-chardefs.cti", "start": 6961612, "end": 6985871}, {"filename": "/ko-g1-rules.cti", "start": 6985871, "end": 7009552}, {"filename": "/de-g2-detailed.ctb", "start": 7009552, "end": 7011826}, {"filename": "/en_US.tbl", "start": 7011826, "end": 7012665}, {"filename": "/no-no-g0.utb", "start": 7012665, "end": 7019376}, {"filename": "/mn-MN-g1.utb", "start": 7019376, "end": 7021323}, {"filename": "/de-g0-core.uti", "start": 7021323, "end": 7048203}, {"filename": "/lt-6dot.utb", "start": 7048203, "end": 7060188}, {"filename": "/gon.ctb", "start": 7060188, "end": 7061132}, {"filename": "/sv-1989.ctb", "start": 7061132, "end": 7070737}, {"filename": "/it.tbl", "start": 7070737, "end": 7071303}, {"filename": "/ko-2006-g2.ctb", "start": 7071303, "end": 7073278}, {"filename": "/vi-charsdef.uti", "start": 7073278, "end": 7075927}, {"filename": "/compress.cti", "start": 7075927, "end": 7077346}, {"filename": "/ru.ctb", "start": 7077346, "end": 7113116}, {"filename": "/hyph_pt_PT.dic", "start": 7113116, "end": 7114404}, {"filename": "/oriya.cti", "start": 7114404, "end": 7123937}, {"filename": "/hr-comp8.utb", "start": 7123937, "end": 7134454}, {"filename": "/eurodefs.cti", "start": 7134454, "end": 7137500}, {"filename": "/Lv-Lv-g1.utb", "start": 7137500, "end": 7158184}, {"filename": "/hyph_pl_PL.dic", "start": 7158184, "end": 7188735}, {"filename": "/unicode-without-blank.dis", "start": 7188735, "end": 7189637}, {"filename": "/my-g1.utb", "start": 7189637, "end": 7209870}, {"filename": "/sr-chardefs.cti", "start": 7209870, "end": 7218611}, {"filename": "/en-us-interline.ctb", "start": 7218611, "end": 7246587}, {"filename": "/sot-za-g2.ctb", "start": 7246587, "end": 7252873}, {"filename": "/da-dk-6miscChars.cti", "start": 7252873, "end": 7273499}, {"filename": "/ca-g1.ctb", "start": 7273499, "end": 7275257}, {"filename": "/iu-ca-g1.ctb", "start": 7275257, "end": 7283429}, {"filename": "/da-dk-g16.ctb", "start": 7283429, "end": 7291525}, {"filename": "/text_nabcc.dis", "start": 7291525, "end": 7313175}, {"filename": "/haw-us-g1.ctb", "start": 7313175, "end": 7314758}, {"filename": "/eo-g1.ctb", "start": 7314758, "end": 7317644}, {"filename": "/sin.utb", "start": 7317644, "end": 7318999}, {"filename": "/vi-puncsdef.uti", "start": 7318999, "end": 7336616}, {"filename": "/uz-g1.utb", "start": 7336616, "end": 7340173}, {"filename": "/sin.cti", "start": 7340173, "end": 7351137}, {"filename": "/vi-vn-g0.utb", "start": 7351137, "end": 7354257}, {"filename": "/zh-tw.ctb", "start": 7354257, "end": 8815161}, {"filename": "/chr-us-g1.ctb", "start": 8815161, "end": 8820723}, {"filename": "/ga-g1.utb", "start": 8820723, "end": 8823986}, {"filename": "/kru.tbl", "start": 8823986, "end": 8824534}, {"filename": "/no-no-latinLetterDef6Dots_diacritics.uti", "start": 8824534, "end": 8873626}, {"filename": "/cy-cy-g2.ctb", "start": 8873626, "end": 8877931}, {"filename": "/da-dk-g16-lit.ctb", "start": 8877931, "end": 8887337}, {"filename": "/mni.tbl", "start": 8887337, "end": 8887904}, {"filename": "/es-new.dis", "start": 8887904, "end": 8895515}, {"filename": "/hy.tbl", "start": 8895515, "end": 8896199}, {"filename": "/mao-nz-g1.ctb", "start": 8896199, "end": 8897999}, {"filename": "/ko-g2-rules.cti", "start": 8897999, "end": 9172775}, {"filename": "/de-chardefs8.cti", "start": 9172775, "end": 9192773}, {"filename": "/latinLowercase.uti", "start": 9192773, "end": 9203219}, {"filename": "/nl-comp8.utb", "start": 9203219, "end": 9213240}, {"filename": "/aw-in-g1.utb", "start": 9213240, "end": 9214456}, {"filename": "/hr-translation.cti", "start": 9214456, "end": 9222926}, {"filename": "/ro.tbl", "start": 9222926, "end": 9223610}, {"filename": "/kru.ctb", "start": 9223610, "end": 9224536}, {"filename": "/ru-litbrl-detailed.utb", "start": 9224536, "end": 9228657}, {"filename": "/nso-za-g2.ctb", "start": 9228657, "end": 9230048}, {"filename": "/zhcn-g1.ctb", "start": 9230048, "end": 9906105}, {"filename": "/en-gb-comp8.ctb", "start": 9906105, "end": 9908387}, {"filename": "/gujarati.cti", "start": 9908387, "end": 9917990}, {"filename": "/da-dk-g26l.ctb", "start": 9917990, "end": 9933138}, {"filename": "/hr-g1.ctb", "start": 9933138, "end": 9934648}, {"filename": "/de-g2-core.cti", "start": 9934648, "end": 9968948}, {"filename": "/grc-international-common.uti", "start": 9968948, "end": 9972421}, {"filename": "/nemethdefs.cti", "start": 9972421, "end": 9988917}, {"filename": "/nl.tbl", "start": 9988917, "end": 9989631}, {"filename": "/bg.ctb", "start": 9989631, "end": 9996451}, {"filename": "/ko-g1.ctb", "start": 9996451, "end": 9998145}, {"filename": "/sa-in-g1.utb", "start": 9998145, "end": 9999364}, {"filename": "/en-GB-g2.ctb", "start": 9999364, "end": 10019018}, {"filename": "/pt.tbl", "start": 10019018, "end": 10019630}, {"filename": "/en_US-comp8-ext.tbl", "start": 10019630, "end": 10020019}, {"filename": "/hyph_hu_HU.dic", "start": 10020019, "end": 10896123}, {"filename": "/hu-hu-g1.ctb", "start": 10896123, "end": 10900989}], "remote_package_size": 10900989});
 
   })();
 
@@ -623,7 +621,7 @@
 var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
 
 if (Module['ENVIRONMENT']) {
-  throw new Error('Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -s ENVIRONMENT=web or -s ENVIRONMENT=node)');
+  throw new Error('Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)');
 }
 
 // `/` should be present at the end if `scriptDirectory` is not empty
@@ -904,7 +902,7 @@
 var NODEFS = 'NODEFS is no longer included by default; build with -lnodefs.js';
 
 
-assert(!ENVIRONMENT_IS_SHELL, "shell environment detected but not enabled at build time.  Add 'shell' to `-s ENVIRONMENT` to enable.");
+assert(!ENVIRONMENT_IS_SHELL, "shell environment detected but not enabled at build time.  Add 'shell' to `-sENVIRONMENT` to enable.");
 
 
 
@@ -914,10 +912,10 @@
 
 function getNativeTypeSize(type) {
   switch (type) {
-    case 'i1': case 'i8': return 1;
-    case 'i16': return 2;
-    case 'i32': return 4;
-    case 'i64': return 8;
+    case 'i1': case 'i8': case 'u8': return 1;
+    case 'i16': case 'u16': return 2;
+    case 'i32': case 'u32': return 4;
+    case 'i64': case 'u64': return 8;
     case 'float': return 4;
     case 'double': return 8;
     default: {
@@ -945,6 +943,37 @@
 // include: runtime_functions.js
 
 
+// This gives correct answers for everything less than 2^{14} = 16384
+// I hope nobody is contemplating functions with 16384 arguments...
+function uleb128Encode(n) {
+  assert(n < 16384);
+  if (n < 128) {
+    return [n];
+  }
+  return [(n % 128) | 128, n >> 7];
+}
+
+// Converts a signature like 'vii' into a description of the wasm types, like
+// { parameters: ['i32', 'i32'], results: [] }.
+function sigToWasmTypes(sig) {
+  var typeNames = {
+    'i': 'i32',
+    'j': 'i64',
+    'f': 'f32',
+    'd': 'f64',
+    'p': 'i32',
+  };
+  var type = {
+    parameters: [],
+    results: sig[0] == 'v' ? [] : [typeNames[sig[0]]]
+  };
+  for (var i = 1; i < sig.length; ++i) {
+    assert(sig[i] in typeNames, 'invalid signature char: ' + sig[i]);
+    type.parameters.push(typeNames[sig[i]]);
+  }
+  return type;
+}
+
 // Wraps a JS function as a wasm function with a given signature.
 function convertJsFunctionToWasm(func, sig) {
 
@@ -953,27 +982,12 @@
   // Otherwise, construct a minimal wasm module importing the JS function and
   // re-exporting it.
   if (typeof WebAssembly.Function == "function") {
-    var typeNames = {
-      'i': 'i32',
-      'j': 'i64',
-      'f': 'f32',
-      'd': 'f64'
-    };
-    var type = {
-      parameters: [],
-      results: sig[0] == 'v' ? [] : [typeNames[sig[0]]]
-    };
-    for (var i = 1; i < sig.length; ++i) {
-      type.parameters.push(typeNames[sig[i]]);
-    }
-    return new WebAssembly.Function(type, func);
+    return new WebAssembly.Function(sigToWasmTypes(sig), func);
   }
 
   // The module is static, with the exception of the type section, which is
   // generated based on the signature passed in.
   var typeSection = [
-    0x01, // id: section,
-    0x00, // length: 0 (placeholder)
     0x01, // count: 1
     0x60, // form: func
   ];
@@ -981,14 +995,16 @@
   var sigParam = sig.slice(1);
   var typeCodes = {
     'i': 0x7f, // i32
+    'p': 0x7f, // i32
     'j': 0x7e, // i64
     'f': 0x7d, // f32
     'd': 0x7c, // f64
   };
 
   // Parameters, length + signatures
-  typeSection.push(sigParam.length);
+  typeSection = typeSection.concat(uleb128Encode(sigParam.length));
   for (var i = 0; i < sigParam.length; ++i) {
+    assert(sigParam[i] in typeCodes, 'invalid signature char: ' + sigParam[i]);
     typeSection.push(typeCodes[sigParam[i]]);
   }
 
@@ -1000,9 +1016,12 @@
     typeSection = typeSection.concat([0x01, typeCodes[sigRet]]);
   }
 
-  // Write the overall length of the type section back into the section header
-  // (excepting the 2 bytes for the section id and length)
-  typeSection[1] = typeSection.length - 2;
+  // Write the section code and overall length of the type section into the
+  // section header
+  typeSection = [0x01 /* Type section code */].concat(
+    uleb128Encode(typeSection.length),
+    typeSection
+  );
 
   // Rest of the module is static
   var bytes = new Uint8Array([
@@ -1130,7 +1149,7 @@
 function unexportedMessage(sym, isFSSybol) {
   var msg = "'" + sym + "' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the FAQ)";
   if (isFSSybol) {
-    msg += '. Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you';
+    msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you';
   }
   return msg;
 }
@@ -1177,49 +1196,6 @@
   abort('no native wasm support detected');
 }
 
-// include: runtime_safe_heap.js
-
-
-// In MINIMAL_RUNTIME, setValue() and getValue() are only available when building with safe heap enabled, for heap safety checking.
-// In traditional runtime, setValue() and getValue() are always available (although their use is highly discouraged due to perf penalties)
-
-/** @param {number} ptr
-    @param {number} value
-    @param {string} type
-    @param {number|boolean=} noSafe */
-function setValue(ptr, value, type = 'i8', noSafe) {
-  if (type.charAt(type.length-1) === '*') type = 'i32';
-    switch (type) {
-      case 'i1': HEAP8[((ptr)>>0)] = value; break;
-      case 'i8': HEAP8[((ptr)>>0)] = value; break;
-      case 'i16': HEAP16[((ptr)>>1)] = value; break;
-      case 'i32': HEAP32[((ptr)>>2)] = value; break;
-      case 'i64': (tempI64 = [value>>>0,(tempDouble=value,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? ((Math.min((+(Math.floor((tempDouble)/4294967296.0))), 4294967295.0))|0)>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)],HEAP32[((ptr)>>2)] = tempI64[0],HEAP32[(((ptr)+(4))>>2)] = tempI64[1]); break;
-      case 'float': HEAPF32[((ptr)>>2)] = value; break;
-      case 'double': HEAPF64[((ptr)>>3)] = value; break;
-      default: abort('invalid type for setValue: ' + type);
-    }
-}
-
-/** @param {number} ptr
-    @param {string} type
-    @param {number|boolean=} noSafe */
-function getValue(ptr, type = 'i8', noSafe) {
-  if (type.charAt(type.length-1) === '*') type = 'i32';
-    switch (type) {
-      case 'i1': return HEAP8[((ptr)>>0)];
-      case 'i8': return HEAP8[((ptr)>>0)];
-      case 'i16': return HEAP16[((ptr)>>1)];
-      case 'i32': return HEAP32[((ptr)>>2)];
-      case 'i64': return HEAP32[((ptr)>>2)];
-      case 'float': return HEAPF32[((ptr)>>2)];
-      case 'double': return Number(HEAPF64[((ptr)>>3)]);
-      default: abort('invalid type for getValue: ' + type);
-    }
-  return null;
-}
-
-// end include: runtime_safe_heap.js
 // Wasm globals
 
 var wasmMemory;
@@ -1277,7 +1253,10 @@
   };
 
   function convertReturnValue(ret) {
-    if (returnType === 'string') return UTF8ToString(ret);
+    if (returnType === 'string') {
+      
+      return UTF8ToString(ret);
+    }
     if (returnType === 'boolean') return Boolean(ret);
     return ret;
   }
@@ -1425,7 +1404,6 @@
  * @return {string}
  */
 function UTF8ToString(ptr, maxBytesToRead) {
-  ;
   return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : '';
 }
 
@@ -1784,8 +1762,8 @@
        'JS engine does not provide full typed array support');
 
 // If memory is defined in wasm, the user can't provide it.
-assert(!Module['wasmMemory'], 'Use of `wasmMemory` detected.  Use -s IMPORTED_MEMORY to define wasmMemory externally');
-assert(INITIAL_MEMORY == 167772160, 'Detected runtime INITIAL_MEMORY setting.  Use -s IMPORTED_MEMORY to define wasmMemory dynamically');
+assert(!Module['wasmMemory'], 'Use of `wasmMemory` detected.  Use -sIMPORTED_MEMORY to define wasmMemory externally');
+assert(INITIAL_MEMORY == 167772160, 'Detected runtime INITIAL_MEMORY setting.  Use -sIMPORTED_MEMORY to define wasmMemory dynamically');
 
 // include: runtime_init_table.js
 // In regular non-RELOCATABLE mode the table is exported
@@ -1807,7 +1785,7 @@
   HEAP32[((max)>>2)] = 0x2135467;
   HEAP32[(((max)+(4))>>2)] = 0x89BACDFE;
   // Also test the global address 0 for integrity.
-  HEAP32[0] = 0x63736d65; /* 'emsc' */
+  HEAPU32[0] = 0x63736d65; /* 'emsc' */
 }
 
 function checkStackCookie() {
@@ -1816,10 +1794,10 @@
   var cookie1 = HEAPU32[((max)>>2)];
   var cookie2 = HEAPU32[(((max)+(4))>>2)];
   if (cookie1 != 0x2135467 || cookie2 != 0x89BACDFE) {
-    abort('Stack overflow! Stack cookie has been overwritten, expected hex dwords 0x89BACDFE and 0x2135467, but received 0x' + cookie2.toString(16) + ' 0x' + cookie1.toString(16));
+    abort('Stack overflow! Stack cookie has been overwritten at 0x' + max.toString(16) + ', expected hex dwords 0x89BACDFE and 0x2135467, but received 0x' + cookie2.toString(16) + ' 0x' + cookie1.toString(16));
   }
   // Also test the global address 0 for integrity.
-  if (HEAP32[0] !== 0x63736d65 /* 'emsc' */) abort('Runtime error: The application has corrupted its heap memory area (address zero)!');
+  if (HEAPU32[0] !== 0x63736d65 /* 'emsc' */) abort('Runtime error: The application has corrupted its heap memory area (address zero)!');
 }
 
 // end include: runtime_stack_check.js
@@ -1831,7 +1809,7 @@
   var h16 = new Int16Array(1);
   var h8 = new Int8Array(h16.buffer);
   h16[0] = 0x6373;
-  if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian! (Run with -s SUPPORT_BIG_ENDIAN=1 to bypass)';
+  if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)';
 })();
 
 // end include: runtime_assertions.js
@@ -1859,10 +1837,11 @@
 }
 
 function initRuntime() {
-  checkStackCookie();
   assert(!runtimeInitialized);
   runtimeInitialized = true;
 
+  checkStackCookie();
+
   
 if (!Module["noFSInit"] && !FS.init.initialized)
   FS.init();
@@ -1999,9 +1978,6 @@
   }
 }
 
-Module["preloadedImages"] = {}; // maps url to image data
-Module["preloadedAudios"] = {}; // maps url to audio data
-
 /** @param {string|number=} what */
 function abort(what) {
   {
@@ -2021,12 +1997,16 @@
   // Use a wasm runtime error, because a JS error might be seen as a foreign
   // exception, which means we'd run destructors on it. We need the error to
   // simply make the program stop.
+  // FIXME This approach does not work in Wasm EH because it currently does not assume
+  // all RuntimeErrors are from traps; it decides whether a RuntimeError is from
+  // a trap or not based on a hidden field within the object. So at the moment
+  // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that
+  // allows this in the wasm spec.
 
   // Suppress closure compiler warning here. Closure compiler's builtin extern
   // defintion for WebAssembly.RuntimeError claims it takes no arguments even
   // though it can.
   // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed.
-
   /** @suppress {checkTypes} */
   var e = new WebAssembly.RuntimeError(what);
 
@@ -2205,6 +2185,13 @@
         !isDataURI(wasmBinaryFile) &&
         // Don't use streaming for file:// delivered objects in a webview, fetch them synchronously.
         !isFileURI(wasmBinaryFile) &&
+        // Avoid instantiateStreaming() on Node.js environment for now, as while
+        // Node.js v18.1.0 implements it, it does not have a full fetch()
+        // implementation yet.
+        //
+        // Reference:
+        //   https://github.com/emscripten-core/emscripten/pull/16917
+        !ENVIRONMENT_IS_NODE &&
         typeof fetch == 'function') {
       return fetch(wasmBinaryFile, { credentials: 'same-origin' }).then(function(response) {
         // Suppress closure warning here since the upstream definition for
@@ -2295,7 +2282,7 @@
       return ret;
     }
   function demangle(func) {
-      warnOnce('warning: build with  -s DEMANGLE_SUPPORT=1  to link in libcxxabi demangling');
+      warnOnce('warning: build with -sDEMANGLE_SUPPORT to link in libcxxabi demangling');
       return func;
     }
 
@@ -2309,6 +2296,26 @@
         });
     }
 
+  
+    /**
+     * @param {number} ptr
+     * @param {string} type
+     */
+  function getValue(ptr, type = 'i8') {
+      if (type.endsWith('*')) type = 'i32';
+      switch (type) {
+        case 'i1': return HEAP8[((ptr)>>0)];
+        case 'i8': return HEAP8[((ptr)>>0)];
+        case 'i16': return HEAP16[((ptr)>>1)];
+        case 'i32': return HEAP32[((ptr)>>2)];
+        case 'i64': return HEAP32[((ptr)>>2)];
+        case 'float': return HEAPF32[((ptr)>>2)];
+        case 'double': return Number(HEAPF64[((ptr)>>3)]);
+        default: abort('invalid type for getValue: ' + type);
+      }
+      return null;
+    }
+
   var wasmTableMirror = [];
   function getWasmTableEntry(funcPtr) {
       var func = wasmTableMirror[funcPtr];
@@ -2335,8 +2342,8 @@
   function jsStackTrace() {
       var error = new Error();
       if (!error.stack) {
-        // IE10+ special cases: It does have callstack info, but it is only populated if an Error object is thrown,
-        // so try that as a special-case.
+        // IE10+ special cases: It does have callstack info, but it is only
+        // populated if an Error object is thrown, so try that as a special-case.
         try {
           throw new Error();
         } catch(e) {
@@ -2349,9 +2356,32 @@
       return error.stack.toString();
     }
 
+  
+    /**
+     * @param {number} ptr
+     * @param {number} value
+     * @param {string} type
+     */
+  function setValue(ptr, value, type = 'i8') {
+      if (type.endsWith('*')) type = 'i32';
+      switch (type) {
+        case 'i1': HEAP8[((ptr)>>0)] = value; break;
+        case 'i8': HEAP8[((ptr)>>0)] = value; break;
+        case 'i16': HEAP16[((ptr)>>1)] = value; break;
+        case 'i32': HEAP32[((ptr)>>2)] = value; break;
+        case 'i64': (tempI64 = [value>>>0,(tempDouble=value,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? ((Math.min((+(Math.floor((tempDouble)/4294967296.0))), 4294967295.0))|0)>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)],HEAP32[((ptr)>>2)] = tempI64[0],HEAP32[(((ptr)+(4))>>2)] = tempI64[1]); break;
+        case 'float': HEAPF32[((ptr)>>2)] = value; break;
+        case 'double': HEAPF64[((ptr)>>3)] = value; break;
+        default: abort('invalid type for setValue: ' + type);
+      }
+    }
+
   function setWasmTableEntry(idx, func) {
       wasmTable.set(idx, func);
-      wasmTableMirror[idx] = func;
+      // With ABORT_ON_WASM_EXCEPTIONS wasmTable.get is overriden to return wrapped
+      // functions so we need to call it here to retrieve the potential wrapper correctly
+      // instead of just storing 'func' directly into wasmTableMirror
+      wasmTableMirror[idx] = wasmTable.get(idx);
     }
 
   function stackTrace() {
@@ -2365,10 +2395,10 @@
       return value;
     }
   
-  var PATH = {splitPath:function(filename) {
+  var PATH = {isAbs:(path) => path.charAt(0) === '/',splitPath:(filename) => {
         var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
         return splitPathRe.exec(filename).slice(1);
-      },normalizeArray:function(parts, allowAboveRoot) {
+      },normalizeArray:(parts, allowAboveRoot) => {
         // if the path tries to go above the root, `up` ends up > 0
         var up = 0;
         for (var i = parts.length - 1; i >= 0; i--) {
@@ -2390,13 +2420,11 @@
           }
         }
         return parts;
-      },normalize:function(path) {
-        var isAbsolute = path.charAt(0) === '/',
+      },normalize:(path) => {
+        var isAbsolute = PATH.isAbs(path),
             trailingSlash = path.substr(-1) === '/';
         // Normalize the path
-        path = PATH.normalizeArray(path.split('/').filter(function(p) {
-          return !!p;
-        }), !isAbsolute).join('/');
+        path = PATH.normalizeArray(path.split('/').filter((p) => !!p), !isAbsolute).join('/');
         if (!path && !isAbsolute) {
           path = '.';
         }
@@ -2404,7 +2432,7 @@
           path += '/';
         }
         return (isAbsolute ? '/' : '') + path;
-      },dirname:function(path) {
+      },dirname:(path) => {
         var result = PATH.splitPath(path),
             root = result[0],
             dir = result[1];
@@ -2417,7 +2445,7 @@
           dir = dir.substr(0, dir.length - 1);
         }
         return root + dir;
-      },basename:function(path) {
+      },basename:(path) => {
         // EMSCRIPTEN return '/'' for '/', not an empty string
         if (path === '/') return '/';
         path = PATH.normalize(path);
@@ -2425,12 +2453,10 @@
         var lastSlash = path.lastIndexOf('/');
         if (lastSlash === -1) return path;
         return path.substr(lastSlash+1);
-      },extname:function(path) {
-        return PATH.splitPath(path)[3];
       },join:function() {
         var paths = Array.prototype.slice.call(arguments, 0);
         return PATH.normalize(paths.join('/'));
-      },join2:function(l, r) {
+      },join2:(l, r) => {
         return PATH.normalize(l + '/' + r);
       }};
   
@@ -2466,15 +2492,13 @@
             return ''; // an invalid portion invalidates the whole thing
           }
           resolvedPath = path + '/' + resolvedPath;
-          resolvedAbsolute = path.charAt(0) === '/';
+          resolvedAbsolute = PATH.isAbs(path);
         }
         // At this point the path should be resolved to a full absolute path, but
         // handle relative paths to be safe (might happen when process.cwd() fails)
-        resolvedPath = PATH.normalizeArray(resolvedPath.split('/').filter(function(p) {
-          return !!p;
-        }), !resolvedAbsolute).join('/');
+        resolvedPath = PATH.normalizeArray(resolvedPath.split('/').filter((p) => !!p), !resolvedAbsolute).join('/');
         return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
-      },relative:function(from, to) {
+      },relative:(from, to) => {
         from = PATH_FS.resolve(from).substr(1);
         to = PATH_FS.resolve(to).substr(1);
         function trim(arr) {
@@ -2925,11 +2949,7 @@
         },allocate:function(stream, offset, length) {
           MEMFS.expandFileStorage(stream.node, offset + length);
           stream.node.usedBytes = Math.max(stream.node.usedBytes, offset + length);
-        },mmap:function(stream, address, length, position, prot, flags) {
-          if (address !== 0) {
-            // We don't currently support location hints for the address of the mapping
-            throw new FS.ErrnoError(28);
-          }
+        },mmap:function(stream, length, position, prot, flags) {
           if (!FS.isFile(stream.node.mode)) {
             throw new FS.ErrnoError(43);
           }
@@ -3211,22 +3231,42 @@
         throw new FS.ErrnoError(33);
       },getStream:(fd) => FS.streams[fd],createStream:(stream, fd_start, fd_end) => {
         if (!FS.FSStream) {
-          FS.FSStream = /** @constructor */ function(){};
-          FS.FSStream.prototype = {
+          FS.FSStream = /** @constructor */ function() {
+            this.shared = { };
+          };
+          FS.FSStream.prototype = {};
+          Object.defineProperties(FS.FSStream.prototype, {
             object: {
+              /** @this {FS.FSStream} */
               get: function() { return this.node; },
+              /** @this {FS.FSStream} */
               set: function(val) { this.node = val; }
             },
             isRead: {
+              /** @this {FS.FSStream} */
               get: function() { return (this.flags & 2097155) !== 1; }
             },
             isWrite: {
+              /** @this {FS.FSStream} */
               get: function() { return (this.flags & 2097155) !== 0; }
             },
             isAppend: {
+              /** @this {FS.FSStream} */
               get: function() { return (this.flags & 1024); }
-            }
-          };
+            },
+            flags: {
+              /** @this {FS.FSStream} */
+              get: function() { return this.shared.flags; },
+              /** @this {FS.FSStream} */
+              set: function(val) { this.shared.flags = val; },
+            },
+            position : {
+              /** @this {FS.FSStream} */
+              get: function() { return this.shared.position; },
+              /** @this {FS.FSStream} */
+              set: function(val) { this.shared.position = val; },
+            },
+          });
         }
         // clone it, so we can return an instance of FSStream
         stream = Object.assign(new FS.FSStream(), stream);
@@ -3690,7 +3730,7 @@
         node.node_ops.setattr(node, {
           timestamp: Math.max(atime, mtime)
         });
-      },open:(path, flags, mode, fd_start, fd_end) => {
+      },open:(path, flags, mode) => {
         if (path === "") {
           throw new FS.ErrnoError(44);
         }
@@ -3750,7 +3790,7 @@
           }
         }
         // do truncation if necessary
-        if ((flags & 512)) {
+        if ((flags & 512) && !created) {
           FS.truncate(node, 0);
         }
         // we've already handled these, don't pass down to the underlying vfs
@@ -3767,7 +3807,7 @@
           // used by the file family libc calls (fopen, fwrite, ferror, etc.)
           ungotten: [],
           error: false
-        }, fd_start, fd_end);
+        });
         // call the new stream's open function
         if (stream.stream_ops.open) {
           stream.stream_ops.open(stream);
@@ -3880,7 +3920,7 @@
           throw new FS.ErrnoError(138);
         }
         stream.stream_ops.allocate(stream, offset, length);
-      },mmap:(stream, address, length, position, prot, flags) => {
+      },mmap:(stream, length, position, prot, flags) => {
         // User requests writing to file (prot & PROT_WRITE != 0).
         // Checking if we have permissions to write to the file unless
         // MAP_PRIVATE flag is set. According to POSIX spec it is possible
@@ -3898,7 +3938,7 @@
         if (!stream.stream_ops.mmap) {
           throw new FS.ErrnoError(43);
         }
-        return stream.stream_ops.mmap(stream, address, length, position, prot, flags);
+        return stream.stream_ops.mmap(stream, length, position, prot, flags);
       },msync:(stream, buffer, offset, length, mmapFlags) => {
         if (!stream || !stream.stream_ops.msync) {
           return 0;
@@ -4099,9 +4139,8 @@
         FS.createStandardStreams();
       },quit:() => {
         FS.init.initialized = false;
-        // Call musl-internal function to close all stdio streams, so nothing is
-        // left in internal buffers.
-        ___stdio_exit();
+        // force-flush all streams, so we get musl std streams printed out
+        _fflush(0);
         // close all of our streams
         for (var i = 0; i < FS.streams.length; i++) {
           var stream = FS.streams[i];
@@ -4394,9 +4433,7 @@
             return fn.apply(null, arguments);
           };
         });
-        // use a custom read function
-        stream_ops.read = (stream, buffer, offset, length, position) => {
-          FS.forceLoadFile(node);
+        function writeChunks(stream, buffer, offset, length, position) {
           var contents = stream.node.contents;
           if (position >= contents.length)
             return 0;
@@ -4412,6 +4449,21 @@
             }
           }
           return size;
+        }
+        // use a custom read function
+        stream_ops.read = (stream, buffer, offset, length, position) => {
+          FS.forceLoadFile(node);
+          return writeChunks(stream, buffer, offset, length, position)
+        };
+        // use a custom mmap function
+        stream_ops.mmap = (stream, length, position, prot, flags) => {
+          FS.forceLoadFile(node);
+          var ptr = mmapAlloc(length);
+          if (!ptr) {
+            throw new FS.ErrnoError(48);
+          }
+          writeChunks(stream, HEAP8, ptr, length, position);
+          return { ptr: ptr, allocated: true };
         };
         node.stream_ops = stream_ops;
         return node;
@@ -4529,7 +4581,7 @@
         abort('FS.standardizePath has been removed; use PATH.normalize instead');
       }};
   var SYSCALLS = {DEFAULT_POLLMASK:5,calculateAt:function(dirfd, path, allowEmpty) {
-        if (path[0] === '/') {
+        if (PATH.isAbs(path)) {
           return path;
         }
         // relative path
@@ -4581,77 +4633,6 @@
       },doMsync:function(addr, stream, len, flags, offset) {
         var buffer = HEAPU8.slice(addr, addr + len);
         FS.msync(stream, buffer, offset, len, flags);
-      },doMkdir:function(path, mode) {
-        // remove a trailing slash, if one - /a/b/ has basename of '', but
-        // we want to create b in the context of this function
-        path = PATH.normalize(path);
-        if (path[path.length-1] === '/') path = path.substr(0, path.length-1);
-        FS.mkdir(path, mode, 0);
-        return 0;
-      },doMknod:function(path, mode, dev) {
-        // we don't want this in the JS API as it uses mknod to create all nodes.
-        switch (mode & 61440) {
-          case 32768:
-          case 8192:
-          case 24576:
-          case 4096:
-          case 49152:
-            break;
-          default: return -28;
-        }
-        FS.mknod(path, mode, dev);
-        return 0;
-      },doReadlink:function(path, buf, bufsize) {
-        if (bufsize <= 0) return -28;
-        var ret = FS.readlink(path);
-  
-        var len = Math.min(bufsize, lengthBytesUTF8(ret));
-        var endChar = HEAP8[buf+len];
-        stringToUTF8(ret, buf, bufsize+1);
-        // readlink is one of the rare functions that write out a C string, but does never append a null to the output buffer(!)
-        // stringToUTF8() always appends a null byte, so restore the character under the null byte after the write.
-        HEAP8[buf+len] = endChar;
-  
-        return len;
-      },doAccess:function(path, amode) {
-        if (amode & ~7) {
-          // need a valid mode
-          return -28;
-        }
-        var lookup = FS.lookupPath(path, { follow: true });
-        var node = lookup.node;
-        if (!node) {
-          return -44;
-        }
-        var perms = '';
-        if (amode & 4) perms += 'r';
-        if (amode & 2) perms += 'w';
-        if (amode & 1) perms += 'x';
-        if (perms /* otherwise, they've just passed F_OK */ && FS.nodePermissions(node, perms)) {
-          return -2;
-        }
-        return 0;
-      },doReadv:function(stream, iov, iovcnt, offset) {
-        var ret = 0;
-        for (var i = 0; i < iovcnt; i++) {
-          var ptr = HEAP32[(((iov)+(i*8))>>2)];
-          var len = HEAP32[(((iov)+(i*8 + 4))>>2)];
-          var curr = FS.read(stream, HEAP8,ptr, len, offset);
-          if (curr < 0) return -1;
-          ret += curr;
-          if (curr < len) break; // nothing more to read
-        }
-        return ret;
-      },doWritev:function(stream, iov, iovcnt, offset) {
-        var ret = 0;
-        for (var i = 0; i < iovcnt; i++) {
-          var ptr = HEAP32[(((iov)+(i*8))>>2)];
-          var len = HEAP32[(((iov)+(i*8 + 4))>>2)];
-          var curr = FS.write(stream, HEAP8,ptr, len, offset);
-          if (curr < 0) return -1;
-          ret += curr;
-        }
-        return ret;
       },varargs:undefined,get:function() {
         assert(SYSCALLS.varargs != undefined);
         SYSCALLS.varargs += 4;
@@ -4664,10 +4645,6 @@
         var stream = FS.getStream(fd);
         if (!stream) throw new FS.ErrnoError(8);
         return stream;
-      },get64:function(low, high) {
-        if (low >= 0) assert(high === 0);
-        else assert(high === -1);
-        return low;
       }};
   function ___syscall_fcntl64(fd, cmd, varargs) {
   SYSCALLS.varargs = varargs;
@@ -4681,7 +4658,7 @@
             return -28;
           }
           var newStream;
-          newStream = FS.open(stream.path, stream.flags, 0, arg);
+          newStream = FS.createStream(stream, arg);
           return newStream.fd;
         }
         case 1:
@@ -4714,7 +4691,7 @@
         case 8:
           return -28; // These are for sockets. We don't have them fully implemented yet.
         case 9:
-          // musl trusts getown return values, due to a bug where they must be, as they overlap with errors. just return -1 here, so fnctl() returns that, and we set errno ourselves.
+          // musl trusts getown return values, due to a bug where they must be, as they overlap with errors. just return -1 here, so fcntl() returns that, and we set errno ourselves.
           setErrNo(28);
           return -1;
         default: {
@@ -4849,12 +4826,12 @@
       HEAPU8.copyWithin(dest, src, src + num);
     }
 
-  function _emscripten_get_heap_max() {
+  function getHeapMax() {
       return HEAPU8.length;
     }
   
   function abortOnCannotGrowMemory(requestedSize) {
-      abort('Cannot enlarge memory arrays to size ' + requestedSize + ' bytes (OOM). Either (1) compile with  -s INITIAL_MEMORY=X  with X higher than the current value ' + HEAP8.length + ', (2) compile with  -s ALLOW_MEMORY_GROWTH=1  which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with  -s ABORTING_MALLOC=0 ');
+      abort('Cannot enlarge memory arrays to size ' + requestedSize + ' bytes (OOM). Either (1) compile with -sINITIAL_MEMORY=X with X higher than the current value ' + HEAP8.length + ', (2) compile with -sALLOW_MEMORY_GROWTH which allows increasing the size at runtime, or (3) if you want malloc to return NULL (0) instead of this abort, compile with -sABORTING_MALLOC=0');
     }
   function _emscripten_resize_heap(requestedSize) {
       var oldSize = HEAPU8.length;
@@ -4901,7 +4878,7 @@
       var bufSize = 0;
       getEnvStrings().forEach(function(string, i) {
         var ptr = environ_buf + bufSize;
-        HEAP32[(((__environ)+(i * 4))>>2)] = ptr;
+        HEAPU32[(((__environ)+(i*4))>>2)] = ptr;
         writeAsciiToMemory(string, ptr);
         bufSize += string.length + 1;
       });
@@ -4910,12 +4887,12 @@
 
   function _environ_sizes_get(penviron_count, penviron_buf_size) {
       var strings = getEnvStrings();
-      HEAP32[((penviron_count)>>2)] = strings.length;
+      HEAPU32[((penviron_count)>>2)] = strings.length;
       var bufSize = 0;
       strings.forEach(function(string) {
         bufSize += string.length + 1;
       });
-      HEAP32[((penviron_buf_size)>>2)] = bufSize;
+      HEAPU32[((penviron_buf_size)>>2)] = bufSize;
       return 0;
     }
 
@@ -4937,11 +4914,25 @@
   }
   }
 
+  /** @param {number=} offset */
+  function doReadv(stream, iov, iovcnt, offset) {
+      var ret = 0;
+      for (var i = 0; i < iovcnt; i++) {
+        var ptr = HEAPU32[((iov)>>2)];
+        var len = HEAPU32[(((iov)+(4))>>2)];
+        iov += 8;
+        var curr = FS.read(stream, HEAP8,ptr, len, offset);
+        if (curr < 0) return -1;
+        ret += curr;
+        if (curr < len) break; // nothing more to read
+      }
+      return ret;
+    }
   function _fd_read(fd, iov, iovcnt, pnum) {
   try {
   
       var stream = SYSCALLS.getStreamFromFD(fd);
-      var num = SYSCALLS.doReadv(stream, iov, iovcnt);
+      var num = doReadv(stream, iov, iovcnt);
       HEAP32[((pnum)>>2)] = num;
       return 0;
     } catch (e) {
@@ -4950,21 +4941,16 @@
   }
   }
 
+  function convertI32PairToI53Checked(lo, hi) {
+      assert(lo == (lo >>> 0) || lo == (lo|0)); // lo should either be a i32 or a u32
+      assert(hi === (hi|0));                    // hi should be a i32
+      return ((hi + 0x200000) >>> 0 < 0x400001 - !!lo) ? (lo >>> 0) + hi * 4294967296 : NaN;
+    }
   function _fd_seek(fd, offset_low, offset_high, whence, newOffset) {
   try {
   
-      
+      var offset = convertI32PairToI53Checked(offset_low, offset_high); if (isNaN(offset)) return 61;
       var stream = SYSCALLS.getStreamFromFD(fd);
-      var HIGH_OFFSET = 0x100000000; // 2^32
-      // use an unsigned operator on low and shift high by 32-bits
-      var offset = offset_high * HIGH_OFFSET + (offset_low >>> 0);
-  
-      var DOUBLE_LIMIT = 0x20000000000000; // 2^53
-      // we also check for equality since DOUBLE_LIMIT + 1 == DOUBLE_LIMIT
-      if (offset <= -DOUBLE_LIMIT || offset >= DOUBLE_LIMIT) {
-        return -61;
-      }
-  
       FS.llseek(stream, offset, whence);
       (tempI64 = [stream.position>>>0,(tempDouble=stream.position,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? ((Math.min((+(Math.floor((tempDouble)/4294967296.0))), 4294967295.0))|0)>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)],HEAP32[((newOffset)>>2)] = tempI64[0],HEAP32[(((newOffset)+(4))>>2)] = tempI64[1]);
       if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null; // reset readdir state
@@ -4975,13 +4961,25 @@
   }
   }
 
+  /** @param {number=} offset */
+  function doWritev(stream, iov, iovcnt, offset) {
+      var ret = 0;
+      for (var i = 0; i < iovcnt; i++) {
+        var ptr = HEAPU32[((iov)>>2)];
+        var len = HEAPU32[(((iov)+(4))>>2)];
+        iov += 8;
+        var curr = FS.write(stream, HEAP8,ptr, len, offset);
+        if (curr < 0) return -1;
+        ret += curr;
+      }
+      return ret;
+    }
   function _fd_write(fd, iov, iovcnt, pnum) {
   try {
   
-      ;
       var stream = SYSCALLS.getStreamFromFD(fd);
-      var num = SYSCALLS.doWritev(stream, iov, iovcnt);
-      HEAP32[((pnum)>>2)] = num;
+      var num = doWritev(stream, iov, iovcnt);
+      HEAPU32[((pnum)>>2)] = num;
       return 0;
     } catch (e) {
     if (typeof FS == 'undefined' || !(e instanceof FS.ErrnoError)) throw e;
@@ -5039,7 +5037,7 @@
    }
   });
   FS.FSNode = FSNode;
-  FS.staticInit();Module["FS_createPath"] = FS.createPath;Module["FS_createDataFile"] = FS.createDataFile;Module["FS_createPreloadedFile"] = FS.createPreloadedFile;Module["FS_createLazyFile"] = FS.createLazyFile;Module["FS_createDevice"] = FS.createDevice;Module["FS_unlink"] = FS.unlink;;
+  FS.staticInit();Module["FS_createPath"] = FS.createPath;Module["FS_createDataFile"] = FS.createDataFile;Module["FS_createPreloadedFile"] = FS.createPreloadedFile;Module["FS_unlink"] = FS.unlink;Module["FS_createLazyFile"] = FS.createLazyFile;Module["FS_createDevice"] = FS.createDevice;;
 ERRNO_CODES = {
       'EPERM': 63,
       'ENOENT': 44,
@@ -5474,7 +5472,7 @@
 var ___errno_location = Module["___errno_location"] = createExportWrapper("__errno_location");
 
 /** @type {function(...*):?} */
-var ___stdio_exit = Module["___stdio_exit"] = createExportWrapper("__stdio_exit");
+var _fflush = Module["_fflush"] = createExportWrapper("fflush");
 
 /** @type {function(...*):?} */
 var _malloc = Module["_malloc"] = createExportWrapper("malloc");
@@ -5520,27 +5518,19 @@
 
 // === Auto-generated postamble setup entry stuff ===
 
-Module["intArrayFromString"] = intArrayFromString;
-Module["intArrayToString"] = intArrayToString;
 Module["ccall"] = ccall;
 Module["cwrap"] = cwrap;
-Module["setValue"] = setValue;
-Module["getValue"] = getValue;
 Module["allocate"] = allocate;
 unexportedRuntimeFunction('UTF8ArrayToString', false);
 unexportedRuntimeFunction('UTF8ToString', false);
 unexportedRuntimeFunction('stringToUTF8Array', false);
 unexportedRuntimeFunction('stringToUTF8', false);
 unexportedRuntimeFunction('lengthBytesUTF8', false);
-unexportedRuntimeFunction('stackTrace', false);
 unexportedRuntimeFunction('addOnPreRun', false);
 unexportedRuntimeFunction('addOnInit', false);
 unexportedRuntimeFunction('addOnPreMain', false);
 unexportedRuntimeFunction('addOnExit', false);
 unexportedRuntimeFunction('addOnPostRun', false);
-unexportedRuntimeFunction('writeStringToMemory', false);
-unexportedRuntimeFunction('writeArrayToMemory', false);
-unexportedRuntimeFunction('writeAsciiToMemory', false);
 Module["addRunDependency"] = addRunDependency;
 Module["removeRunDependency"] = removeRunDependency;
 unexportedRuntimeFunction('FS_createFolder', false);
@@ -5557,9 +5547,7 @@
 unexportedRuntimeFunction('registerFunctions', false);
 unexportedRuntimeFunction('addFunction', false);
 unexportedRuntimeFunction('removeFunction', false);
-unexportedRuntimeFunction('getFuncWrapper', false);
 unexportedRuntimeFunction('prettyPrint', false);
-unexportedRuntimeFunction('dynCall', false);
 unexportedRuntimeFunction('getCompilerSetting', false);
 unexportedRuntimeFunction('print', false);
 unexportedRuntimeFunction('printErr', false);
@@ -5568,8 +5556,33 @@
 unexportedRuntimeFunction('callMain', false);
 unexportedRuntimeFunction('abort', false);
 unexportedRuntimeFunction('keepRuntimeAlive', false);
+unexportedRuntimeFunction('wasmMemory', false);
+unexportedRuntimeFunction('warnOnce', false);
+unexportedRuntimeFunction('stackSave', false);
+unexportedRuntimeFunction('stackRestore', false);
+unexportedRuntimeFunction('stackAlloc', false);
+unexportedRuntimeFunction('AsciiToString', false);
+unexportedRuntimeFunction('stringToAscii', false);
+Module["UTF16ToString"] = UTF16ToString;
+Module["stringToUTF16"] = stringToUTF16;
+unexportedRuntimeFunction('lengthBytesUTF16', false);
+unexportedRuntimeFunction('UTF32ToString', false);
+unexportedRuntimeFunction('stringToUTF32', false);
+unexportedRuntimeFunction('lengthBytesUTF32', false);
+unexportedRuntimeFunction('allocateUTF8', false);
+unexportedRuntimeFunction('allocateUTF8OnStack', false);
+unexportedRuntimeFunction('ExitStatus', false);
+Module["intArrayFromString"] = intArrayFromString;
+Module["intArrayToString"] = intArrayToString;
+unexportedRuntimeFunction('writeStringToMemory', false);
+unexportedRuntimeFunction('writeArrayToMemory', false);
+unexportedRuntimeFunction('writeAsciiToMemory', false);
+Module["writeStackCookie"] = writeStackCookie;
+Module["checkStackCookie"] = checkStackCookie;
+unexportedRuntimeFunction('ptrToString', false);
 unexportedRuntimeFunction('zeroMemory', false);
 unexportedRuntimeFunction('stringToNewUTF8', false);
+unexportedRuntimeFunction('getHeapMax', false);
 unexportedRuntimeFunction('abortOnCannotGrowMemory', false);
 unexportedRuntimeFunction('emscripten_realloc_buffer', false);
 unexportedRuntimeFunction('ENV', false);
@@ -5611,10 +5624,23 @@
 unexportedRuntimeFunction('asyncLoad', false);
 unexportedRuntimeFunction('alignMemory', false);
 unexportedRuntimeFunction('mmapAlloc', false);
+unexportedRuntimeFunction('writeI53ToI64', false);
+unexportedRuntimeFunction('writeI53ToI64Clamped', false);
+unexportedRuntimeFunction('writeI53ToI64Signaling', false);
+unexportedRuntimeFunction('writeI53ToU64Clamped', false);
+unexportedRuntimeFunction('writeI53ToU64Signaling', false);
+unexportedRuntimeFunction('readI53FromI64', false);
+unexportedRuntimeFunction('readI53FromU64', false);
+unexportedRuntimeFunction('convertI32PairToI53', false);
+unexportedRuntimeFunction('convertI32PairToI53Checked', false);
+unexportedRuntimeFunction('convertU32PairToI53', false);
 unexportedRuntimeFunction('reallyNegative', false);
 unexportedRuntimeFunction('unSign', false);
+unexportedRuntimeFunction('strLen', false);
 unexportedRuntimeFunction('reSign', false);
 unexportedRuntimeFunction('formatString', false);
+Module["setValue"] = setValue;
+Module["getValue"] = getValue;
 unexportedRuntimeFunction('PATH', false);
 unexportedRuntimeFunction('PATH_FS', false);
 unexportedRuntimeFunction('SYSCALLS', false);
@@ -5641,6 +5667,8 @@
 unexportedRuntimeFunction('registerOrientationChangeEventCallback', false);
 unexportedRuntimeFunction('fillFullscreenChangeEventData', false);
 unexportedRuntimeFunction('registerFullscreenChangeEventCallback', false);
+unexportedRuntimeFunction('JSEvents_requestFullscreen', false);
+unexportedRuntimeFunction('JSEvents_resizeCanvasForFullscreen', false);
 unexportedRuntimeFunction('registerRestoreOldStyle', false);
 unexportedRuntimeFunction('hideEverythingExceptGivenElement', false);
 unexportedRuntimeFunction('restoreHiddenElements', false);
@@ -5670,15 +5698,9 @@
 unexportedRuntimeFunction('stackTrace', false);
 unexportedRuntimeFunction('getEnvStrings', false);
 unexportedRuntimeFunction('checkWasiClock', false);
-unexportedRuntimeFunction('writeI53ToI64', false);
-unexportedRuntimeFunction('writeI53ToI64Clamped', false);
-unexportedRuntimeFunction('writeI53ToI64Signaling', false);
-unexportedRuntimeFunction('writeI53ToU64Clamped', false);
-unexportedRuntimeFunction('writeI53ToU64Signaling', false);
-unexportedRuntimeFunction('readI53FromI64', false);
-unexportedRuntimeFunction('readI53FromU64', false);
-unexportedRuntimeFunction('convertI32PairToI53', false);
-unexportedRuntimeFunction('convertU32PairToI53', false);
+unexportedRuntimeFunction('doReadv', false);
+unexportedRuntimeFunction('doWritev', false);
+unexportedRuntimeFunction('dlopenMissingError', false);
 unexportedRuntimeFunction('setImmediateWrapped', false);
 unexportedRuntimeFunction('clearImmediateWrapped', false);
 unexportedRuntimeFunction('polyfillSetImmediate', false);
@@ -5686,12 +5708,9 @@
 unexportedRuntimeFunction('exceptionLast', false);
 unexportedRuntimeFunction('exceptionCaught', false);
 unexportedRuntimeFunction('ExceptionInfo', false);
-unexportedRuntimeFunction('CatchInfo', false);
 unexportedRuntimeFunction('exception_addRef', false);
 unexportedRuntimeFunction('exception_decRef', false);
 unexportedRuntimeFunction('Browser', false);
-unexportedRuntimeFunction('funcWrappers', false);
-unexportedRuntimeFunction('getFuncWrapper', false);
 unexportedRuntimeFunction('setMainLoop', false);
 unexportedRuntimeFunction('wget', false);
 Module["FS"] = FS;
@@ -5727,22 +5746,6 @@
 unexportedRuntimeFunction('GLEW', false);
 unexportedRuntimeFunction('IDBStore', false);
 unexportedRuntimeFunction('runAndAbortIfError', false);
-unexportedRuntimeFunction('warnOnce', false);
-unexportedRuntimeFunction('stackSave', false);
-unexportedRuntimeFunction('stackRestore', false);
-unexportedRuntimeFunction('stackAlloc', false);
-unexportedRuntimeFunction('AsciiToString', false);
-unexportedRuntimeFunction('stringToAscii', false);
-Module["UTF16ToString"] = UTF16ToString;
-Module["stringToUTF16"] = stringToUTF16;
-unexportedRuntimeFunction('lengthBytesUTF16', false);
-unexportedRuntimeFunction('UTF32ToString', false);
-unexportedRuntimeFunction('stringToUTF32', false);
-unexportedRuntimeFunction('lengthBytesUTF32', false);
-unexportedRuntimeFunction('allocateUTF8', false);
-unexportedRuntimeFunction('allocateUTF8OnStack', false);
-Module["writeStackCookie"] = writeStackCookie;
-Module["checkStackCookie"] = checkStackCookie;
 Module["ALLOC_NORMAL"] = ALLOC_NORMAL;
 unexportedRuntimeSymbol('ALLOC_STACK', false);
 
@@ -5770,8 +5773,8 @@
   // This is normally called automatically during __wasm_call_ctors but need to
   // get these values before even running any of the ctors so we call it redundantly
   // here.
-  // TODO(sbc): Move writeStackCookie to native to to avoid this.
   _emscripten_stack_init();
+  // TODO(sbc): Move writeStackCookie to native to to avoid this.
   writeStackCookie();
 }
 
@@ -5783,7 +5786,7 @@
     return;
   }
 
-  stackCheckInit();
+    stackCheckInit();
 
   preRun();
 
@@ -5846,7 +5849,7 @@
     has = true;
   }
   try { // it doesn't matter if it fails
-    ___stdio_exit();
+    _fflush(0);
     // also flush in the JS FS layer
     ['stdout', 'stderr'].forEach(function(name) {
       var info = FS.analyzePath('/dev/' + name);
diff --git a/third_party/liblouis/wasm/liblouis_wasm.wasm b/third_party/liblouis/wasm/liblouis_wasm.wasm
index 49c2e7a..1d152d7 100644
--- a/third_party/liblouis/wasm/liblouis_wasm.wasm
+++ b/third_party/liblouis/wasm/liblouis_wasm.wasm
Binary files differ
diff --git a/third_party/liblouis/wasm/liblouis_wrapper.js b/third_party/liblouis/wasm/liblouis_wrapper.js
index 3f266b83..6f21013 100644
--- a/third_party/liblouis/wasm/liblouis_wrapper.js
+++ b/third_party/liblouis/wasm/liblouis_wrapper.js
@@ -156,6 +156,7 @@
     // length given by liblouis.
     let outLen = inLen;
     const maxAlloc = (inLen + 1) * 8;
+    let msg;
     while (outLen < maxAlloc) {
       // This is required as consecutive tries to [back]Translate requires
       // resetting the value of this int pointer.
@@ -189,7 +190,7 @@
       const actualInLen = this.module.getValue(inLenPtr, 'i32');
       const actualOutLen = this.module.getValue(outLenPtr, 'i32');
       if ((inLen - 1) <= actualInLen && actualOutLen > 0) {
-        const msg = {in_reply_to: messageId, success: true};
+        msg = {in_reply_to: messageId, success: true};
         if (backTranslate) {
           let outBuf = '';
           for (let i = 0; i < actualOutLen; i++) {
@@ -213,7 +214,6 @@
         // next uprev to LibLouis.
         if (backTranslate || actualInLen !== 1 || actualOutLen !== 1 ||
             msg['cells'] !== '00') {
-          self.postMessage(JSON.stringify(msg));
           break;
         }
       }
@@ -221,9 +221,11 @@
       outLen = outLen * 2;
     }
 
-    this.pool_.freeAll();
+    if (msg) {
+      self.postMessage(JSON.stringify(msg));
+    }
 
-    // TODO: raise an error if we didn't send a result in the loop above.
+    this.pool_.freeAll();
   },
 
   getHexEncoding_: function(bufPtr, len) {
diff --git a/third_party/lzma_sdk/BUILD.gn b/third_party/lzma_sdk/BUILD.gn
index b276c727..67756fc 100644
--- a/third_party/lzma_sdk/BUILD.gn
+++ b/third_party/lzma_sdk/BUILD.gn
@@ -10,7 +10,10 @@
                              arm_use_neon && !(is_win && !is_clang)
 
 config("lzma_sdk_config") {
-  include_dirs = [ "." ]
+  include_dirs = [
+    ".",
+    "./C",
+  ]
 }
 
 # Must be in a config for -Wno-self-assign because of how GN orders flags
@@ -55,46 +58,46 @@
 
 static_library("lzma_sdk") {
   sources = [
-    "7z.h",
-    "7zAlloc.c",
-    "7zAlloc.h",
-    "7zArcIn.c",
-    "7zBuf.c",
-    "7zBuf.h",
-    "7zCrc.c",
-    "7zCrc.h",
-    "7zCrcOpt.c",
-    "7zDec.c",
-    "7zFile.c",
-    "7zFile.h",
-    "7zStream.c",
-    "7zTypes.h",
-    "Alloc.c",
-    "Alloc.h",
-    "Bcj2.c",
-    "Bcj2.h",
-    "Bra.c",
-    "Bra.h",
-    "Bra86.c",
-    "Compiler.h",
-    "CpuArch.c",
-    "CpuArch.h",
-    "Delta.c",
-    "Delta.h",
-    "DllSecur.c",
-    "DllSecur.h",
-    "LzFind.c",
-    "LzFind.h",
-    "LzHash.h",
-    "Lzma2Dec.c",
-    "Lzma2Dec.h",
-    "LzmaDec.c",
-    "LzmaDec.h",
-    "LzmaEnc.c",
-    "LzmaEnc.h",
-    "LzmaLib.c",
-    "LzmaLib.h",
-    "Precomp.h",
+    "C/7z.h",
+    "C/7zAlloc.c",
+    "C/7zAlloc.h",
+    "C/7zArcIn.c",
+    "C/7zBuf.c",
+    "C/7zBuf.h",
+    "C/7zCrc.c",
+    "C/7zCrc.h",
+    "C/7zCrcOpt.c",
+    "C/7zDec.c",
+    "C/7zFile.c",
+    "C/7zFile.h",
+    "C/7zStream.c",
+    "C/7zTypes.h",
+    "C/Alloc.c",
+    "C/Alloc.h",
+    "C/Bcj2.c",
+    "C/Bcj2.h",
+    "C/Bra.c",
+    "C/Bra.h",
+    "C/Bra86.c",
+    "C/Compiler.h",
+    "C/CpuArch.c",
+    "C/CpuArch.h",
+    "C/Delta.c",
+    "C/Delta.h",
+    "C/DllSecur.c",
+    "C/DllSecur.h",
+    "C/LzFind.c",
+    "C/LzFind.h",
+    "C/LzHash.h",
+    "C/Lzma2Dec.c",
+    "C/Lzma2Dec.h",
+    "C/LzmaDec.c",
+    "C/LzmaDec.h",
+    "C/LzmaEnc.c",
+    "C/LzmaEnc.h",
+    "C/LzmaLib.c",
+    "C/LzmaLib.h",
+    "C/Precomp.h",
   ]
 
   configs -= [ "//build/config/compiler:chromium_code" ]
@@ -109,21 +112,21 @@
 
 static_library("lzma_sdk_xz") {
   sources = [
-    "BraIA64.c",
-    "RotateDefs.h",
-    "Sha256.c",
-    "Sha256.h",
-    "Xz.c",
-    "Xz.h",
-    "XzCrc64.c",
-    "XzCrc64.h",
-    "XzCrc64Opt.c",
-    "XzDec.c",
+    "C/BraIA64.c",
+    "C/RotateDefs.h",
+    "C/Sha256.c",
+    "C/Sha256.h",
+    "C/Xz.c",
+    "C/Xz.h",
+    "C/XzCrc64.c",
+    "C/XzCrc64.h",
+    "C/XzCrc64Opt.c",
+    "C/XzDec.c",
   ]
 
   # TODO(crbug.com/1338627): Enable ARM optimizations
   if (target_cpu == "x86" || target_cpu == "x64") {
-    sources += [ "Sha256Opt.c" ]
+    sources += [ "C/Sha256Opt.c" ]
   }
 
   deps = [ ":lzma_sdk" ]
diff --git a/third_party/lzma_sdk/7z.h b/third_party/lzma_sdk/C/7z.h
similarity index 100%
rename from third_party/lzma_sdk/7z.h
rename to third_party/lzma_sdk/C/7z.h
diff --git a/third_party/lzma_sdk/7zAlloc.c b/third_party/lzma_sdk/C/7zAlloc.c
similarity index 100%
rename from third_party/lzma_sdk/7zAlloc.c
rename to third_party/lzma_sdk/C/7zAlloc.c
diff --git a/third_party/lzma_sdk/7zAlloc.h b/third_party/lzma_sdk/C/7zAlloc.h
similarity index 100%
rename from third_party/lzma_sdk/7zAlloc.h
rename to third_party/lzma_sdk/C/7zAlloc.h
diff --git a/third_party/lzma_sdk/7zArcIn.c b/third_party/lzma_sdk/C/7zArcIn.c
similarity index 100%
rename from third_party/lzma_sdk/7zArcIn.c
rename to third_party/lzma_sdk/C/7zArcIn.c
diff --git a/third_party/lzma_sdk/7zBuf.c b/third_party/lzma_sdk/C/7zBuf.c
similarity index 100%
rename from third_party/lzma_sdk/7zBuf.c
rename to third_party/lzma_sdk/C/7zBuf.c
diff --git a/third_party/lzma_sdk/7zBuf.h b/third_party/lzma_sdk/C/7zBuf.h
similarity index 100%
rename from third_party/lzma_sdk/7zBuf.h
rename to third_party/lzma_sdk/C/7zBuf.h
diff --git a/third_party/lzma_sdk/7zCrc.c b/third_party/lzma_sdk/C/7zCrc.c
similarity index 100%
rename from third_party/lzma_sdk/7zCrc.c
rename to third_party/lzma_sdk/C/7zCrc.c
diff --git a/third_party/lzma_sdk/7zCrc.h b/third_party/lzma_sdk/C/7zCrc.h
similarity index 100%
rename from third_party/lzma_sdk/7zCrc.h
rename to third_party/lzma_sdk/C/7zCrc.h
diff --git a/third_party/lzma_sdk/7zCrcOpt.c b/third_party/lzma_sdk/C/7zCrcOpt.c
similarity index 100%
rename from third_party/lzma_sdk/7zCrcOpt.c
rename to third_party/lzma_sdk/C/7zCrcOpt.c
diff --git a/third_party/lzma_sdk/7zDec.c b/third_party/lzma_sdk/C/7zDec.c
similarity index 100%
rename from third_party/lzma_sdk/7zDec.c
rename to third_party/lzma_sdk/C/7zDec.c
diff --git a/third_party/lzma_sdk/7zFile.c b/third_party/lzma_sdk/C/7zFile.c
similarity index 100%
rename from third_party/lzma_sdk/7zFile.c
rename to third_party/lzma_sdk/C/7zFile.c
diff --git a/third_party/lzma_sdk/7zFile.h b/third_party/lzma_sdk/C/7zFile.h
similarity index 100%
rename from third_party/lzma_sdk/7zFile.h
rename to third_party/lzma_sdk/C/7zFile.h
diff --git a/third_party/lzma_sdk/7zStream.c b/third_party/lzma_sdk/C/7zStream.c
similarity index 100%
rename from third_party/lzma_sdk/7zStream.c
rename to third_party/lzma_sdk/C/7zStream.c
diff --git a/third_party/lzma_sdk/7zTypes.h b/third_party/lzma_sdk/C/7zTypes.h
similarity index 100%
rename from third_party/lzma_sdk/7zTypes.h
rename to third_party/lzma_sdk/C/7zTypes.h
diff --git a/third_party/lzma_sdk/7zVersion.h b/third_party/lzma_sdk/C/7zVersion.h
similarity index 100%
rename from third_party/lzma_sdk/7zVersion.h
rename to third_party/lzma_sdk/C/7zVersion.h
diff --git a/third_party/lzma_sdk/7zVersion.rc b/third_party/lzma_sdk/C/7zVersion.rc
similarity index 100%
rename from third_party/lzma_sdk/7zVersion.rc
rename to third_party/lzma_sdk/C/7zVersion.rc
diff --git a/third_party/lzma_sdk/Alloc.c b/third_party/lzma_sdk/C/Alloc.c
similarity index 100%
rename from third_party/lzma_sdk/Alloc.c
rename to third_party/lzma_sdk/C/Alloc.c
diff --git a/third_party/lzma_sdk/Alloc.h b/third_party/lzma_sdk/C/Alloc.h
similarity index 100%
rename from third_party/lzma_sdk/Alloc.h
rename to third_party/lzma_sdk/C/Alloc.h
diff --git a/third_party/lzma_sdk/Bcj2.c b/third_party/lzma_sdk/C/Bcj2.c
similarity index 100%
rename from third_party/lzma_sdk/Bcj2.c
rename to third_party/lzma_sdk/C/Bcj2.c
diff --git a/third_party/lzma_sdk/Bcj2.h b/third_party/lzma_sdk/C/Bcj2.h
similarity index 100%
rename from third_party/lzma_sdk/Bcj2.h
rename to third_party/lzma_sdk/C/Bcj2.h
diff --git a/third_party/lzma_sdk/Bra.c b/third_party/lzma_sdk/C/Bra.c
similarity index 100%
rename from third_party/lzma_sdk/Bra.c
rename to third_party/lzma_sdk/C/Bra.c
diff --git a/third_party/lzma_sdk/Bra.h b/third_party/lzma_sdk/C/Bra.h
similarity index 100%
rename from third_party/lzma_sdk/Bra.h
rename to third_party/lzma_sdk/C/Bra.h
diff --git a/third_party/lzma_sdk/Bra86.c b/third_party/lzma_sdk/C/Bra86.c
similarity index 100%
rename from third_party/lzma_sdk/Bra86.c
rename to third_party/lzma_sdk/C/Bra86.c
diff --git a/third_party/lzma_sdk/BraIA64.c b/third_party/lzma_sdk/C/BraIA64.c
similarity index 100%
rename from third_party/lzma_sdk/BraIA64.c
rename to third_party/lzma_sdk/C/BraIA64.c
diff --git a/third_party/lzma_sdk/Compiler.h b/third_party/lzma_sdk/C/Compiler.h
similarity index 100%
rename from third_party/lzma_sdk/Compiler.h
rename to third_party/lzma_sdk/C/Compiler.h
diff --git a/third_party/lzma_sdk/CpuArch.c b/third_party/lzma_sdk/C/CpuArch.c
similarity index 100%
rename from third_party/lzma_sdk/CpuArch.c
rename to third_party/lzma_sdk/C/CpuArch.c
diff --git a/third_party/lzma_sdk/CpuArch.h b/third_party/lzma_sdk/C/CpuArch.h
similarity index 100%
rename from third_party/lzma_sdk/CpuArch.h
rename to third_party/lzma_sdk/C/CpuArch.h
diff --git a/third_party/lzma_sdk/Delta.c b/third_party/lzma_sdk/C/Delta.c
similarity index 100%
rename from third_party/lzma_sdk/Delta.c
rename to third_party/lzma_sdk/C/Delta.c
diff --git a/third_party/lzma_sdk/Delta.h b/third_party/lzma_sdk/C/Delta.h
similarity index 100%
rename from third_party/lzma_sdk/Delta.h
rename to third_party/lzma_sdk/C/Delta.h
diff --git a/third_party/lzma_sdk/DllSecur.c b/third_party/lzma_sdk/C/DllSecur.c
similarity index 100%
rename from third_party/lzma_sdk/DllSecur.c
rename to third_party/lzma_sdk/C/DllSecur.c
diff --git a/third_party/lzma_sdk/DllSecur.h b/third_party/lzma_sdk/C/DllSecur.h
similarity index 100%
rename from third_party/lzma_sdk/DllSecur.h
rename to third_party/lzma_sdk/C/DllSecur.h
diff --git a/third_party/lzma_sdk/LzFind.c b/third_party/lzma_sdk/C/LzFind.c
similarity index 100%
rename from third_party/lzma_sdk/LzFind.c
rename to third_party/lzma_sdk/C/LzFind.c
diff --git a/third_party/lzma_sdk/LzFind.h b/third_party/lzma_sdk/C/LzFind.h
similarity index 100%
rename from third_party/lzma_sdk/LzFind.h
rename to third_party/lzma_sdk/C/LzFind.h
diff --git a/third_party/lzma_sdk/LzHash.h b/third_party/lzma_sdk/C/LzHash.h
similarity index 100%
rename from third_party/lzma_sdk/LzHash.h
rename to third_party/lzma_sdk/C/LzHash.h
diff --git a/third_party/lzma_sdk/Lzma2Dec.c b/third_party/lzma_sdk/C/Lzma2Dec.c
similarity index 100%
rename from third_party/lzma_sdk/Lzma2Dec.c
rename to third_party/lzma_sdk/C/Lzma2Dec.c
diff --git a/third_party/lzma_sdk/Lzma2Dec.h b/third_party/lzma_sdk/C/Lzma2Dec.h
similarity index 100%
rename from third_party/lzma_sdk/Lzma2Dec.h
rename to third_party/lzma_sdk/C/Lzma2Dec.h
diff --git a/third_party/lzma_sdk/LzmaDec.c b/third_party/lzma_sdk/C/LzmaDec.c
similarity index 100%
rename from third_party/lzma_sdk/LzmaDec.c
rename to third_party/lzma_sdk/C/LzmaDec.c
diff --git a/third_party/lzma_sdk/LzmaDec.h b/third_party/lzma_sdk/C/LzmaDec.h
similarity index 100%
rename from third_party/lzma_sdk/LzmaDec.h
rename to third_party/lzma_sdk/C/LzmaDec.h
diff --git a/third_party/lzma_sdk/LzmaEnc.c b/third_party/lzma_sdk/C/LzmaEnc.c
similarity index 100%
rename from third_party/lzma_sdk/LzmaEnc.c
rename to third_party/lzma_sdk/C/LzmaEnc.c
diff --git a/third_party/lzma_sdk/LzmaEnc.h b/third_party/lzma_sdk/C/LzmaEnc.h
similarity index 100%
rename from third_party/lzma_sdk/LzmaEnc.h
rename to third_party/lzma_sdk/C/LzmaEnc.h
diff --git a/third_party/lzma_sdk/LzmaLib.c b/third_party/lzma_sdk/C/LzmaLib.c
similarity index 100%
rename from third_party/lzma_sdk/LzmaLib.c
rename to third_party/lzma_sdk/C/LzmaLib.c
diff --git a/third_party/lzma_sdk/LzmaLib.h b/third_party/lzma_sdk/C/LzmaLib.h
similarity index 100%
rename from third_party/lzma_sdk/LzmaLib.h
rename to third_party/lzma_sdk/C/LzmaLib.h
diff --git a/third_party/lzma_sdk/Precomp.h b/third_party/lzma_sdk/C/Precomp.h
similarity index 100%
rename from third_party/lzma_sdk/Precomp.h
rename to third_party/lzma_sdk/C/Precomp.h
diff --git a/third_party/lzma_sdk/RotateDefs.h b/third_party/lzma_sdk/C/RotateDefs.h
similarity index 100%
rename from third_party/lzma_sdk/RotateDefs.h
rename to third_party/lzma_sdk/C/RotateDefs.h
diff --git a/third_party/lzma_sdk/Sha256.c b/third_party/lzma_sdk/C/Sha256.c
similarity index 100%
rename from third_party/lzma_sdk/Sha256.c
rename to third_party/lzma_sdk/C/Sha256.c
diff --git a/third_party/lzma_sdk/Sha256.h b/third_party/lzma_sdk/C/Sha256.h
similarity index 100%
rename from third_party/lzma_sdk/Sha256.h
rename to third_party/lzma_sdk/C/Sha256.h
diff --git a/third_party/lzma_sdk/Sha256Opt.c b/third_party/lzma_sdk/C/Sha256Opt.c
similarity index 100%
rename from third_party/lzma_sdk/Sha256Opt.c
rename to third_party/lzma_sdk/C/Sha256Opt.c
diff --git a/third_party/lzma_sdk/Util/SfxSetup/BUILD.gn b/third_party/lzma_sdk/C/Util/SfxSetup/BUILD.gn
similarity index 100%
rename from third_party/lzma_sdk/Util/SfxSetup/BUILD.gn
rename to third_party/lzma_sdk/C/Util/SfxSetup/BUILD.gn
diff --git a/third_party/lzma_sdk/Util/SfxSetup/Precomp.h b/third_party/lzma_sdk/C/Util/SfxSetup/Precomp.h
similarity index 100%
rename from third_party/lzma_sdk/Util/SfxSetup/Precomp.h
rename to third_party/lzma_sdk/C/Util/SfxSetup/Precomp.h
diff --git a/third_party/lzma_sdk/Util/SfxSetup/SfxSetup.c b/third_party/lzma_sdk/C/Util/SfxSetup/SfxSetup.c
similarity index 100%
rename from third_party/lzma_sdk/Util/SfxSetup/SfxSetup.c
rename to third_party/lzma_sdk/C/Util/SfxSetup/SfxSetup.c
diff --git a/third_party/lzma_sdk/Util/SfxSetup/chromium.patch b/third_party/lzma_sdk/C/Util/SfxSetup/chromium.patch
similarity index 100%
rename from third_party/lzma_sdk/Util/SfxSetup/chromium.patch
rename to third_party/lzma_sdk/C/Util/SfxSetup/chromium.patch
diff --git a/third_party/lzma_sdk/Util/SfxSetup/resource.rc b/third_party/lzma_sdk/C/Util/SfxSetup/resource.rc
similarity index 100%
rename from third_party/lzma_sdk/Util/SfxSetup/resource.rc
rename to third_party/lzma_sdk/C/Util/SfxSetup/resource.rc
diff --git a/third_party/lzma_sdk/Util/SfxSetup/setup.ico b/third_party/lzma_sdk/C/Util/SfxSetup/setup.ico
similarity index 100%
rename from third_party/lzma_sdk/Util/SfxSetup/setup.ico
rename to third_party/lzma_sdk/C/Util/SfxSetup/setup.ico
Binary files differ
diff --git a/third_party/lzma_sdk/Xz.c b/third_party/lzma_sdk/C/Xz.c
similarity index 100%
rename from third_party/lzma_sdk/Xz.c
rename to third_party/lzma_sdk/C/Xz.c
diff --git a/third_party/lzma_sdk/Xz.h b/third_party/lzma_sdk/C/Xz.h
similarity index 100%
rename from third_party/lzma_sdk/Xz.h
rename to third_party/lzma_sdk/C/Xz.h
diff --git a/third_party/lzma_sdk/XzCrc64.c b/third_party/lzma_sdk/C/XzCrc64.c
similarity index 100%
rename from third_party/lzma_sdk/XzCrc64.c
rename to third_party/lzma_sdk/C/XzCrc64.c
diff --git a/third_party/lzma_sdk/XzCrc64.h b/third_party/lzma_sdk/C/XzCrc64.h
similarity index 100%
rename from third_party/lzma_sdk/XzCrc64.h
rename to third_party/lzma_sdk/C/XzCrc64.h
diff --git a/third_party/lzma_sdk/XzCrc64Opt.c b/third_party/lzma_sdk/C/XzCrc64Opt.c
similarity index 100%
rename from third_party/lzma_sdk/XzCrc64Opt.c
rename to third_party/lzma_sdk/C/XzCrc64Opt.c
diff --git a/third_party/lzma_sdk/XzDec.c b/third_party/lzma_sdk/C/XzDec.c
similarity index 100%
rename from third_party/lzma_sdk/XzDec.c
rename to third_party/lzma_sdk/C/XzDec.c
diff --git a/third_party/lzma_sdk/chromium.patch b/third_party/lzma_sdk/C/chromium.patch
similarity index 100%
rename from third_party/lzma_sdk/chromium.patch
rename to third_party/lzma_sdk/C/chromium.patch
diff --git a/third_party/lzma_sdk/Executable/7za.exe b/third_party/lzma_sdk/bin/7za.exe
similarity index 100%
rename from third_party/lzma_sdk/Executable/7za.exe
rename to third_party/lzma_sdk/bin/7za.exe
Binary files differ
diff --git a/third_party/lzma_sdk/7zr.exe b/third_party/lzma_sdk/bin/7zr.exe
similarity index 100%
rename from third_party/lzma_sdk/7zr.exe
rename to third_party/lzma_sdk/bin/7zr.exe
Binary files differ
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index 79fadb4..2575c1b 100644
--- a/third_party/nearby/README.chromium
+++ b/third_party/nearby/README.chromium
@@ -1,7 +1,7 @@
 Name: Nearby Connections Library
 Short Name: Nearby
 URL: https://github.com/google/nearby
-Version: 75aa59d57e1d34331c67fc84ba00a159ce2fc370
+Version: 7e081c663af2762216cf6f85a2d95bfc84de1407
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 41aacbf7..45d5619 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -33634,6 +33634,7 @@
   <suffix name="PasswordSuggestions" label="For PasswordSuggestions feature."/>
   <suffix name="PreviewsOmniboxUI"
       label="For the Previews UI in the Android Omnibox feature."/>
+  <suffix name="PriceDropNTP" label="For PriceDropNTP feature."/>
   <suffix name="ProfileSwitch"
       label="In product help for switching profiles using the profile menu."/>
   <suffix name="PwaInstallAvailableFeature"
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 65baed32..1eba77c 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -55877,6 +55877,7 @@
       label="AutofillLocalCardMigrationUsesStrikeSystemV2:disabled"/>
   <int value="-1614912400" label="enable-link-disambiguation-popup"/>
   <int value="-1613583483" label="UseNewAcceptLanguageHeader:enabled"/>
+  <int value="-1612641543" label="CCTRealTimeEngagementSignals:disabled"/>
   <int value="-1612418793"
       label="ExperimentalAccessibilitySwitchAccessSetupGuide:enabled"/>
   <int value="-1612253158" label="HardwareSecureDecryptionExperiment:enabled"/>
@@ -57390,6 +57391,7 @@
   <int value="-669761849" label="SplitSettings:disabled"/>
   <int value="-669400536" label="FastPairSoftwareScanning:disabled"/>
   <int value="-668114930" label="WindowsFollowCursor:disabled"/>
+  <int value="-667952041" label="CCTRealTimeEngagementSignals:enabled"/>
   <int value="-667517406" label="overscroll-history-navigation"/>
   <int value="-667018797"
       label="OmniboxUIExperimentBlueTitlesAndGrayUrlsOnPageSuggestions:disabled"/>
@@ -78436,6 +78438,9 @@
 </enum>
 
 <enum name="PrintPreviewPrintDestinationBuckets">
+  <obsolete>
+    Deprecated 2022-06.
+  </obsolete>
   <int value="0" label="DESTINATION_SHOWN"/>
   <int value="1" label="DESTINATION_CLOSED_CHANGED"/>
   <int value="2" label="DESTINATION_CLOSED_UNCHANGED"/>
diff --git a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
index 3dd7a5b..dc96fda 100644
--- a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
+++ b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
@@ -65,7 +65,7 @@
 </histogram>
 
 <histogram name="CustomTabs.ClientAppId.Incognito" enum="ClientAppId"
-    expires_after="2022-09-03">
+    expires_after="M110">
   <owner>roagarwal@chromium.org</owner>
   <owner>chrome-incognito@google.com</owner>
   <owner>cct-team@google.com</owner>
@@ -185,7 +185,7 @@
 </histogram>
 
 <histogram name="CustomTabs.IncognitoCCTCallerId" enum="IncognitoCCTCallerId"
-    expires_after="2022-08-07">
+    expires_after="M110">
   <owner>roagarwal@chromium.org</owner>
   <owner>chrome-incognito@google.com</owner>
   <owner>cct-team@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/feature_engagement/histograms.xml b/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
index 9a46c01..353a269 100644
--- a/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
+++ b/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
@@ -167,6 +167,9 @@
       summary="the Autofill password suggestions on iOS"/>
   <variant name="IPH_PreviewsOmniboxUI"
       summary="the Previews UI in the Android Omnibox"/>
+  <variant name="IPH_PriceDropNTP"
+      summary="the in product help message to inform users that a price drop
+               has occurred in a tab"/>
   <variant name="IPH_ProfileSwitch"
       summary="switching profiles using the profile menu"/>
   <variant name="IPH_PwaInstallAvailableFeature"
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index 804d098e..06b142d 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -6130,33 +6130,6 @@
   <affected-histogram name="Omnibox.ClipboardSuggestionShownWithCurrentURL"/>
 </histogram_suffixes>
 
-<histogram_suffixes name="OmniboxProviderTime" separator=".">
-  <suffix name="Bookmark" label=""/>
-  <suffix name="Builtin" label=""/>
-  <suffix name="Clipboard" label=""/>
-  <suffix name="Contact" label=""/>
-  <suffix name="Document" label=""/>
-  <suffix name="ExtensionApp" label=""/>
-  <suffix name="HistoryCluster" label=""/>
-  <suffix name="HistoryContents" label=""/>
-  <suffix name="HistoryFuzzy" label=""/>
-  <suffix name="HistoryQuick" label=""/>
-  <suffix name="HistoryURL" label=""/>
-  <suffix name="Keyword" label=""/>
-  <suffix name="LocalHistoryZeroSuggest" label=""/>
-  <suffix name="MostVisitedSites" label=""/>
-  <suffix name="OnDeviceHead" label=""/>
-  <suffix name="OpenTab" label=""/>
-  <suffix name="QueryTile" label=""/>
-  <suffix name="Search" label=""/>
-  <suffix name="Shortcuts" label=""/>
-  <suffix name="VerbatimMatch" label=""/>
-  <suffix name="VoiceSuggest" label=""/>
-  <suffix name="ZeroSuggest" label=""/>
-  <affected-histogram name="Omnibox.ProviderTime"/>
-  <affected-histogram name="Omnibox.ProviderTime2"/>
-</histogram_suffixes>
-
 <histogram_suffixes name="OobeMarketingCountries" separator=".">
   <suffix name="au" label="Australia"/>
   <suffix name="ca" label="Canada"/>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index e7954793..264380d 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -32,6 +32,26 @@
   </summary>
 </histogram>
 
+<histogram name="IOS.BackgroundTimeBeforeColdStart" units="minutes"
+    expires_after="2023-04-27">
+  <owner>thegreenfrog@chromium.org</owner>
+  <owner>bling-team@google.com</owner>
+  <summary>
+    This event is recorded on every cold start app open. The value represents
+    how long the app was away from the foreground.
+  </summary>
+</histogram>
+
+<histogram name="IOS.BackgroundTimeBeforeWarmStart" units="minutes"
+    expires_after="2023-04-27">
+  <owner>thegreenfrog@chromium.org</owner>
+  <owner>bling-team@google.com</owner>
+  <summary>
+    This event is recorded on every warm start app open. The value represents
+    how long the app was away from the foreground.
+  </summary>
+</histogram>
+
 <histogram name="IOS.CaptivePortal.SecureConnectionFailed"
     enum="CaptivePortalStatus" expires_after="2022-10-16">
   <owner>ajuma@chromium.org</owner>
@@ -42,13 +62,14 @@
   </summary>
 </histogram>
 
-<histogram name="IOS.ColdStartBackgroundTime" units="minutes"
+<histogram name="IOS.ColdStartBackgroundTime" units="msec"
     expires_after="2023-04-27">
   <owner>thegreenfrog@chromium.org</owner>
   <owner>bling-team@google.com</owner>
   <summary>
     This event is recorded on every cold start app open. The value represents
-    how long the app was away from the foreground.
+    how long the app was away from the foreground. NOTE: this metric is
+    obsolete, IOS.BackgroundTimeBeforeColdStart is the new metric.
   </summary>
 </histogram>
 
@@ -1664,13 +1685,14 @@
   </summary>
 </histogram>
 
-<histogram name="IOS.WarmStartBackgroundTime" units="minutes"
+<histogram name="IOS.WarmStartBackgroundTime" units="msec"
     expires_after="2023-04-27">
   <owner>thegreenfrog@chromium.org</owner>
   <owner>bling-team@google.com</owner>
   <summary>
     This event is recorded on every warm start app open. The value represents
-    how long the app was away from the foreground.
+    how long the app was away from the foreground. NOTE: this metric is
+    obsolete, IOS.BackgroundTimeBeforeWarmStart is the new metric.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index e814baa..d66ac087 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -3749,6 +3749,9 @@
 
 <histogram name="Media.PepperVideoDecoderPictureCount" units="units"
     expires_after="M77">
+  <obsolete>
+    Removed 07/2022.
+  </obsolete>
   <owner>ihf@chromium.org</owner>
   <owner>posciak@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml
index eb4a430a..c89caf6 100644
--- a/tools/metrics/histograms/metadata/omnibox/histograms.xml
+++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml
@@ -863,13 +863,39 @@
   </summary>
 </histogram>
 
-<histogram name="Omnibox.ProviderTime2" units="ms" expires_after="2022-12-25">
+<histogram name="Omnibox.ProviderTime2.{OmniboxProvider}" units="ms"
+    expires_after="2022-12-25">
   <owner>jdonnelly@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
   <owner>chrome-omnibox-team@google.com</owner>
   <summary>
-    The length of time taken by the named provider&quot;s synchronous pass.
+    The length of time taken by the {OmniboxProvider}Provider's synchronous
+    pass.
   </summary>
+  <token key="OmniboxProvider">
+    <variant name="Bookmark"/>
+    <variant name="Builtin"/>
+    <variant name="Clipboard"/>
+    <variant name="Contact"/>
+    <variant name="Document"/>
+    <variant name="ExtensionApp"/>
+    <variant name="HistoryCluster"/>
+    <variant name="HistoryContents"/>
+    <variant name="HistoryFuzzy"/>
+    <variant name="HistoryQuick"/>
+    <variant name="HistoryURL"/>
+    <variant name="Keyword"/>
+    <variant name="LocalHistoryZeroSuggest"/>
+    <variant name="MostVisitedSites"/>
+    <variant name="OnDeviceHead"/>
+    <variant name="OpenTab"/>
+    <variant name="QueryTile"/>
+    <variant name="Search"/>
+    <variant name="Shortcuts"/>
+    <variant name="VerbatimMatch"/>
+    <variant name="VoiceSuggest"/>
+    <variant name="ZeroSuggest"/>
+  </token>
 </histogram>
 
 <histogram name="Omnibox.QueryTime2" units="ms" expires_after="2022-11-13">
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 1324653..79c5476 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -1610,8 +1610,52 @@
   </summary>
 </histogram>
 
+<histogram name="BootTime.Chrome" units="ms" expires_after="never">
+<!-- expires-never: This is a primary metric for the ChromeOS total boot time. -->
+
+  <owner>alemate@chromium.org</owner>
+  <owner>briannorris@chromium.org</owner>
+  <owner>rsorokin@chromium.org</owner>
+  <owner>cros-oac@google.com</owner>
+  <summary>
+    Time from chrome binary exec to login UI ready on Chrome OS. This is
+    recorded from the platform side after receiving &quot;login-ui-visible&quot;
+    event on the platform side in the send-boot-metrics.conf script.
+
+    Warning: this histogram was expired from M83 to M105; data may be missing.
+  </summary>
+</histogram>
+
+<histogram name="BootTime.Firmware" units="ms" expires_after="never">
+<!-- expires-never: This is a primary metric for ChromeOS boot time. -->
+
+  <owner>briannorris@chromium.org</owner>
+  <owner>ctshao@google.com</owner>
+  <owner>alemate@chromium.org</owner>
+  <owner>baseos-perf@google.com</owner>
+  <summary>
+    Time from power on to kernel start (Chrome OS).
+
+    Warning: this histogram was expired from M83 to M105; data may be missing.
+  </summary>
+</histogram>
+
+<histogram name="BootTime.Kernel" units="ms" expires_after="never">
+<!-- expires-never: This is a primary metric for ChromeOS boot time. -->
+
+  <owner>briannorris@chromium.org</owner>
+  <owner>ctshao@google.com</owner>
+  <owner>alemate@chromium.org</owner>
+  <owner>baseos-perf@google.com</owner>
+  <summary>
+    Time from kernel start to system code being called (Chrome OS).
+
+    Warning: this histogram was expired from M83 to M105; data may be missing.
+  </summary>
+</histogram>
+
 <histogram name="BootTime.Login2" units="ms" expires_after="never">
-<!-- expires-never: This is primary metric for the ChromeOS login. -->
+<!-- expires-never: This is a primary metric for the ChromeOS login. -->
 
   <owner>rsorokin@chromium.org</owner>
   <owner>antrim@chromium.org</owner>
@@ -1623,10 +1667,35 @@
   </summary>
 </histogram>
 
-<histogram name="BootTime.Total2" units="ms" expires_after="2022-04-03">
-  <owner>bccheng@chromium.org</owner>
-  <owner>semenzato@chromium.org</owner>
-  <summary>Time from power on to login panel ready (Chrome OS).</summary>
+<histogram name="BootTime.System" units="ms" expires_after="never">
+<!-- expires-never: This is a primary metric for the ChromeOS total boot time. -->
+
+  <owner>briannorris@chromium.org</owner>
+  <owner>ctshao@google.com</owner>
+  <owner>alemate@chromium.org</owner>
+  <owner>baseos-perf@google.com</owner>
+  <summary>
+    Time from system code being called to starting Chrome (Chrome OS).
+
+    Warning: this histogram was expired from M83 to M105; data may be missing.
+  </summary>
+</histogram>
+
+<histogram name="BootTime.Total2" units="ms" expires_after="never">
+<!-- expires-never: This is a primary metric for the ChromeOS total boot time. -->
+
+  <owner>alemate@chromium.org</owner>
+  <owner>briannorris@chromium.org</owner>
+  <owner>rsorokin@chromium.org</owner>
+  <owner>cros-oac@google.com</owner>
+  <summary>
+    Time from power on to login UI ready on Chrome OS. This is recorded from the
+    platform side after receiving &quot;login-ui-visible&quot; event on the
+    platform side in the send-boot-metrics.conf script.
+
+    Warning: this histogram was expired from 2022-04-03 to M105; data may be
+    missing.
+  </summary>
 </histogram>
 
 <histogram name="BrotliFilter.CompressionPercent" units="%"
diff --git a/tools/metrics/histograms/metadata/print/histograms.xml b/tools/metrics/histograms/metadata/print/histograms.xml
index 91233c5..22fd7a1 100644
--- a/tools/metrics/histograms/metadata/print/histograms.xml
+++ b/tools/metrics/histograms/metadata/print/histograms.xml
@@ -24,6 +24,9 @@
 
 <histogram name="PrintPreview.DestinationAction"
     enum="PrintPreviewPrintDestinationBuckets" expires_after="2022-07-01">
+  <obsolete>
+    Data no longer needed 2022-06.
+  </obsolete>
   <owner>rbpotter@chromium.org</owner>
   <owner>thestig@chromium.org</owner>
   <summary>
@@ -131,7 +134,7 @@
 </histogram>
 
 <histogram name="PrintPreview.PrintSettingsUi"
-    enum="PrintPreviewPrintSettingsUiBuckets" expires_after="2022-07-01">
+    enum="PrintPreviewPrintSettingsUiBuckets" expires_after="2023-07-01">
   <owner>rbpotter@chromium.org</owner>
   <owner>thestig@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/printing/histograms.xml b/tools/metrics/histograms/metadata/printing/histograms.xml
index bc2b8a2..973d4bb 100644
--- a/tools/metrics/histograms/metadata/printing/histograms.xml
+++ b/tools/metrics/histograms/metadata/printing/histograms.xml
@@ -305,7 +305,7 @@
 </histogram>
 
 <histogram name="Printing.CUPS.UnknownPpdColorModel" enum="Boolean"
-    expires_after="2022-08-07">
+    expires_after="2023-08-07">
   <owner>dhoss@chromium.org</owner>
   <owner>thestig@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/security/histograms.xml b/tools/metrics/histograms/metadata/security/histograms.xml
index 9089534..c9007e6 100644
--- a/tools/metrics/histograms/metadata/security/histograms.xml
+++ b/tools/metrics/histograms/metadata/security/histograms.xml
@@ -413,6 +413,18 @@
   </token>
 </histogram>
 
+<histogram name="Security.SCTAuditing.NumPersistedReportsLoaded"
+    units="reports" expires_after="2022-12-01">
+  <owner>cthomp@chromium.org</owner>
+  <owner>trusty-transport@chromium.org</owner>
+  <summary>
+    Records the number of persisted SCT auditing reports that were loaded and
+    deserialized from disk, if SCT auditing is enabled. Invalid persisted
+    reports are not counted. Recorded once shortly after browser startup (or
+    when the network service restarts).
+  </summary>
+</histogram>
+
 <histogram name="Security.SCTAuditing.OptIn.DedupeCacheHWM" units="reports"
     expires_after="2022-09-01">
   <owner>cthomp@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/translate/histograms.xml b/tools/metrics/histograms/metadata/translate/histograms.xml
index 2e99adf..7a86daf 100644
--- a/tools/metrics/histograms/metadata/translate/histograms.xml
+++ b/tools/metrics/histograms/metadata/translate/histograms.xml
@@ -105,7 +105,7 @@
     expires_after="2022-11-13">
   <owner>groby@google.com</owner>
   <owner>chrome-language@google.com</owner>
-  <summary>Tracks UI events related to the translate bubble.</summary>
+  <summary>Tracks UI events related to the Full Page Translate bubble.</summary>
 </histogram>
 
 <histogram name="Translate.CaptureText" units="ms" expires_after="2022-11-13">
@@ -678,7 +678,7 @@
     enum="PartialTranslateBubbleUiEvent" expires_after="2022-11-01">
   <owner>cuianthony@google.com</owner>
   <owner>chrome-language@google.com</owner>
-  <summary>Tracks UI events related to the partial translate bubble.</summary>
+  <summary>Tracks UI events related to the Partial Translate bubble.</summary>
 </histogram>
 
 <histogram name="Translate.Ranker.Model.Status" enum="RankerModelStatus"
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index bfc2a159f..0ab8288 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,24 +5,24 @@
             "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux_arm64/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "win": {
-            "hash": "4d710b8a4c4e9507a55721551e1fa3f80eb4f61f",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/3a4f21146fc825313f1dfe81638477f9d3d433cd/trace_processor_shell.exe"
+            "hash": "836c993b348d0ca5b59f58999143ddf5d019e1ba",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/d86457ab539f83842f0e6c891c459bd764267b8d/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "58893933be305d3bfe0a72ebebcacde2ac3ca893",
             "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "mac": {
-            "hash": "247c890ff8ac22d4bb4b2a1597b95fc57ab05461",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/659ec76603a48c1c95c35ad4843314cd735a5216/trace_processor_shell"
+            "hash": "86145908aed91663341ff82850a62108e55baac2",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/d86457ab539f83842f0e6c891c459bd764267b8d/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "e1ad4861384b06d911a65f035317914b8cc975c6",
             "full_remote_path": "perfetto-luci-artifacts/v25.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "095289b461ad5dca4860e9c3cb728a060be94d34",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/3a4f21146fc825313f1dfe81638477f9d3d433cd/trace_processor_shell"
+            "hash": "3963de6f2c1864244db56893a436bf609dcda8a3",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/d86457ab539f83842f0e6c891c459bd764267b8d/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/polymer/css_to_wrapper.gni b/tools/polymer/css_to_wrapper.gni
index 79173ab..3b305ee 100644
--- a/tools/polymer/css_to_wrapper.gni
+++ b/tools/polymer/css_to_wrapper.gni
@@ -5,28 +5,43 @@
 template("css_to_wrapper") {
   action(target_name) {
     script = "//tools/polymer/css_to_wrapper.py"
-    forward_variables_from(invoker, [ "visibility" ])
+    forward_variables_from(invoker,
+                           [
+                             "deps",
+                             "public_deps",
+                             "visibility",
+                           ])
 
     inputs = []
     outputs = []
 
     wrapper_extension = ".ts"
 
+    in_folder = "."
+    if (defined(invoker.in_folder)) {
+      in_folder = invoker.in_folder
+    }
+
+    out_folder = target_gen_dir
+    if (defined(invoker.out_folder)) {
+      out_folder = invoker.out_folder
+    }
+
     foreach(css_file, invoker.in_files) {
       extension = get_path_info(css_file, "extension")
       assert(extension == "css")
 
       wrapper_file = get_path_info(css_file, "dir") + "/" +
                      get_path_info(css_file, "file") + wrapper_extension
-      inputs += [ css_file ]
-      outputs += [ "$target_gen_dir/" + wrapper_file ]
+      inputs += [ "$in_folder/" + css_file ]
+      outputs += [ "$out_folder/" + wrapper_file ]
     }
 
     args = [
              "--in_folder",
-             rebase_path(".", root_build_dir),
+             rebase_path(in_folder, root_build_dir),
              "--out_folder",
-             rebase_path(target_gen_dir, root_build_dir),
+             rebase_path(out_folder, root_build_dir),
              "--in_files",
            ] + invoker.in_files
   }
diff --git a/ui/accessibility/ax_node.cc b/ui/accessibility/ax_node.cc
index 75687a6..ad5b32d 100644
--- a/ui/accessibility/ax_node.cc
+++ b/ui/accessibility/ax_node.cc
@@ -1560,7 +1560,20 @@
 }
 
 bool AXNode::IsReadOnlySupported() const {
-  return IsCellOrHeaderOfAriaGrid() || ui::IsReadOnlySupported(GetRole());
+  // Grid cells and headers can't be derived solely from the role (need to check
+  // the ancestor chain) so check this first.
+  if (IsCellOrHeaderOfAriaGrid())
+    return true;
+
+  // kPopUpButton is special in that it is the role Blink assigns for both
+  // role=button with aria-haspopup set, along with <select> elements.
+  // HTML AAM (https://w3c.github.io/html-aam/) maps <select> to the combobox
+  // role, which supports readonly, but readonly is not supported for button
+  // roles.
+  if (GetRole() == ax::mojom::Role::kPopUpButton && !IsMenuListPopUpButton())
+    return false;
+
+  return ui::IsReadOnlySupported(GetRole());
 }
 
 bool AXNode::IsReadOnlyOrDisabled() const {
@@ -1829,11 +1842,9 @@
          grandparent_node->GetRole() == ax::mojom::Role::kListMarker;
 }
 
-bool AXNode::IsCollapsedMenuListPopUpButton() const {
-  if (GetRole() != ax::mojom::Role::kPopUpButton ||
-      !HasState(ax::mojom::State::kCollapsed)) {
+bool AXNode::IsMenuListPopUpButton() const {
+  if (GetRole() != ax::mojom::Role::kPopUpButton)
     return false;
-  }
 
   // When a popup button contains a menu list popup, its only child is unignored
   // and is a menu list popup.
@@ -1844,6 +1855,13 @@
   return node->GetRole() == ax::mojom::Role::kMenuListPopup;
 }
 
+bool AXNode::IsCollapsedMenuListPopUpButton() const {
+  if (!HasState(ax::mojom::State::kCollapsed))
+    return false;
+
+  return IsMenuListPopUpButton();
+}
+
 AXNode* AXNode::GetCollapsedMenuListPopUpButtonAncestor() const {
   AXNode* node = GetOrderedSet();
 
diff --git a/ui/accessibility/ax_node.h b/ui/accessibility/ax_node.h
index 1009978..f4b75143 100644
--- a/ui/accessibility/ax_node.h
+++ b/ui/accessibility/ax_node.h
@@ -659,6 +659,10 @@
   // of a list marker node. Returns false otherwise.
   bool IsInListMarker() const;
 
+  // Returns true if this node is a popup button that is a parent to a menu list
+  // popup.
+  bool IsMenuListPopUpButton() const;
+
   // Returns true if this node is a collapsed popup button that is parent to a
   // menu list popup.
   bool IsCollapsedMenuListPopUpButton() const;
diff --git a/ui/accessibility/ax_role_properties.cc b/ui/accessibility/ax_role_properties.cc
index 1141967..29c3ed7 100644
--- a/ui/accessibility/ax_role_properties.cc
+++ b/ui/accessibility/ax_role_properties.cc
@@ -547,7 +547,6 @@
     case ax::mojom::Role::kSwitch:
     case ax::mojom::Role::kTextField:
     case ax::mojom::Role::kTextFieldWithComboBox:
-    case ax::mojom::Role::kToggleButton:
     case ax::mojom::Role::kTreeGrid:
       return true;
 
diff --git a/ui/base/theme_provider.h b/ui/base/theme_provider.h
index a728a0e..f554197a 100644
--- a/ui/base/theme_provider.h
+++ b/ui/base/theme_provider.h
@@ -40,9 +40,6 @@
   // have its own source of ids (e.g. an enum, or external resource bundle).
   virtual gfx::ImageSkia* GetImageSkiaNamed(int id) const = 0;
 
-  // Get the color specified by |id|.
-  virtual SkColor GetColor(int id) const = 0;
-
   // Get the HSL shift specified by |id|.
   virtual color_utils::HSL GetTint(int id) const = 0;
 
diff --git a/ui/color/color_provider_manager.cc b/ui/color/color_provider_manager.cc
index 8718e88..8988742 100644
--- a/ui/color/color_provider_manager.cc
+++ b/ui/color/color_provider_manager.cc
@@ -55,6 +55,7 @@
           ContrastMode::kNormal,
           SystemTheme::kDefault,
           FrameType::kChromium,
+          absl::nullopt,
           nullptr) {}
 
 ColorProviderManager::Key::Key(
@@ -62,12 +63,14 @@
     ContrastMode contrast_mode,
     SystemTheme system_theme,
     FrameType frame_type,
+    absl::optional<SkColor> user_color,
     scoped_refptr<ThemeInitializerSupplier> custom_theme)
     : color_mode(color_mode),
       contrast_mode(contrast_mode),
       elevation_mode(ElevationMode::kLow),
       system_theme(system_theme),
       frame_type(frame_type),
+      user_color(user_color),
       custom_theme(std::move(custom_theme)) {}
 
 ColorProviderManager::Key::Key(const Key&) = default;
diff --git a/ui/color/color_provider_manager.h b/ui/color/color_provider_manager.h
index 58b3cc2..5daa6ac 100644
--- a/ui/color/color_provider_manager.h
+++ b/ui/color/color_provider_manager.h
@@ -13,6 +13,7 @@
 #include "base/component_export.h"
 #include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/color_utils.h"
 
@@ -110,7 +111,8 @@
         ContrastMode contrast_mode,
         SystemTheme system_theme,
         FrameType frame_type,
-        scoped_refptr<ThemeInitializerSupplier> custom_theme);
+        absl::optional<SkColor> user_color = absl::nullopt,
+        scoped_refptr<ThemeInitializerSupplier> custom_theme = nullptr);
     Key(const Key&);
     Key& operator=(const Key&);
     ~Key();
@@ -119,6 +121,7 @@
     ElevationMode elevation_mode;
     SystemTheme system_theme;
     FrameType frame_type;
+    absl::optional<SkColor> user_color;
     scoped_refptr<ThemeInitializerSupplier> custom_theme;
     base::WeakPtr<InitializerSupplier> app_controller;
 
@@ -126,10 +129,12 @@
       auto* lhs_app_controller = app_controller.get();
       auto* rhs_app_controller = other.app_controller.get();
       return std::tie(color_mode, contrast_mode, elevation_mode, system_theme,
-                      frame_type, custom_theme, lhs_app_controller) <
+                      frame_type, user_color, custom_theme,
+                      lhs_app_controller) <
              std::tie(other.color_mode, other.contrast_mode,
                       other.elevation_mode, other.system_theme,
-                      other.frame_type, other.custom_theme, rhs_app_controller);
+                      other.frame_type, other.user_color, other.custom_theme,
+                      rhs_app_controller);
     }
   };
 
diff --git a/ui/color/color_provider_manager_unittest.cc b/ui/color/color_provider_manager_unittest.cc
index a32144e7..796db34 100644
--- a/ui/color/color_provider_manager_unittest.cc
+++ b/ui/color/color_provider_manager_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/color/color_mixer.h"
 #include "ui/color/color_provider.h"
@@ -32,7 +33,7 @@
       {ColorProviderManager::ColorMode::kLight,
        ColorProviderManager::ContrastMode::kNormal,
        ColorProviderManager::SystemTheme::kDefault,
-       ColorProviderManager::FrameType::kChromium, nullptr});
+       ColorProviderManager::FrameType::kChromium, absl::nullopt, nullptr});
 }
 
 }  // namespace
diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc
index 8a3c01e2..202d142 100644
--- a/ui/gtk/gtk_ui.cc
+++ b/ui/gtk/gtk_ui.cc
@@ -24,6 +24,7 @@
 #include "base/observer_list.h"
 #include "base/strings/string_split.h"
 #include "chrome/browser/themes/theme_properties.h"  // nogncheck
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "third_party/skia/include/core/SkShader.h"
@@ -730,7 +731,7 @@
            // Some theme colors, e.g. COLOR_NTP_LINK, are derived from color
            // provider colors. We assume that those sources' colors won't change
            // with frame type.
-           ui::ColorProviderManager::FrameType::kChromium, nullptr});
+           ui::ColorProviderManager::FrameType::kChromium});
 
   SkColor location_bar_border = GetBorderColor("GtkEntry#entry");
   if (SkColorGetA(location_bar_border))
diff --git a/ui/native_theme/native_theme.cc b/ui/native_theme/native_theme.cc
index 3f596e0d..5dd898d8 100644
--- a/ui/native_theme/native_theme.cc
+++ b/ui/native_theme/native_theme.cc
@@ -51,7 +51,7 @@
                               : ColorProviderManager::SystemTheme::kDefault,
       use_custom_frame ? ui::ColorProviderManager::FrameType::kChromium
                        : ui::ColorProviderManager::FrameType::kNative,
-      std::move(custom_theme));
+      user_color_, std::move(custom_theme));
 }
 
 SkColor NativeTheme::GetSystemButtonPressedColor(SkColor base_color) const {
diff --git a/ui/native_theme/native_theme.h b/ui/native_theme/native_theme.h
index 3b9c884..a064f4c 100644
--- a/ui/native_theme/native_theme.h
+++ b/ui/native_theme/native_theme.h
@@ -471,6 +471,11 @@
   void set_system_colors(const std::map<SystemThemeColor, SkColor>& colors);
   bool is_custom_system_theme() const { return is_custom_system_theme_; }
 
+  // Set the user_color for ColorProviderManager::Key.
+  void set_user_color(absl::optional<SkColor> user_color) {
+    user_color_ = user_color;
+  }
+
   // Updates the state of dark mode, forced colors mode, and the map of system
   // colors. Returns true if NativeTheme was updated as a result, or false if
   // the state of NativeTheme was untouched.
@@ -552,6 +557,9 @@
   // Observers to notify when the native theme changes.
   base::ObserverList<NativeThemeObserver>::Unchecked native_theme_observers_;
 
+  // User's primary color. Included in the ColorProvider Key.
+  absl::optional<SkColor> user_color_;
+
   bool should_use_dark_colors_ = false;
   const bool is_custom_system_theme_;
   bool forced_colors_ = false;
diff --git a/ui/views/animation/bounds_animator.cc b/ui/views/animation/bounds_animator.cc
index 312efee1..3458584 100644
--- a/ui/views/animation/bounds_animator.cc
+++ b/ui/views/animation/bounds_animator.cc
@@ -52,8 +52,13 @@
 
   // Return early if the existing animation on |view| has the same target
   // bounds.
-  if (is_animating && target == data_[view].target_bounds)
+  if (is_animating && target == data_[view].target_bounds) {
+    // If this animation specifies a different delegate, swap them out.
+    if (delegate && delegate != data_[view].delegate)
+      SetAnimationDelegate(view, std::move(delegate));
+
     return;
+  }
 
   Data existing_data;
   if (is_animating) {
@@ -156,6 +161,17 @@
   return !data_.empty();
 }
 
+void BoundsAnimator::Complete() {
+  if (data_.empty())
+    return;
+
+  while (!data_.empty())
+    data_.begin()->second.animation->End();
+
+  // Invoke AnimationContainerProgressed to force a repaint and notify delegate.
+  AnimationContainerProgressed(container_.get());
+}
+
 void BoundsAnimator::Cancel() {
   if (data_.empty())
     return;
@@ -237,6 +253,13 @@
   View* view = animation_to_view_[animation];
   DCHECK(view);
 
+  // Notify the delegate so it has a chance to paint the final state of a
+  // completed animation.
+  if (type == AnimationEndType::kEnded) {
+    DCHECK_EQ(animation->GetCurrentValue(), 1.0);
+    AnimationProgressed(animation);
+  }
+
   // Save the data for later clean up.
   Data data = RemoveFromMaps(view);
 
diff --git a/ui/views/animation/bounds_animator.h b/ui/views/animation/bounds_animator.h
index 87364f5..20a08706 100644
--- a/ui/views/animation/bounds_animator.h
+++ b/ui/views/animation/bounds_animator.h
@@ -87,6 +87,10 @@
   // Returns true if BoundsAnimator is animating any view.
   bool IsAnimating() const;
 
+  // Finishes all animations, teleporting the views to their target bounds. Any
+  // views marked for deletion are deleted.
+  void Complete();
+
   // Cancels all animations, leaving the views at their current location and
   // size. Any views marked for deletion are deleted.
   void Cancel();
diff --git a/ui/views/animation/bounds_animator_unittest.cc b/ui/views/animation/bounds_animator_unittest.cc
index 303b6c9..f876166 100644
--- a/ui/views/animation/bounds_animator_unittest.cc
+++ b/ui/views/animation/bounds_animator_unittest.cc
@@ -295,6 +295,25 @@
   EXPECT_FALSE(OwnedDelegate::GetAndClearCanceled());
 }
 
+// Make sure that a duplicate animation request that specifies a different
+// delegate swaps out that delegate.
+TEST_F(BoundsAnimatorTest, DuplicateAnimationsCanReplaceDelegate) {
+  const gfx::Rect target_bounds(0, 0, 10, 10);
+
+  animator()->AnimateViewTo(child(), target_bounds);
+  animator()->SetAnimationDelegate(child(), std::make_unique<OwnedDelegate>());
+
+  // Request the animation with the same view/target bounds but a different
+  // delegate.
+  animator()->AnimateViewTo(child(), target_bounds,
+                            std::make_unique<OwnedDelegate>());
+
+  // Verify that the delegate was replaced.
+  EXPECT_TRUE(OwnedDelegate::GetAndClearDeleted());
+  // The animation still should not have been canceled.
+  EXPECT_FALSE(OwnedDelegate::GetAndClearCanceled());
+}
+
 // Makes sure StopAnimating works.
 TEST_F(BoundsAnimatorTest, StopAnimating) {
   std::unique_ptr<OwnedDelegate> delegate(std::make_unique<OwnedDelegate>());
@@ -313,6 +332,28 @@
   EXPECT_TRUE(OwnedDelegate::GetAndClearCanceled());
 }
 
+// Make sure Complete completes in-progress animations.
+TEST_F(BoundsAnimatorTest, CompleteAnimation) {
+  std::unique_ptr<OwnedDelegate> delegate(std::make_unique<OwnedDelegate>());
+  const gfx::Rect target_bounds = gfx::Rect(0, 0, 10, 10);
+
+  animator()->AnimateViewTo(child(), target_bounds);
+  animator()->SetAnimationDelegate(child(), std::make_unique<OwnedDelegate>());
+
+  animator()->Complete();
+
+  // Shouldn't be animating now.
+  EXPECT_FALSE(animator()->IsAnimating());
+  EXPECT_FALSE(animator()->IsAnimating(child()));
+
+  // Child should have been moved to the animation's target.
+  EXPECT_EQ(target_bounds, child()->bounds());
+
+  // Completing should delete the delegate.
+  EXPECT_TRUE(OwnedDelegate::GetAndClearDeleted());
+  EXPECT_FALSE(OwnedDelegate::GetAndClearCanceled());
+}
+
 // Verify that transform is used when the animation target bounds have the
 // same size with the current bounds' meanwhile having the transform option
 // enabled.